ente/web/packages/accounts/pages/change-password.tsx
2024-09-07 13:27:23 +05:30

160 lines
5.2 KiB
TypeScript

import {
getSRPAttributes,
startSRPSetup,
updateSRPAndKeys,
} from "@/accounts/api/srp";
import SetPasswordForm, {
type SetPasswordFormProps,
} from "@/accounts/components/SetPasswordForm";
import { PAGES } from "@/accounts/constants/pages";
import {
generateSRPClient,
generateSRPSetupAttributes,
} from "@/accounts/services/srp";
import type { UpdatedKey } from "@/accounts/types/user";
import { convertBase64ToBuffer, convertBufferToBase64 } from "@/accounts/utils";
import { sharedCryptoWorker } from "@/base/crypto";
import { ensure } from "@/utils/ensure";
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 {
generateAndSaveIntermediateKeyAttributes,
generateLoginSubKey,
saveKeyInSessionStore,
} from "@ente/shared/crypto/helpers";
import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage";
import { SESSION_KEYS } from "@ente/shared/storage/sessionStorage";
import { getActualKey } from "@ente/shared/user";
import type { KEK, KeyAttributes, User } from "@ente/shared/user/types";
import { t } from "i18next";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { appHomeRoute, stashRedirect } from "../services/redirect";
import type { PageProps } from "../types/page";
const Page: React.FC<PageProps> = () => {
const [token, setToken] = useState<string>();
const [user, setUser] = useState<User>();
const router = useRouter();
useEffect(() => {
const user = getData(LS_KEYS.USER);
setUser(user);
if (!user?.token) {
stashRedirect(PAGES.CHANGE_PASSWORD);
router.push("/");
} else {
setToken(user.token);
}
}, []);
const onSubmit: SetPasswordFormProps["callback"] = async (
passphrase,
setFieldError,
) => {
const cryptoWorker = await sharedCryptoWorker();
const key = await getActualKey();
const keyAttributes: KeyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES);
const kekSalt = await cryptoWorker.generateSaltToDeriveKey();
let kek: KEK;
try {
kek = await cryptoWorker.deriveSensitiveKey(passphrase, kekSalt);
} catch (e) {
setFieldError("confirm", t("PASSWORD_GENERATION_FAILED"));
return;
}
const encryptedKeyAttributes = await cryptoWorker.encryptToB64(
key,
kek.key,
);
const updatedKey: UpdatedKey = {
kekSalt,
encryptedKey: encryptedKeyAttributes.encryptedData,
keyDecryptionNonce: encryptedKeyAttributes.nonce,
opsLimit: kek.opsLimit,
memLimit: kek.memLimit,
};
const loginSubKey = await generateLoginSubKey(kek.key);
const { srpUserID, srpSalt, srpVerifier } =
await generateSRPSetupAttributes(loginSubKey);
const srpClient = await generateSRPClient(
srpSalt,
srpUserID,
loginSubKey,
);
const srpA = convertBufferToBase64(srpClient.computeA());
const { setupID, srpB } = await startSRPSetup(ensure(token), {
srpUserID,
srpSalt,
srpVerifier,
srpA,
});
srpClient.setB(convertBase64ToBuffer(srpB));
const srpM1 = convertBufferToBase64(srpClient.computeM1());
await updateSRPAndKeys(ensure(token), {
setupID,
srpM1,
updatedKeyAttr: updatedKey,
});
// Update the SRP attributes that are stored locally.
if (user?.email) {
const srpAttributes = await getSRPAttributes(user.email);
if (srpAttributes) {
setData(LS_KEYS.SRP_ATTRIBUTES, srpAttributes);
}
}
const updatedKeyAttributes = Object.assign(keyAttributes, updatedKey);
await generateAndSaveIntermediateKeyAttributes(
passphrase,
updatedKeyAttributes,
key,
);
await saveKeyInSessionStore(SESSION_KEYS.ENCRYPTION_KEY, key);
redirectToAppHome();
};
const redirectToAppHome = () => {
setData(LS_KEYS.SHOW_BACK_BUTTON, { value: true });
router.push(appHomeRoute);
};
// TODO: Handle the case where user is not loaded yet.
return (
<VerticallyCentered>
<FormPaper>
<FormPaperTitle>{t("CHANGE_PASSWORD")}</FormPaperTitle>
<SetPasswordForm
userEmail={user?.email ?? ""}
callback={onSubmit}
buttonText={t("CHANGE_PASSWORD")}
/>
{(getData(LS_KEYS.SHOW_BACK_BUTTON)?.value ?? true) && (
<FormPaperFooter>
<LinkButton onClick={router.back}>
{t("GO_BACK")}
</LinkButton>
</FormPaperFooter>
)}
</FormPaper>
</VerticallyCentered>
);
};
export default Page;