183 lines
5.1 KiB
TypeScript
Raw Normal View History

2019-12-07 13:28:52 +02:00
import React from 'react';
import { FormattedMessage as Message, defineMessages } from 'react-intl';
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 17:22:19 +03:00
2019-12-12 09:26:23 +02:00
import Context from '../Context';
2017-09-09 17:22:19 +03:00
import Instructions from './instructions';
import KeyForm from './keyForm';
2020-06-04 19:54:33 +03:00
import Confirmation from './Confirmation';
2017-09-09 17:22:19 +03:00
const STEPS_TOTAL = 3;
export type MfaStep = 0 | 1 | 2;
2017-09-09 17:22:19 +03:00
type Props = {
2020-05-24 02:08:24 +03:00
onChangeStep: (nextStep: number) => void;
confirmationForm: FormModel;
onSubmit: (form: FormModel, sendData: () => Promise<void>) => Promise<void>;
onComplete: () => void;
step: MfaStep;
2017-09-09 17:22:19 +03:00
};
const labels = defineMessages({
theAppIsInstalled: 'App has been installed',
ready: 'Ready',
enable: 'Enable',
});
interface State {
2020-05-24 02:08:24 +03:00
isLoading: boolean;
activeStep: MfaStep;
secret: string;
qrCodeSrc: string;
}
2019-12-10 09:47:32 +02:00
export default class MfaEnable extends React.PureComponent<Props, State> {
2020-05-24 02:08:24 +03:00
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: '',
};
confirmationFormEl: Form | null;
componentDidMount() {
this.syncState(this.props);
2019-12-10 09:47:32 +02:00
}
2020-05-24 02:08:24 +03:00
static getDerivedStateFromProps(props: Props, state: State) {
if (props.step !== state.activeStep) {
return {
activeStep: props.step,
};
}
return null;
2017-09-09 17:22:19 +03:00
}
2020-05-24 02:08:24 +03:00
componentDidUpdate() {
this.syncState(this.props);
}
2017-09-09 17:22:19 +03:00
2020-05-24 02:08:24 +03:00
render() {
const { activeStep, isLoading } = this.state;
const stepsData = [
{
buttonLabel: labels.theAppIsInstalled,
2020-05-24 02:08:24 +03:00
buttonAction: () => this.nextStep(),
},
{
buttonLabel: labels.ready,
2020-05-24 02:08:24 +03:00
buttonAction: () => this.nextStep(),
},
{
buttonLabel: labels.enable,
2020-05-24 02:08:24 +03:00
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>
<Message {...buttonLabel} />
</Button>
2020-05-24 02:08:24 +03:00
</div>
</div>
);
2017-09-09 17:22:19 +03:00
}
2020-05-24 02:08:24 +03:00
renderStepForms() {
const { activeStep, secret, qrCodeSrc } = this.state;
return (
<SlideMotion activeStep={activeStep}>
<Instructions key="step1" />
<KeyForm key="step2" secret={secret} qrCodeSrc={qrCodeSrc} />
<Confirmation
key="step3"
form={this.props.confirmationForm}
formRef={(el) => (this.confirmationFormEl = el)}
onSubmit={this.onTotpSubmit}
onInvalid={() => this.forceUpdate()}
/>
</SlideMotion>
);
}
syncState(props: Props) {
const { isLoading, qrCodeSrc } = this.state;
if (props.step === 1 && !isLoading && !qrCodeSrc) {
this.setState({ isLoading: true });
2017-09-09 17:22:19 +03:00
2020-05-24 02:08:24 +03:00
getSecret(this.context.userId).then((resp) => {
this.setState({
isLoading: false,
secret: resp.secret,
qrCodeSrc: resp.qr,
});
});
}
}
2017-09-09 17:22:19 +03:00
2020-05-24 02:08:24 +03:00
nextStep() {
const nextStep = this.state.activeStep + 1;
2017-09-09 17:22:19 +03:00
2020-05-24 02:08:24 +03:00
if (nextStep < STEPS_TOTAL) {
this.props.onChangeStep(nextStep);
2017-09-09 17:22:19 +03:00
}
2020-05-24 02:08:24 +03:00
}
2017-09-09 17:22:19 +03:00
2020-05-24 02:08:24 +03:00
onTotpSubmit = (form: FormModel): Promise<void> => {
this.setState({ isLoading: true });
return this.props
.onSubmit(form, () => {
const data = form.serialize();
return enableMFA(this.context.userId, data.totp, data.password);
})
.then(() => this.props.onComplete())
.catch((resp) => {
const { errors } = resp || {};
if (errors) {
return Promise.reject(errors);
}
logger.error('MFA: Unexpected form submit result', {
resp,
});
})
.finally(() => this.setState({ isLoading: false }));
};
2017-09-09 17:22:19 +03:00
}