ente/web/packages/accounts/pages/recover.tsx
2024-05-26 21:55:16 +05:30

137 lines
5.1 KiB
TypeScript

import log from "@/next/log";
import { ensure } from "@/utils/ensure";
import { sendOtt } from "@ente/accounts/api/user";
import { PAGES } from "@ente/accounts/constants/pages";
import { APP_HOMES, appNameToAppNameOld } from "@ente/shared/apps/constants";
import { VerticallyCentered } from "@ente/shared/components/Container";
import FormPaper from "@ente/shared/components/Form/FormPaper";
import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title";
import LinkButton from "@ente/shared/components/LinkButton";
import SingleInputForm, {
type SingleInputFormProps,
} from "@ente/shared/components/SingleInputForm";
import ComlinkCryptoWorker from "@ente/shared/crypto";
import {
decryptAndStoreToken,
saveKeyInSessionStore,
} from "@ente/shared/crypto/helpers";
import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore";
import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage";
import { SESSION_KEYS, getKey } from "@ente/shared/storage/sessionStorage";
import type { KeyAttributes, User } from "@ente/shared/user/types";
import { t } from "i18next";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import type { PageProps } from "../types/page";
const bip39 = require("bip39");
// mobile client library only supports english.
bip39.setDefaultWordlist("english");
const Page: React.FC<PageProps> = ({ appContext }) => {
const { appName } = appContext;
const appNameOld = appNameToAppNameOld(appName);
const [keyAttributes, setKeyAttributes] = useState<
KeyAttributes | undefined
>();
const router = useRouter();
useEffect(() => {
const user: User = getData(LS_KEYS.USER);
const keyAttributes: KeyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES);
const key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
if (!user?.email) {
router.push(PAGES.ROOT);
return;
}
if (!user?.encryptedToken && !user?.token) {
sendOtt(appNameOld, user.email);
InMemoryStore.set(MS_KEYS.REDIRECT_URL, PAGES.RECOVER);
router.push(PAGES.VERIFY);
return;
}
if (!keyAttributes) {
router.push(PAGES.GENERATE);
} else if (key) {
// TODO: Refactor the type of APP_HOMES to not require the ??
router.push(APP_HOMES.get(appNameOld) ?? "/");
} else {
setKeyAttributes(keyAttributes);
}
appContext.showNavBar(true);
}, []);
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 keyAttr = ensure(keyAttributes);
const masterKey = await cryptoWorker.decryptB64(
keyAttr.masterKeyEncryptedWithRecoveryKey,
keyAttr.masterKeyDecryptionNonce,
await cryptoWorker.fromHex(recoveryKey),
);
await saveKeyInSessionStore(SESSION_KEYS.ENCRYPTION_KEY, masterKey);
await decryptAndStoreToken(keyAttr, masterKey);
setData(LS_KEYS.SHOW_BACK_BUTTON, { value: false });
router.push(PAGES.CHANGE_PASSWORD);
} catch (e) {
log.error("password recovery failed", e);
setFieldError(t("INCORRECT_RECOVERY_KEY"));
}
};
const showNoRecoveryKeyMessage = () => {
appContext.setDialogBoxAttributesV2({
title: t("SORRY"),
close: {},
content: t("NO_RECOVERY_KEY_MESSAGE"),
});
};
return (
<VerticallyCentered>
<FormPaper>
<FormPaperTitle>{t("RECOVER_ACCOUNT")}</FormPaperTitle>
<SingleInputForm
callback={recover}
fieldType="text"
placeholder={t("RECOVERY_KEY_HINT")}
buttonText={t("RECOVER")}
disableAutoComplete
/>
<FormPaperFooter style={{ justifyContent: "space-between" }}>
<LinkButton onClick={showNoRecoveryKeyMessage}>
{t("NO_RECOVERY_KEY")}
</LinkButton>
<LinkButton onClick={router.back}>
{t("GO_BACK")}
</LinkButton>
</FormPaperFooter>
</FormPaper>
</VerticallyCentered>
);
};
export default Page;