2017-07-22 21:27:38 +05:30
|
|
|
// @flow
|
|
|
|
import React, { Component } from 'react';
|
|
|
|
|
|
|
|
import { FormattedMessage as Message } from 'react-intl';
|
|
|
|
import Helmet from 'react-helmet';
|
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
import { Button, FormModel } from 'components/ui/form';
|
2017-07-22 21:27:38 +05:30
|
|
|
import { BackButton } from 'components/profile/ProfileForm';
|
|
|
|
import styles from 'components/profile/profileForm.scss';
|
|
|
|
import Stepper from 'components/ui/stepper';
|
|
|
|
import { ScrollMotion } from 'components/ui/motion';
|
2017-08-20 21:15:21 +05:30
|
|
|
import logger from 'services/logger';
|
2017-08-02 01:30:02 +05:30
|
|
|
import mfa from 'services/api/mfa';
|
2017-07-22 21:27:38 +05:30
|
|
|
|
|
|
|
import Instructions from './instructions';
|
|
|
|
import KeyForm from './keyForm';
|
|
|
|
import Confirmation from './confirmation';
|
|
|
|
import messages from './MultiFactorAuth.intl.json';
|
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
import type { Form } from 'components/ui/form';
|
|
|
|
|
2017-07-22 21:27:38 +05:30
|
|
|
const STEPS_TOTAL = 3;
|
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
export type MfaStep = 0|1|2;
|
2017-07-22 21:27:38 +05:30
|
|
|
type Props = {
|
|
|
|
onChangeStep: Function,
|
|
|
|
lang: string,
|
|
|
|
email: string,
|
2017-08-20 21:15:21 +05:30
|
|
|
confirmationForm: FormModel,
|
|
|
|
onSubmit: (form: FormModel, sendData: () => Promise<*>) => Promise<*>,
|
|
|
|
onComplete: Function,
|
|
|
|
step: MfaStep
|
2017-07-22 21:27:38 +05:30
|
|
|
};
|
|
|
|
|
2017-08-23 02:01:41 +05:30
|
|
|
export default class MultiFactorAuth extends Component<Props, {
|
|
|
|
isLoading: bool,
|
|
|
|
activeStep: MfaStep,
|
|
|
|
secret: string,
|
|
|
|
qrCodeSrc: string
|
|
|
|
}> {
|
2017-07-22 21:27:38 +05:30
|
|
|
static defaultProps = {
|
2017-08-20 21:15:21 +05:30
|
|
|
confirmationForm: new FormModel(),
|
2017-07-22 21:27:38 +05:30
|
|
|
step: 0
|
|
|
|
};
|
|
|
|
|
|
|
|
state: {
|
2017-08-02 01:30:02 +05:30
|
|
|
isLoading: bool,
|
2017-08-20 21:15:21 +05:30
|
|
|
activeStep: MfaStep,
|
2017-08-02 01:30:02 +05:30
|
|
|
secret: string,
|
2017-08-20 21:15:21 +05:30
|
|
|
qrCodeSrc: string
|
2017-07-22 21:27:38 +05:30
|
|
|
} = {
|
2017-08-02 01:30:02 +05:30
|
|
|
isLoading: false,
|
2017-07-22 21:27:38 +05:30
|
|
|
activeStep: this.props.step,
|
2017-08-02 01:30:02 +05:30
|
|
|
qrCodeSrc: '',
|
2017-08-20 21:15:21 +05:30
|
|
|
secret: ''
|
2017-07-22 21:27:38 +05:30
|
|
|
};
|
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
confirmationFormEl: ?Form;
|
|
|
|
|
2017-08-04 10:21:41 +05:30
|
|
|
componentWillMount() {
|
|
|
|
this.syncState(this.props);
|
|
|
|
}
|
|
|
|
|
2017-07-22 21:27:38 +05:30
|
|
|
componentWillReceiveProps(nextProps: Props) {
|
2017-08-04 10:21:41 +05:30
|
|
|
this.syncState(nextProps);
|
2017-07-22 21:27:38 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2017-08-02 01:30:02 +05:30
|
|
|
const {activeStep, isLoading} = this.state;
|
2017-07-22 21:27:38 +05:30
|
|
|
|
|
|
|
const stepsData = [
|
|
|
|
{
|
2017-08-20 21:15:21 +05:30
|
|
|
buttonLabel: messages.theAppIsInstalled,
|
|
|
|
buttonAction: () => this.nextStep()
|
2017-07-22 21:27:38 +05:30
|
|
|
},
|
|
|
|
{
|
2017-08-20 21:15:21 +05:30
|
|
|
buttonLabel: messages.ready,
|
|
|
|
buttonAction: () => this.nextStep()
|
2017-07-22 21:27:38 +05:30
|
|
|
},
|
|
|
|
{
|
2017-08-20 21:15:21 +05:30
|
|
|
buttonLabel: messages.enableTwoFactorAuth,
|
|
|
|
buttonAction: () => this.confirmationFormEl && this.confirmationFormEl.submit()
|
2017-07-22 21:27:38 +05:30
|
|
|
}
|
|
|
|
];
|
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
const {buttonLabel, buttonAction} = stepsData[activeStep];
|
2017-07-22 21:27:38 +05:30
|
|
|
|
|
|
|
return (
|
2017-08-20 21:15:21 +05:30
|
|
|
<div className={styles.contentWithBackButton}>
|
|
|
|
<BackButton />
|
|
|
|
|
|
|
|
<div className={styles.form}>
|
|
|
|
<div className={styles.formBody}>
|
|
|
|
<Message {...messages.mfaTitle}>
|
|
|
|
{(pageTitle) => (
|
|
|
|
<h3 className={styles.title}>
|
|
|
|
<Helmet title={pageTitle} />
|
|
|
|
{pageTitle}
|
|
|
|
</h3>
|
|
|
|
)}
|
|
|
|
</Message>
|
|
|
|
|
|
|
|
<div className={styles.formRow}>
|
|
|
|
<p className={styles.description}>
|
|
|
|
<Message {...messages.mfaDescription} />
|
|
|
|
</p>
|
2017-07-22 21:27:38 +05:30
|
|
|
</div>
|
|
|
|
</div>
|
2017-08-20 21:15:21 +05:30
|
|
|
</div>
|
2017-07-22 21:27:38 +05:30
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
<div className={styles.stepper}>
|
|
|
|
<Stepper totalSteps={STEPS_TOTAL} activeStep={activeStep} />
|
|
|
|
</div>
|
2017-07-22 21:27:38 +05:30
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
<div className={styles.form}>
|
|
|
|
{this.renderStepForms()}
|
2017-07-22 21:27:38 +05:30
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
<Button
|
|
|
|
color="green"
|
|
|
|
onClick={buttonAction}
|
|
|
|
loading={isLoading}
|
|
|
|
block
|
|
|
|
label={buttonLabel}
|
|
|
|
/>
|
2017-07-22 21:27:38 +05:30
|
|
|
</div>
|
2017-08-20 21:15:21 +05:30
|
|
|
</div>
|
2017-07-22 21:27:38 +05:30
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
renderStepForms() {
|
2017-08-02 01:30:02 +05:30
|
|
|
const {activeStep, secret, qrCodeSrc} = this.state;
|
2017-07-22 21:27:38 +05:30
|
|
|
|
|
|
|
return (
|
|
|
|
<ScrollMotion activeStep={activeStep}>
|
2017-08-20 21:15:21 +05:30
|
|
|
{[
|
|
|
|
<Instructions key="step1" />,
|
|
|
|
<KeyForm key="step2"
|
|
|
|
secret={secret}
|
|
|
|
qrCodeSrc={qrCodeSrc}
|
|
|
|
/>,
|
|
|
|
<Confirmation key="step3"
|
|
|
|
form={this.props.confirmationForm}
|
|
|
|
formRef={(el: Form) => this.confirmationFormEl = el}
|
|
|
|
onSubmit={this.onTotpSubmit}
|
|
|
|
onInvalid={() => this.forceUpdate()}
|
|
|
|
/>
|
|
|
|
]}
|
2017-07-22 21:27:38 +05:30
|
|
|
</ScrollMotion>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-08-04 10:21:41 +05:30
|
|
|
syncState(props: Props) {
|
|
|
|
if (props.step === 1) {
|
|
|
|
this.setState({isLoading: true});
|
2017-08-20 21:15:21 +05:30
|
|
|
|
2017-08-04 10:21:41 +05:30
|
|
|
mfa.getSecret().then((resp) => {
|
|
|
|
this.setState({
|
|
|
|
isLoading: false,
|
|
|
|
secret: resp.secret,
|
|
|
|
qrCodeSrc: resp.qr
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2017-08-20 21:15:21 +05:30
|
|
|
|
|
|
|
this.setState({
|
|
|
|
activeStep: typeof props.step === 'number' ? props.step : this.state.activeStep
|
|
|
|
});
|
2017-08-04 10:21:41 +05:30
|
|
|
}
|
|
|
|
|
2017-07-22 21:27:38 +05:30
|
|
|
nextStep() {
|
2017-08-20 21:15:21 +05:30
|
|
|
const nextStep = this.state.activeStep + 1;
|
2017-07-22 21:27:38 +05:30
|
|
|
|
|
|
|
if (nextStep < STEPS_TOTAL) {
|
|
|
|
this.props.onChangeStep(nextStep);
|
2017-08-20 21:15:21 +05:30
|
|
|
} else {
|
|
|
|
this.props.onComplete();
|
2017-07-22 21:27:38 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
onTotpSubmit = (form: FormModel): Promise<*> => {
|
|
|
|
this.setState({isLoading: true});
|
2017-07-22 21:27:38 +05:30
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
return this.props.onSubmit(
|
|
|
|
form,
|
|
|
|
() => {
|
|
|
|
const data = form.serialize();
|
2017-07-22 21:27:38 +05:30
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
return mfa.enable(data);
|
|
|
|
}
|
|
|
|
)
|
2017-08-23 02:30:10 +05:30
|
|
|
.catch((resp) => {
|
|
|
|
const {errors} = resp || {};
|
2017-07-22 21:27:38 +05:30
|
|
|
|
2017-08-23 02:30:10 +05:30
|
|
|
if (errors) {
|
|
|
|
return Promise.reject(errors);
|
|
|
|
}
|
2017-07-22 21:27:38 +05:30
|
|
|
|
2017-08-23 02:30:10 +05:30
|
|
|
logger.error('MFA: Unexpected form submit result', {
|
|
|
|
resp
|
|
|
|
});
|
|
|
|
})
|
|
|
|
.finally(() => this.setState({isLoading: false}));
|
2017-07-22 21:27:38 +05:30
|
|
|
};
|
|
|
|
}
|