import React from 'react'; import { connect } from 'react-redux'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import QRCode from 'qrcode.react'; import Column from '../ui/components/column'; import ColumnSubheading from '../ui/components/column_subheading'; import LoadingIndicator from 'soapbox/components/loading_indicator'; import Button from 'soapbox/components/button'; import { changeSetting, getSettings } from 'soapbox/actions/settings'; import snackbar from 'soapbox/actions/snackbar'; import { SimpleForm, SimpleInput, FieldsGroup, TextInput, } from 'soapbox/features/forms'; import { fetchBackupCodes, fetchToptSetup, confirmToptSetup, fetchUserMfaSettings, disableToptSetup, } from '../../actions/mfa'; /* Security settings page for user account Routed to /auth/mfa Includes following features: - Set up Multi-factor Auth */ const messages = defineMessages({ heading: { id: 'column.security', defaultMessage: 'Security' }, subheading: { id: 'column.mfa', defaultMessage: 'Multi-Factor Authentication' }, mfa_cancel_button: { id: 'column.mfa_cancel', defaultMessage: 'Cancel' }, mfa_setup_button: { id: 'column.mfa_setup', defaultMessage: 'Proceed to Setup' }, mfa_setup_confirm_button: { id: 'column.mfa_confirm_button', defaultMessage: 'Confirm' }, mfa_setup_disable_button: { id: 'column.mfa_disable_button', defaultMessage: 'Disable' }, passwordFieldLabel: { id: 'security.fields.password.label', defaultMessage: 'Password' }, confirmFail: { id: 'security.confirm.fail', defaultMessage: 'Incorrect code or password. Try again.' }, qrFail: { id: 'security.qr.fail', defaultMessage: 'Failed to fetch setup key' }, codesFail: { id: 'security.codes.fail', defaultMessage: 'Failed to fetch backup codes' }, disableFail: { id: 'security.disable.fail', defaultMessage: 'Incorrect password. Try again.' }, }); const mapStateToProps = state => ({ backup_codes: state.getIn(['auth', 'backup_codes', 'codes']), settings: getSettings(state), }); export default @connect(mapStateToProps) @injectIntl 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 = { router: PropTypes.object, }; static propTypes = { intl: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, settings: ImmutablePropTypes.map.isRequired, }; state = { displayOtpForm: false, } handleSetupProceedClick = e => { e.preventDefault(); this.setState({ displayOtpForm: true }); } render() { const { intl, settings } = this.props; const { displayOtpForm } = this.state; return ( { settings.get('otpEnabled') === true && } { settings.get('otpEnabled') === false && } { settings.get('otpEnabled') === false && displayOtpForm && } ); } } @connect() @injectIntl class DisableOtpForm extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object, }; static propTypes = { intl: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, }; state = { password: '', } handleInputChange = e => { this.setState({ [e.target.name]: e.target.value }); } handleOtpDisableClick = e => { e.preventDefault(); const { password } = this.state; const { dispatch, intl } = this.props; dispatch(disableToptSetup(password)).then(response => { this.context.router.history.push('../auth/edit'); dispatch(changeSetting(['otpEnabled'], false)); }).catch(error => { dispatch(snackbar.error(intl.formatMessage(messages.disableFail))); }); } render() { const { intl } = this.props; return (

); } } @connect() @injectIntl class EnableOtpForm extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object, }; static propTypes = { intl: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, }; state = { backupCodes: [], } componentDidMount() { const { dispatch, intl } = this.props; dispatch(fetchBackupCodes()).then(response => { this.setState({ backupCodes: response.data.codes }); }).catch(error => { dispatch(snackbar.error(intl.formatMessage(messages.codesFail))); }); } handleCancelClick = e => { this.context.router.history.push('../auth/edit'); } render() { const { intl } = this.props; const { backupCodes, displayOtpForm } = this.state; return (

{ backupCodes.length ?
{backupCodes.map((code, i) => (
{code}
))}
: }
{ !displayOtpForm &&
}
); } } @connect() @injectIntl class OtpConfirmForm extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object, }; static propTypes = { intl: PropTypes.object.isRequired, }; state = { password: '', done: false, code: '', qrCodeURI: '', confirm_key: '', } componentDidMount() { const { dispatch, intl } = this.props; dispatch(fetchToptSetup()).then(response => { this.setState({ qrCodeURI: response.data.provisioning_uri, confirm_key: response.data.key }); }).catch(error => { dispatch(snackbar.error(intl.formatMessage(messages.qrFail))); }); } handleInputChange = e => { this.setState({ [e.target.name]: e.target.value }); } handleOtpConfirmClick = e => { e.preventDefault(); const { code, password } = this.state; const { dispatch, intl } = this.props; dispatch(confirmToptSetup(code, password)).then(response => { dispatch(changeSetting(['otpEnabled'], true)); }).catch(error => { dispatch(snackbar.error(intl.formatMessage(messages.confirmFail))); }); } render() { const { intl } = this.props; const { qrCodeURI, confirm_key } = this.state; return (

{confirm_key}

); } }