Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / components / containers / account / ReauthUsingRecoveryModal.tsx
blob4667075a6ea61331ca833a750b98163a471b99d6
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;
33     onBack?: () => void;
34     availableRecoveryMethods: ('mnemonic' | 'email' | 'sms')[];
35     title?: string;
36     onSuccess: () => void;
39 const ReauthUsingRecoveryModal = ({
40     onInitiateSessionRecoveryClick,
41     onBack,
42     availableRecoveryMethods,
43     title,
44     onClose,
45     onSuccess,
46     ...rest
47 }: Props) => {
48     const api = useApi();
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()) {
66             return;
67         }
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));
78             await srpAuth({
79                 info,
80                 api,
81                 config: reauthMnemonic({
82                     Username: username,
83                     PersistentCookies: persistent,
84                 }),
85                 credentials: {
86                     username: username,
87                     password: randomBytes,
88                 },
89             });
90         }
92         await onSuccess();
93         onClose?.();
94     };
96     const toProceed = c('Info').t`To proceed, we must verify the request.`;
97     const codeResetString = (value: string) => {
98         const boldValue = (
99             <b data-testid="recovery-modal-code-destination" key="bold-reset-method-value">
100                 {value}
101             </b>
102         );
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}.`;
105     };
106     const phraseString = c('Info').t`Enter your recovery phrase to change your password now.`;
108     return (
109         <Modal onClose={onClose} as={Form} onSubmit={() => withSubmitting(onSubmit())} {...rest}>
110             <ModalHeader title={title || c('Title').t`Reset password`} subline={user.Email} />
111             <ModalContent>
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.`}
115                     </div>
116                 )}
118                 <Tabs
119                     fullWidth
120                     tabs={[
121                         availableRecoveryMethods.includes('email') && {
122                             title: c('Recovery method').t`Email`,
123                             content: (
124                                 <>
125                                     <div className={clsx(availableRecoveryMethods.length === 1 && 'mt-2')}>
126                                         {availableRecoveryMethods.length === 1 && toProceed}{' '}
127                                         {codeResetString(userSettings.Email.Value)}
128                                     </div>
130                                     {isSessionRecoveryInitiationAvailable && onInitiateSessionRecoveryClick && (
131                                         <InlineLinkButton className="mt-2" onClick={onInitiateSessionRecoveryClick}>
132                                             {c('Info').t`Can’t access your recovery email?`}
133                                         </InlineLinkButton>
134                                     )}
135                                 </>
136                             ),
137                         },
138                         availableRecoveryMethods.includes('sms') && {
139                             title: c('Recovery method').t`Phone number`,
140                             content: (
141                                 <>
142                                     <div className={clsx(availableRecoveryMethods.length === 1 && 'mt-2')}>
143                                         {availableRecoveryMethods.length === 1 && toProceed}{' '}
144                                         {codeResetString(userSettings.Phone.Value)}
145                                     </div>
147                                     {isSessionRecoveryInitiationAvailable && onInitiateSessionRecoveryClick && (
148                                         <InlineLinkButton className="mt-2" onClick={onInitiateSessionRecoveryClick}>
149                                             {c('Info').t`Can’t access your recovery phone?`}
150                                         </InlineLinkButton>
151                                     )}
152                                 </>
153                             ),
154                         },
155                         availableRecoveryMethods.includes('mnemonic') && {
156                             title: c('Recovery method').t`Phrase`,
157                             content: (
158                                 <>
159                                     <div className={clsx('mb-4', availableRecoveryMethods.length === 1 && 'mt-2')}>
160                                         {availableRecoveryMethods.length === 1 && toProceed} {phraseString}
161                                     </div>
163                                     <MnemonicInputField
164                                         disableChange={submitting}
165                                         value={mnemonic}
166                                         onValue={setMnemonic}
167                                         autoFocus
168                                         error={validator(
169                                             currentMethod === 'mnemonic'
170                                                 ? [requiredValidator(mnemonic), ...mnemonicValidation]
171                                                 : []
172                                         )}
173                                     />
175                                     {isSessionRecoveryInitiationAvailable && onInitiateSessionRecoveryClick && (
176                                         <InlineLinkButton className="mt-2" onClick={onInitiateSessionRecoveryClick}>
177                                             {c('Info').t`Don’t know your recovery phrase?`}
178                                         </InlineLinkButton>
179                                     )}
180                                 </>
181                             ),
182                         },
183                     ].filter(isTruthy)}
184                     value={tabIndex}
185                     onChange={(newIndex: number) => {
186                         if (submitting) {
187                             return;
188                         }
190                         setTabIndex(newIndex);
191                     }}
192                 />
193             </ModalContent>
194             <ModalFooter>
195                 {onBack ? (
196                     <Button onClick={onBack}>{c('Action').t`Back`}</Button>
197                 ) : (
198                     <Button onClick={onClose}>{c('Action').t`Cancel`}</Button>
199                 )}
200                 <Button type="submit" color="norm" loading={submitting}>{c('Action').t`Continue`}</Button>
201             </ModalFooter>
202         </Modal>
203     );
206 export default ReauthUsingRecoveryModal;