accounts-frontend/packages/app/components/profile/multiFactorAuth/MfaEnable.tsx

181 lines
4.4 KiB
TypeScript
Raw Normal View History

2019-12-07 16:58:52 +05:30
import React from 'react';
import { Button, FormModel } from 'app/components/ui/form';
import styles from 'app/components/profile/profileForm.scss';
import Stepper from 'app/components/ui/stepper';
import { SlideMotion } from 'app/components/ui/motion';
import { ScrollIntoView } from 'app/components/ui/scroll';
import logger from 'app/services/logger';
import { getSecret, enable as enableMFA } from 'app/services/api/mfa';
import { Form } from 'app/components/ui/form';
2017-09-09 19:52:19 +05:30
2019-12-12 12:56:23 +05:30
import Context from '../Context';
2017-09-09 19:52:19 +05:30
import Instructions from './instructions';
import KeyForm from './keyForm';
import Confirmation from './confirmation';
import messages from './MultiFactorAuth.intl.json';
const STEPS_TOTAL = 3;
export type MfaStep = 0 | 1 | 2;
2017-09-09 19:52:19 +05:30
type Props = {
2019-12-07 16:58:52 +05:30
onChangeStep: (nextStep: number) => void;
confirmationForm: FormModel;
onSubmit: (form: FormModel, sendData: () => Promise<void>) => Promise<void>;
onComplete: () => void;
step: MfaStep;
2017-09-09 19:52:19 +05:30
};
interface State {
isLoading: boolean;
activeStep: MfaStep;
secret: string;
qrCodeSrc: string;
}
2019-12-10 13:17:32 +05:30
export default class MfaEnable extends React.PureComponent<Props, State> {
2019-12-12 12:56:23 +05:30
static contextType = Context;
/* TODO: use declare */ context: React.ContextType<typeof Context>;
static defaultProps = {
confirmationForm: new FormModel(),
step: 0,
};
state = {
isLoading: false,
activeStep: this.props.step,
qrCodeSrc: '',
secret: '',
};
2019-12-07 16:58:52 +05:30
confirmationFormEl: Form | null;
2019-12-10 13:17:32 +05:30
componentDidMount() {
this.syncState(this.props);
}
2019-12-10 13:17:32 +05:30
static getDerivedStateFromProps(props: Props, state: State) {
if (typeof props.step === 'number' && props.step !== state.activeStep) {
return {
activeStep: props.step,
};
}
return null;
}
componentDidUpdate() {
this.syncState(this.props);
}
render() {
const { activeStep, isLoading } = this.state;
const stepsData = [
{
buttonLabel: messages.theAppIsInstalled,
buttonAction: () => this.nextStep(),
},
{
buttonLabel: messages.ready,
buttonAction: () => this.nextStep(),
},
{
buttonLabel: messages.enable,
buttonAction: () =>
this.confirmationFormEl && this.confirmationFormEl.submit(),
},
];
const { buttonLabel, buttonAction } = stepsData[activeStep];
return (
<div>
<div className={styles.stepper}>
<Stepper totalSteps={STEPS_TOTAL} activeStep={activeStep} />
</div>
<div className={styles.form}>
{activeStep > 0 ? <ScrollIntoView /> : null}
{this.renderStepForms()}
<Button
color="green"
onClick={buttonAction}
loading={isLoading}
block
label={buttonLabel}
/>
</div>
</div>
);
}
renderStepForms() {
const { activeStep, secret, qrCodeSrc } = this.state;
return (
<SlideMotion activeStep={activeStep}>
2019-12-10 13:17:32 +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()}
/>
</SlideMotion>
);
}
syncState(props: Props) {
2019-12-10 13:17:32 +05:30
const { isLoading, qrCodeSrc } = this.state;
if (props.step === 1 && !isLoading && !qrCodeSrc) {
this.setState({ isLoading: true });
getSecret(this.context.userId).then(resp => {
this.setState({
isLoading: false,
secret: resp.secret,
qrCodeSrc: resp.qr,
});
});
2017-09-09 19:52:19 +05:30
}
}
2017-09-09 19:52:19 +05:30
nextStep() {
const nextStep = this.state.activeStep + 1;
2017-09-09 19:52:19 +05:30
if (nextStep < STEPS_TOTAL) {
this.props.onChangeStep(nextStep);
2017-09-09 19:52:19 +05:30
}
}
2017-09-09 19:52:19 +05:30
2019-12-07 16:58:52 +05:30
onTotpSubmit = (form: FormModel): Promise<void> => {
this.setState({ isLoading: true });
2017-09-09 19:52:19 +05:30
return this.props
.onSubmit(form, () => {
const data = form.serialize();
2017-09-09 19:52:19 +05:30
return enableMFA(this.context.userId, data.totp, data.password);
})
.then(() => this.props.onComplete())
.catch(resp => {
const { errors } = resp || {};
2017-09-09 19:52:19 +05:30
if (errors) {
return Promise.reject(errors);
2017-09-09 19:52:19 +05:30
}
logger.error('MFA: Unexpected form submit result', {
resp,
});
})
.finally(() => this.setState({ isLoading: false }));
};
2017-09-09 19:52:19 +05:30
}