Move to service layer
This commit is contained in:
@@ -11,7 +11,7 @@ import { LinkButton } from "ente-base/components/LinkButton";
|
||||
import { LoadingButton } from "ente-base/components/mui/LoadingButton";
|
||||
import { isHTTPErrorWithStatus } from "ente-base/http";
|
||||
import log from "ente-base/log";
|
||||
import { getData, setLSUser } from "ente-shared/storage/localStorage";
|
||||
import { getData } from "ente-shared/storage/localStorage";
|
||||
import { Formik, type FormikHelpers } from "formik";
|
||||
import { t } from "i18next";
|
||||
import { useRouter } from "next/router";
|
||||
@@ -86,7 +86,6 @@ const ChangeEmailForm: React.FC = () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await changeEmail(email, ott!);
|
||||
await setLSUser({ ...getData("user"), email });
|
||||
setLoading(false);
|
||||
void goToApp();
|
||||
} catch (e) {
|
||||
|
||||
@@ -8,30 +8,18 @@ import SetPasswordForm, {
|
||||
} from "ente-accounts/components/SetPasswordForm";
|
||||
import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect";
|
||||
import {
|
||||
generateSRPSetupAttributes,
|
||||
getSRPAttributes,
|
||||
updateSRPAndKeyAttributes,
|
||||
type UpdatedKeyAttr,
|
||||
} from "ente-accounts/services/srp";
|
||||
import {
|
||||
ensureSavedKeyAttributes,
|
||||
generateAndSaveInteractiveKeyAttributes,
|
||||
changePassword,
|
||||
localUser,
|
||||
type LocalUser,
|
||||
} from "ente-accounts/services/user";
|
||||
import { LinkButton } from "ente-base/components/LinkButton";
|
||||
import { LoadingIndicator } from "ente-base/components/loaders";
|
||||
import { deriveSensitiveKey, encryptBox } from "ente-base/crypto";
|
||||
import { deriveKeyInsufficientMemoryErrorMessage } from "ente-base/crypto/types";
|
||||
import log from "ente-base/log";
|
||||
import {
|
||||
ensureMasterKeyFromSession,
|
||||
saveMasterKeyInSessionAndSafeStore,
|
||||
} from "ente-base/session";
|
||||
import { getData, setData } from "ente-shared/storage/localStorage";
|
||||
import { t } from "i18next";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
|
||||
/**
|
||||
* A page that allows a user to reset or change their password.
|
||||
@@ -63,71 +51,27 @@ interface PageContentsProps {
|
||||
const PageContents: React.FC<PageContentsProps> = ({ user }) => {
|
||||
const router = useRouter();
|
||||
|
||||
const redirectToAppHome = useCallback(() => {
|
||||
setData("showBackButton", { value: true });
|
||||
void router.push(appHomeRoute);
|
||||
}, [router]);
|
||||
|
||||
const onSubmit: SetPasswordFormProps["callback"] = async (
|
||||
password,
|
||||
setFieldError,
|
||||
) => {
|
||||
try {
|
||||
await onSubmit2(password);
|
||||
} catch (e) {
|
||||
log.error("Could not change password", e);
|
||||
setFieldError(
|
||||
"confirm",
|
||||
e instanceof Error &&
|
||||
e.message == deriveKeyInsufficientMemoryErrorMessage
|
||||
? t("password_generation_failed")
|
||||
: t("generic_error"),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit2 = async (password: string) => {
|
||||
const masterKey = await ensureMasterKeyFromSession();
|
||||
const keyAttributes = ensureSavedKeyAttributes();
|
||||
|
||||
const {
|
||||
key: kek,
|
||||
salt: kekSalt,
|
||||
opsLimit,
|
||||
memLimit,
|
||||
} = await deriveSensitiveKey(password);
|
||||
|
||||
const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } =
|
||||
await encryptBox(masterKey, kek);
|
||||
const updatedKeyAttr: UpdatedKeyAttr = {
|
||||
encryptedKey,
|
||||
keyDecryptionNonce,
|
||||
kekSalt,
|
||||
opsLimit,
|
||||
memLimit,
|
||||
};
|
||||
|
||||
await updateSRPAndKeyAttributes(
|
||||
await generateSRPSetupAttributes(kek),
|
||||
updatedKeyAttr,
|
||||
);
|
||||
|
||||
// Update the SRP attributes that are stored locally.
|
||||
const srpAttributes = await getSRPAttributes(user.email);
|
||||
if (srpAttributes) {
|
||||
setData("srpAttributes", srpAttributes);
|
||||
}
|
||||
|
||||
await generateAndSaveInteractiveKeyAttributes(
|
||||
password,
|
||||
{ ...keyAttributes, ...updatedKeyAttr },
|
||||
masterKey,
|
||||
);
|
||||
|
||||
await saveMasterKeyInSessionAndSafeStore(masterKey);
|
||||
|
||||
redirectToAppHome();
|
||||
};
|
||||
|
||||
const redirectToAppHome = () => {
|
||||
setData("showBackButton", { value: true });
|
||||
void router.push(appHomeRoute);
|
||||
};
|
||||
) =>
|
||||
changePassword(password)
|
||||
.then(redirectToAppHome)
|
||||
.catch((e: unknown) => {
|
||||
log.error("Could not change password", e);
|
||||
setFieldError(
|
||||
"confirm",
|
||||
e instanceof Error &&
|
||||
e.message == deriveKeyInsufficientMemoryErrorMessage
|
||||
? t("password_generation_failed")
|
||||
: t("generic_error"),
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<AccountsPageContents>
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
import {
|
||||
generateSRPSetupAttributes,
|
||||
getSRPAttributes,
|
||||
saveSRPAttributes,
|
||||
updateSRPAndKeyAttributes,
|
||||
type UpdatedKeyAttr,
|
||||
} from "ente-accounts/services/srp";
|
||||
import {
|
||||
decryptBox,
|
||||
deriveInteractiveKey,
|
||||
@@ -6,14 +13,20 @@ import {
|
||||
generateKey,
|
||||
generateKeyPair,
|
||||
} from "ente-base/crypto";
|
||||
import { isDevBuild } from "ente-base/env";
|
||||
import {
|
||||
authenticatedRequestHeaders,
|
||||
ensureOk,
|
||||
publicRequestHeaders,
|
||||
} from "ente-base/http";
|
||||
import { apiURL } from "ente-base/origins";
|
||||
import {
|
||||
ensureMasterKeyFromSession,
|
||||
saveMasterKeyInSessionAndSafeStore,
|
||||
} from "ente-base/session";
|
||||
import { getAuthToken } from "ente-base/token";
|
||||
import { getData, setLSUser } from "ente-shared/storage/localStorage";
|
||||
import { ensure } from "ente-utils/ensure";
|
||||
import { nullToUndefined } from "ente-utils/transform";
|
||||
import { z } from "zod/v4";
|
||||
import { getUserRecoveryKey, recoveryKeyFromMnemonic } from "./recovery-key";
|
||||
@@ -656,13 +669,22 @@ export const generateAndSaveInteractiveKeyAttributes = async (
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the email associated with the user's account on remote.
|
||||
* Change the email associated with the user's account (both locally and on
|
||||
* remote)
|
||||
*
|
||||
* @param email The new email.
|
||||
*
|
||||
* @param ott The verification code that was sent to the new email.
|
||||
*/
|
||||
export const changeEmail = async (email: string, ott: string) =>
|
||||
export const changeEmail = async (email: string, ott: string) => {
|
||||
await postChangeEmail(email, ott);
|
||||
await setLSUser({ ...getData("user"), email });
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the email associated with the user's account on remote.
|
||||
*/
|
||||
const postChangeEmail = async (email: string, ott: string) =>
|
||||
ensureOk(
|
||||
await fetch(await apiURL("/users/change-email"), {
|
||||
method: "POST",
|
||||
@@ -671,6 +693,60 @@ export const changeEmail = async (email: string, ott: string) =>
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* Change the user's password on both remote and locally.
|
||||
*
|
||||
* @param password The new password.
|
||||
*/
|
||||
export const changePassword = async (password: string) => {
|
||||
const user = ensureLocalUser();
|
||||
const masterKey = await ensureMasterKeyFromSession();
|
||||
const keyAttributes = ensureSavedKeyAttributes();
|
||||
|
||||
// Generate new KEK.
|
||||
const {
|
||||
key: kek,
|
||||
salt: kekSalt,
|
||||
opsLimit,
|
||||
memLimit,
|
||||
} = await deriveSensitiveKey(password);
|
||||
|
||||
// Generate new key attributes.
|
||||
const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } =
|
||||
await encryptBox(masterKey, kek);
|
||||
const updatedKeyAttr: UpdatedKeyAttr = {
|
||||
encryptedKey,
|
||||
keyDecryptionNonce,
|
||||
kekSalt,
|
||||
opsLimit,
|
||||
memLimit,
|
||||
};
|
||||
|
||||
// Update SRP and key attributes on remote.
|
||||
await updateSRPAndKeyAttributes(
|
||||
await generateSRPSetupAttributes(kek),
|
||||
updatedKeyAttr,
|
||||
);
|
||||
|
||||
// Update SRP attributes locally.
|
||||
const srpAttributes = await getSRPAttributes(user.email);
|
||||
saveSRPAttributes(ensure(srpAttributes));
|
||||
|
||||
// Update key attributes locally, generating a new interactive kek while
|
||||
// we're at it.
|
||||
await generateAndSaveInteractiveKeyAttributes(
|
||||
password,
|
||||
{ ...keyAttributes, ...updatedKeyAttr },
|
||||
masterKey,
|
||||
);
|
||||
|
||||
// TODO(RE): This shouldn't be needed, remove me. As a soft remove,
|
||||
// disabling it for dev builds. (tag: Migration)
|
||||
if (!isDevBuild) {
|
||||
await saveMasterKeyInSessionAndSafeStore(masterKey);
|
||||
}
|
||||
};
|
||||
|
||||
const TwoFactorSecret = z.object({
|
||||
/**
|
||||
* The 2FA secret code.
|
||||
|
||||
@@ -8,6 +8,28 @@ export const ensurePrecondition = (v: unknown): void => {
|
||||
if (!v) throw new Error("Precondition failed");
|
||||
};
|
||||
|
||||
/**
|
||||
* Throw an exception if the given value is `null` or `undefined`.
|
||||
*
|
||||
* This is different from TypeScript's built in null assertion operator `!` in
|
||||
* that `ensure` involves a runtime check, and will throw if the given value is
|
||||
* null-ish. On the other hand the TypeScript null assertion is only an
|
||||
* indication to the type system and does not involve any runtime checks.
|
||||
*
|
||||
* However, still it is preferable to use the TypeScript build in null assertion
|
||||
* since the stack traces are more informative. The stack trace is not at the
|
||||
* point of the assertion, but later at the point of the use, so it is not
|
||||
* _directly_ pointing at the issue, but usually it is not hard to backtrace.
|
||||
*
|
||||
* Still, in rare cases we might want to, well, ensure that a undefined value
|
||||
* doesn't sneak into the machinery. So this.
|
||||
*/
|
||||
export const ensure = <T>(v: T | null | undefined): T => {
|
||||
if (v === null) throw new Error("Required value was null");
|
||||
if (v === undefined) throw new Error("Required value was undefined");
|
||||
return v;
|
||||
};
|
||||
|
||||
/**
|
||||
* Throw an exception if the given value is not a string.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user