mirror of
https://github.com/ente-io/ente.git
synced 2025-05-24 12:09:17 +00:00
Something in the previous arrangement was causing webpack to not pack worker/worker.ts as a web worker.
195 lines
6.8 KiB
TypeScript
195 lines
6.8 KiB
TypeScript
import {
|
|
recoverTwoFactor,
|
|
removeTwoFactor,
|
|
type TwoFactorType,
|
|
} from "@/accounts/api/user";
|
|
import { PAGES } from "@/accounts/constants/pages";
|
|
import type { AccountsContextT } from "@/accounts/types/context";
|
|
import { sharedCryptoWorker } from "@/base/crypto";
|
|
import type { B64EncryptionResult } from "@/base/crypto/libsodium";
|
|
import log from "@/base/log";
|
|
import { ensure } from "@/utils/ensure";
|
|
import { VerticallyCentered } from "@ente/shared/components/Container";
|
|
import type { DialogBoxAttributesV2 } from "@ente/shared/components/DialogBoxV2/types";
|
|
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 { ApiError } from "@ente/shared/error";
|
|
import {
|
|
LS_KEYS,
|
|
getData,
|
|
setData,
|
|
setLSUser,
|
|
} from "@ente/shared/storage/localStorage";
|
|
import { Link } from "@mui/material";
|
|
import { HttpStatusCode } from "axios";
|
|
import { t } from "i18next";
|
|
import { useRouter } from "next/router";
|
|
import { useEffect, useState } from "react";
|
|
import { Trans } from "react-i18next";
|
|
|
|
const bip39 = require("bip39");
|
|
// mobile client library only supports english.
|
|
bip39.setDefaultWordlist("english");
|
|
|
|
export interface RecoverPageProps {
|
|
appContext: AccountsContextT;
|
|
twoFactorType: TwoFactorType;
|
|
}
|
|
|
|
const Page: React.FC<RecoverPageProps> = ({ appContext, twoFactorType }) => {
|
|
const { logout } = appContext;
|
|
|
|
const [encryptedTwoFactorSecret, setEncryptedTwoFactorSecret] =
|
|
useState<Omit<B64EncryptionResult, "key"> | null>(null);
|
|
const [sessionID, setSessionID] = useState<string | null>(null);
|
|
const [doesHaveEncryptedRecoveryKey, setDoesHaveEncryptedRecoveryKey] =
|
|
useState(false);
|
|
|
|
const router = useRouter();
|
|
|
|
useEffect(() => {
|
|
const user = getData(LS_KEYS.USER);
|
|
const sid = user.passkeySessionID || user.twoFactorSessionID;
|
|
if (!user || !user.email || !sid) {
|
|
router.push("/");
|
|
} else if (
|
|
!(user.isTwoFactorEnabled || user.isTwoFactorEnabledPasskey) &&
|
|
(user.encryptedToken || user.token)
|
|
) {
|
|
router.push(PAGES.GENERATE);
|
|
} else {
|
|
setSessionID(sid);
|
|
}
|
|
const main = async () => {
|
|
try {
|
|
const resp = await recoverTwoFactor(sid, twoFactorType);
|
|
setDoesHaveEncryptedRecoveryKey(!!resp.encryptedSecret);
|
|
if (!resp.encryptedSecret) {
|
|
showContactSupportDialog({
|
|
text: t("GO_BACK"),
|
|
action: router.back,
|
|
});
|
|
} else {
|
|
setEncryptedTwoFactorSecret({
|
|
encryptedData: resp.encryptedSecret,
|
|
nonce: resp.secretDecryptionNonce,
|
|
});
|
|
}
|
|
} catch (e) {
|
|
if (
|
|
e instanceof ApiError &&
|
|
e.httpStatusCode === HttpStatusCode.NotFound
|
|
) {
|
|
logout();
|
|
} else {
|
|
log.error("two factor recovery page setup failed", e);
|
|
setDoesHaveEncryptedRecoveryKey(false);
|
|
showContactSupportDialog({
|
|
text: t("GO_BACK"),
|
|
action: router.back,
|
|
});
|
|
}
|
|
}
|
|
};
|
|
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 sharedCryptoWorker();
|
|
const { encryptedData, nonce } = ensure(encryptedTwoFactorSecret);
|
|
const twoFactorSecret = await cryptoWorker.decryptB64(
|
|
encryptedData,
|
|
nonce,
|
|
await cryptoWorker.fromHex(recoveryKey),
|
|
);
|
|
const resp = await removeTwoFactor(
|
|
ensure(sessionID),
|
|
twoFactorSecret,
|
|
twoFactorType,
|
|
);
|
|
const { keyAttributes, encryptedToken, token, id } = resp;
|
|
await setLSUser({
|
|
...getData(LS_KEYS.USER),
|
|
token,
|
|
encryptedToken,
|
|
id,
|
|
isTwoFactorEnabled: false,
|
|
});
|
|
setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes);
|
|
router.push(PAGES.CREDENTIALS);
|
|
} catch (e) {
|
|
log.error("two factor recovery failed", e);
|
|
setFieldError(t("INCORRECT_RECOVERY_KEY"));
|
|
}
|
|
};
|
|
|
|
const showContactSupportDialog = (
|
|
dialogClose?: DialogBoxAttributesV2["close"],
|
|
) => {
|
|
appContext.setDialogBoxAttributesV2({
|
|
title: t("CONTACT_SUPPORT"),
|
|
close: dialogClose ?? {},
|
|
content: (
|
|
<Trans
|
|
i18nKey={"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE"}
|
|
components={{
|
|
a: <Link href="mailto:support@ente.io" />,
|
|
}}
|
|
values={{ emailID: "support@ente.io" }}
|
|
/>
|
|
),
|
|
});
|
|
};
|
|
|
|
if (!doesHaveEncryptedRecoveryKey) {
|
|
return <></>;
|
|
}
|
|
|
|
return (
|
|
<VerticallyCentered>
|
|
<FormPaper>
|
|
<FormPaperTitle>{t("RECOVER_TWO_FACTOR")}</FormPaperTitle>
|
|
<SingleInputForm
|
|
callback={recover}
|
|
fieldType="text"
|
|
placeholder={t("RECOVERY_KEY_HINT")}
|
|
buttonText={t("RECOVER")}
|
|
disableAutoComplete
|
|
/>
|
|
<FormPaperFooter style={{ justifyContent: "space-between" }}>
|
|
<LinkButton onClick={() => showContactSupportDialog()}>
|
|
{t("NO_RECOVERY_KEY")}
|
|
</LinkButton>
|
|
<LinkButton onClick={router.back}>
|
|
{t("GO_BACK")}
|
|
</LinkButton>
|
|
</FormPaperFooter>
|
|
</FormPaper>
|
|
</VerticallyCentered>
|
|
);
|
|
};
|
|
|
|
export default Page;
|