Refactor MFA setup, fixes #792
This commit is contained in:
parent
8192c93873
commit
2fd5e5cd35
|
@ -1,180 +1,80 @@
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
|
|
||||||
export const TOTP_SETTINGS_FETCH_REQUEST = 'TOTP_SETTINGS_FETCH_REQUEST';
|
export const MFA_FETCH_REQUEST = 'MFA_FETCH_REQUEST';
|
||||||
export const TOTP_SETTINGS_FETCH_SUCCESS = 'TOTP_SETTINGS_FETCH_SUCCESS';
|
export const MFA_FETCH_SUCCESS = 'MFA_FETCH_SUCCESS';
|
||||||
export const TOTP_SETTINGS_FETCH_FAIL = 'TOTP_SETTINGS_FETCH_FAIL';
|
export const MFA_FETCH_FAIL = 'MFA_FETCH_FAIL';
|
||||||
|
|
||||||
export const BACKUP_CODES_FETCH_REQUEST = 'BACKUP_CODES_FETCH_REQUEST';
|
export const MFA_BACKUP_CODES_FETCH_REQUEST = 'MFA_BACKUP_CODES_FETCH_REQUEST';
|
||||||
export const BACKUP_CODES_FETCH_SUCCESS = 'BACKUP_CODES_FETCH_SUCCESS';
|
export const MFA_BACKUP_CODES_FETCH_SUCCESS = 'MFA_BACKUP_CODES_FETCH_SUCCESS';
|
||||||
export const BACKUP_CODES_FETCH_FAIL = 'BACKUP_CODES_FETCH_FAIL';
|
export const MFA_BACKUP_CODES_FETCH_FAIL = 'MFA_BACKUP_CODES_FETCH_FAIL';
|
||||||
|
|
||||||
export const TOTP_SETUP_FETCH_REQUEST = 'TOTP_SETUP_FETCH_REQUEST';
|
export const MFA_SETUP_REQUEST = 'MFA_SETUP_REQUEST';
|
||||||
export const TOTP_SETUP_FETCH_SUCCESS = 'TOTP_SETUP_FETCH_SUCCESS';
|
export const MFA_SETUP_SUCCESS = 'MFA_SETUP_SUCCESS';
|
||||||
export const TOTP_SETUP_FETCH_FAIL = 'TOTP_SETUP_FETCH_FAIL';
|
export const MFA_SETUP_FAIL = 'MFA_SETUP_FAIL';
|
||||||
|
|
||||||
export const CONFIRM_TOTP_REQUEST = 'CONFIRM_TOTP_REQUEST';
|
export const MFA_CONFIRM_REQUEST = 'MFA_CONFIRM_REQUEST';
|
||||||
export const CONFIRM_TOTP_SUCCESS = 'CONFIRM_TOTP_SUCCESS';
|
export const MFA_CONFIRM_SUCCESS = 'MFA_CONFIRM_SUCCESS';
|
||||||
export const CONFIRM_TOTP_FAIL = 'CONFIRM_TOTP_FAIL';
|
export const MFA_CONFIRM_FAIL = 'MFA_CONFIRM_FAIL';
|
||||||
|
|
||||||
export const DISABLE_TOTP_REQUEST = 'DISABLE_TOTP_REQUEST';
|
export const MFA_DISABLE_REQUEST = 'MFA_DISABLE_REQUEST';
|
||||||
export const DISABLE_TOTP_SUCCESS = 'DISABLE_TOTP_SUCCESS';
|
export const MFA_DISABLE_SUCCESS = 'MFA_DISABLE_SUCCESS';
|
||||||
export const DISABLE_TOTP_FAIL = 'DISABLE_TOTP_FAIL';
|
export const MFA_DISABLE_FAIL = 'MFA_DISABLE_FAIL';
|
||||||
|
|
||||||
export function fetchUserMfaSettings() {
|
export function fetchMfa() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({ type: TOTP_SETTINGS_FETCH_REQUEST });
|
dispatch({ type: MFA_FETCH_REQUEST });
|
||||||
return api(getState).get('/api/pleroma/accounts/mfa').then(response => {
|
return api(getState).get('/api/pleroma/accounts/mfa').then(({ data }) => {
|
||||||
dispatch({ type: TOTP_SETTINGS_FETCH_SUCCESS, totpEnabled: response.data.totp });
|
dispatch({ type: MFA_FETCH_SUCCESS, data });
|
||||||
return response;
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({ type: TOTP_SETTINGS_FETCH_FAIL });
|
dispatch({ type: MFA_FETCH_FAIL });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchUserMfaSettingsRequest() {
|
|
||||||
return {
|
|
||||||
type: TOTP_SETTINGS_FETCH_REQUEST,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchUserMfaSettingsSuccess() {
|
|
||||||
return {
|
|
||||||
type: TOTP_SETTINGS_FETCH_SUCCESS,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchUserMfaSettingsFail() {
|
|
||||||
return {
|
|
||||||
type: TOTP_SETTINGS_FETCH_FAIL,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchBackupCodes() {
|
export function fetchBackupCodes() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({ type: BACKUP_CODES_FETCH_REQUEST });
|
dispatch({ type: MFA_BACKUP_CODES_FETCH_REQUEST });
|
||||||
return api(getState).get('/api/pleroma/accounts/mfa/backup_codes').then(response => {
|
return api(getState).get('/api/pleroma/accounts/mfa/backup_codes').then(({ data }) => {
|
||||||
dispatch({ type: BACKUP_CODES_FETCH_SUCCESS, backup_codes: response.data });
|
dispatch({ type: MFA_BACKUP_CODES_FETCH_SUCCESS, data });
|
||||||
return response;
|
return data;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({ type: BACKUP_CODES_FETCH_FAIL });
|
dispatch({ type: MFA_BACKUP_CODES_FETCH_FAIL });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchBackupCodesRequest() {
|
export function setupMfa(method) {
|
||||||
return {
|
|
||||||
type: BACKUP_CODES_FETCH_REQUEST,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchBackupCodesSuccess(backup_codes, response) {
|
|
||||||
return {
|
|
||||||
type: BACKUP_CODES_FETCH_SUCCESS,
|
|
||||||
backup_codes: response.data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchBackupCodesFail(error) {
|
|
||||||
return {
|
|
||||||
type: BACKUP_CODES_FETCH_FAIL,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchToptSetup() {
|
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({ type: TOTP_SETUP_FETCH_REQUEST });
|
dispatch({ type: MFA_SETUP_REQUEST, method });
|
||||||
return api(getState).get('/api/pleroma/accounts/mfa/setup/totp').then(response => {
|
return api(getState).get(`/api/pleroma/accounts/mfa/setup/${method}`).then(({ data }) => {
|
||||||
dispatch({ type: TOTP_SETUP_FETCH_SUCCESS, totp_setup: response.data });
|
dispatch({ type: MFA_SETUP_SUCCESS, data });
|
||||||
return response;
|
return data;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({ type: TOTP_SETUP_FETCH_FAIL });
|
dispatch({ type: MFA_SETUP_FAIL });
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchToptSetupRequest() {
|
export function confirmMfa(method, code, password) {
|
||||||
return {
|
|
||||||
type: TOTP_SETUP_FETCH_REQUEST,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchToptSetupSuccess(totp_setup, response) {
|
|
||||||
return {
|
|
||||||
type: TOTP_SETUP_FETCH_SUCCESS,
|
|
||||||
totp_setup: response.data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchToptSetupFail(error) {
|
|
||||||
return {
|
|
||||||
type: TOTP_SETUP_FETCH_FAIL,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function confirmToptSetup(code, password) {
|
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({ type: CONFIRM_TOTP_REQUEST, code });
|
const params = { code, password };
|
||||||
return api(getState).post('/api/pleroma/accounts/mfa/confirm/totp', {
|
dispatch({ type: MFA_CONFIRM_REQUEST, method, code });
|
||||||
code,
|
return api(getState).post(`/api/pleroma/accounts/mfa/confirm/${method}`, params).then(() => {
|
||||||
password,
|
dispatch({ type: MFA_CONFIRM_SUCCESS, method, code });
|
||||||
}).then(response => {
|
|
||||||
dispatch({ type: CONFIRM_TOTP_SUCCESS });
|
|
||||||
return response;
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({ type: CONFIRM_TOTP_FAIL });
|
dispatch({ type: MFA_CONFIRM_FAIL, method, code, error });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function confirmToptRequest() {
|
export function disableMfa(method, password) {
|
||||||
return {
|
|
||||||
type: CONFIRM_TOTP_REQUEST,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function confirmToptSuccess(backup_codes, response) {
|
|
||||||
return {
|
|
||||||
type: CONFIRM_TOTP_SUCCESS,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function confirmToptFail(error) {
|
|
||||||
return {
|
|
||||||
type: CONFIRM_TOTP_FAIL,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function disableToptSetup(password) {
|
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({ type: DISABLE_TOTP_REQUEST });
|
dispatch({ type: MFA_DISABLE_REQUEST, method });
|
||||||
return api(getState).delete('/api/pleroma/accounts/mfa/totp', { data: { password } }).then(response => {
|
return api(getState).delete(`/api/pleroma/accounts/mfa/${method}`, { data: { password } }).then(response => {
|
||||||
dispatch({ type: DISABLE_TOTP_SUCCESS });
|
dispatch({ type: MFA_DISABLE_SUCCESS, method });
|
||||||
return response;
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({ type: DISABLE_TOTP_FAIL });
|
dispatch({ type: MFA_DISABLE_FAIL, method });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function disableToptRequest() {
|
|
||||||
return {
|
|
||||||
type: DISABLE_TOTP_REQUEST,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function disableToptSuccess(backup_codes, response) {
|
|
||||||
return {
|
|
||||||
type: DISABLE_TOTP_SUCCESS,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function disableToptFail(error) {
|
|
||||||
return {
|
|
||||||
type: DISABLE_TOTP_FAIL,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -30,7 +30,6 @@ export const defaultSettings = ImmutableMap({
|
||||||
locale: navigator.language.split(/[-_]/)[0] || 'en',
|
locale: navigator.language.split(/[-_]/)[0] || 'en',
|
||||||
showExplanationBox: true,
|
showExplanationBox: true,
|
||||||
explanationBox: true,
|
explanationBox: true,
|
||||||
otpEnabled: false,
|
|
||||||
autoloadTimelines: true,
|
autoloadTimelines: true,
|
||||||
autoloadMore: true,
|
autoloadMore: true,
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,9 @@ import {
|
||||||
deleteAccount,
|
deleteAccount,
|
||||||
} from 'soapbox/actions/security';
|
} from 'soapbox/actions/security';
|
||||||
import { fetchOAuthTokens, revokeOAuthTokenById } from 'soapbox/actions/security';
|
import { fetchOAuthTokens, revokeOAuthTokenById } from 'soapbox/actions/security';
|
||||||
import { fetchUserMfaSettings } from '../../actions/mfa';
|
import { fetchMfa } from '../../actions/mfa';
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
import { changeSetting, getSettings } from 'soapbox/actions/settings';
|
import { getSettings } from 'soapbox/actions/settings';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Security settings page for user account
|
Security settings page for user account
|
||||||
|
@ -64,6 +64,7 @@ const messages = defineMessages({
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
settings: getSettings(state),
|
settings: getSettings(state),
|
||||||
tokens: state.getIn(['security', 'tokens']),
|
tokens: state.getIn(['security', 'tokens']),
|
||||||
|
mfa: state.getIn(['security', 'mfa']),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
export default @connect(mapStateToProps)
|
||||||
|
@ -242,33 +243,30 @@ class ChangePasswordForm extends ImmutablePureComponent {
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class SetUpMfa extends ImmutablePureComponent {
|
class SetUpMfa extends ImmutablePureComponent {
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.props.dispatch(fetchUserMfaSettings()).then(response => {
|
|
||||||
this.props.dispatch(changeSetting(['otpEnabled'], response.data.settings.enabled));
|
|
||||||
}).catch(e => e);
|
|
||||||
}
|
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
router: PropTypes.object,
|
router: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
settings: ImmutablePropTypes.map.isRequired,
|
mfa: ImmutablePropTypes.map.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleMfaClick = e => {
|
handleMfaClick = e => {
|
||||||
this.context.router.history.push('../auth/mfa');
|
this.context.router.history.push('../auth/mfa');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.dispatch(fetchMfa());
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { intl, settings } = this.props;
|
const { intl, mfa } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SimpleForm>
|
<SimpleForm>
|
||||||
<h2>{intl.formatMessage(messages.mfaHeader)}</h2>
|
<h2>{intl.formatMessage(messages.mfaHeader)}</h2>
|
||||||
{ settings.get('otpEnabled') === false ?
|
{!mfa.getIn(['settings', 'totp']) ?
|
||||||
<div>
|
<div>
|
||||||
<p className='hint'>
|
<p className='hint'>
|
||||||
{intl.formatMessage(messages.mfa_setup_hint)}
|
{intl.formatMessage(messages.mfa_setup_hint)}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import Column from '../ui/components/column';
|
||||||
import ColumnSubheading from '../ui/components/column_subheading';
|
import ColumnSubheading from '../ui/components/column_subheading';
|
||||||
import LoadingIndicator from 'soapbox/components/loading_indicator';
|
import LoadingIndicator from 'soapbox/components/loading_indicator';
|
||||||
import Button from 'soapbox/components/button';
|
import Button from 'soapbox/components/button';
|
||||||
import { changeSetting, getSettings } from 'soapbox/actions/settings';
|
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
import ShowablePassword from 'soapbox/components/showable_password';
|
import ShowablePassword from 'soapbox/components/showable_password';
|
||||||
import {
|
import {
|
||||||
|
@ -18,11 +17,11 @@ import {
|
||||||
TextInput,
|
TextInput,
|
||||||
} from 'soapbox/features/forms';
|
} from 'soapbox/features/forms';
|
||||||
import {
|
import {
|
||||||
|
fetchMfa,
|
||||||
fetchBackupCodes,
|
fetchBackupCodes,
|
||||||
fetchToptSetup,
|
setupMfa,
|
||||||
confirmToptSetup,
|
confirmMfa,
|
||||||
fetchUserMfaSettings,
|
disableMfa,
|
||||||
disableToptSetup,
|
|
||||||
} from '../../actions/mfa';
|
} from '../../actions/mfa';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -44,26 +43,19 @@ const messages = defineMessages({
|
||||||
qrFail: { id: 'security.qr.fail', defaultMessage: 'Failed to fetch setup key' },
|
qrFail: { id: 'security.qr.fail', defaultMessage: 'Failed to fetch setup key' },
|
||||||
codesFail: { id: 'security.codes.fail', defaultMessage: 'Failed to fetch backup codes' },
|
codesFail: { id: 'security.codes.fail', defaultMessage: 'Failed to fetch backup codes' },
|
||||||
disableFail: { id: 'security.disable.fail', defaultMessage: 'Incorrect password. Try again.' },
|
disableFail: { id: 'security.disable.fail', defaultMessage: 'Incorrect password. Try again.' },
|
||||||
|
mfaDisableSuccess: { id: 'mfa.disable.success_message', defaultMessage: 'MFA disabled' },
|
||||||
|
mfaConfirmSuccess: { id: 'mfa.confirm.success_message', defaultMessage: 'MFA confirmed' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
backup_codes: state.getIn(['auth', 'backup_codes', 'codes']),
|
backup_codes: state.getIn(['auth', 'backup_codes', 'codes']),
|
||||||
settings: getSettings(state),
|
mfa: state.getIn(['security', 'mfa']),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
export default @connect(mapStateToProps)
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class MfaForm extends ImmutablePureComponent {
|
class MfaForm extends ImmutablePureComponent {
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.props.dispatch(fetchUserMfaSettings()).then(response => {
|
|
||||||
this.props.dispatch(changeSetting(['otpEnabled'], response.data.settings.enabled));
|
|
||||||
// this.setState({ otpEnabled: response.data.settings.enabled });
|
|
||||||
}).catch(e => e);
|
|
||||||
this.handleSetupProceedClick = this.handleSetupProceedClick.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
router: PropTypes.object,
|
router: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
@ -71,7 +63,7 @@ class MfaForm extends ImmutablePureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
settings: ImmutablePropTypes.map.isRequired,
|
mfa: ImmutablePropTypes.map.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -79,20 +71,29 @@ class MfaForm extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSetupProceedClick = e => {
|
handleSetupProceedClick = e => {
|
||||||
e.preventDefault();
|
|
||||||
this.setState({ displayOtpForm: true });
|
this.setState({ displayOtpForm: true });
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.dispatch(fetchMfa());
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { intl, settings } = this.props;
|
const { intl, mfa } = this.props;
|
||||||
const { displayOtpForm } = this.state;
|
const { displayOtpForm } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='lock' heading={intl.formatMessage(messages.heading)}>
|
<Column icon='lock' heading={intl.formatMessage(messages.heading)}>
|
||||||
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
|
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
|
||||||
{ settings.get('otpEnabled') === true && <DisableOtpForm />}
|
{mfa.getIn(['settings', 'totp']) ? (
|
||||||
{ settings.get('otpEnabled') === false && <EnableOtpForm handleSetupProceedClick={this.handleSetupProceedClick} />}
|
<DisableOtpForm />
|
||||||
{ settings.get('otpEnabled') === false && displayOtpForm && <OtpConfirmForm /> }
|
) : (
|
||||||
|
<>
|
||||||
|
<EnableOtpForm handleSetupProceedClick={this.handleSetupProceedClick} />
|
||||||
|
{displayOtpForm && <OtpConfirmForm />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -122,15 +123,17 @@ class DisableOtpForm extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOtpDisableClick = e => {
|
handleOtpDisableClick = e => {
|
||||||
e.preventDefault();
|
|
||||||
const { password } = this.state;
|
const { password } = this.state;
|
||||||
const { dispatch, intl } = this.props;
|
const { dispatch, intl } = this.props;
|
||||||
dispatch(disableToptSetup(password)).then(response => {
|
|
||||||
this.context.router.history.push('../auth/edit');
|
dispatch(disableMfa('totp', password)).then(() => {
|
||||||
dispatch(changeSetting(['otpEnabled'], false));
|
dispatch(snackbar.success(intl.formatMessage(messages.mfaDisableSuccess)));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(snackbar.error(intl.formatMessage(messages.disableFail)));
|
dispatch(snackbar.error(intl.formatMessage(messages.disableFail)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.context.router.history.push('../auth/edit');
|
||||||
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -176,8 +179,9 @@ class EnableOtpForm extends ImmutablePureComponent {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { dispatch, intl } = this.props;
|
const { dispatch, intl } = this.props;
|
||||||
dispatch(fetchBackupCodes()).then(response => {
|
|
||||||
this.setState({ backupCodes: response.data.codes });
|
dispatch(fetchBackupCodes()).then(({ codes: backupCodes }) => {
|
||||||
|
this.setState({ backupCodes });
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(snackbar.error(intl.formatMessage(messages.codesFail)));
|
dispatch(snackbar.error(intl.formatMessage(messages.codesFail)));
|
||||||
});
|
});
|
||||||
|
@ -207,26 +211,26 @@ class EnableOtpForm extends ImmutablePureComponent {
|
||||||
<FormattedMessage id='mfa.setup_recoverycodes' defaultMessage='Recovery codes' />
|
<FormattedMessage id='mfa.setup_recoverycodes' defaultMessage='Recovery codes' />
|
||||||
</h2>
|
</h2>
|
||||||
<div className='backup_codes'>
|
<div className='backup_codes'>
|
||||||
{ backupCodes.length ?
|
{backupCodes.length > 0 ? (
|
||||||
<div>
|
<div>
|
||||||
{backupCodes.map((code, i) => (
|
{backupCodes.map((code, i) => (
|
||||||
<div key={i} className='backup_code'>
|
<div key={i} className='backup_code'>
|
||||||
<div className='backup_code'>{code}</div>
|
<div className='backup_code'>{code}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div> :
|
</div>
|
||||||
|
) : (
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{ !displayOtpForm &&
|
{!displayOtpForm && (
|
||||||
<div className='security-settings-panel__setup-otp__buttons'>
|
<div className='security-settings-panel__setup-otp__buttons'>
|
||||||
<Button className='button button-secondary cancel' text={intl.formatMessage(messages.mfa_cancel_button)} onClick={this.handleCancelClick} />
|
<Button className='button button-secondary cancel' text={intl.formatMessage(messages.mfa_cancel_button)} onClick={this.handleCancelClick} />
|
||||||
{ backupCodes.length ?
|
{backupCodes.length > 0 && (
|
||||||
<Button className='button button-primary setup' text={intl.formatMessage(messages.mfa_setup_button)} onClick={this.props.handleSetupProceedClick} /> :
|
<Button className='button button-primary setup' text={intl.formatMessage(messages.mfa_setup_button)} onClick={this.props.handleSetupProceedClick} />
|
||||||
null
|
)}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</SimpleForm>
|
</SimpleForm>
|
||||||
);
|
);
|
||||||
|
@ -257,8 +261,9 @@ class OtpConfirmForm extends ImmutablePureComponent {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { dispatch, intl } = this.props;
|
const { dispatch, intl } = this.props;
|
||||||
dispatch(fetchToptSetup()).then(response => {
|
|
||||||
this.setState({ qrCodeURI: response.data.provisioning_uri, confirm_key: response.data.key });
|
dispatch(setupMfa('totp')).then(data => {
|
||||||
|
this.setState({ qrCodeURI: data.provisioning_uri, confirm_key: data.key });
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(snackbar.error(intl.formatMessage(messages.qrFail)));
|
dispatch(snackbar.error(intl.formatMessage(messages.qrFail)));
|
||||||
});
|
});
|
||||||
|
@ -269,14 +274,17 @@ class OtpConfirmForm extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOtpConfirmClick = e => {
|
handleOtpConfirmClick = e => {
|
||||||
e.preventDefault();
|
|
||||||
const { code, password } = this.state;
|
const { code, password } = this.state;
|
||||||
const { dispatch, intl } = this.props;
|
const { dispatch, intl } = this.props;
|
||||||
dispatch(confirmToptSetup(code, password)).then(response => {
|
|
||||||
dispatch(changeSetting(['otpEnabled'], true));
|
dispatch(confirmMfa('totp', code, password)).then(() => {
|
||||||
|
dispatch(snackbar.success(intl.formatMessage(messages.mfaConfirmSuccess)));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(snackbar.error(intl.formatMessage(messages.confirmFail)));
|
dispatch(snackbar.error(intl.formatMessage(messages.confirmFail)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.context.router.history.push('../auth/edit');
|
||||||
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -2,10 +2,22 @@ import {
|
||||||
FETCH_TOKENS_SUCCESS,
|
FETCH_TOKENS_SUCCESS,
|
||||||
REVOKE_TOKEN_SUCCESS,
|
REVOKE_TOKEN_SUCCESS,
|
||||||
} from '../actions/security';
|
} from '../actions/security';
|
||||||
|
import {
|
||||||
|
MFA_FETCH_SUCCESS,
|
||||||
|
MFA_CONFIRM_SUCCESS,
|
||||||
|
MFA_DISABLE_REQUEST,
|
||||||
|
MFA_DISABLE_SUCCESS,
|
||||||
|
MFA_DISABLE_FAIL,
|
||||||
|
} from '../actions/mfa';
|
||||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||||
|
|
||||||
const initialState = ImmutableMap({
|
const initialState = ImmutableMap({
|
||||||
tokens: ImmutableList(),
|
tokens: ImmutableList(),
|
||||||
|
mfa: ImmutableMap({
|
||||||
|
settings: ImmutableMap({
|
||||||
|
totp: false,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleteToken = (state, tokenId) => {
|
const deleteToken = (state, tokenId) => {
|
||||||
|
@ -14,12 +26,33 @@ const deleteToken = (state, tokenId) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const importMfa = (state, data) => {
|
||||||
|
return state.set('mfa', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const enableMfa = (state, method) => {
|
||||||
|
return state.setIn(['mfa', 'settings', method], true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const disableMfa = (state, method) => {
|
||||||
|
return state.setIn(['mfa', 'settings', method], false);
|
||||||
|
};
|
||||||
|
|
||||||
export default function security(state = initialState, action) {
|
export default function security(state = initialState, action) {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case FETCH_TOKENS_SUCCESS:
|
case FETCH_TOKENS_SUCCESS:
|
||||||
return state.set('tokens', fromJS(action.tokens));
|
return state.set('tokens', fromJS(action.tokens));
|
||||||
case REVOKE_TOKEN_SUCCESS:
|
case REVOKE_TOKEN_SUCCESS:
|
||||||
return deleteToken(state, action.id);
|
return deleteToken(state, action.id);
|
||||||
|
case MFA_FETCH_SUCCESS:
|
||||||
|
return importMfa(state, fromJS(action.data));
|
||||||
|
case MFA_CONFIRM_SUCCESS:
|
||||||
|
return enableMfa(state, action.method);
|
||||||
|
case MFA_DISABLE_REQUEST:
|
||||||
|
case MFA_DISABLE_SUCCESS:
|
||||||
|
return disableMfa(state, action.method);
|
||||||
|
case MFA_DISABLE_FAIL:
|
||||||
|
return enableMfa(state, action.method);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,6 @@
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
div {
|
|
||||||
display: block;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.security-warning {
|
.security-warning {
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
padding: 15px 20px;
|
padding: 15px 20px;
|
||||||
|
@ -44,10 +39,6 @@
|
||||||
.backup_code {
|
.backup_code {
|
||||||
margin: 5px auto;
|
margin: 5px auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-indicator {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.security-settings-panel__setup-otp__buttons {
|
.security-settings-panel__setup-otp__buttons {
|
||||||
|
|
Loading…
Reference in New Issue