revert commit and migrate code to use i18n

This commit is contained in:
Abhinav 2023-03-31 18:40:40 +05:30
parent 4885f198e5
commit 5ddd80a080
6 changed files with 171 additions and 25 deletions

View file

@ -9,6 +9,7 @@ export enum PAGES {
SIGNUP = '/signup',
TWO_FACTOR_SETUP = '/two-factor/setup',
TWO_FACTOR_VERIFY = '/two-factor/verify',
TWO_FACTOR_RECOVER = '/two-factor/recover',
VERIFY = '/verify',
ROOT = '/',
SHARED_ALBUMS = '/shared-albums',

View file

@ -0,0 +1,132 @@
import React, { useContext, useEffect, useState } from 'react';
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
import { useRouter } from 'next/router';
import SingleInputForm, {
SingleInputFormProps,
} from 'components/SingleInputForm';
import VerticallyCentered from 'components/Container';
import { logError } from 'utils/sentry';
import { recoverTwoFactor, removeTwoFactor } from 'services/userService';
import { AppContext } from 'pages/_app';
import { PAGES } from 'constants/pages';
import FormPaper from 'components/Form/FormPaper';
import FormPaperTitle from 'components/Form/FormPaper/Title';
import FormPaperFooter from 'components/Form/FormPaper/Footer';
import LinkButton from 'components/pages/gallery/LinkButton';
import { B64EncryptionResult } from 'types/crypto';
import ComlinkCryptoWorker from 'utils/comlink/ComlinkCryptoWorker';
import { t } from 'i18next';
import { Trans } from 'react-i18next';
import { Link } from '@mui/material';
import { SUPPORT_EMAIL } from 'constants/urls';
const bip39 = require('bip39');
// mobile client library only supports english.
bip39.setDefaultWordlist('english');
export default function Recover() {
const router = useRouter();
const [encryptedTwoFactorSecret, setEncryptedTwoFactorSecret] =
useState<B64EncryptionResult>(null);
const [sessionID, setSessionID] = useState(null);
const appContext = useContext(AppContext);
useEffect(() => {
router.prefetch(PAGES.GALLERY);
const user = getData(LS_KEYS.USER);
if (!user.isTwoFactorEnabled && (user.encryptedToken || user.token)) {
router.push(PAGES.GENERATE);
} else if (!user.email || !user.twoFactorSessionID) {
router.push(PAGES.ROOT);
} else {
setSessionID(user.twoFactorSessionID);
}
const main = async () => {
const resp = await recoverTwoFactor(user.twoFactorSessionID);
setEncryptedTwoFactorSecret({
encryptedData: resp.encryptedSecret,
nonce: resp.secretDecryptionNonce,
key: null,
});
};
main();
}, []);
const recover: SingleInputFormProps['callback'] = async (
recoveryKey: string,
setFieldError
) => {
try {
recoveryKey = recoveryKey
.trim()
.split(' ')
.map((part) => part.trim())
.filter((part) => !!part)
.join(' ');
// check if user is entering mnemonic recovery key
if (recoveryKey.indexOf(' ') > 0) {
if (recoveryKey.split(' ').length !== 24) {
throw new Error('recovery code should have 24 words');
}
recoveryKey = bip39.mnemonicToEntropy(recoveryKey);
}
const cryptoWorker = await ComlinkCryptoWorker.getInstance();
const twoFactorSecret = await cryptoWorker.decryptB64(
encryptedTwoFactorSecret.encryptedData,
encryptedTwoFactorSecret.nonce,
await cryptoWorker.fromHex(recoveryKey)
);
const resp = await removeTwoFactor(sessionID, twoFactorSecret);
const { keyAttributes, encryptedToken, token, id } = resp;
setData(LS_KEYS.USER, {
...getData(LS_KEYS.USER),
token,
encryptedToken,
id,
isTwoFactorEnabled: false,
});
setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes);
router.push(PAGES.CREDENTIALS);
} catch (e) {
logError(e, 'two factor recovery failed');
setFieldError(t('INCORRECT_RECOVERY_KEY'));
}
};
const showNoRecoveryKeyMessage = () => {
appContext.setDialogMessage({
title: t('CONTACT_SUPPORT'),
close: {},
content: (
<Trans
i18nKey={'NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE'}
components={{
a: <Link href={`mailto:${SUPPORT_EMAIL}`} />,
}}
/>
),
});
};
return (
<VerticallyCentered>
<FormPaper>
<FormPaperTitle>{t('RECOVER_TWO_FACTOR')}</FormPaperTitle>
<SingleInputForm
callback={recover}
fieldType="text"
placeholder={t('RECOVERY_KEY_HINT')}
buttonText={t('RECOVER')}
/>
<FormPaperFooter style={{ justifyContent: 'space-between' }}>
<LinkButton onClick={showNoRecoveryKeyMessage}>
{t('NO_RECOVERY_KEY')}
</LinkButton>
<LinkButton onClick={router.back}>
{t('GO_BACK')}
</LinkButton>
</FormPaperFooter>
</FormPaper>
</VerticallyCentered>
);
}

View file

