ente/web/packages/accounts/pages/credentials.tsx

284 lines
10 KiB
TypeScript
Raw Normal View History

2024-04-08 14:36:36 +00:00
import log from "@/next/log";
2024-04-05 15:18:23 +00:00
import { APP_HOMES } from "@ente/shared/apps/constants";
import { PageProps } from "@ente/shared/apps/types";
import { VerticallyCentered } from "@ente/shared/components/Container";
import EnteSpinner from "@ente/shared/components/EnteSpinner";
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";
2023-11-02 07:19:01 +00:00
import VerifyMasterPasswordForm, {
VerifyMasterPasswordFormProps,
} from "@ente/shared/components/VerifyMasterPasswordForm";
2024-04-05 15:18:23 +00:00
import ComlinkCryptoWorker from "@ente/shared/crypto";
2024-04-08 14:36:36 +00:00
import {
decryptAndStoreToken,
generateAndSaveIntermediateKeyAttributes,
generateLoginSubKey,
saveKeyInSessionStore,
} from "@ente/shared/crypto/helpers";
2024-04-05 15:18:23 +00:00
import { B64EncryptionResult } from "@ente/shared/crypto/types";
import { CustomError } from "@ente/shared/error";
import { getAccountsURL } from "@ente/shared/network/api";
2024-04-05 15:18:23 +00:00
import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore";
2024-04-08 14:36:36 +00:00
import {
LS_KEYS,
clearData,
getData,
setData,
} from "@ente/shared/storage/localStorage";
2024-02-08 20:42:51 +00:00
import {
2024-03-25 12:14:27 +00:00
getToken,
2024-02-08 20:42:51 +00:00
isFirstLogin,
setIsFirstLogin,
} from "@ente/shared/storage/localStorage/helpers";
2024-04-08 14:36:36 +00:00
import {
SESSION_KEYS,
getKey,
removeKey,
setKey,
} from "@ente/shared/storage/sessionStorage";
import { KeyAttributes, User } from "@ente/shared/user/types";
2024-04-08 14:36:36 +00:00
import { t } from "i18next";
2024-04-05 15:18:23 +00:00
import { useRouter } from "next/router";
2024-04-08 14:36:36 +00:00
import { useEffect, useState } from "react";
import { getSRPAttributes } from "../api/srp";
2024-04-08 14:36:36 +00:00
import { PAGES } from "../constants/pages";
import {
configureSRP,
generateSRPSetupAttributes,
loginViaSRP,
} from "../services/srp";
import { logoutUser } from "../services/user";
import { SRPAttributes } from "../types/srp";
2023-11-02 07:19:01 +00:00
2024-04-05 08:56:37 +00:00
export default function Credentials({ appContext, appName }: PageProps) {
2023-11-02 07:19:01 +00:00
const [srpAttributes, setSrpAttributes] = useState<SRPAttributes>();
const [keyAttributes, setKeyAttributes] = useState<KeyAttributes>();
const [user, setUser] = useState<User>();
2024-04-05 08:56:37 +00:00
const router = useRouter();
2023-11-02 07:19:01 +00:00
useEffect(() => {
const main = async () => {
const user: User = getData(LS_KEYS.USER);
if (!user?.email) {
router.push(PAGES.ROOT);
return;
}
setUser(user);
2023-11-03 04:19:02 +00:00
let key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
2024-04-09 06:42:25 +00:00
const electron = globalThis.electron;
if (!key && electron) {
try {
2024-04-10 05:33:36 +00:00
key = await electron.encryptionKey();
} catch (e) {
2024-04-10 05:33:36 +00:00
log.error("Failed to get encryption key from electron", e);
}
2023-11-03 04:19:02 +00:00
if (key) {
await saveKeyInSessionStore(
SESSION_KEYS.ENCRYPTION_KEY,
key,
true,
2023-11-03 04:19:02 +00:00
);
}
}
2024-03-25 12:14:27 +00:00
const token = getToken();
if (key && token) {
2023-11-10 07:43:09 +00:00
router.push(APP_HOMES.get(appName));
2023-11-02 07:19:01 +00:00
return;
}
const kekEncryptedAttributes: B64EncryptionResult = getKey(
SESSION_KEYS.KEY_ENCRYPTION_KEY,
2023-11-02 07:19:01 +00:00
);
const keyAttributes: KeyAttributes = getData(
LS_KEYS.KEY_ATTRIBUTES,
2023-11-02 07:19:01 +00:00
);
if (kekEncryptedAttributes && keyAttributes) {
removeKey(SESSION_KEYS.KEY_ENCRYPTION_KEY);
const cryptoWorker = await ComlinkCryptoWorker.getInstance();
const kek = await cryptoWorker.decryptB64(
kekEncryptedAttributes.encryptedData,
kekEncryptedAttributes.nonce,
kekEncryptedAttributes.key,
2023-11-02 07:19:01 +00:00
);
const key = await cryptoWorker.decryptB64(
keyAttributes.encryptedKey,
keyAttributes.keyDecryptionNonce,
kek,
2023-11-02 07:19:01 +00:00
);
useMasterPassword(key, kek, keyAttributes);
return;
}
if (keyAttributes) {
if (
(!user?.token && !user?.encryptedToken) ||
(keyAttributes && !keyAttributes.memLimit)
) {
clearData();
router.push(PAGES.ROOT);
return;
}
setKeyAttributes(keyAttributes);
return;
}
const srpAttributes: SRPAttributes = getData(
LS_KEYS.SRP_ATTRIBUTES,
2023-11-02 07:19:01 +00:00
);
if (srpAttributes) {
setSrpAttributes(srpAttributes);
} else {
router.push(PAGES.ROOT);
}
};
main();
appContext.showNavBar(true);
}, []);
const getKeyAttributes: VerifyMasterPasswordFormProps["getKeyAttributes"] =
2023-11-02 07:19:01 +00:00
async (kek: string) => {
try {
const cryptoWorker = await ComlinkCryptoWorker.getInstance();
const {
keyAttributes,
encryptedToken,
token,
id,
twoFactorSessionID,
2023-12-28 06:20:12 +00:00
passkeySessionID,
} = await loginViaSRP(srpAttributes, kek);
setIsFirstLogin(true);
2023-12-28 06:20:12 +00:00
if (passkeySessionID) {
const sessionKeyAttributes =
await cryptoWorker.generateKeyAndEncryptToB64(kek);
setKey(
SESSION_KEYS.KEY_ENCRYPTION_KEY,
sessionKeyAttributes,
2023-12-28 06:20:12 +00:00
);
const user = getData(LS_KEYS.USER);
setData(LS_KEYS.USER, {
...user,
passkeySessionID,
isTwoFactorEnabled: true,
isTwoFactorPasskeysEnabled: true,
});
2023-12-28 07:00:03 +00:00
InMemoryStore.set(MS_KEYS.REDIRECT_URL, PAGES.ROOT);
2024-02-17 07:32:45 +00:00
window.location.href = `${getAccountsURL()}/passkeys/flow?passkeySessionID=${passkeySessionID}&redirect=${
window.location.origin
}/passkeys/finish`;
2023-12-28 07:01:30 +00:00
return;
2023-12-28 06:20:12 +00:00
} else if (twoFactorSessionID) {
2023-11-02 07:19:01 +00:00
const sessionKeyAttributes =
await cryptoWorker.generateKeyAndEncryptToB64(kek);
setKey(
SESSION_KEYS.KEY_ENCRYPTION_KEY,
sessionKeyAttributes,
2023-11-02 07:19:01 +00:00
);
const user = getData(LS_KEYS.USER);
setData(LS_KEYS.USER, {
...user,
twoFactorSessionID,
isTwoFactorEnabled: true,
});
router.push(PAGES.TWO_FACTOR_VERIFY);
throw Error(CustomError.TWO_FACTOR_ENABLED);
} else {
const user = getData(LS_KEYS.USER);
setData(LS_KEYS.USER, {
...user,
token,
encryptedToken,
id,
isTwoFactorEnabled: false,
});
setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes);
2023-11-02 07:19:01 +00:00
return keyAttributes;
}
} catch (e) {
2023-11-10 09:38:10 +00:00
if (e.message !== CustomError.TWO_FACTOR_ENABLED) {
log.error("getKeyAttributes failed", e);
2023-11-02 07:19:01 +00:00
}
throw e;
}
};
const useMasterPassword: VerifyMasterPasswordFormProps["callback"] = async (
2023-11-02 07:19:01 +00:00
key,
kek,
keyAttributes,
passphrase,
2023-11-02 07:19:01 +00:00
) => {
try {
if (isFirstLogin() && passphrase) {
await generateAndSaveIntermediateKeyAttributes(
passphrase,
keyAttributes,
key,
2023-11-02 07:19:01 +00:00
);
}
await saveKeyInSessionStore(SESSION_KEYS.ENCRYPTION_KEY, key);
await decryptAndStoreToken(keyAttributes, key);
try {
let srpAttributes: SRPAttributes = getData(
LS_KEYS.SRP_ATTRIBUTES,
2023-11-02 07:19:01 +00:00
);
if (!srpAttributes) {
srpAttributes = await getSRPAttributes(user.email);
if (srpAttributes) {
setData(LS_KEYS.SRP_ATTRIBUTES, srpAttributes);
}
}
2024-04-08 14:36:36 +00:00
log.debug(() => `userSRPSetupPending ${!srpAttributes}`);
2023-11-02 07:19:01 +00:00
if (!srpAttributes) {
const loginSubKey = await generateLoginSubKey(kek);
2024-02-22 06:06:17 +00:00
const srpSetupAttributes =
await generateSRPSetupAttributes(loginSubKey);
2023-11-02 07:19:01 +00:00
await configureSRP(srpSetupAttributes);
}
} catch (e) {
log.error("migrate to srp failed", e);
2023-11-02 07:19:01 +00:00
}
const redirectURL = InMemoryStore.get(MS_KEYS.REDIRECT_URL);
InMemoryStore.delete(MS_KEYS.REDIRECT_URL);
2024-01-05 15:09:52 +00:00
router.push(redirectURL ?? APP_HOMES.get(appName));
2023-11-02 07:19:01 +00:00
} catch (e) {
log.error("useMasterPassword failed", e);
2023-11-02 07:19:01 +00:00
}
};
const redirectToRecoverPage = () => router.push(PAGES.RECOVER);
if (!keyAttributes && !srpAttributes) {
return (
<VerticallyCentered>
<EnteSpinner />
</VerticallyCentered>
);
}
return (
<VerticallyCentered>
<FormPaper style={{ minWidth: "320px" }}>
<FormPaperTitle>{t("PASSWORD")}</FormPaperTitle>
2023-11-02 07:19:01 +00:00
<VerifyMasterPasswordForm
buttonText={t("VERIFY_PASSPHRASE")}
2023-11-02 07:19:01 +00:00
callback={useMasterPassword}
user={user}
keyAttributes={keyAttributes}
getKeyAttributes={getKeyAttributes}
srpAttributes={srpAttributes}
/>
<FormPaperFooter style={{ justifyContent: "space-between" }}>
2023-11-02 07:19:01 +00:00
<LinkButton onClick={redirectToRecoverPage}>
{t("FORGOT_PASSWORD")}
2023-11-02 07:19:01 +00:00
</LinkButton>
2023-11-02 15:51:48 +00:00
<LinkButton onClick={logoutUser}>
{t("CHANGE_EMAIL")}
2023-11-02 07:19:01 +00:00
</LinkButton>
</FormPaperFooter>
</FormPaper>
</VerticallyCentered>
);
}