Rework
This commit is contained in:
@@ -38,7 +38,7 @@ import {
|
||||
haveCredentialsInSession,
|
||||
masterKeyFromSession,
|
||||
} from "ente-base/session";
|
||||
import { getAuthToken } from "ente-base/token";
|
||||
import { savedAuthToken } from "ente-base/token";
|
||||
import { FullScreenDropZone } from "ente-gallery/components/FullScreenDropZone";
|
||||
import { type UploadTypeSelectorIntent } from "ente-gallery/components/Upload";
|
||||
import { type Collection } from "ente-media/collection";
|
||||
@@ -282,7 +282,7 @@ const Page: React.FC = () => {
|
||||
let syncIntervalID: ReturnType<typeof setInterval> | undefined;
|
||||
|
||||
void (async () => {
|
||||
if (!haveCredentialsInSession() || !(await getAuthToken())) {
|
||||
if (!haveCredentialsInSession() || !(await savedAuthToken())) {
|
||||
// If we don't have master key or auth token, reauthenticate.
|
||||
stashRedirect("/gallery");
|
||||
router.push("/");
|
||||
|
||||
@@ -11,15 +11,33 @@ import log from "ente-base/log";
|
||||
import { useFormik } from "formik";
|
||||
import { t } from "i18next";
|
||||
import { useCallback, useState } from "react";
|
||||
import { twoFactorEnabledErrorMessage } from "./utils/second-factor-choice";
|
||||
|
||||
export interface VerifyMasterPasswordFormProps {
|
||||
/**
|
||||
* The email of the user whose password we're trying to verify.
|
||||
*/
|
||||
userEmail: string;
|
||||
/**
|
||||
* The user's SRP attributes.
|
||||
*
|
||||
* The SRP attributes are used to derive the KEK from the user's password.
|
||||
* If they are not present, the {@link keyAttributes} will be used instead.
|
||||
*
|
||||
* At least one of {@link srpAttributes} and {@link keyAttributes} must be
|
||||
* present, otherwise the verification will fail.
|
||||
*/
|
||||
srpAttributes?: SRPAttributes;
|
||||
/**
|
||||
* The user's key attributes.
|
||||
*
|
||||
* If they are present, they are used to derive the KEK from the user's
|
||||
* password when {@link srpAttributes} are not present. This is the case
|
||||
* when the user has already logged in (or signed up) on this client before,
|
||||
* and is now doing a reauthentication.
|
||||
*
|
||||
* If they are not present, then {@link getKeyAttributes} must be present
|
||||
* and will be used to obtain the user's key attributes. This is the case
|
||||
* when the user is logging into a new client.
|
||||
*/
|
||||
keyAttributes: KeyAttributes | undefined;
|
||||
/**
|
||||
@@ -30,20 +48,18 @@ export interface VerifyMasterPasswordFormProps {
|
||||
* used for reauthenticating the user after they've already logged in, then
|
||||
* this function will not be provided.
|
||||
*
|
||||
* @throws A Error with message {@link twoFactorEnabledErrorMessage} to
|
||||
* signal to the form that some other form of second factor is enabled and
|
||||
* the user has been redirected to a two factor verification page.
|
||||
* @returns The user's key attributes obtained from remote, or
|
||||
* "redirecting-second-factor" if the user has an additional second factor
|
||||
* verification required and the app is redirecting there.
|
||||
*
|
||||
* @throws A Error with message
|
||||
* {@link srpVerificationUnauthorizedErrorMessage} to signal that either
|
||||
* that the password is incorrect, or no account with the provided email
|
||||
* exists.
|
||||
*/
|
||||
getKeyAttributes?: (kek: string) => Promise<KeyAttributes | undefined>;
|
||||
/**
|
||||
* The user's SRP attributes.
|
||||
*/
|
||||
srpAttributes?: SRPAttributes;
|
||||
getKeyAttributes?: (
|
||||
kek: string,
|
||||
) => Promise<KeyAttributes | "redirecting-second-factor" | undefined>;
|
||||
/**
|
||||
* The title of the submit button on the form.
|
||||
*/
|
||||
@@ -152,24 +168,24 @@ export const VerifyMasterPasswordForm: React.FC<
|
||||
}
|
||||
} else throw new Error("Both SRP and key attributes are missing");
|
||||
|
||||
if (!keyAttributes && typeof getKeyAttributes == "function") {
|
||||
if (!keyAttributes && getKeyAttributes) {
|
||||
try {
|
||||
keyAttributes = await getKeyAttributes(kek);
|
||||
const result = await getKeyAttributes(kek);
|
||||
if (result == "redirecting-second-factor") {
|
||||
// Two factor enabled, user has been redirected to the
|
||||
// corresponding second factor verification page.
|
||||
return;
|
||||
} else {
|
||||
keyAttributes = result;
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
switch (e.message) {
|
||||
case twoFactorEnabledErrorMessage:
|
||||
// Two factor enabled, user has been redirected to
|
||||
// the two-factor verification page.
|
||||
return;
|
||||
|
||||
case srpVerificationUnauthorizedErrorMessage:
|
||||
log.error("Incorrect password or no account", e);
|
||||
setFieldError(
|
||||
t("incorrect_password_or_no_account"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
e instanceof Error &&
|
||||
e.message == srpVerificationUnauthorizedErrorMessage
|
||||
) {
|
||||
log.error("Incorrect password or no account", e);
|
||||
setFieldError(t("incorrect_password_or_no_account"));
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
@@ -8,15 +8,6 @@ import { useModalVisibility } from "ente-base/components/utils/modal";
|
||||
import { useCallback, useMemo, useRef } from "react";
|
||||
import type { SecondFactorType } from "../SecondFactorChoice";
|
||||
|
||||
/**
|
||||
* The message of the {@link Error} that is thrown when the user has enabled a
|
||||
* second factor so further authentication is needed during the login sequence.
|
||||
*
|
||||
* TODO: This is not really an error but rather is a code flow flag; consider
|
||||
* not using exceptions for flow control.
|
||||
*/
|
||||
export const twoFactorEnabledErrorMessage = "two factor enabled";
|
||||
|
||||
/**
|
||||
* A convenience hook for keeping track of the state and logic that is needed
|
||||
* after password verification to determine which second factor (if any) we
|
||||
|
||||
@@ -6,17 +6,12 @@ import {
|
||||
} from "ente-accounts/components/LoginComponents";
|
||||
import { SecondFactorChoice } from "ente-accounts/components/SecondFactorChoice";
|
||||
import { sessionExpiredDialogAttributes } from "ente-accounts/components/utils/dialog";
|
||||
import {
|
||||
twoFactorEnabledErrorMessage,
|
||||
useSecondFactorChoiceIfNeeded,
|
||||
} from "ente-accounts/components/utils/second-factor-choice";
|
||||
import { useSecondFactorChoiceIfNeeded } from "ente-accounts/components/utils/second-factor-choice";
|
||||
import {
|
||||
VerifyMasterPasswordForm,
|
||||
type VerifyMasterPasswordFormProps,
|
||||
} from "ente-accounts/components/VerifyMasterPasswordForm";
|
||||
import {
|
||||
getData,
|
||||
getToken,
|
||||
savedIsFirstLogin,
|
||||
savedKeyAttributes,
|
||||
savedPartialLocalUser,
|
||||
@@ -24,7 +19,6 @@ import {
|
||||
saveIsFirstLogin,
|
||||
saveKeyAttributes,
|
||||
saveSRPAttributes,
|
||||
setLSUser,
|
||||
updateSavedLocalUser,
|
||||
} from "ente-accounts/services/accounts-db";
|
||||
import {
|
||||
@@ -47,13 +41,13 @@ import {
|
||||
import {
|
||||
generateAndSaveInteractiveKeyAttributes,
|
||||
type KeyAttributes,
|
||||
type PartialLocalUser,
|
||||
} from "ente-accounts/services/user";
|
||||
import { decryptAndStoreToken } from "ente-accounts/utils/helpers";
|
||||
import { LinkButton } from "ente-base/components/LinkButton";
|
||||
import { LoadingIndicator } from "ente-base/components/loaders";
|
||||
import { useBaseContext } from "ente-base/context";
|
||||
import { decryptBox } from "ente-base/crypto";
|
||||
import { isDevBuild } from "ente-base/env";
|
||||
import { clearLocalStorage } from "ente-base/local-storage";
|
||||
import log from "ente-base/log";
|
||||
import {
|
||||
@@ -63,6 +57,7 @@ import {
|
||||
unstashKeyEncryptionKeyFromSession,
|
||||
updateSessionFromElectronSafeStorageIfNeeded,
|
||||
} from "ente-base/session";
|
||||
import { saveAuthToken } from "ente-base/token";
|
||||
import { t } from "i18next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
@@ -77,19 +72,25 @@ import { useCallback, useEffect, useState } from "react";
|
||||
* - Subsequent reauthentication, when the user opens the web app in a new tab.
|
||||
* Such a tab won't have the user's master key in session storage, so we ask
|
||||
* the user to reauthenticate using their password.
|
||||
*
|
||||
* See: [Note: Login pages]
|
||||
*/
|
||||
const Page: React.FC = () => {
|
||||
const { logout, showMiniDialog } = useBaseContext();
|
||||
|
||||
const [user, setUser] = useState<PartialLocalUser | undefined>(undefined);
|
||||
const [keyAttributes, setKeyAttributes] = useState<KeyAttributes>();
|
||||
const [srpAttributes, setSRPAttributes] = useState<SRPAttributes>();
|
||||
const [userEmail, setUserEmail] = useState<string>("");
|
||||
const [keyAttributes, setKeyAttributes] = useState<
|
||||
KeyAttributes | undefined
|
||||
>(undefined);
|
||||
const [srpAttributes, setSRPAttributes] = useState<
|
||||
SRPAttributes | undefined
|
||||
>(undefined);
|
||||
const [passkeyVerificationData, setPasskeyVerificationData] = useState<
|
||||
{ passkeySessionID: string; url: string } | undefined
|
||||
>();
|
||||
>(undefined);
|
||||
const [sessionValidityCheck, setSessionValidityCheck] = useState<
|
||||
Promise<void> | undefined
|
||||
>();
|
||||
>(undefined);
|
||||
|
||||
const {
|
||||
secondFactorChoiceProps,
|
||||
@@ -128,28 +129,55 @@ const Page: React.FC = () => {
|
||||
}
|
||||
}, [logout, showMiniDialog]);
|
||||
|
||||
const postVerification = useCallback(
|
||||
async (
|
||||
userEmail: string,
|
||||
masterKey: string,
|
||||
kek: string,
|
||||
keyAttributes: KeyAttributes,
|
||||
) => {
|
||||
await saveMasterKeyInSessionAndSafeStore(masterKey);
|
||||
await decryptAndStoreToken(keyAttributes, masterKey);
|
||||
try {
|
||||
let srpAttributes = savedSRPAttributes();
|
||||
if (!srpAttributes) {
|
||||
srpAttributes = await getSRPAttributes(userEmail);
|
||||
if (srpAttributes) {
|
||||
saveSRPAttributes(srpAttributes);
|
||||
} else {
|
||||
await setupSRP(await generateSRPSetupAttributes(kek));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log.error("SRP migration failed", e);
|
||||
}
|
||||
void router.push(unstashRedirect() ?? appHomeRoute);
|
||||
},
|
||||
[router],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const main = async () => {
|
||||
void (async () => {
|
||||
const user = savedPartialLocalUser();
|
||||
if (!user?.email) {
|
||||
const userEmail = user?.email;
|
||||
if (!userEmail) {
|
||||
void router.push("/");
|
||||
return;
|
||||
}
|
||||
|
||||
setUser(user);
|
||||
await updateSessionFromElectronSafeStorageIfNeeded();
|
||||
if (await haveAuthenticatedSession()) {
|
||||
void router.push(appHomeRoute);
|
||||
return;
|
||||
}
|
||||
|
||||
setUserEmail(userEmail);
|
||||
if (user.token) setSessionValidityCheck(validateSession());
|
||||
|
||||
const kek = await unstashKeyEncryptionKeyFromSession();
|
||||
const keyAttributes = savedKeyAttributes();
|
||||
const srpAttributes = savedSRPAttributes();
|
||||
|
||||
if (getToken()) {
|
||||
setSessionValidityCheck(validateSession());
|
||||
}
|
||||
|
||||
// Refreshing an existing tab, or desktop app.
|
||||
if (kek && keyAttributes) {
|
||||
const masterKey = await decryptBox(
|
||||
{
|
||||
@@ -158,15 +186,21 @@ const Page: React.FC = () => {
|
||||
},
|
||||
kek,
|
||||
);
|
||||
await postVerification(masterKey, kek, keyAttributes);
|
||||
await postVerification(
|
||||
userEmail,
|
||||
masterKey,
|
||||
kek,
|
||||
keyAttributes,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Reauthentication in a new tab on the web app. Use previously
|
||||
// generated interactive key attributes to verify password.
|
||||
if (keyAttributes) {
|
||||
if (
|
||||
(!user?.token && !user?.encryptedToken) ||
|
||||
(keyAttributes && !keyAttributes.memLimit)
|
||||
) {
|
||||
if (!user?.token && !user?.encryptedToken) {
|
||||
// TODO(RE): Why? For now, add a dev mode circuit breaker.
|
||||
if (isDevBuild) throw new Error("Unexpected case reached");
|
||||
clearLocalStorage();
|
||||
void router.push("/");
|
||||
return;
|
||||
@@ -175,127 +209,103 @@ const Page: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// First login on a new client. `getKeyAttributes` from below will
|
||||
// be used during password verification to generate interactive key
|
||||
// attributes for subsequent reauthentications.
|
||||
const srpAttributes = savedSRPAttributes();
|
||||
if (srpAttributes) {
|
||||
setSRPAttributes(srpAttributes);
|
||||
} else {
|
||||
void router.push("/");
|
||||
return;
|
||||
}
|
||||
};
|
||||
void main();
|
||||
// TODO: validateSession is a dependency, but add that only after we've
|
||||
// wrapped items from the callback (like logout) in useCallback too.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
void router.push("/");
|
||||
})();
|
||||
}, [router, validateSession, postVerification]);
|
||||
|
||||
const getKeyAttributes: VerifyMasterPasswordFormProps["getKeyAttributes"] =
|
||||
async (kek: string) => {
|
||||
try {
|
||||
// Currently the page will get reloaded if any of the attributes
|
||||
// have changed, so we don't need to worry about the KEK having
|
||||
// been generated using stale credentials. This await on the
|
||||
// promise is here to only ensure we're done with the check
|
||||
// before we let the user in.
|
||||
if (sessionValidityCheck) await sessionValidityCheck;
|
||||
const {
|
||||
id,
|
||||
keyAttributes,
|
||||
token,
|
||||
encryptedToken,
|
||||
twoFactorSessionID,
|
||||
passkeySessionID,
|
||||
accountsUrl,
|
||||
} = await userVerificationResultAfterResolvingSecondFactorChoice(
|
||||
await verifySRP(srpAttributes!, kek),
|
||||
);
|
||||
|
||||
const {
|
||||
keyAttributes,
|
||||
encryptedToken,
|
||||
token,
|
||||
id,
|
||||
twoFactorSessionID,
|
||||
// If we had to ask remote for the key attributes, it is the initial
|
||||
// login on this client.
|
||||
saveIsFirstLogin();
|
||||
|
||||
if (passkeySessionID) {
|
||||
await stashKeyEncryptionKeyInSessionStore(kek);
|
||||
updateSavedLocalUser({ passkeySessionID });
|
||||
stashRedirect("/");
|
||||
const url = passkeyVerificationRedirectURL(
|
||||
accountsUrl!,
|
||||
passkeySessionID,
|
||||
accountsUrl,
|
||||
} =
|
||||
await userVerificationResultAfterResolvingSecondFactorChoice(
|
||||
await verifySRP(srpAttributes!, kek),
|
||||
);
|
||||
saveIsFirstLogin();
|
||||
|
||||
if (passkeySessionID) {
|
||||
await stashKeyEncryptionKeyInSessionStore(kek);
|
||||
updateSavedLocalUser({ passkeySessionID });
|
||||
stashRedirect("/");
|
||||
const url = passkeyVerificationRedirectURL(
|
||||
accountsUrl!,
|
||||
passkeySessionID,
|
||||
);
|
||||
setPasskeyVerificationData({ passkeySessionID, url });
|
||||
openPasskeyVerificationURL({ passkeySessionID, url });
|
||||
throw new Error(twoFactorEnabledErrorMessage);
|
||||
} else if (twoFactorSessionID) {
|
||||
await stashKeyEncryptionKeyInSessionStore(kek);
|
||||
updateSavedLocalUser({
|
||||
isTwoFactorEnabled: true,
|
||||
twoFactorSessionID,
|
||||
});
|
||||
void router.push("/two-factor/verify");
|
||||
throw new Error(twoFactorEnabledErrorMessage);
|
||||
} else {
|
||||
const user = getData("user");
|
||||
await setLSUser({
|
||||
...user,
|
||||
token,
|
||||
encryptedToken,
|
||||
id,
|
||||
isTwoFactorEnabled: undefined,
|
||||
twoFactorSessionID: undefined,
|
||||
passkeySessionID: undefined,
|
||||
});
|
||||
if (keyAttributes) saveKeyAttributes(keyAttributes);
|
||||
return keyAttributes;
|
||||
}
|
||||
} catch (e) {
|
||||
if (
|
||||
e instanceof Error &&
|
||||
e.message != twoFactorEnabledErrorMessage
|
||||
) {
|
||||
log.error("getKeyAttributes failed", e);
|
||||
}
|
||||
throw e;
|
||||
);
|
||||
setPasskeyVerificationData({ passkeySessionID, url });
|
||||
openPasskeyVerificationURL({ passkeySessionID, url });
|
||||
return "redirecting-second-factor";
|
||||
} else if (twoFactorSessionID) {
|
||||
await stashKeyEncryptionKeyInSessionStore(kek);
|
||||
updateSavedLocalUser({
|
||||
isTwoFactorEnabled: true,
|
||||
twoFactorSessionID,
|
||||
});
|
||||
void router.push("/two-factor/verify");
|
||||
return "redirecting-second-factor";
|
||||
} else {
|
||||
// In rare cases, if the user hasn't already setup their key
|
||||
// attributes, we might get the plaintext token from remote.
|
||||
if (token) await saveAuthToken(token);
|
||||
updateSavedLocalUser({
|
||||
id,
|
||||
token,
|
||||
encryptedToken,
|
||||
isTwoFactorEnabled: undefined,
|
||||
twoFactorSessionID: undefined,
|
||||
passkeySessionID: undefined,
|
||||
});
|
||||
if (keyAttributes) saveKeyAttributes(keyAttributes);
|
||||
return keyAttributes;
|
||||
}
|
||||
};
|
||||
|
||||
const handleVerifyMasterPassword: VerifyMasterPasswordFormProps["onVerify"] =
|
||||
(key, kek, keyAttributes, password) => {
|
||||
void (async () => {
|
||||
const updatedKeyAttributes = savedIsFirstLogin()
|
||||
? await generateAndSaveInteractiveKeyAttributes(
|
||||
password,
|
||||
keyAttributes,
|
||||
key,
|
||||
)
|
||||
: keyAttributes;
|
||||
await postVerification(key, kek, updatedKeyAttributes);
|
||||
})();
|
||||
};
|
||||
useCallback(
|
||||
(key, kek, keyAttributes, password) => {
|
||||
void (async () => {
|
||||
// Currently the page will get reloaded if any of the
|
||||
// attributes have changed, so we don't need to worry about
|
||||
// the KEK having been generated using stale credentials.
|
||||
//
|
||||
// This await on the promise is here to only ensure we're
|
||||
// done with the check before we let the user in.
|
||||
if (sessionValidityCheck) await sessionValidityCheck;
|
||||
|
||||
const postVerification = async (
|
||||
masterKey: string,
|
||||
kek: string,
|
||||
keyAttributes: KeyAttributes,
|
||||
) => {
|
||||
await saveMasterKeyInSessionAndSafeStore(masterKey);
|
||||
await decryptAndStoreToken(keyAttributes, masterKey);
|
||||
try {
|
||||
let srpAttributes = savedSRPAttributes();
|
||||
if (!srpAttributes && user?.email) {
|
||||
srpAttributes = await getSRPAttributes(user.email);
|
||||
if (srpAttributes) {
|
||||
saveSRPAttributes(srpAttributes);
|
||||
}
|
||||
}
|
||||
// TODO: todo?
|
||||
log.debug(() => `userSRPSetupPending ${!srpAttributes}`);
|
||||
if (!srpAttributes) {
|
||||
await setupSRP(await generateSRPSetupAttributes(kek));
|
||||
}
|
||||
} catch (e) {
|
||||
log.error("migrate to srp failed", e);
|
||||
}
|
||||
void router.push(unstashRedirect() ?? appHomeRoute);
|
||||
};
|
||||
const updatedKeyAttributes = savedIsFirstLogin()
|
||||
? await generateAndSaveInteractiveKeyAttributes(
|
||||
password,
|
||||
keyAttributes,
|
||||
key,
|
||||
)
|
||||
: keyAttributes;
|
||||
|
||||
const userEmail = user?.email;
|
||||
await postVerification(
|
||||
userEmail,
|
||||
key,
|
||||
kek,
|
||||
updatedKeyAttributes,
|
||||
);
|
||||
})();
|
||||
},
|
||||
[postVerification, userEmail, sessionValidityCheck],
|
||||
);
|
||||
|
||||
if (!userEmail) {
|
||||
return <LoadingIndicator />;
|
||||
@@ -321,7 +331,7 @@ const Page: React.FC = () => {
|
||||
|
||||
return (
|
||||
<VerifyingPasskey
|
||||
email={user?.email}
|
||||
email={userEmail}
|
||||
passkeySessionID={passkeyVerificationData?.passkeySessionID}
|
||||
onRetry={() =>
|
||||
openPasskeyVerificationURL(passkeyVerificationData)
|
||||
|
||||
@@ -58,6 +58,18 @@ import React, { useCallback, useEffect, useState } from "react";
|
||||
* - Redirects to "/" if there is no `email` present in the saved partial
|
||||
* local user.
|
||||
*
|
||||
* - Redirects to "/two-factor/verify" if saved key attributes are not present
|
||||
* once password is verified and the user has setup an additional TOTP
|
||||
* second factor that also needs to be verified.
|
||||
*
|
||||
* - Redirects to the passkey app once password is verified if saved key
|
||||
* attributes are not present if the user has setup an additional passkey
|
||||
* that also needs to be verified. Before redirecting, it sets the
|
||||
* `inflightPasskeySessionID` in session storage.
|
||||
*
|
||||
* - Redirects to the `appHomeRoute` otherwise (e.g. /gallery). The flow is
|
||||
* complete.
|
||||
*
|
||||
* - "/generate" - A page that allows the user to generate key attributes if
|
||||
* needed, and shows them their recovery key.
|
||||
*
|
||||
@@ -112,7 +124,7 @@ import React, { useCallback, useEffect, useState } from "react";
|
||||
* or either of `twoFactorSessionID` and `twoFactorSessionID` is set.
|
||||
*
|
||||
* - Redirects to "/generate" if there is an `encryptedToken` or `token` in
|
||||
* the saved partial local user (TODO: Why?).
|
||||
* the saved partial local user.
|
||||
*
|
||||
* - Redirects to "/credentials" after recovery.
|
||||
*
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
import { getKVS, removeKV, setKV } from "ente-base/kv";
|
||||
import log from "ente-base/log";
|
||||
import { getAuthToken } from "ente-base/token";
|
||||
import { savedAuthToken } from "ente-base/token";
|
||||
import { nullToUndefined } from "ente-utils/transform";
|
||||
import { z } from "zod/v4";
|
||||
import {
|
||||
@@ -264,7 +264,7 @@ export const migrateKVToken = async (user: unknown) => {
|
||||
* token in local storage, then it should also be present in IndexedDB.
|
||||
*/
|
||||
export const isLocalStorageAndIndexedDBMismatch = async () =>
|
||||
savedPartialLocalUser()?.token && !(await getAuthToken());
|
||||
savedPartialLocalUser()?.token && !(await savedAuthToken());
|
||||
|
||||
/**
|
||||
* Return the user's {@link KeyAttributes} if they are present in local storage.
|
||||
@@ -381,11 +381,6 @@ export const unstashAfterUseSRPSetupAttributes = async (
|
||||
localStorage.removeItem("srpSetupAttributes");
|
||||
};
|
||||
|
||||
export const getToken = (): string => {
|
||||
const token = getData("user")?.token;
|
||||
return token;
|
||||
};
|
||||
|
||||
/**
|
||||
* Zod schema for the legacy format in which the {@link savedIsFirstLogin} and
|
||||
* {@link savedJustSignedUp} flags were saved in local storage.
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { KeyAttributes } from "ente-accounts/services/user";
|
||||
import { authenticatedRequestHeaders, HTTPError } from "ente-base/http";
|
||||
import log from "ente-base/log";
|
||||
import { apiURL } from "ente-base/origins";
|
||||
import { getAuthToken } from "ente-base/token";
|
||||
import { savedAuthToken } from "ente-base/token";
|
||||
import { nullToUndefined } from "ente-utils/transform";
|
||||
import { z } from "zod/v4";
|
||||
import { getSRPAttributes, type SRPAttributes } from "./srp";
|
||||
@@ -150,7 +150,7 @@ export const checkSessionValidity = async (): Promise<SessionValidity> => {
|
||||
* e.g. transient network issues.
|
||||
*/
|
||||
export const isSessionInvalid = async (): Promise<boolean> => {
|
||||
const token = await getAuthToken();
|
||||
const token = await savedAuthToken();
|
||||
if (!token) {
|
||||
return true; /* No saved token, session is invalid */
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
ensureMasterKeyFromSession,
|
||||
saveMasterKeyInSessionAndSafeStore,
|
||||
} from "ente-base/session";
|
||||
import { getAuthToken } from "ente-base/token";
|
||||
import { savedAuthToken } from "ente-base/token";
|
||||
import { ensure } from "ente-utils/ensure";
|
||||
import { nullToUndefined } from "ente-utils/transform";
|
||||
import { z } from "zod/v4";
|
||||
@@ -67,7 +67,7 @@ export interface LocalUser {
|
||||
* the value of the X-Auth-Token header in the HTTP request.
|
||||
*
|
||||
* Usually you shouldn't be needing to access this property; instead use
|
||||
* {@link getAuthToken()} which is kept in sync with this value, and lives
|
||||
* {@link savedAuthToken()} which is kept in sync with this value, and lives
|
||||
* in IndexedDB and thus can also be used in web workers.
|
||||
*/
|
||||
token: string;
|
||||
@@ -582,9 +582,9 @@ export const verifyEmail = async (
|
||||
* Log the user out on remote, if possible and needed.
|
||||
*/
|
||||
export const remoteLogoutIfNeeded = async () => {
|
||||
if (!(await getAuthToken())) {
|
||||
// If the logout is attempted during the signup flow itself, then we
|
||||
// won't have an auth token.
|
||||
if (!(await savedAuthToken())) {
|
||||
// If the logout is attempted during the login / signup flow itself,
|
||||
// then we won't have an auth token. Handle that gracefully.
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from "zod/v4";
|
||||
import { decryptBox, encryptBox, generateKey } from "./crypto";
|
||||
import log from "./log";
|
||||
import { getAuthToken } from "./token";
|
||||
import { savedAuthToken } from "./token";
|
||||
|
||||
/**
|
||||
* Remove all data stored in session storage (data tied to the browser tab).
|
||||
@@ -159,7 +159,7 @@ export const updateSessionFromElectronSafeStorageIfNeeded = async () => {
|
||||
* and their auth token in KV DB.
|
||||
*/
|
||||
export const haveAuthenticatedSession = async () =>
|
||||
(await masterKeyFromSession()) && !!(await getAuthToken());
|
||||
(await masterKeyFromSession()) && !!(await savedAuthToken());
|
||||
|
||||
/**
|
||||
* Save the user's encypted key encryption key ("key") in session store
|
||||
|
||||
@@ -1,25 +1,35 @@
|
||||
import { getKVS } from "./kv";
|
||||
|
||||
/**
|
||||
* Return the user's auth token, if present.
|
||||
*
|
||||
* The user's auth token is stored in KV DB after they have successfully logged
|
||||
* in. This function returns that saved auth token.
|
||||
*
|
||||
* The underlying data is stored in IndexedDB, and can be accessed from web
|
||||
* workers.
|
||||
*/
|
||||
export const getAuthToken = () => getKVS("token");
|
||||
import { getKVS, setKV } from "./kv";
|
||||
|
||||
/**
|
||||
* Return the user's auth token, or throw an error.
|
||||
*
|
||||
* The user's auth token can be retrieved using {@link getAuthToken}. This
|
||||
* The user's auth token can be retrieved using {@link savedAuthToken}. This
|
||||
* function is a wrapper which throws an error if the token is not found (which
|
||||
* should only happen if the user is not logged in).
|
||||
*/
|
||||
export const ensureAuthToken = async () => {
|
||||
const token = await getAuthToken();
|
||||
const token = await savedAuthToken();
|
||||
if (!token) throw new Error("Not logged in");
|
||||
return token;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the user's auth token, if available.
|
||||
*
|
||||
* The user's auth token is stored in KV DB using {@link saveAuthToken} during
|
||||
* the login / signup flow. This function returns that saved auth token.
|
||||
*
|
||||
* The underlying data is stored in IndexedDB, and can be accessed from web
|
||||
* workers.
|
||||
*
|
||||
* If your code is running in a context where the user is already expected to be
|
||||
* logged in, use {@link ensureAuthToken} instead.
|
||||
*/
|
||||
export const savedAuthToken = () => getKVS("token");
|
||||
|
||||
/**
|
||||
* Save the user's auth token in KV DB.
|
||||
*
|
||||
* This is the setter corresponding to {@link savedAuthToken}.
|
||||
*/
|
||||
export const saveAuthToken = (token: string) => setKV("token", token);
|
||||
|
||||
Reference in New Issue
Block a user