1 import { c } from 'ttag';
3 import { useGetAddresses } from '@proton/account/addresses/hooks';
4 import { useGetUser, useUser } from '@proton/account/user/hooks';
5 import { useGetUserKeys } from '@proton/account/userKeys/hooks';
6 import { useGetUserSettings, useUserSettings } from '@proton/account/userSettings/hooks';
7 import { Button, Href } from '@proton/atoms';
8 import Icon from '@proton/components/components/icon/Icon';
9 import Info from '@proton/components/components/link/Info';
10 import useModalState from '@proton/components/components/modalTwo/useModalState';
11 import Toggle from '@proton/components/components/toggle/Toggle';
12 import useIsRecoveryFileAvailable from '@proton/components/hooks/recoveryFile/useIsRecoveryFileAvailable';
13 import useApi from '@proton/components/hooks/useApi';
14 import useAuthentication from '@proton/components/hooks/useAuthentication';
15 import useConfig from '@proton/components/hooks/useConfig';
16 import useEventManager from '@proton/components/hooks/useEventManager';
17 import useHasOutdatedRecoveryFile from '@proton/components/hooks/useHasOutdatedRecoveryFile';
18 import useIsMnemonicAvailable from '@proton/components/hooks/useIsMnemonicAvailable';
19 import useRecoverySecrets from '@proton/components/hooks/useRecoverySecrets';
20 import { useLoading } from '@proton/hooks';
21 import { updateDeviceRecovery } from '@proton/shared/lib/api/settingsRecovery';
22 import { BRAND_NAME } from '@proton/shared/lib/constants';
23 import { getKnowledgeBaseUrl } from '@proton/shared/lib/helpers/url';
24 import type { UserSettings } from '@proton/shared/lib/interfaces';
25 import { MNEMONIC_STATUS } from '@proton/shared/lib/interfaces';
26 import { syncDeviceRecovery } from '@proton/shared/lib/recoveryFile/deviceRecovery';
28 import useSearchParamsEffect from '../../hooks/useSearchParamsEffect';
29 import SettingsLayout from '../account/SettingsLayout';
30 import SettingsLayoutLeft from '../account/SettingsLayoutLeft';
31 import SettingsLayoutRight from '../account/SettingsLayoutRight';
32 import SettingsParagraph from '../account/SettingsParagraph';
33 import SettingsSection from '../account/SettingsSection';
34 import DisableMnemonicModal from '../mnemonic/DisableMnemonicModal';
35 import GenerateMnemonicModal from '../mnemonic/GenerateMnemonicModal';
36 import ExportRecoveryFileButton from './ExportRecoveryFileButton';
37 import VoidRecoveryFilesModal from './VoidRecoveryFilesModal';
39 export const DataRecoverySection = () => {
40 const [user] = useUser();
41 const [userSettings] = useUserSettings();
42 const { call } = useEventManager();
44 const { APP_NAME } = useConfig();
45 const authentication = useAuthentication();
47 const getUser = useGetUser();
48 const getUserKeys = useGetUserKeys();
49 const getAddresses = useGetAddresses();
50 const getUserSettings = useGetUserSettings();
51 const [isRecoveryFileAvailable] = useIsRecoveryFileAvailable();
52 const [isMnemonicAvailable, loadingIsMnemonicAvailable] = useIsMnemonicAvailable();
54 const [disableMnemonicModal, setDisableMnemonicModalOpen, renderDisableMnemonicModal] = useModalState();
55 const [generateMnemonicModal, setGenerateMnemonicModalOpen, renderGenerateMnemonicModal] = useModalState();
56 const [generateMnemonicModalButton, setGenerateMnemonicModalButtonOpen, renderGenerateMnemonicModalButton] =
58 const [voidRecoveryFilesModal, setVoidRecoveryFilesModalOpen, renderVoidRecoveryFilesModal] = useModalState();
60 const hasOutdatedRecoveryFile = useHasOutdatedRecoveryFile();
61 const recoverySecrets = useRecoverySecrets();
62 const canRevokeRecoveryFiles = recoverySecrets?.length > 0;
64 const [loadingDeviceRecovery, withLoadingDeviceRecovery] = useLoading();
66 useSearchParamsEffect(
68 if (!isMnemonicAvailable) {
72 const actionParam = params.get('action');
77 if (actionParam === 'generate-recovery-phrase') {
78 if (user.MnemonicStatus === MNEMONIC_STATUS.SET || user.MnemonicStatus === MNEMONIC_STATUS.OUTDATED) {
79 setGenerateMnemonicModalButtonOpen(true);
81 setGenerateMnemonicModalOpen(true);
84 params.delete('action');
88 [loadingIsMnemonicAvailable]
91 const syncDeviceRecoveryHelper = async (partialUserSettings: Partial<UserSettings>) => {
92 const [user, userKeys, addresses, userSettings] = await Promise.all([
98 return syncDeviceRecovery({
103 userSettings: { ...userSettings, ...partialUserSettings },
109 const handleChangeDeviceRecoveryToggle = async (checked: boolean) => {
110 const DeviceRecovery = Number(checked) as 0 | 1;
111 if (userSettings.DeviceRecovery === DeviceRecovery) {
114 await api(updateDeviceRecovery({ DeviceRecovery }));
115 await syncDeviceRecoveryHelper({ DeviceRecovery });
121 {renderDisableMnemonicModal && <DisableMnemonicModal {...disableMnemonicModal} />}
122 {renderGenerateMnemonicModalButton && (
123 <GenerateMnemonicModal confirmStep {...generateMnemonicModalButton} />
125 {renderGenerateMnemonicModal && <GenerateMnemonicModal {...generateMnemonicModal} />}
126 {renderVoidRecoveryFilesModal && (
127 <VoidRecoveryFilesModal
128 onVoid={() => handleChangeDeviceRecoveryToggle(false)}
129 deviceRecoveryEnabled={Boolean(userSettings.DeviceRecovery)}
130 {...voidRecoveryFilesModal}
137 .t`Activate at least one data recovery method to make sure you can continue to access the contents of your ${BRAND_NAME} Account if you lose your password.`}
139 <Href href={getKnowledgeBaseUrl('/set-account-recovery-methods#how-to-enable-a-recovery-phrase')}>
140 {c('Link').t`Learn more about data recovery`}
144 {isMnemonicAvailable && (
146 {user.MnemonicStatus === MNEMONIC_STATUS.OUTDATED && (
147 <p className="color-danger">
148 <Icon className="mr-2 float-left mt-1" name="exclamation-circle-filled" size={3.5} />
150 .t`Your recovery phrase is outdated. It can't recover new data if you reset your password again.`}
156 <label className="pt-0 mb-2 md:mb-0 text-semibold" htmlFor="mnemonicToggle">
157 <span className="mr-2">{c('label').t`Recovery phrase`}</span>
160 .t`A recovery phrase lets you access your account and recover your encrypted messages if you forget your password`}
163 </SettingsLayoutLeft>
164 <SettingsLayoutRight isToggleContainer={user.MnemonicStatus !== MNEMONIC_STATUS.OUTDATED}>
165 {user.MnemonicStatus === MNEMONIC_STATUS.OUTDATED ? (
166 <Button color="norm" onClick={() => setGenerateMnemonicModalButtonOpen(true)}>
167 {c('Action').t`Update recovery phrase`}
171 <div className="flex items-start">
174 loading={disableMnemonicModal.open || generateMnemonicModal.open}
175 checked={user.MnemonicStatus === MNEMONIC_STATUS.SET}
177 onChange={({ target: { checked } }) => {
179 setGenerateMnemonicModalOpen(true);
181 setDisableMnemonicModalOpen(true);
187 data-testid="account:recovery:mnemonicToggle"
188 htmlFor="mnemonicToggle"
189 className="flex-1 mt-0.5"
191 {c('Label').t`Allow recovery by recovery phrase`}
195 {user.MnemonicStatus === MNEMONIC_STATUS.SET && (
199 onClick={() => setGenerateMnemonicModalButtonOpen(true)}
201 {c('Action').t`Generate new recovery phrase`}
206 </SettingsLayoutRight>
211 {isMnemonicAvailable && isRecoveryFileAvailable && <hr className="my-8" />}
213 {isRecoveryFileAvailable && (
217 <label className="pt-0 mb-2 md:mb-0 text-semibold" htmlFor="deviceRecoveryToggle">
218 <span className="mr-2">{c('label').t`Device-based recovery`}</span>
220 url={getKnowledgeBaseUrl('/device-data-recovery')}
222 .t`We securely store recovery information on your trusted device to prevent you from losing your data`}
225 </SettingsLayoutLeft>
226 <SettingsLayoutRight isToggleContainer>
227 <div className="flex items-start">
230 loading={loadingDeviceRecovery}
231 checked={!!userSettings.DeviceRecovery}
232 id="deviceRecoveryToggle"
233 onChange={({ target: { checked } }) =>
234 withLoadingDeviceRecovery(handleChangeDeviceRecoveryToggle(checked))
238 htmlFor="deviceRecoveryToggle"
239 className="flex-1 mt-0.5"
240 data-testid="account:recovery:trustedDevice"
242 {c('Label').t`Allow recovery using a trusted device`}
245 </SettingsLayoutRight>
249 <span className="pt-0 mb-2 md:mb-0 text-semibold">
250 <span className="mr-2">{c('Title').t`Recovery file`}</span>
253 .t`A recovery file lets you unlock and view your data after account recovery`}
256 </SettingsLayoutLeft>
257 <SettingsLayoutRight>
258 <ExportRecoveryFileButton className="block" color="norm">
259 {hasOutdatedRecoveryFile
260 ? c('Action').t`Update recovery file`
261 : c('Action').t`Download recovery file`}
262 </ExportRecoveryFileButton>
263 {canRevokeRecoveryFiles && (
268 onClick={() => setVoidRecoveryFilesModalOpen(true)}
270 {c('Action').t`Void all recovery files`}
273 </SettingsLayoutRight>
275 {hasOutdatedRecoveryFile && (
276 <p className="color-danger flex flex-nowrap">
277 <Icon className="mr-2 shrink-0 mt-0.5" name="exclamation-circle-filled" size={3.5} />
278 <span className="flex-1">{c('Warning')
279 .t`Your recovery file is outdated. It can't recover new data if you reset your password again.`}</span>