[web] Accounts DB cleanup (#6447)
This commit is contained in:
@@ -15,11 +15,9 @@ import { Sidebar } from "components/Sidebar";
|
||||
import { Upload } from "components/Upload";
|
||||
import { sessionExpiredDialogAttributes } from "ente-accounts/components/utils/dialog";
|
||||
import {
|
||||
getAndClearIsFirstLogin,
|
||||
getAndClearJustSignedUp,
|
||||
getData,
|
||||
isFirstLogin,
|
||||
justSignedUp,
|
||||
setIsFirstLogin,
|
||||
setJustSignedUp,
|
||||
} from "ente-accounts/services/accounts-db";
|
||||
import { stashRedirect } from "ente-accounts/services/redirect";
|
||||
import { isSessionInvalid } from "ente-accounts/services/session";
|
||||
@@ -299,16 +297,27 @@ const Page: React.FC = () => {
|
||||
|
||||
// We are logged in and everything looks fine. Proceed with page
|
||||
// load initialization.
|
||||
initSettings();
|
||||
|
||||
// One time inits.
|
||||
preloadImage("/images/subscription-card-background");
|
||||
initSettings();
|
||||
await initUserDetailsOrTriggerPull();
|
||||
setupSelectAllKeyBoardShortcutHandler();
|
||||
|
||||
// Show the initial state while the rest of the sequence proceeds.
|
||||
dispatch({ type: "showAll" });
|
||||
setIsFirstLoad(isFirstLogin());
|
||||
if (justSignedUp()) {
|
||||
|
||||
// If this is the user's first login on this client, then show them
|
||||
// a message informing the that the initial load might take time.
|
||||
setIsFirstLoad(getAndClearIsFirstLogin());
|
||||
|
||||
// If the user created a new account on this client, show them the
|
||||
// plan options.
|
||||
if (getAndClearJustSignedUp()) {
|
||||
showPlanSelector();
|
||||
}
|
||||
setIsFirstLogin(false);
|
||||
|
||||
// Initialize the reducer.
|
||||
const user = getData("user");
|
||||
// TODO: Pass entire snapshot to reducer?
|
||||
const familyData = userDetailsSnapshot()?.familyData;
|
||||
@@ -320,13 +329,19 @@ const Page: React.FC = () => {
|
||||
collectionFiles: await savedCollectionFiles(),
|
||||
trashItems: await savedTrashItems(),
|
||||
});
|
||||
|
||||
// Fetch data from remote.
|
||||
await remotePull();
|
||||
|
||||
// Clear the first load message if needed.
|
||||
setIsFirstLoad(false);
|
||||
setJustSignedUp(false);
|
||||
|
||||
// Start the interval that does a periodic pull.
|
||||
syncIntervalID = setInterval(
|
||||
() => remotePull({ silent: true }),
|
||||
5 * 60 * 1000 /* 5 minutes */,
|
||||
);
|
||||
|
||||
if (electron) {
|
||||
electron.onMainWindowFocus(() => remotePull({ silent: true }));
|
||||
if (await shouldShowWhatsNew(electron)) showWhatsNew();
|
||||
|
||||
@@ -14,9 +14,9 @@ import {
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
saveJustSignedUp,
|
||||
setData,
|
||||
setJustSignedUp,
|
||||
setLocalReferralSource,
|
||||
stashReferralSource,
|
||||
} from "ente-accounts/services/accounts-db";
|
||||
import {
|
||||
generateSRPSetupAttributes,
|
||||
@@ -107,7 +107,8 @@ export const SignUpContents: React.FC<SignUpContentsProps> = ({
|
||||
}
|
||||
|
||||
try {
|
||||
setLocalReferralSource(referral);
|
||||
const cleanedReferral = referral.trim();
|
||||
if (cleanedReferral) stashReferralSource(cleanedReferral);
|
||||
|
||||
try {
|
||||
await sendOTT(email, "signup");
|
||||
@@ -154,7 +155,7 @@ export const SignUpContents: React.FC<SignUpContentsProps> = ({
|
||||
);
|
||||
await saveMasterKeyInSessionAndSafeStore(masterKey);
|
||||
|
||||
setJustSignedUp(true);
|
||||
saveJustSignedUp();
|
||||
void router.push("/verify");
|
||||
} catch (e) {
|
||||
log.error("Signup failed", e);
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
AccountsPageFooter,
|
||||
AccountsPageTitle,
|
||||
} from "ente-accounts/components/layouts/centered-paper";
|
||||
import { getData, setData } from "ente-accounts/services/accounts-db";
|
||||
import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect";
|
||||
import {
|
||||
changePassword,
|
||||
@@ -31,6 +30,9 @@ const Page: React.FC = () => {
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// We're invoked with the "?op=reset" query parameter in the recovery flow.
|
||||
const isReset = router.query.op == "reset";
|
||||
|
||||
useEffect(() => {
|
||||
const user = localUser();
|
||||
if (user) {
|
||||
@@ -41,38 +43,41 @@ const Page: React.FC = () => {
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
return user ? <PageContents {...{ user }} /> : <LoadingIndicator />;
|
||||
return user ? (
|
||||
<PageContents {...{ user, isReset }} />
|
||||
) : (
|
||||
<LoadingIndicator />
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
||||
interface PageContentsProps {
|
||||
user: LocalUser;
|
||||
/**
|
||||
* True if the password is being reset during the account recovery flow.
|
||||
*/
|
||||
isReset: boolean;
|
||||
}
|
||||
|
||||
const PageContents: React.FC<PageContentsProps> = ({ user }) => {
|
||||
const PageContents: React.FC<PageContentsProps> = ({ user, isReset }) => {
|
||||
const router = useRouter();
|
||||
|
||||
const redirectToAppHome = useCallback(() => {
|
||||
setData("showBackButton", { value: true });
|
||||
void router.push(appHomeRoute);
|
||||
}, [router]);
|
||||
|
||||
const handleSubmit: NewPasswordFormProps["onSubmit"] = async (
|
||||
password,
|
||||
setPasswordsFieldError,
|
||||
) =>
|
||||
changePassword(password)
|
||||
.then(redirectToAppHome)
|
||||
.catch((e: unknown) => {
|
||||
log.error("Could not change password", e);
|
||||
setPasswordsFieldError(
|
||||
e instanceof Error &&
|
||||
e.message == deriveKeyInsufficientMemoryErrorMessage
|
||||
? t("password_generation_failed")
|
||||
: t("generic_error"),
|
||||
);
|
||||
});
|
||||
const handleSubmit: NewPasswordFormProps["onSubmit"] = useCallback(
|
||||
async (password, setPasswordsFieldError) =>
|
||||
changePassword(password)
|
||||
.then(() => void router.push(appHomeRoute))
|
||||
.catch((e: unknown) => {
|
||||
log.error("Could not change password", e);
|
||||
setPasswordsFieldError(
|
||||
e instanceof Error &&
|
||||
e.message == deriveKeyInsufficientMemoryErrorMessage
|
||||
? t("password_generation_failed")
|
||||
: t("generic_error"),
|
||||
);
|
||||
}),
|
||||
[router],
|
||||
);
|
||||
|
||||
return (
|
||||
<AccountsPageContents>
|
||||
@@ -82,7 +87,7 @@ const PageContents: React.FC<PageContentsProps> = ({ user }) => {
|
||||
submitButtonTitle={t("change_password")}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
{(getData("showBackButton")?.value ?? true) && (
|
||||
{!isReset && (
|
||||
<>
|
||||
<Divider sx={{ mt: 1 }} />
|
||||
<AccountsPageFooter>
|
||||
|
||||
@@ -17,9 +17,9 @@ import {
|
||||
import {
|
||||
getData,
|
||||
getToken,
|
||||
isFirstLogin,
|
||||
savedIsFirstLogin,
|
||||
saveIsFirstLogin,
|
||||
setData,
|
||||
setIsFirstLogin,
|
||||
setLSUser,
|
||||
} from "ente-accounts/services/accounts-db";
|
||||
import {
|
||||
@@ -98,7 +98,7 @@ const Page: React.FC = () => {
|
||||
setData("srpAttributes", session.updatedSRPAttributes);
|
||||
// Set a flag that causes new interactive key attributes to
|
||||
// be generated.
|
||||
setIsFirstLogin(true);
|
||||
saveIsFirstLogin();
|
||||
// This should be a rare occurrence, instead of building the
|
||||
// scaffolding to update all the in-memory state, just
|
||||
// reload everything.
|
||||
@@ -191,7 +191,7 @@ const Page: React.FC = () => {
|
||||
await userVerificationResultAfterResolvingSecondFactorChoice(
|
||||
await verifySRP(srpAttributes!, kek),
|
||||
);
|
||||
setIsFirstLogin(true);
|
||||
saveIsFirstLogin();
|
||||
|
||||
if (passkeySessionID) {
|
||||
await stashKeyEncryptionKeyInSessionStore(kek);
|
||||
@@ -246,7 +246,7 @@ const Page: React.FC = () => {
|
||||
const handleVerifyMasterPassword: VerifyMasterPasswordFormProps["onVerify"] =
|
||||
(key, kek, keyAttributes, password) => {
|
||||
void (async () => {
|
||||
const updatedKeyAttributes = isFirstLogin()
|
||||
const updatedKeyAttributes = savedIsFirstLogin()
|
||||
? await generateAndSaveInteractiveKeyAttributes(
|
||||
password,
|
||||
keyAttributes,
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
import { RecoveryKey } from "ente-accounts/components/RecoveryKey";
|
||||
import {
|
||||
getData,
|
||||
justSignedUp,
|
||||
setJustSignedUp,
|
||||
savedJustSignedUp,
|
||||
saveJustSignedUp,
|
||||
} from "ente-accounts/services/accounts-db";
|
||||
import { appHomeRoute } from "ente-accounts/services/redirect";
|
||||
import {
|
||||
@@ -54,7 +54,7 @@ const Page: React.FC = () => {
|
||||
if (!user?.token) {
|
||||
void router.push("/");
|
||||
} else if (haveCredentialsInSession()) {
|
||||
if (justSignedUp()) {
|
||||
if (savedJustSignedUp()) {
|
||||
setOpenRecoveryKey(true);
|
||||
setLoading(false);
|
||||
} else {
|
||||
@@ -82,7 +82,7 @@ const Page: React.FC = () => {
|
||||
masterKey,
|
||||
);
|
||||
await saveMasterKeyInSessionAndSafeStore(masterKey);
|
||||
setJustSignedUp(true);
|
||||
saveJustSignedUp();
|
||||
setOpenRecoveryKey(true);
|
||||
} catch (e) {
|
||||
log.error("failed to generate password", e);
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
AccountsPageFooter,
|
||||
AccountsPageTitle,
|
||||
} from "ente-accounts/components/layouts/centered-paper";
|
||||
import { getData, setData } from "ente-accounts/services/accounts-db";
|
||||
import { getData } from "ente-accounts/services/accounts-db";
|
||||
import { recoveryKeyFromMnemonic } from "ente-accounts/services/recovery-key";
|
||||
import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect";
|
||||
import type { KeyAttributes, User } from "ente-accounts/services/user";
|
||||
@@ -72,8 +72,7 @@ const Page: React.FC = () => {
|
||||
await saveMasterKeyInSessionAndSafeStore(masterKey);
|
||||
await decryptAndStoreToken(keyAttr, masterKey);
|
||||
|
||||
setData("showBackButton", { value: false });
|
||||
void router.push("/change-password");
|
||||
void router.push("/change-password?op=reset");
|
||||
} catch (e) {
|
||||
log.error("password recovery failed", e);
|
||||
setFieldError(t("incorrect_recovery_key"));
|
||||
|
||||
@@ -9,10 +9,10 @@ import { SecondFactorChoice } from "ente-accounts/components/SecondFactorChoice"
|
||||
import { useSecondFactorChoiceIfNeeded } from "ente-accounts/components/utils/second-factor-choice";
|
||||
import {
|
||||
getData,
|
||||
getLocalReferralSource,
|
||||
saveIsFirstLogin,
|
||||
setData,
|
||||
setIsFirstLogin,
|
||||
setLSUser,
|
||||
unstashReferralSource,
|
||||
} from "ente-accounts/services/accounts-db";
|
||||
import {
|
||||
openPasskeyVerificationURL,
|
||||
@@ -83,8 +83,7 @@ const Page: React.FC = () => {
|
||||
setFieldError,
|
||||
) => {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
const referralSource = getLocalReferralSource()?.trim();
|
||||
const referralSource = unstashReferralSource();
|
||||
const cleanedReferral = referralSource
|
||||
? `web:${referralSource}`
|
||||
: undefined;
|
||||
@@ -107,12 +106,7 @@ const Page: React.FC = () => {
|
||||
isTwoFactorEnabled: true,
|
||||
isTwoFactorPasskeysEnabled: true,
|
||||
});
|
||||
// TODO: This is not the first login though if they already have
|
||||
// 2FA. Does this flag mean first login on this device?
|
||||
//
|
||||
// Update: This flag causes the interactive encryption key to be
|
||||
// generated, so it has a functional impact we need.
|
||||
setIsFirstLogin(true);
|
||||
saveIsFirstLogin();
|
||||
const url = passkeyVerificationRedirectURL(
|
||||
accountsUrl!,
|
||||
passkeySessionID,
|
||||
@@ -125,7 +119,7 @@ const Page: React.FC = () => {
|
||||
twoFactorSessionID,
|
||||
isTwoFactorEnabled: true,
|
||||
});
|
||||
setIsFirstLogin(true);
|
||||
saveIsFirstLogin();
|
||||
void router.push("/two-factor/verify");
|
||||
} else {
|
||||
await setLSUser({
|
||||
@@ -147,7 +141,7 @@ const Page: React.FC = () => {
|
||||
}
|
||||
await unstashAndUseSRPSetupAttributes(setupSRP);
|
||||
}
|
||||
setIsFirstLogin(true);
|
||||
saveIsFirstLogin();
|
||||
const redirectURL = unstashRedirect();
|
||||
if (keyAttributes?.encryptedKey) {
|
||||
clearSessionStorage();
|
||||
@@ -196,7 +190,7 @@ const Page: React.FC = () => {
|
||||
return (
|
||||
<VerifyingPasskey
|
||||
email={email}
|
||||
passkeySessionID={passkeyVerificationData?.passkeySessionID}
|
||||
passkeySessionID={passkeyVerificationData.passkeySessionID}
|
||||
onRetry={() =>
|
||||
openPasskeyVerificationURL(passkeyVerificationData)
|
||||
}
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
import { getKVS, removeKV, setKV } from "ente-base/kv";
|
||||
import log from "ente-base/log";
|
||||
import { nullToUndefined } from "ente-utils/transform";
|
||||
import { z } from "zod/v4";
|
||||
import { RemoteKeyAttributes, type KeyAttributes } from "./user";
|
||||
|
||||
export type LocalStorageKey =
|
||||
| "user"
|
||||
// See also savedKeyAttributes.
|
||||
| "keyAttributes"
|
||||
| "originalKeyAttributes"
|
||||
| "isFirstLogin"
|
||||
| "justSignedUp"
|
||||
| "showBackButton"
|
||||
// Moved to ente-accounts
|
||||
// "srpSetupAttributes"
|
||||
| "srpAttributes"
|
||||
| "referralSource";
|
||||
|
||||
export const setData = (key: LocalStorageKey, value: object) =>
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
|
||||
export const removeData = (key: LocalStorageKey) =>
|
||||
localStorage.removeItem(key);
|
||||
| "srpAttributes";
|
||||
|
||||
/**
|
||||
* [Note: Accounts DB]
|
||||
@@ -54,6 +47,9 @@ export const getData = (key: LocalStorageKey) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const setData = (key: LocalStorageKey, value: object) =>
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
|
||||
// TODO: Migrate this to `local-user.ts`, with (a) more precise optionality
|
||||
// indication of the constituent fields, (b) moving any fields that need to be
|
||||
// accessed from web workers to KV DB.
|
||||
@@ -126,27 +122,178 @@ export const isLocalStorageAndIndexedDBMismatch = async () => {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the user's {@link KeyAttributes} if they are present in local storage.
|
||||
*
|
||||
* The key attributes are stored in the browser's localStorage. Thus, this
|
||||
* function only works from the main thread, not from web workers (local storage
|
||||
* is not accessible to web workers).
|
||||
*/
|
||||
export const savedKeyAttributes = (): KeyAttributes | undefined => {
|
||||
const jsonString = localStorage.getItem("keyAttributes");
|
||||
if (!jsonString) return undefined;
|
||||
return RemoteKeyAttributes.parse(JSON.parse(jsonString));
|
||||
};
|
||||
|
||||
/**
|
||||
* Save the user's {@link KeyAttributes} in local storage.
|
||||
*
|
||||
* Use {@link savedKeyAttributes} to retrieve them.
|
||||
*/
|
||||
export const saveKeyAttributes = (keyAttributes: KeyAttributes) =>
|
||||
localStorage.setItem("keyAttributes", JSON.stringify(keyAttributes));
|
||||
|
||||
export const getToken = (): string => {
|
||||
const token = getData("user")?.token;
|
||||
return token;
|
||||
};
|
||||
|
||||
export const isFirstLogin = () => getData("isFirstLogin")?.status ?? false;
|
||||
/**
|
||||
* Zod schema for the legacy format in which the {@link savedIsFirstLogin} and
|
||||
* {@link savedJustSignedUp} flags were saved in local storage.
|
||||
*
|
||||
* Starting 1.7.15-beta (July 2025), we started saving the booleans directly,
|
||||
* but when reading we fallback to the old format if needed. This fallback can
|
||||
* be removed, and soonish, since these are transient flags that are saved
|
||||
* during the login / signup sequence and wouldn't be expected to remain in the
|
||||
* user's local storage for long anyway. (tag: Migration).
|
||||
*/
|
||||
const LocalLegacyBooleanFlag = z.object({
|
||||
status: z.boolean().nullish().transform(nullToUndefined),
|
||||
});
|
||||
|
||||
export function setIsFirstLogin(status: boolean) {
|
||||
setData("isFirstLogin", { status });
|
||||
}
|
||||
/**
|
||||
* Return `true` if it is the user's first login on this client.
|
||||
*
|
||||
* The {@link savedIsFirstLogin} flag is saved in local storage (using
|
||||
* {@link saveIsFirstLogin}) if we determine during the login flow that it a
|
||||
* fresh login on this client. If so, we
|
||||
*
|
||||
* - Generate interactive key attributes for them, and
|
||||
*
|
||||
* - Display them a special indicator post login to notify them that the first
|
||||
* load might take extra time. At this point, we also clear the flag (the read
|
||||
* and clear is done by the same {@link getAndClearIsFirstLogin} function).
|
||||
*/
|
||||
export const savedIsFirstLogin = () => {
|
||||
const jsonString = localStorage.getItem("isFirstLogin");
|
||||
if (!jsonString) return false;
|
||||
try {
|
||||
return z.boolean().parse(JSON.parse(jsonString)) ?? false;
|
||||
} catch {
|
||||
return (
|
||||
LocalLegacyBooleanFlag.parse(JSON.parse(jsonString)).status ?? false
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const justSignedUp = () => getData("justSignedUp")?.status ?? false;
|
||||
/**
|
||||
* Save a flag in local storage to indicate that this is the user's first login
|
||||
* on this client.
|
||||
*
|
||||
* This is the setter corresponding to {@link savedIsFirstLogin}.
|
||||
*/
|
||||
export const saveIsFirstLogin = () => {
|
||||
localStorage.setItem("isFirstLogin", JSON.stringify({ status: true }));
|
||||
};
|
||||
|
||||
export function setJustSignedUp(status: boolean) {
|
||||
setData("justSignedUp", { status });
|
||||
}
|
||||
/**
|
||||
* Get the saved value of the local storage flag that indicates that this is the
|
||||
* user' first login on this client. Also remove the flag after reading.
|
||||
*
|
||||
* The flag can be set by using {@link saveIsFirstLogin}, and can be read
|
||||
* without clearing it by using {@link savedIsFirstLogin}.
|
||||
*/
|
||||
export const getAndClearIsFirstLogin = () => {
|
||||
// Completely unrelated, but since this code runs on each /gallery load, use
|
||||
// this as a chance to remove the unused "showBackButton" property saved in
|
||||
// local storage. This code was added 1.7.15-beta (July 2025) and can be
|
||||
// removed after a while, soonish (tag: Migration).
|
||||
localStorage.removeItem("showBackButton");
|
||||
|
||||
export function getLocalReferralSource() {
|
||||
return getData("referralSource")?.source;
|
||||
}
|
||||
const result = savedIsFirstLogin();
|
||||
localStorage.removeItem("isFirstLogin");
|
||||
return result;
|
||||
};
|
||||
|
||||
export function setLocalReferralSource(source: string) {
|
||||
setData("referralSource", { source });
|
||||
}
|
||||
/**
|
||||
* Return `true` if the user created a new account on this client during the
|
||||
* current (in-progress) or just completed login / signup sequence.
|
||||
*/
|
||||
export const savedJustSignedUp = () => {
|
||||
const jsonString = localStorage.getItem("justSignedUp");
|
||||
if (!jsonString) return false;
|
||||
try {
|
||||
return z.boolean().parse(JSON.parse(jsonString)) ?? false;
|
||||
} catch {
|
||||
return (
|
||||
LocalLegacyBooleanFlag.parse(JSON.parse(jsonString)).status ?? false
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Save a flag in local storage to indicate that the user signed up for a
|
||||
* new Ente account during the current login / signup sequence.
|
||||
*
|
||||
* This is the setter corresponding to {@link savedJustSignedUp}.
|
||||
*/
|
||||
export const saveJustSignedUp = () => {
|
||||
localStorage.setItem("justSignedUp", JSON.stringify({ status: true }));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the saved value of the local storage flag that indicates that the user just
|
||||
* signed up. Also remove the flag from local storage after reading.
|
||||
*
|
||||
* The flag can be set by using {@link saveJustSignedUp}, and can be read
|
||||
* without clearing it by using {@link savedJustSignedUp}.
|
||||
*/
|
||||
export const getAndClearJustSignedUp = () => {
|
||||
const result = savedJustSignedUp();
|
||||
localStorage.removeItem("justSignedUp");
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Zod schema for the format in which the {@link stashReferralSource} used to
|
||||
* saved the referral source in local storage.
|
||||
*
|
||||
* Starting 1.7.15-beta (July 2025), we started saving the string directly, but
|
||||
* when reading we fallback to the old format if needed. This fallback can be
|
||||
* removed, and soonish, since these is a transient value that isn't expected to
|
||||
* remain in the user's local storage for long anyway. (tag: Migration).
|
||||
*/
|
||||
const LocalLegacyReferralSource = z.object({ source: z.string() });
|
||||
|
||||
/**
|
||||
* Save the referral source entered by the user on the signup screen in local
|
||||
* storage.
|
||||
*
|
||||
* The saved value can be retrieved post email verification using
|
||||
* {@link unstashReferralSource}.
|
||||
*/
|
||||
export const stashReferralSource = (referralSource: string) => {
|
||||
localStorage.setItem("referralSource", referralSource);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the previously saved referral source (using
|
||||
* {@link stashReferralSource}), returning the saved value and also clearing it
|
||||
* from local storage.
|
||||
*/
|
||||
export const unstashReferralSource = () => {
|
||||
const jsonString = localStorage.getItem("referralSource");
|
||||
if (!jsonString) return undefined;
|
||||
localStorage.removeItem("referralSource");
|
||||
try {
|
||||
// Try the old format first. The trim is also a legacy expectation and
|
||||
// can be removed when we remove this fallfront.
|
||||
return LocalLegacyReferralSource.parse(
|
||||
JSON.parse(jsonString),
|
||||
).source.trim();
|
||||
} catch {
|
||||
// Otherwise try the new format.
|
||||
return jsonString;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
toHex,
|
||||
} from "ente-base/crypto";
|
||||
import { ensureMasterKeyFromSession } from "ente-base/session";
|
||||
import { putUserRecoveryKeyAttributes, saveKeyAttributes } from "./user";
|
||||
import { saveKeyAttributes } from "./accounts-db";
|
||||
import { putUserRecoveryKeyAttributes } from "./user";
|
||||
|
||||
// Mobile client library only supports English.
|
||||
bip39.setDefaultWordlist("english");
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { getData, setLSUser } from "ente-accounts/services/accounts-db";
|
||||
import {
|
||||
getData,
|
||||
savedKeyAttributes,
|
||||
saveKeyAttributes,
|
||||
setLSUser,
|
||||
} from "ente-accounts/services/accounts-db";
|
||||
import {
|
||||
generateSRPSetupAttributes,
|
||||
getSRPAttributes,
|
||||
@@ -350,19 +355,6 @@ export const RemoteKeyAttributes = z.object({
|
||||
recoveryKeyDecryptionNonce: z.string().nullish().transform(nullToUndefined),
|
||||
});
|
||||
|
||||
/**
|
||||
* Return the user's {@link KeyAttributes} if they are present in local storage.
|
||||
*
|
||||
* The key attributes are stored in the browser's localStorage. Thus, this
|
||||
* function only works from the main thread, not from web workers (local storage
|
||||
* is not accessible to web workers).
|
||||
*/
|
||||
export const savedKeyAttributes = (): KeyAttributes | undefined => {
|
||||
const jsonString = localStorage.getItem("keyAttributes");
|
||||
if (!jsonString) return undefined;
|
||||
return RemoteKeyAttributes.parse(JSON.parse(jsonString));
|
||||
};
|
||||
|
||||
/**
|
||||
* A variant of {@link savedKeyAttributes} that throws if keyAttributes are not
|
||||
* present in local storage.
|
||||
@@ -370,14 +362,6 @@ export const savedKeyAttributes = (): KeyAttributes | undefined => {
|
||||
export const ensureSavedKeyAttributes = (): KeyAttributes =>
|
||||
ensureExpectedLoggedInValue(savedKeyAttributes());
|
||||
|
||||
/**
|
||||
* Save the user's {@link KeyAttributes} in local storage.
|
||||
*
|
||||
* Use {@link savedKeyAttributes} to retrieve them.
|
||||
*/
|
||||
export const saveKeyAttributes = (keyAttributes: KeyAttributes) =>
|
||||
localStorage.setItem("keyAttributes", JSON.stringify(keyAttributes));
|
||||
|
||||
export interface GenerateKeysAndAttributesResult {
|
||||
masterKey: string;
|
||||
kek: string;
|
||||
|
||||
Reference in New Issue
Block a user