1 import { useState } from 'react';
3 import { c } from 'ttag';
5 import { useUser } from '@proton/account/user/hooks';
6 import { useUserSettings } from '@proton/account/userSettings/hooks';
7 import { Button, InlineLinkButton } from '@proton/atoms';
8 import Form from '@proton/components/components/form/Form';
9 import type { ModalProps } from '@proton/components/components/modalTwo/Modal';
10 import Modal from '@proton/components/components/modalTwo/Modal';
11 import ModalContent from '@proton/components/components/modalTwo/ModalContent';
12 import ModalFooter from '@proton/components/components/modalTwo/ModalFooter';
13 import ModalHeader from '@proton/components/components/modalTwo/ModalHeader';
14 import Tabs from '@proton/components/components/tabs/Tabs';
15 import useFormErrors from '@proton/components/components/v2/useFormErrors';
16 import useApi from '@proton/components/hooks/useApi';
17 import useAuthentication from '@proton/components/hooks/useAuthentication';
18 import useLoading from '@proton/hooks/useLoading';
19 import { getMnemonicAuthInfo, reauthMnemonic } from '@proton/shared/lib/api/auth';
20 import { reauthByEmailVerification, reauthBySmsVerification } from '@proton/shared/lib/api/verify';
21 import type { InfoResponse } from '@proton/shared/lib/authentication/interface';
22 import { requiredValidator } from '@proton/shared/lib/helpers/formValidators';
23 import { mnemonicToBase64RandomBytes } from '@proton/shared/lib/mnemonic';
24 import { srpAuth } from '@proton/shared/lib/srp';
25 import clsx from '@proton/utils/clsx';
26 import isTruthy from '@proton/utils/isTruthy';
28 import { useIsSessionRecoveryInitiationAvailable } from '../../hooks/useSessionRecovery';
29 import MnemonicInputField, { useMnemonicInputValidation } from '../mnemonic/MnemonicInputField';
31 interface Props extends ModalProps {
32 onInitiateSessionRecoveryClick?: () => void;
34 availableRecoveryMethods: ('mnemonic' | 'email' | 'sms')[];
36 onSuccess: () => void;
39 const ReauthUsingRecoveryModal = ({
40 onInitiateSessionRecoveryClick,
42 availableRecoveryMethods,
50 const [user] = useUser();
51 const authentication = useAuthentication();
52 const { validator, onFormSubmit } = useFormErrors();
53 const [userSettings] = useUserSettings();
55 const [submitting, withSubmitting] = useLoading();
56 const [tabIndex, setTabIndex] = useState(0);
57 const [mnemonic, setMnemonic] = useState('');
58 const mnemonicValidation = useMnemonicInputValidation(mnemonic);
60 const currentMethod = availableRecoveryMethods[tabIndex];
62 const isSessionRecoveryInitiationAvailable = useIsSessionRecoveryInitiationAvailable();
64 const onSubmit = async () => {
65 if (!onFormSubmit()) {
69 if (currentMethod === 'email') {
70 await api(reauthByEmailVerification());
71 } else if (currentMethod === 'sms') {
72 await api(reauthBySmsVerification());
73 } else if (currentMethod === 'mnemonic') {
74 const persistent = authentication.getPersistent();
75 const username = user.Email || user.Name;
76 const randomBytes = await mnemonicToBase64RandomBytes(mnemonic);
77 const info = await api<InfoResponse>(getMnemonicAuthInfo(username));
81 config: reauthMnemonic({
83 PersistentCookies: persistent,
87 password: randomBytes,
96 const toProceed = c('Info').t`To proceed, we must verify the request.`;
97 const codeResetString = (value: string) => {
99 <b data-testid="recovery-modal-code-destination" key="bold-reset-method-value">
103 // translator: boldValue will be the recovery email address or recovery phone number in bold
104 return c('Info').jt`We’ll send a reset code to ${boldValue}.`;
106 const phraseString = c('Info').t`Enter your recovery phrase to change your password now.`;
109 <Modal onClose={onClose} as={Form} onSubmit={() => withSubmitting(onSubmit())} {...rest}>
110 <ModalHeader title={title || c('Title').t`Reset password`} subline={user.Email} />
112 {availableRecoveryMethods.length > 1 && (
113 <div className="mb-2">
114 {c('Info').t`To proceed, select an account recovery method so we can verify the request.`}
121 availableRecoveryMethods.includes('email') && {
122 title: c('Recovery method').t`Email`,
125 <div className={clsx(availableRecoveryMethods.length === 1 && 'mt-2')}>
126 {availableRecoveryMethods.length === 1 && toProceed}{' '}
127 {codeResetString(userSettings.Email.Value)}
130 {isSessionRecoveryInitiationAvailable && onInitiateSessionRecoveryClick && (
131 <InlineLinkButton className="mt-2" onClick={onInitiateSessionRecoveryClick}>
132 {c('Info').t`Can’t access your recovery email?`}
138 availableRecoveryMethods.includes('sms') && {
139 title: c('Recovery method').t`Phone number`,
142 <div className={clsx(availableRecoveryMethods.length === 1 && 'mt-2')}>
143 {availableRecoveryMethods.length === 1 && toProceed}{' '}
144 {codeResetString(userSettings.Phone.Value)}
147 {isSessionRecoveryInitiationAvailable && onInitiateSessionRecoveryClick && (
148 <InlineLinkButton className="mt-2" onClick={onInitiateSessionRecoveryClick}>
149 {c('Info').t`Can’t access your recovery phone?`}
155 availableRecoveryMethods.includes('mnemonic') && {
156 title: c('Recovery method').t`Phrase`,
159 <div className={clsx('mb-4', availableRecoveryMethods.length === 1 && 'mt-2')}>
160 {availableRecoveryMethods.length === 1 && toProceed} {phraseString}
164 disableChange={submitting}
166 onValue={setMnemonic}
169 currentMethod === 'mnemonic'
170 ? [requiredValidator(mnemonic), ...mnemonicValidation]
175 {isSessionRecoveryInitiationAvailable && onInitiateSessionRecoveryClick && (
176 <InlineLinkButton className="mt-2" onClick={onInitiateSessionRecoveryClick}>
177 {c('Info').t`Don’t know your recovery phrase?`}
185 onChange={(newIndex: number) => {
190 setTabIndex(newIndex);
196 <Button onClick={onBack}>{c('Action').t`Back`}</Button>
198 <Button onClick={onClose}>{c('Action').t`Cancel`}</Button>
200 <Button type="submit" color="norm" loading={submitting}>{c('Action').t`Continue`}</Button>
206 export default ReauthUsingRecoveryModal;