Disable 2FA recovery (#972)

This commit is contained in:
Vishnu Mohandas 2023-03-08 14:55:58 +05:30 committed by GitHub
commit 7284a6f0b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 17 additions and 250 deletions

View file

@ -9,7 +9,6 @@ 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

@ -1,121 +0,0 @@
import React, { useContext, useEffect, useState } from 'react';
import constants from 'utils/strings/constants';
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';
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(constants.INCORRECT_RECOVERY_KEY);
}
};
const showNoRecoveryKeyMessage = () => {
appContext.setDialogMessage({
title: constants.CONTACT_SUPPORT,
close: {},
content: constants.NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE(),
});
};
return (
<VerticallyCentered>
<FormPaper>
<FormPaperTitle>{constants.RECOVER_TWO_FACTOR}</FormPaperTitle>
<SingleInputForm
callback={recover}
fieldType="text"
placeholder={constants.RECOVERY_KEY_HINT}
buttonText={constants.RECOVER}
/>
<FormPaperFooter style={{ justifyContent: 'space-between' }}>
<LinkButton onClick={showNoRecoveryKeyMessage}>
{constants.NO_RECOVERY_KEY}
</LinkButton>
<LinkButton onClick={router.back}>
{constants.GO_BACK}
</LinkButton>
</FormPaperFooter>
</FormPaper>
</VerticallyCentered>
);
}

View file

