Skip to content

Commit 13dd6c6

Browse files
committed
refactor(accounts): password security enhancements
1 parent d107f12 commit 13dd6c6

File tree

21 files changed

+579
-344
lines changed

21 files changed

+579
-344
lines changed

Diff for: package.json

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"body-parser": "1.19.2",
3333
"busboy": "1.4.0",
3434
"chance": "1.1.8",
35+
"check-password-strength": "2.0.5",
3536
"cheerio": "1.0.0-rc.10",
3637
"clone": "2.1.2",
3738
"clsx": "1.1.1",

Diff for: src/client/components/Nav/Sidebar/index.jsx

+6
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,12 @@ class Sidebar extends React.Component {
280280
href='/settings'
281281
active={activeSubItem === 'settings-general'}
282282
/>
283+
<SubmenuItem
284+
text='Accounts'
285+
icon='tune'
286+
href='/settings/accounts'
287+
active={activeSubItem === 'settings-accounts'}
288+
/>
283289
<SubmenuItem
284290
text='Appearance'
285291
icon='style'

Diff for: src/client/containers/Modals/CreateAccountModal.jsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,9 @@ class CreateAccountModal extends React.Component {
203203
name={'password_confirmation'}
204204
value={this.password}
205205
onChange={e => this.onInputChanged(e, 'password')}
206-
data-validation={'length'}
207-
data-validation-length={'min6'}
208-
data-validation-error-msg={'Password must contain at least 6 characters.'}
206+
data-validation={this.props.common.accountsPasswordComplexity ? 'length' : 'none'}
207+
data-validation-length={'min8'}
208+
data-validation-error-msg={'Password must contain at least 8 characters.'}
209209
/>
210210
</div>
211211
<div className='uk-float-left uk-width-1-2'>

Diff for: src/client/containers/Settings/Accounts/index.jsx

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* . .o8 oooo
3+
* .o8 "888 `888
4+
* .o888oo oooo d8b oooo oooo .oooo888 .ooooo. .oooo.o 888 oooo
5+
* 888 `888""8P `888 `888 d88' `888 d88' `88b d88( "8 888 .8P'
6+
* 888 888 888 888 888 888 888ooo888 `"Y88b. 888888.
7+
* 888 . 888 888 888 888 888 888 .o o. )88b 888 `88b.
8+
* "888" d888b `V88V"V8P' `Y8bod88P" `Y8bod8P' 8""888P' o888o o888o
9+
* ========================================================================
10+
* Author: Chris Brame
11+
* Updated: 5/17/22 2:20 PM
12+
* Copyright (c) 2014-2022. All rights reserved.
13+
*/
14+
15+
import React from 'react'
16+
import PropTypes from 'prop-types'
17+
import { connect } from 'react-redux'
18+
import { updateSetting, updateMultipleSettings } from 'actions/settings'
19+
20+
import Button from 'components/Button'
21+
import SettingItem from 'components/Settings/SettingItem'
22+
23+
import helpers from 'lib/helpers'
24+
import axios from 'axios'
25+
import Log from '../../../logger'
26+
import EnableSwitch from 'components/Settings/EnableSwitch'
27+
import { observer } from 'mobx-react'
28+
import { makeObservable, observable } from 'mobx'
29+
import UIKit from 'uikit'
30+
31+
@observer
32+
class AccountsSettingsContainer extends React.Component {
33+
@observable passwordComplexityEnabled = false
34+
@observable allowUserRegistrationEnabled = false
35+
36+
constructor (props) {
37+
super(props)
38+
39+
makeObservable(this)
40+
41+
this.state = {
42+
restarting: false
43+
}
44+
45+
this.restartServer = this.restartServer.bind(this)
46+
}
47+
48+
componentDidMount () {
49+
// helpers.UI.inputs()
50+
}
51+
52+
componentDidUpdate (prevProps) {
53+
// helpers.UI.reRenderInputs()
54+
if (prevProps.settings !== this.props.settings) {
55+
if (this.passwordComplexityEnabled !== this.getSetting('accountsPasswordComplexity'))
56+
this.passwordComplexityEnabled = this.getSetting('accountsPasswordComplexity')
57+
if (this.allowUserRegistrationEnabled !== this.getSetting('allowUserRegistration'))
58+
this.allowUserRegistrationEnabled = this.getSetting('allowUserRegistration')
59+
}
60+
}
61+
62+
restartServer () {
63+
this.setState({ restarting: true })
64+
65+
const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
66+
axios
67+
.post(
68+
'/api/v1/admin/restart',
69+
{},
70+
{
71+
headers: {
72+
'CSRF-TOKEN': token
73+
}
74+
}
75+
)
76+
.catch(error => {
77+
helpers.hideLoader()
78+
Log.error(error.responseText)
79+
Log.error('Unable to restart server. Server must run under PM2 and Account must have admin rights.')
80+
helpers.UI.showSnackbar('Unable to restart server. Are you an Administrator?', true)
81+
})
82+
.then(() => {
83+
this.setState({ restarting: false })
84+
})
85+
}
86+
87+
getSetting (stateName) {
88+
return this.props.settings.getIn(['settings', stateName, 'value'])
89+
? this.props.settings.getIn(['settings', stateName, 'value'])
90+
: ''
91+
}
92+
93+
updateSetting (stateName, name, value) {
94+
this.props.updateSetting({ stateName, name, value })
95+
}
96+
97+
render () {
98+
const { active } = this.props
99+
return (
100+
<div className={active ? 'active' : 'hide'}>
101+
<SettingItem
102+
title='Allow User Registration'
103+
subtitle='Allow users to create accounts on the login screen.'
104+
component={
105+
<EnableSwitch
106+
stateName='allowUserRegistration'
107+
label='Enable'
108+
checked={this.allowUserRegistrationEnabled}
109+
onChange={e => {
110+
this.updateSetting('allowUserRegistration', 'allowUserRegistration:enable', e.target.checked)
111+
}}
112+
/>
113+
}
114+
/>
115+
<SettingItem
116+
title={'Password Complexity'}
117+
subtitle={'Require users passwords to meet minimum password complexity'}
118+
tooltip={'Minimum 8 characters with uppercase and numeric.'}
119+
component={
120+
<EnableSwitch
121+
stateName={'accountsPasswordComplexity'}
122+
label={'Enable'}
123+
checked={this.passwordComplexityEnabled}
124+
onChange={e => {
125+
this.updateSetting('accountsPasswordComplexity', 'accountsPasswordComplexity:enable', e.target.checked)
126+
}}
127+
/>
128+
}
129+
/>
130+
</div>
131+
)
132+
}
133+
}
134+
135+
AccountsSettingsContainer.propTypes = {
136+
active: PropTypes.bool.isRequired,
137+
updateSetting: PropTypes.func.isRequired,
138+
updateMultipleSettings: PropTypes.func.isRequired,
139+
settings: PropTypes.object.isRequired
140+
}
141+
142+
const mapStateToProps = state => ({
143+
settings: state.settings.settings
144+
})
145+
146+
export default connect(mapStateToProps, { updateSetting, updateMultipleSettings })(AccountsSettingsContainer)

Diff for: src/client/containers/Settings/General/index.jsx

-16
Original file line numberDiff line numberDiff line change
@@ -94,17 +94,6 @@ class GeneralSettings extends React.Component {
9494
/>
9595
)
9696

97-
const AllowUserRegistration = (
98-
<EnableSwitch
99-
stateName='allowUserRegistration'
100-
label='Enable'
101-
checked={this.getSettingsValue('allowUserRegistration')}
102-
onChange={e => {
103-
this.updateSetting('allowUserRegistration', 'allowUserRegistration:enable', e.target.checked)
104-
}}
105-
/>
106-
)
107-
10897
return (
10998
<div className={active ? 'active' : 'hide'}>
11099
<SettingItem
@@ -184,11 +173,6 @@ class GeneralSettings extends React.Component {
184173
</ZoneBox>
185174
</Zone>
186175
</SettingItem>
187-
<SettingItem
188-
title='Allow User Registration'
189-
subtitle='Allow users to create accounts on the login screen.'
190-
component={AllowUserRegistration}
191-
/>
192176
</div>
193177
)
194178
}

Diff for: src/client/containers/Settings/SettingsContainer.jsx

+9
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { fetchSettings } from 'actions/settings'
2121
import Menu from 'components/Settings/Menu'
2222
import MenuItem from 'components/Settings/MenuItem'
2323
import GeneralSettings from './General'
24+
import AccountsSettings from './Accounts'
2425
import AppearanceSettings from './Appearance'
2526
import PermissionsSettingsContainer from './Permissions'
2627
import TicketsSettings from './Tickets'
@@ -89,6 +90,13 @@ class SettingsContainer extends React.Component {
8990
this.onMenuItemClick(e, 'general')
9091
}}
9192
/>
93+
<MenuItem
94+
title='Accounts'
95+
active={this.state.activeCategory === 'settings-accounts'}
96+
onClick={e => {
97+
this.onMenuItemClick(e, 'accounts')
98+
}}
99+
/>
92100
<MenuItem
93101
title='Appearance'
94102
active={this.state.activeCategory === 'settings-appearance'}
@@ -156,6 +164,7 @@ class SettingsContainer extends React.Component {
156164
<div className='page-wrapper full-height scrollable no-overflow-x' ref={i => (this.page = i)}>
157165
<div className='settings-wrap'>
158166
<GeneralSettings active={this.state.activeCategory === 'settings-general'} />
167+
<AccountsSettings active={this.state.activeCategory === 'settings-accounts'} />
159168
<AppearanceSettings active={this.state.activeCategory === 'settings-appearance'} />
160169
<PermissionsSettingsContainer active={this.state.activeCategory === 'settings-permissions'} />
161170
<TicketsSettings active={this.state.activeCategory === 'settings-tickets'} />

Diff for: src/client/sagas/accounts/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ function * saveEditAccount ({ payload }) {
8080
} catch (error) {
8181
let errorText = ''
8282
if (error.response) errorText = error.response.data.error
83+
if (errorText.message) errorText = errorText.message
8384
helpers.UI.showSnackbar(`Error: ${errorText}`, true)
8485
Log.error(errorText, error.response || error)
8586
yield put({ type: SAVE_EDIT_ACCOUNT.ERROR, error })

Diff for: src/controllers/api/apiUtils.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ apiUtils.sendApiSuccess = function (res, object) {
2424
}
2525

2626
apiUtils.sendApiError = function (res, errorNum, error) {
27-
return res.status(errorNum).json({ success: false, error: error })
27+
return res.status(errorNum).json({ success: false, error })
2828
}
2929
apiUtils.sendApiError_InvalidPostData = function (res) {
3030
return apiUtils.sendApiError(res, 400, 'Invalid Post Data')

0 commit comments

Comments
 (0)