Skip to content

Commit

Permalink
refactor(accounts): password security enhancements
Browse files Browse the repository at this point in the history
  • Loading branch information
polonel committed May 18, 2022
1 parent d107f12 commit 13dd6c6
Show file tree
Hide file tree
Showing 21 changed files with 579 additions and 344 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"body-parser": "1.19.2",
"busboy": "1.4.0",
"chance": "1.1.8",
"check-password-strength": "2.0.5",
"cheerio": "1.0.0-rc.10",
"clone": "2.1.2",
"clsx": "1.1.1",
Expand Down
6 changes: 6 additions & 0 deletions src/client/components/Nav/Sidebar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,12 @@ class Sidebar extends React.Component {
href='/settings'
active={activeSubItem === 'settings-general'}
/>
<SubmenuItem
text='Accounts'
icon='tune'
href='/settings/accounts'
active={activeSubItem === 'settings-accounts'}
/>
<SubmenuItem
text='Appearance'
icon='style'
Expand Down
6 changes: 3 additions & 3 deletions src/client/containers/Modals/CreateAccountModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,9 @@ class CreateAccountModal extends React.Component {
name={'password_confirmation'}
value={this.password}
onChange={e => this.onInputChanged(e, 'password')}
data-validation={'length'}
data-validation-length={'min6'}
data-validation-error-msg={'Password must contain at least 6 characters.'}
data-validation={this.props.common.accountsPasswordComplexity ? 'length' : 'none'}
data-validation-length={'min8'}
data-validation-error-msg={'Password must contain at least 8 characters.'}
/>
</div>
<div className='uk-float-left uk-width-1-2'>
Expand Down
146 changes: 146 additions & 0 deletions src/client/containers/Settings/Accounts/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* . .o8 oooo
* .o8 "888 `888
* .o888oo oooo d8b oooo oooo .oooo888 .ooooo. .oooo.o 888 oooo
* 888 `888""8P `888 `888 d88' `888 d88' `88b d88( "8 888 .8P'
* 888 888 888 888 888 888 888ooo888 `"Y88b. 888888.
* 888 . 888 888 888 888 888 888 .o o. )88b 888 `88b.
* "888" d888b `V88V"V8P' `Y8bod88P" `Y8bod8P' 8""888P' o888o o888o
* ========================================================================
* Author: Chris Brame
* Updated: 5/17/22 2:20 PM
* Copyright (c) 2014-2022. All rights reserved.
*/

import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { updateSetting, updateMultipleSettings } from 'actions/settings'

import Button from 'components/Button'
import SettingItem from 'components/Settings/SettingItem'

import helpers from 'lib/helpers'
import axios from 'axios'
import Log from '../../../logger'
import EnableSwitch from 'components/Settings/EnableSwitch'
import { observer } from 'mobx-react'
import { makeObservable, observable } from 'mobx'
import UIKit from 'uikit'

@observer
class AccountsSettingsContainer extends React.Component {
@observable passwordComplexityEnabled = false
@observable allowUserRegistrationEnabled = false

constructor (props) {
super(props)

makeObservable(this)

this.state = {
restarting: false
}

this.restartServer = this.restartServer.bind(this)
}

componentDidMount () {
// helpers.UI.inputs()
}

componentDidUpdate (prevProps) {
// helpers.UI.reRenderInputs()
if (prevProps.settings !== this.props.settings) {
if (this.passwordComplexityEnabled !== this.getSetting('accountsPasswordComplexity'))
this.passwordComplexityEnabled = this.getSetting('accountsPasswordComplexity')
if (this.allowUserRegistrationEnabled !== this.getSetting('allowUserRegistration'))
this.allowUserRegistrationEnabled = this.getSetting('allowUserRegistration')
}
}

restartServer () {
this.setState({ restarting: true })

const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
axios
.post(
'/api/v1/admin/restart',
{},
{
headers: {
'CSRF-TOKEN': token
}
}
)
.catch(error => {
helpers.hideLoader()
Log.error(error.responseText)
Log.error('Unable to restart server. Server must run under PM2 and Account must have admin rights.')
helpers.UI.showSnackbar('Unable to restart server. Are you an Administrator?', true)
})
.then(() => {
this.setState({ restarting: false })
})
}

getSetting (stateName) {
return this.props.settings.getIn(['settings', stateName, 'value'])
? this.props.settings.getIn(['settings', stateName, 'value'])
: ''
}

updateSetting (stateName, name, value) {
this.props.updateSetting({ stateName, name, value })
}

render () {
const { active } = this.props
return (
<div className={active ? 'active' : 'hide'}>
<SettingItem
title='Allow User Registration'
subtitle='Allow users to create accounts on the login screen.'
component={
<EnableSwitch
stateName='allowUserRegistration'
label='Enable'
checked={this.allowUserRegistrationEnabled}
onChange={e => {
this.updateSetting('allowUserRegistration', 'allowUserRegistration:enable', e.target.checked)
}}
/>
}
/>
<SettingItem
title={'Password Complexity'}
subtitle={'Require users passwords to meet minimum password complexity'}
tooltip={'Minimum 8 characters with uppercase and numeric.'}
component={
<EnableSwitch
stateName={'accountsPasswordComplexity'}
label={'Enable'}
checked={this.passwordComplexityEnabled}
onChange={e => {
this.updateSetting('accountsPasswordComplexity', 'accountsPasswordComplexity:enable', e.target.checked)
}}
/>
}
/>
</div>
)
}
}

AccountsSettingsContainer.propTypes = {
active: PropTypes.bool.isRequired,
updateSetting: PropTypes.func.isRequired,
updateMultipleSettings: PropTypes.func.isRequired,
settings: PropTypes.object.isRequired
}

const mapStateToProps = state => ({
settings: state.settings.settings
})

export default connect(mapStateToProps, { updateSetting, updateMultipleSettings })(AccountsSettingsContainer)
16 changes: 0 additions & 16 deletions src/client/containers/Settings/General/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,6 @@ class GeneralSettings extends React.Component {
/>
)

const AllowUserRegistration = (
<EnableSwitch
stateName='allowUserRegistration'
label='Enable'
checked={this.getSettingsValue('allowUserRegistration')}
onChange={e => {
this.updateSetting('allowUserRegistration', 'allowUserRegistration:enable', e.target.checked)
}}
/>
)

return (
<div className={active ? 'active' : 'hide'}>
<SettingItem
Expand Down Expand Up @@ -184,11 +173,6 @@ class GeneralSettings extends React.Component {
</ZoneBox>
</Zone>
</SettingItem>
<SettingItem
title='Allow User Registration'
subtitle='Allow users to create accounts on the login screen.'
component={AllowUserRegistration}
/>
</div>
)
}
Expand Down
9 changes: 9 additions & 0 deletions src/client/containers/Settings/SettingsContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { fetchSettings } from 'actions/settings'
import Menu from 'components/Settings/Menu'
import MenuItem from 'components/Settings/MenuItem'
import GeneralSettings from './General'
import AccountsSettings from './Accounts'
import AppearanceSettings from './Appearance'
import PermissionsSettingsContainer from './Permissions'
import TicketsSettings from './Tickets'
Expand Down Expand Up @@ -89,6 +90,13 @@ class SettingsContainer extends React.Component {
this.onMenuItemClick(e, 'general')
}}
/>
<MenuItem
title='Accounts'
active={this.state.activeCategory === 'settings-accounts'}
onClick={e => {
this.onMenuItemClick(e, 'accounts')
}}
/>
<MenuItem
title='Appearance'
active={this.state.activeCategory === 'settings-appearance'}
Expand Down Expand Up @@ -156,6 +164,7 @@ class SettingsContainer extends React.Component {
<div className='page-wrapper full-height scrollable no-overflow-x' ref={i => (this.page = i)}>
<div className='settings-wrap'>
<GeneralSettings active={this.state.activeCategory === 'settings-general'} />
<AccountsSettings active={this.state.activeCategory === 'settings-accounts'} />
<AppearanceSettings active={this.state.activeCategory === 'settings-appearance'} />
<PermissionsSettingsContainer active={this.state.activeCategory === 'settings-permissions'} />
<TicketsSettings active={this.state.activeCategory === 'settings-tickets'} />
Expand Down
1 change: 1 addition & 0 deletions src/client/sagas/accounts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ function * saveEditAccount ({ payload }) {
} catch (error) {
let errorText = ''
if (error.response) errorText = error.response.data.error
if (errorText.message) errorText = errorText.message
helpers.UI.showSnackbar(`Error: ${errorText}`, true)
Log.error(errorText, error.response || error)
yield put({ type: SAVE_EDIT_ACCOUNT.ERROR, error })
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/api/apiUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ apiUtils.sendApiSuccess = function (res, object) {
}

apiUtils.sendApiError = function (res, errorNum, error) {
return res.status(errorNum).json({ success: false, error: error })
return res.status(errorNum).json({ success: false, error })
}
apiUtils.sendApiError_InvalidPostData = function (res) {
return apiUtils.sendApiError(res, 400, 'Invalid Post Data')
Expand Down
Loading

0 comments on commit 13dd6c6

Please sign in to comment.