@ -6,7 +6,6 @@ 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,10 +44,7 @@ export default function SetupTwoFactor() {
otp: string,
markSuccessful
) => {
const recoveryEncryptedTwoFactorSecret = await encryptWithRecoveryKey(
twoFactorSecret.secretCode
);
await enableTwoFactor(otp, recoveryEncryptedTwoFactorSecret);
await enableTwoFactor(otp);
await markSuccessful();
setData(LS_KEYS.USER, {
...getData(LS_KEYS.USER),

View file

@ -2,8 +2,9 @@ import VerifyTwoFactor, {
VerifyTwoFactorCallback,
} from 'components/TwoFactor/VerifyForm';
import router from 'next/router';
import React, { useEffect, useState } from 'react';
import React, { useContext, 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';
@ -16,6 +17,7 @@ import FormPaperFooter from 'components/Form/FormPaper/Footer';
export default function Home() {
const [sessionID, setSessionID] = useState('');
const appContext = useContext(AppContext);
useEffect(() => {
const main = async () => {
@ -35,6 +37,14 @@ export default function Home() {
main();
}, []);
const showContactSupport = () => {
appContext.setDialogMessage({
title: constants.CONTACT_SUPPORT,
close: {},
content: constants.NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE(),
});
};
const onSubmit: VerifyTwoFactorCallback = async (otp) => {
try {
const resp = await verifyTwoFactor(otp, sessionID);
@ -65,8 +75,7 @@ export default function Home() {
/>
<FormPaperFooter>
<LinkButton
onClick={() => router.push(PAGES.TWO_FACTOR_RECOVER)}>
<LinkButton onClick={showContactSupport}>
{constants.LOST_DEVICE}
</LinkButton>
</FormPaperFooter>

View file

@ -15,7 +15,6 @@ import {
RecoveryKey,
TwoFactorSecret,
TwoFactorVerificationResponse,
TwoFactorRecoveryResponse,
UserDetails,
DeleteChallengeResponse,
GetRemoteStoreValueResponse,
@ -24,7 +23,6 @@ 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';
@ -219,18 +217,11 @@ export const setupTwoFactor = async () => {
return resp.data as TwoFactorSecret;
};
export const enableTwoFactor = async (
code: string,
recoveryEncryptedTwoFactorSecret: B64EncryptionResult
) => {
export const enableTwoFactor = async (code: string) => {
await HTTPService.post(
`${ENDPOINT}/users/two-factor/enable`,
{
code,
encryptedTwoFactorSecret:
recoveryEncryptedTwoFactorSecret.encryptedData,
twoFactorSecretDecryptionNonce:
recoveryEncryptedTwoFactorSecret.nonce,
},
null,
{
@ -251,21 +242,6 @@ 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,11 +61,6 @@ export interface TwoFactorSecret {
qrCode: string;
}
export interface TwoFactorRecoveryResponse {
encryptedSecret: string;
secretDecryptionNonce: string;
}
export interface FamilyMember {
email: string;
usage: number;

View file

@ -1340,16 +1340,6 @@ blazeface-back@^0.0.8:
resolved "https://registry.yarnpkg.com/blazeface-back/-/blazeface-back-0.0.8.tgz#d997a9cfcd5b7ad03367d65d7c0b365c5a835b83"
integrity sha512-7eYoSPJ9JvV2cHp+qwW+YNvrdTDFzMGnSGCGR24Lx6OluwPa+qX4NqNrBv31ukw2Ne/FkvFHhZuUDpxpowr+Bg==
blueimp-load-image@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/blueimp-load-image/-/blueimp-load-image-3.0.0.tgz#d71c39440a7d2f1a83e3e86a625e329116a51705"
integrity sha512-Q9rFbd4ZUNvzSFmRXx9MoG0RwWwJeMjjEUbG7WIOJgUg22Jgkow0wL5b35B6qwiBscxACW9OHdrP5s2vQ3x8DQ==
bmp-js@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/bmp-js/-/bmp-js-0.1.0.tgz#e05a63f796a6c1ff25f4771ec7adadc148c07233"
integrity sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==
bootstrap@^4.5.2:
version "4.6.0"
resolved "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz"
@ -1501,11 +1491,6 @@ colorette@^1.2.2:
resolved "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz"
integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==
colors@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
comlink@^4.3.0:
version "4.3.1"
resolved "https://registry.npmjs.org/comlink/-/comlink-4.3.1.tgz"
@ -2224,11 +2209,6 @@ file-selector@^0.2.2:
dependencies:
tslib "^2.0.3"
file-type@^12.4.1:
version "12.4.2"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-12.4.2.tgz#a344ea5664a1d01447ee7fb1b635f72feb6169d9"
integrity sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==
file-type@^16.5.4:
version "16.5.4"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.4.tgz#474fb4f704bee427681f98dd390058a172a6c2fd"
@ -2415,18 +2395,6 @@ glob@7.1.7, glob@^7.1.3:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.1.6:
version "7.2.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
globals@^11.1.0:
version "11.12.0"
resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz"
@ -2641,11 +2609,6 @@ husky@^7.0.1:
resolved "https://registry.npmjs.org/husky/-/husky-7.0.2.tgz"
integrity sha512-8yKEWNX4z2YsofXAMT7KvA1g8p+GxtB1ffV8XtpAEGuXNAbCV5wdNKH+qTpw8SM9fh4aMPDR+yQuKfgnreyZlg==
idb-keyval@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-3.2.0.tgz#cbbf354deb5684b6cdc84376294fc05932845bd6"
integrity sha512-slx8Q6oywCCSfKgPgL0sEsXtPVnSbTLWpyiDcu6msHOyKOLari1TD1qocXVCft80umnkk3/Qqh3lwoFt8T/BPQ==
idb@^6.0.0:
version "6.1.3"
resolved "https://registry.npmjs.org/idb/-/idb-6.1.3.tgz"
@ -2995,18 +2958,7 @@ isexe@^2.0.0:
resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
jpeg-autorotate@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/jpeg-autorotate/-/jpeg-autorotate-7.1.1.tgz#c57905c6afd3b54373a6a1d0249ed6e07f7b043b"
integrity sha512-ewTZTG/QWOM0D5h/yKcQ3QgyrnQYsr3qmcS+bqoAwgQAY1KBa31aJ+q+FlElaxo/rSYqfF1ixf+8EIgluBkgTg==
dependencies:
colors "^1.4.0"
glob "^7.1.6"
jpeg-js "^0.4.2"
piexifjs "^1.0.6"
yargs-parser "^20.2.1"
jpeg-js@^0.4.1, jpeg-js@^0.4.2:
jpeg-js@^0.4.1:
version "0.4.4"
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa"
integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==
@ -3547,11 +3499,6 @@ open@^8.4.0:
is-docker "^2.1.1"
is-wsl "^2.2.0"
opencollective-postinstall@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==
opener@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
@ -3991,7 +3938,7 @@ regenerator-runtime@^0.13.11:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7:
regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7:
version "0.13.9"
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
@ -4020,11 +3967,6 @@ resolve-from@^4.0.0:
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==
resolve@^1.20.0:
version "1.20.0"
resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz"
@ -4484,27 +4426,8 @@ tapable@^2.2.0:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
tesseract.js-core@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tesseract.js-core/-/tesseract.js-core-2.2.0.tgz#6ef78051272a381969fac3e45a226e85022cffef"
integrity sha512-a8L+OJTbUipBsEDsJhDPlnLB0TY1MkTZqw5dqUwmiDSjUzwvU7HWLg/2+WDRulKUi4LE+7PnHlaBlW0k+V0U0w==
"tesseract.js@file:./thirdparty/tesseract":
version "2.1.5"
dependencies:
blueimp-load-image "^3.0.0"
bmp-js "^0.1.0"
file-type "^12.4.1"
idb-keyval "^3.2.0"
is-electron "^2.2.0"
is-url "^1.2.4"
jpeg-autorotate "^7.1.1"
node-fetch "^2.6.0"
opencollective-postinstall "^2.0.2"
regenerator-runtime "^0.13.3"
resolve-url "^0.2.1"
tesseract.js-core "^2.2.0"
zlibjs "^0.3.1"
version "0.0.0"
text-table@^0.2.0:
version "0.2.0"
@ -4881,11 +4804,6 @@ yaml@^1.10.0:
resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
yargs-parser@^20.2.1:
version "20.2.9"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
@ -4904,11 +4822,6 @@ yup@^0.29.3:
synchronous-promise "^2.0.13"
toposort "^2.0.2"
zlibjs@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/zlibjs/-/zlibjs-0.3.1.tgz#50197edb28a1c42ca659cc8b4e6a9ddd6d444554"
integrity sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==
zxcvbn@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/zxcvbn/-/zxcvbn-4.4.2.tgz#28ec17cf09743edcab056ddd8b1b06262cc73c30"