@ -7,6 +7,7 @@ import { useRouter } from 'next/router';
import VerifyTwoFactor, {
VerifyTwoFactorCallback,
} from 'components/TwoFactor/VerifyForm';
import { encryptWithRecoveryKey } from 'utils/crypto';
import { setData, LS_KEYS, getData } from 'utils/storage/localStorage';
import { PAGES } from 'constants/pages';
import { TwoFactorSecret } from 'types/user';
@ -45,7 +46,10 @@ export default function SetupTwoFactor() {
otp: string,
markSuccessful
) => {
await enableTwoFactor(otp);
const recoveryEncryptedTwoFactorSecret = await encryptWithRecoveryKey(
twoFactorSecret.secretCode
);
await enableTwoFactor(otp, recoveryEncryptedTwoFactorSecret);
await markSuccessful();
setData(LS_KEYS.USER, {
...getData(LS_KEYS.USER),

View file

@ -2,13 +2,11 @@ import VerifyTwoFactor, {
VerifyTwoFactorCallback,
} from 'components/TwoFactor/VerifyForm';
import router from 'next/router';
import React, { useContext, useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { logoutUser, verifyTwoFactor } from 'services/userService';
import { AppContext } from 'pages/_app';
import { PAGES } from 'constants/pages';
import { User } from 'types/user';
import { setData, LS_KEYS, getData } from 'utils/storage/localStorage';
import { Trans } from 'react-i18next';
import { t } from 'i18next';
import LinkButton from 'components/pages/gallery/LinkButton';
@ -16,12 +14,9 @@ import FormContainer from 'components/Form/FormContainer';
import FormPaper from 'components/Form/FormPaper';
import FormTitle from 'components/Form/FormPaper/Title';
import FormPaperFooter from 'components/Form/FormPaper/Footer';
import { Link } from '@mui/material';
import { SUPPORT_EMAIL } from 'constants/urls';
export default function Home() {
const [sessionID, setSessionID] = useState('');
const appContext = useContext(AppContext);
useEffect(() => {
const main = async () => {
@ -41,22 +36,6 @@ export default function Home() {
main();
}, []);
const showContactSupport = () => {
appContext.setDialogMessage({
title: t('CONTACT_SUPPORT'),
close: {},
content: (
<Trans
i18nKey="NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE"
components={{
a: <Link href={`mailto:${SUPPORT_EMAIL}`} />,
}}
values={{ emailID: SUPPORT_EMAIL }}
/>
),
});
};
const onSubmit: VerifyTwoFactorCallback = async (otp) => {
try {
const resp = await verifyTwoFactor(otp, sessionID);
@ -84,7 +63,8 @@ export default function Home() {
<VerifyTwoFactor onSubmit={onSubmit} buttonText={t('VERIFY')} />
<FormPaperFooter>
<LinkButton onClick={showContactSupport}>
<LinkButton
onClick={() => router.push(PAGES.TWO_FACTOR_RECOVER)}>
{t('LOST_DEVICE')}
</LinkButton>
</FormPaperFooter>

View file

@ -15,6 +15,7 @@ import {
RecoveryKey,
TwoFactorSecret,
TwoFactorVerificationResponse,
TwoFactorRecoveryResponse,
UserDetails,
DeleteChallengeResponse,
GetRemoteStoreValueResponse,
@ -23,6 +24,7 @@ import { ServerErrorCodes } from 'utils/error';
import isElectron from 'is-electron';
import safeStorageService from './electron/safeStorage';
import { deleteAllCache } from 'utils/storage/cache';
import { B64EncryptionResult } from 'types/crypto';
import { getLocalFamilyData, isPartOfFamily } from 'utils/user/family';
import { AxiosResponse } from 'axios';
@ -217,11 +219,18 @@ export const setupTwoFactor = async () => {
return resp.data as TwoFactorSecret;
};
export const enableTwoFactor = async (code: string) => {
export const enableTwoFactor = async (
code: string,
recoveryEncryptedTwoFactorSecret: B64EncryptionResult
) => {
await HTTPService.post(
`${ENDPOINT}/users/two-factor/enable`,
{
code,
encryptedTwoFactorSecret:
recoveryEncryptedTwoFactorSecret.encryptedData,
twoFactorSecretDecryptionNonce:
recoveryEncryptedTwoFactorSecret.nonce,
},
null,
{
@ -242,6 +251,21 @@ export const verifyTwoFactor = async (code: string, sessionID: string) => {
return resp.data as TwoFactorVerificationResponse;
};
export const recoverTwoFactor = async (sessionID: string) => {
const resp = await HTTPService.get(`${ENDPOINT}/users/two-factor/recover`, {
sessionID,
});
return resp.data as TwoFactorRecoveryResponse;
};
export const removeTwoFactor = async (sessionID: string, secret: string) => {
const resp = await HTTPService.post(`${ENDPOINT}/users/two-factor/remove`, {
sessionID,
secret,
});
return resp.data as TwoFactorVerificationResponse;
};
export const disableTwoFactor = async () => {
await HTTPService.post(`${ENDPOINT}/users/two-factor/disable`, null, null, {
'X-Auth-Token': getToken(),

View file

@ -61,6 +61,11 @@ export interface TwoFactorSecret {
qrCode: string;
}
export interface TwoFactorRecoveryResponse {
encryptedSecret: string;
secretDecryptionNonce: string;
}
export interface FamilyMember {
email: string;
usage: number;