[web] General code improvements (#6168)
This commit is contained in:
@@ -2,6 +2,7 @@ import "@fontsource-variable/inter";
|
||||
import { CssBaseline } from "@mui/material";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import { accountLogout } from "ente-accounts/services/logout";
|
||||
import type { User } from "ente-accounts/services/user";
|
||||
import { clientPackageName, staticAppTitle } from "ente-base/app";
|
||||
import { CustomHead } from "ente-base/components/Head";
|
||||
import {
|
||||
@@ -20,7 +21,6 @@ import { BaseContext, deriveBaseContext } from "ente-base/context";
|
||||
import { logStartupBanner } from "ente-base/log-web";
|
||||
import HTTPService from "ente-shared/network/HTTPService";
|
||||
import { getData } from "ente-shared/storage/localStorage";
|
||||
import type { User } from "ente-shared/user/types";
|
||||
import { t } from "i18next";
|
||||
import type { AppProps } from "next/app";
|
||||
import React, { useCallback, useEffect, useMemo } from "react";
|
||||
|
||||
@@ -195,16 +195,14 @@ const decryptEnteFile = async (
|
||||
pubMagicMetadata,
|
||||
...restFileProps
|
||||
} = encryptedFile;
|
||||
const fileKey = await worker.decryptB64(
|
||||
encryptedKey,
|
||||
keyDecryptionNonce,
|
||||
const fileKey = await worker.decryptBoxB64(
|
||||
{ encryptedData: encryptedKey, nonce: keyDecryptionNonce },
|
||||
collectionKey,
|
||||
);
|
||||
const fileMetadata = await worker.decryptMetadataJSON({
|
||||
encryptedDataB64: metadata.encryptedData,
|
||||
decryptionHeaderB64: metadata.decryptionHeader,
|
||||
keyB64: fileKey,
|
||||
});
|
||||
const fileMetadata = await worker.decryptMetadataJSON_New(
|
||||
metadata,
|
||||
fileKey,
|
||||
);
|
||||
let fileMagicMetadata: FileMagicMetadata | undefined;
|
||||
let filePubMagicMetadata: FilePublicMagicMetadata | undefined;
|
||||
if (magicMetadata?.data) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { VerifyMasterPasswordForm } from "ente-accounts/components/VerifyMasterPasswordForm";
|
||||
import { checkSessionValidity } from "ente-accounts/services/session";
|
||||
import type { KeyAttributes, User } from "ente-accounts/services/user";
|
||||
import {
|
||||
TitledMiniDialog,
|
||||
type MiniDialogAttributes,
|
||||
@@ -8,7 +9,6 @@ import type { ModalVisibilityProps } from "ente-base/components/utils/modal";
|
||||
import { useBaseContext } from "ente-base/context";
|
||||
import log from "ente-base/log";
|
||||
import { getData } from "ente-shared/storage/localStorage";
|
||||
import type { KeyAttributes, User } from "ente-shared/user/types";
|
||||
import { t } from "i18next";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
|
||||
import { CssBaseline, Typography } from "@mui/material";
|
||||
import { styled, ThemeProvider } from "@mui/material/styles";
|
||||
import { useNotification } from "components/utils/hooks-app";
|
||||
import type { User } from "ente-accounts/services/user";
|
||||
import { clientPackageName, isDesktop, staticAppTitle } from "ente-base/app";
|
||||
import { CenteredRow } from "ente-base/components/containers";
|
||||
import { CustomHead } from "ente-base/components/Head";
|
||||
@@ -43,7 +44,6 @@ import {
|
||||
getData,
|
||||
isLocalStorageAndIndexedDBMismatch,
|
||||
} from "ente-shared/storage/localStorage";
|
||||
import type { User } from "ente-shared/user/types";
|
||||
import { t } from "i18next";
|
||||
import type { AppProps } from "next/app";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { User } from "ente-accounts/services/user";
|
||||
import { encryptMetadataJSON, sharedCryptoWorker } from "ente-base/crypto";
|
||||
import { ensureLocalUser } from "ente-base/local-user";
|
||||
import log from "ente-base/log";
|
||||
@@ -43,7 +44,6 @@ import HTTPService from "ente-shared/network/HTTPService";
|
||||
import { getData } from "ente-shared/storage/localStorage";
|
||||
import { getToken } from "ente-shared/storage/localStorage/helpers";
|
||||
import { getActualKey } from "ente-shared/user";
|
||||
import type { User } from "ente-shared/user/types";
|
||||
import { batch } from "ente-utils/array";
|
||||
import {
|
||||
changeCollectionSubType,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TimeStampListItem } from "components/FileList";
|
||||
import { FilesDownloadProgressAttributes } from "components/FilesDownloadProgress";
|
||||
import type { User } from "ente-accounts/services/user";
|
||||
import { type SelectionContext } from "ente-new/photos/components/gallery";
|
||||
import type { User } from "ente-shared/user/types";
|
||||
|
||||
export interface SelectedState {
|
||||
[k: number]: boolean;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { User } from "ente-accounts/services/user";
|
||||
import { ensureElectron } from "ente-base/electron";
|
||||
import { joinPath } from "ente-base/file-name";
|
||||
import log from "ente-base/log";
|
||||
@@ -30,7 +31,6 @@ import {
|
||||
} from "ente-new/photos/services/files";
|
||||
import { safeDirectoryName } from "ente-new/photos/utils/native-fs";
|
||||
import { getData } from "ente-shared/storage/localStorage";
|
||||
import type { User } from "ente-shared/user/types";
|
||||
import {
|
||||
createAlbum,
|
||||
removeFromCollection,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { User } from "ente-accounts/services/user";
|
||||
import { joinPath } from "ente-base/file-name";
|
||||
import log from "ente-base/log";
|
||||
import { type Electron } from "ente-base/types/ipc";
|
||||
@@ -21,7 +22,6 @@ import {
|
||||
} from "ente-new/photos/services/collection";
|
||||
import { safeFileName } from "ente-new/photos/utils/native-fs";
|
||||
import { getData } from "ente-shared/storage/localStorage";
|
||||
import type { User } from "ente-shared/user/types";
|
||||
import { wait } from "ente-utils/promise";
|
||||
import { t } from "i18next";
|
||||
import {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Input, TextField } from "@mui/material";
|
||||
import type { SRPAttributes } from "ente-accounts/services/srp-remote";
|
||||
import type { KeyAttributes, User } from "ente-accounts/services/user";
|
||||
import { LoadingButton } from "ente-base/components/mui/LoadingButton";
|
||||
import { ShowHidePasswordInputAdornment } from "ente-base/components/mui/PasswordInputAdornment";
|
||||
import { sharedCryptoWorker } from "ente-base/crypto";
|
||||
import log from "ente-base/log";
|
||||
import { CustomError } from "ente-shared/error";
|
||||
import type { KeyAttributes, User } from "ente-shared/user/types";
|
||||
import { useFormik } from "formik";
|
||||
import { t } from "i18next";
|
||||
import { useCallback, useState } from "react";
|
||||
@@ -101,7 +101,12 @@ export const VerifyMasterPasswordForm: React.FC<
|
||||
return;
|
||||
}
|
||||
|
||||
await verifyPassphrase(password, setPasswordFieldError);
|
||||
try {
|
||||
await verifyPassphrase(password, setPasswordFieldError);
|
||||
} catch (e) {
|
||||
log.error("Failed to to verify passphrase", e);
|
||||
setPasswordFieldError(t("generic_error"));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -109,76 +114,77 @@ export const VerifyMasterPasswordForm: React.FC<
|
||||
passphrase: string,
|
||||
setFieldError: (message: string) => void,
|
||||
) => {
|
||||
try {
|
||||
const cryptoWorker = await sharedCryptoWorker();
|
||||
let kek: string;
|
||||
if (srpAttributes) {
|
||||
try {
|
||||
kek = await cryptoWorker.deriveKey(
|
||||
passphrase,
|
||||
srpAttributes.kekSalt,
|
||||
srpAttributes.opsLimit,
|
||||
srpAttributes.memLimit,
|
||||
);
|
||||
} catch (e) {
|
||||
log.error("Failed to derive kek", e);
|
||||
setFieldError(t("weak_device_hint"));
|
||||
return;
|
||||
}
|
||||
} else if (keyAttributes) {
|
||||
try {
|
||||
kek = await cryptoWorker.deriveKey(
|
||||
passphrase,
|
||||
keyAttributes.kekSalt,
|
||||
keyAttributes.opsLimit,
|
||||
keyAttributes.memLimit,
|
||||
);
|
||||
} catch (e) {
|
||||
log.error("Failed to derive kek", e);
|
||||
setFieldError(t("weak_device_hint"));
|
||||
return;
|
||||
}
|
||||
} else throw new Error("Both SRP and key attributes are missing");
|
||||
|
||||
if (!keyAttributes && typeof getKeyAttributes == "function") {
|
||||
keyAttributes = await getKeyAttributes(kek);
|
||||
}
|
||||
if (!keyAttributes) {
|
||||
throw Error("couldn't get key attributes");
|
||||
}
|
||||
|
||||
const cryptoWorker = await sharedCryptoWorker();
|
||||
let kek: string;
|
||||
if (srpAttributes) {
|
||||
try {
|
||||
const key = await cryptoWorker.decryptB64(
|
||||
keyAttributes.encryptedKey,
|
||||
keyAttributes.keyDecryptionNonce,
|
||||
kek,
|
||||
kek = await cryptoWorker.deriveKey(
|
||||
passphrase,
|
||||
srpAttributes.kekSalt,
|
||||
srpAttributes.opsLimit,
|
||||
srpAttributes.memLimit,
|
||||
);
|
||||
onVerify(key, kek, keyAttributes, passphrase);
|
||||
} catch (e) {
|
||||
log.error("user entered a wrong password", e);
|
||||
throw Error(CustomError.INCORRECT_PASSWORD);
|
||||
log.error("Failed to derive kek", e);
|
||||
setFieldError(t("weak_device_hint"));
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
if (e.message === CustomError.TWO_FACTOR_ENABLED) {
|
||||
// two factor enabled, user has been redirected to two factor page
|
||||
return;
|
||||
} else if (keyAttributes) {
|
||||
try {
|
||||
kek = await cryptoWorker.deriveKey(
|
||||
passphrase,
|
||||
keyAttributes.kekSalt,
|
||||
keyAttributes.opsLimit,
|
||||
keyAttributes.memLimit,
|
||||
);
|
||||
} catch (e) {
|
||||
log.error("Failed to derive kek", e);
|
||||
setFieldError(t("weak_device_hint"));
|
||||
return;
|
||||
}
|
||||
} else throw new Error("Both SRP and key attributes are missing");
|
||||
|
||||
if (!keyAttributes && typeof getKeyAttributes == "function") {
|
||||
try {
|
||||
keyAttributes = await getKeyAttributes(kek);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
switch (e.message) {
|
||||
case CustomError.TWO_FACTOR_ENABLED:
|
||||
// Two factor enabled, user has been redirected to
|
||||
// the two-factor verification page.
|
||||
return;
|
||||
|
||||
case CustomError.INCORRECT_PASSWORD_OR_NO_ACCOUNT:
|
||||
log.error("Incorrect password or no account", e);
|
||||
setFieldError(
|
||||
t("incorrect_password_or_no_account"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
log.error("failed to verify passphrase", e);
|
||||
switch (e.message) {
|
||||
case CustomError.INCORRECT_PASSWORD:
|
||||
setFieldError(t("incorrect_password"));
|
||||
break;
|
||||
case CustomError.INCORRECT_PASSWORD_OR_NO_ACCOUNT:
|
||||
setFieldError(t("incorrect_password_or_no_account"));
|
||||
break;
|
||||
default:
|
||||
setFieldError(t("generic_error"));
|
||||
}
|
||||
} else {
|
||||
log.error("failed to verify passphrase", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (!keyAttributes) throw Error("Couldn't get key attributes");
|
||||
|
||||
let key: string;
|
||||
try {
|
||||
key = await cryptoWorker.decryptBoxB64(
|
||||
{
|
||||
encryptedData: keyAttributes.encryptedKey,
|
||||
nonce: keyAttributes.keyDecryptionNonce,
|
||||
},
|
||||
kek,
|
||||
);
|
||||
} catch (e) {
|
||||
log.warn("Incorrect password", e);
|
||||
setFieldError(t("incorrect_password"));
|
||||
return;
|
||||
}
|
||||
|
||||
onVerify(key, kek, keyAttributes, passphrase);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -18,7 +18,11 @@ import {
|
||||
startSRPSetup,
|
||||
updateSRPAndKeys,
|
||||
} from "ente-accounts/services/srp-remote";
|
||||
import type { UpdatedKey } from "ente-accounts/services/user";
|
||||
import type {
|
||||
KeyAttributes,
|
||||
UpdatedKey,
|
||||
User,
|
||||
} from "ente-accounts/services/user";
|
||||
import { LinkButton } from "ente-base/components/LinkButton";
|
||||
import { sharedCryptoWorker } from "ente-base/crypto";
|
||||
import {
|
||||
@@ -28,7 +32,6 @@ import {
|
||||
} from "ente-shared/crypto/helpers";
|
||||
import { getData, setData } from "ente-shared/storage/localStorage";
|
||||
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";
|
||||
@@ -58,7 +61,7 @@ const Page: React.FC = () => {
|
||||
const key = await getActualKey();
|
||||
const keyAttributes: KeyAttributes = getData("keyAttributes");
|
||||
const kekSalt = await cryptoWorker.generateSaltToDeriveKey();
|
||||
let kek: KEK;
|
||||
let kek: { key: string; opsLimit: number; memLimit: number };
|
||||
try {
|
||||
kek = await cryptoWorker.deriveSensitiveKey(passphrase, kekSalt);
|
||||
} catch {
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
} from "ente-accounts/services/srp";
|
||||
import type { SRPAttributes } from "ente-accounts/services/srp-remote";
|
||||
import { getSRPAttributes } from "ente-accounts/services/srp-remote";
|
||||
import type { KeyAttributes, User } from "ente-accounts/services/user";
|
||||
import { LinkButton } from "ente-base/components/LinkButton";
|
||||
import { LoadingIndicator } from "ente-base/components/loaders";
|
||||
import { useBaseContext } from "ente-base/context";
|
||||
@@ -49,7 +50,6 @@ import {
|
||||
setIsFirstLogin,
|
||||
} from "ente-shared/storage/localStorage/helpers";
|
||||
import { getKey, removeKey, setKey } from "ente-shared/storage/sessionStorage";
|
||||
import type { KeyAttributes, User } from "ente-shared/user/types";
|
||||
import { t } from "i18next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
@@ -140,14 +140,18 @@ const Page: React.FC = () => {
|
||||
if (kekEncryptedAttributes && keyAttributes) {
|
||||
removeKey("keyEncryptionKey");
|
||||
const cryptoWorker = await sharedCryptoWorker();
|
||||
const kek = await cryptoWorker.decryptB64(
|
||||
kekEncryptedAttributes.encryptedData,
|
||||
kekEncryptedAttributes.nonce,
|
||||
const kek = await cryptoWorker.decryptBoxB64(
|
||||
{
|
||||
encryptedData: kekEncryptedAttributes.encryptedData,
|
||||
nonce: kekEncryptedAttributes.nonce,
|
||||
},
|
||||
kekEncryptedAttributes.key,
|
||||
);
|
||||
const key = await cryptoWorker.decryptB64(
|
||||
keyAttributes.encryptedKey,
|
||||
keyAttributes.keyDecryptionNonce,
|
||||
const key = await cryptoWorker.decryptBoxB64(
|
||||
{
|
||||
encryptedData: keyAttributes.encryptedKey,
|
||||
nonce: keyAttributes.keyDecryptionNonce,
|
||||
},
|
||||
kek,
|
||||
);
|
||||
void postVerification(key, kek, keyAttributes);
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
configureSRP,
|
||||
generateKeyAndSRPAttributes,
|
||||
} from "ente-accounts/services/srp";
|
||||
import type { KeyAttributes, User } from "ente-accounts/services/user";
|
||||
import { putUserKeyAttributes } from "ente-accounts/services/user";
|
||||
import { LinkButton } from "ente-base/components/LinkButton";
|
||||
import { LoadingIndicator } from "ente-base/components/loaders";
|
||||
@@ -27,7 +28,6 @@ import {
|
||||
setJustSignedUp,
|
||||
} from "ente-shared/storage/localStorage/helpers";
|
||||
import { getKey } from "ente-shared/storage/sessionStorage";
|
||||
import type { KeyAttributes, User } from "ente-shared/user/types";
|
||||
import { t } from "i18next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from "ente-accounts/components/layouts/centered-paper";
|
||||
import { recoveryKeyB64FromMnemonic } from "ente-accounts/services/recovery-key";
|
||||
import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect";
|
||||
import type { KeyAttributes, User } from "ente-accounts/services/user";
|
||||
import { sendOTT } from "ente-accounts/services/user";
|
||||
import { LinkButton } from "ente-base/components/LinkButton";
|
||||
import {
|
||||
@@ -20,7 +21,6 @@ import {
|
||||
} from "ente-shared/crypto/helpers";
|
||||
import { getData, setData } from "ente-shared/storage/localStorage";
|
||||
import { getKey } from "ente-shared/storage/sessionStorage";
|
||||
import type { KeyAttributes, User } from "ente-shared/user/types";
|
||||
import { t } from "i18next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Verify2FACodeForm } from "ente-accounts/components/Verify2FACodeForm";
|
||||
import type { User } from "ente-accounts/services/user";
|
||||
import { verifyTwoFactor } from "ente-accounts/services/user";
|
||||
import { LinkButton } from "ente-base/components/LinkButton";
|
||||
import { useBaseContext } from "ente-base/context";
|
||||
import { HTTPError } from "ente-base/http";
|
||||
import { getData, setData, setLSUser } from "ente-shared/storage/localStorage";
|
||||
import type { User } from "ente-shared/user/types";
|
||||
import { t } from "i18next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
@@ -22,6 +22,7 @@ import type {
|
||||
SRPSetupAttributes,
|
||||
} from "ente-accounts/services/srp-remote";
|
||||
import { getSRPAttributes } from "ente-accounts/services/srp-remote";
|
||||
import type { KeyAttributes, User } from "ente-accounts/services/user";
|
||||
import {
|
||||
putUserKeyAttributes,
|
||||
sendOTT,
|
||||
@@ -44,7 +45,6 @@ import {
|
||||
getLocalReferralSource,
|
||||
setIsFirstLogin,
|
||||
} from "ente-shared/storage/localStorage/helpers";
|
||||
import type { KeyAttributes, User } from "ente-shared/user/types";
|
||||
import { t } from "i18next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as bip39 from "bip39";
|
||||
import type { KeyAttributes } from "ente-accounts/services/user";
|
||||
import {
|
||||
decryptBoxB64,
|
||||
fromHex,
|
||||
@@ -7,7 +8,6 @@ import {
|
||||
} from "ente-base/crypto";
|
||||
import { masterKeyFromSession } from "ente-base/session";
|
||||
import { getData, setData } from "ente-shared/storage/localStorage";
|
||||
import type { KeyAttributes } from "ente-shared/user/types";
|
||||
import { putUserRecoveryKeyAttributes } from "./user";
|
||||
|
||||
// Mobile client library only supports English.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { KeyAttributes } from "ente-accounts/services/user";
|
||||
import { authenticatedRequestHeaders, HTTPError } from "ente-base/http";
|
||||
import { ensureLocalUser, getAuthToken } from "ente-base/local-user";
|
||||
import log from "ente-base/log";
|
||||
import { apiURL } from "ente-base/origins";
|
||||
import { getData } from "ente-shared/storage/localStorage";
|
||||
import type { KeyAttributes } from "ente-shared/user/types";
|
||||
import { nullToUndefined } from "ente-utils/transform";
|
||||
import { z } from "zod/v4";
|
||||
import type { SRPAttributes } from "./srp-remote";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { KeyAttributes } from "ente-accounts/services/user";
|
||||
import { sharedCryptoWorker } from "ente-base/crypto";
|
||||
import log from "ente-base/log";
|
||||
import { generateLoginSubKey } from "ente-shared/crypto/helpers";
|
||||
import { getToken } from "ente-shared/storage/localStorage/helpers";
|
||||
import type { KeyAttributes } from "ente-shared/user/types";
|
||||
import { SRP, SrpClient } from "fast-srp-hap";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import {
|
||||
|
||||
@@ -6,10 +6,163 @@ import {
|
||||
import { apiURL } from "ente-base/origins";
|
||||
import HTTPService from "ente-shared/network/HTTPService";
|
||||
import { getToken } from "ente-shared/storage/localStorage/helpers";
|
||||
import type { KeyAttributes } from "ente-shared/user/types";
|
||||
import { nullToUndefined } from "ente-utils/transform";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
email: string;
|
||||
token: string;
|
||||
encryptedToken: string;
|
||||
isTwoFactorEnabled: boolean;
|
||||
twoFactorSessionID: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The user's various encrypted keys and their related attributes.
|
||||
*
|
||||
* - Attributes to derive the KEK, the (master) key encryption key.
|
||||
* - Encrypted master key (with KEK)
|
||||
* - Encrypted master key (with recovery key)
|
||||
* - Encrypted recovery key (with master key).
|
||||
* - Public key and encrypted private key (with master key).
|
||||
*
|
||||
* The various "key" attributes are base64 encoded representations of the
|
||||
* underlying binary data.
|
||||
*/
|
||||
export interface KeyAttributes {
|
||||
/**
|
||||
* The user's master key encrypted with the key encryption key.
|
||||
*
|
||||
* Base 64 encoded.
|
||||
*
|
||||
* [Note: Key encryption key]
|
||||
*
|
||||
* The user's master key is encrypted with a "key encryption key" (lovingly
|
||||
* called a "kek" sometimes).
|
||||
*
|
||||
* The kek itself is derived from the user's passphrase.
|
||||
*
|
||||
* 1. User enters passphrase on new device.
|
||||
*
|
||||
* 2. Client derives kek from this passphrase (using the {@link kekSalt},
|
||||
* {@link opsLimit} and {@link memLimit} as parameters for the
|
||||
* derivation).
|
||||
*
|
||||
* 3. Client use kek to decrypt the master key from {@link encryptedKey} and
|
||||
* {@link keyDecryptionNonce}.
|
||||
*/
|
||||
encryptedKey: string;
|
||||
/**
|
||||
* The nonce used during the encryption of the master key.
|
||||
*
|
||||
* Base 64 encoded.
|
||||
*
|
||||
* @see {@link encryptedKey}.
|
||||
*/
|
||||
keyDecryptionNonce: string;
|
||||
/**
|
||||
* The salt used during the derivation of the kek.
|
||||
*
|
||||
* Base 64 encoded.
|
||||
*
|
||||
* See: [Note: Key encryption key].
|
||||
*/
|
||||
kekSalt: string;
|
||||
/**
|
||||
* The operation limit used during the derivation of the kek.
|
||||
*
|
||||
* The {@link opsLimit} and {@link memLimit} are complementary parameters
|
||||
* that define the amount of work done by the key derivation function. See
|
||||
* the {@link deriveKey}, {@link deriveSensitiveKey} and
|
||||
* {@link deriveInteractiveKey} functions for more detail about them.
|
||||
*
|
||||
* See: [Note: Key encryption key].
|
||||
*/
|
||||
opsLimit: number;
|
||||
/**
|
||||
* The memory limit used during the derivation of the kek.
|
||||
*
|
||||
* See {@link opsLimit} for more details.
|
||||
*/
|
||||
memLimit: number;
|
||||
/**
|
||||
* The user's public key (part of their public-key keypair, the other half
|
||||
* being the {@link encryptedSecretKey}).
|
||||
*
|
||||
* Base 64 encoded.
|
||||
*/
|
||||
publicKey: string;
|
||||
/**
|
||||
* The user's private key (part of their public-key keypair, the other half
|
||||
* being the {@link publicKey}) encrypted with their master key.
|
||||
*
|
||||
* Base 64 encoded.
|
||||
*/
|
||||
encryptedSecretKey: string;
|
||||
/**
|
||||
* The nonce used during the encryption of {@link encryptedSecretKey}.
|
||||
*/
|
||||
secretKeyDecryptionNonce: string;
|
||||
/**
|
||||
* The user's master key after being encrypted with their recovery key.
|
||||
*
|
||||
* Base 64 encoded.
|
||||
*
|
||||
* This allows the user to recover their master key if they forget their
|
||||
* passphrase but still have their recovery key.
|
||||
*
|
||||
* Note: This value doesn't change after being initially created.
|
||||
*/
|
||||
masterKeyEncryptedWithRecoveryKey?: string;
|
||||
/**
|
||||
* The nonce used during the encryption of
|
||||
* {@link masterKeyEncryptedWithRecoveryKey}.
|
||||
*
|
||||
* Base 64 encoded.
|
||||
*/
|
||||
masterKeyDecryptionNonce?: string;
|
||||
/**
|
||||
* The user's recovery key after being encrypted with their master key.
|
||||
*
|
||||
* Base 64 encoded.
|
||||
*
|
||||
* Note: This value doesn't change after being initially created.
|
||||
*/
|
||||
recoveryKeyEncryptedWithMasterKey?: string;
|
||||
/**
|
||||
* The nonce used during the encryption of
|
||||
* {@link recoveryKeyEncryptedWithMasterKey}.
|
||||
*
|
||||
* Base 64 encoded.
|
||||
*/
|
||||
recoveryKeyDecryptionNonce?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zod schema for {@link KeyAttributes}.
|
||||
*/
|
||||
export const RemoteKeyAttributes = z.object({
|
||||
kekSalt: z.string(),
|
||||
encryptedKey: z.string(),
|
||||
keyDecryptionNonce: z.string(),
|
||||
publicKey: z.string(),
|
||||
encryptedSecretKey: z.string(),
|
||||
secretKeyDecryptionNonce: z.string(),
|
||||
memLimit: z.number(),
|
||||
opsLimit: z.number(),
|
||||
masterKeyEncryptedWithRecoveryKey: z
|
||||
.string()
|
||||
.nullish()
|
||||
.transform(nullToUndefined),
|
||||
masterKeyDecryptionNonce: z.string().nullish().transform(nullToUndefined),
|
||||
recoveryKeyEncryptedWithMasterKey: z
|
||||
.string()
|
||||
.nullish()
|
||||
.transform(nullToUndefined),
|
||||
recoveryKeyDecryptionNonce: z.string().nullish().transform(nullToUndefined),
|
||||
});
|
||||
|
||||
export interface UserVerificationResponse {
|
||||
id: number;
|
||||
keyAttributes?: KeyAttributes | undefined;
|
||||
@@ -117,30 +270,6 @@ export const verifyEmail = async (
|
||||
return EmailOrSRPAuthorizationResponse.parse(await res.json());
|
||||
};
|
||||
|
||||
/**
|
||||
* Zod schema for {@link KeyAttributes}.
|
||||
*/
|
||||
export const RemoteKeyAttributes = z.object({
|
||||
kekSalt: z.string(),
|
||||
encryptedKey: z.string(),
|
||||
keyDecryptionNonce: z.string(),
|
||||
publicKey: z.string(),
|
||||
encryptedSecretKey: z.string(),
|
||||
secretKeyDecryptionNonce: z.string(),
|
||||
memLimit: z.number(),
|
||||
opsLimit: z.number(),
|
||||
masterKeyEncryptedWithRecoveryKey: z
|
||||
.string()
|
||||
.nullish()
|
||||
.transform(nullToUndefined),
|
||||
masterKeyDecryptionNonce: z.string().nullish().transform(nullToUndefined),
|
||||
recoveryKeyEncryptedWithMasterKey: z
|
||||
.string()
|
||||
.nullish()
|
||||
.transform(nullToUndefined),
|
||||
recoveryKeyDecryptionNonce: z.string().nullish().transform(nullToUndefined),
|
||||
});
|
||||
|
||||
/**
|
||||
* Zod schema for response from remote on a successful user verification, either
|
||||
* via {@link verifyEmail} or {@link verifySRPSession}.
|
||||
|
||||
@@ -321,16 +321,15 @@ export async function decryptFile(
|
||||
pubMagicMetadata,
|
||||
...restFileProps
|
||||
} = file;
|
||||
const fileKey = await worker.decryptB64(
|
||||
encryptedKey,
|
||||
keyDecryptionNonce,
|
||||
const fileKey = await worker.decryptBoxB64(
|
||||
{ encryptedData: encryptedKey, nonce: keyDecryptionNonce },
|
||||
collectionKey,
|
||||
);
|
||||
const fileMetadata = await worker.decryptMetadataJSON({
|
||||
encryptedDataB64: metadata.encryptedData,
|
||||
decryptionHeaderB64: metadata.decryptionHeader,
|
||||
keyB64: fileKey,
|
||||
});
|
||||
const fileMetadata = await worker.decryptMetadataJSON_New(
|
||||
metadata,
|
||||
fileKey,
|
||||
);
|
||||
|
||||
let fileMagicMetadata: FileMagicMetadata;
|
||||
let filePubMagicMetadata: FilePublicMagicMetadata;
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
*/
|
||||
|
||||
import { getUserRecoveryKeyB64 } from "ente-accounts/services/recovery-key";
|
||||
import type { User } from "ente-accounts/services/user";
|
||||
import log from "ente-base/log";
|
||||
import type { Collection } from "ente-media/collection";
|
||||
import type { FamilyData } from "ente-new/photos/services/user-details";
|
||||
import type { User } from "ente-shared/user/types";
|
||||
|
||||
/**
|
||||
* Ensure that the keys in local storage are not malformed by verifying that the
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { User } from "ente-accounts/services/user";
|
||||
import {
|
||||
isArchivedCollection,
|
||||
isPinnedCollection,
|
||||
@@ -10,7 +11,6 @@ import {
|
||||
createCollectionNameByID,
|
||||
isHiddenCollection,
|
||||
} from "ente-new/photos/services/collection";
|
||||
import type { User } from "ente-shared/user/types";
|
||||
import { splitByPredicate } from "ente-utils/array";
|
||||
import { t } from "i18next";
|
||||
import React, { useReducer } from "react";
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { User } from "ente-accounts/services/user";
|
||||
import { encryptBoxB64 } from "ente-base/crypto";
|
||||
import { authenticatedRequestHeaders, ensureOk } from "ente-base/http";
|
||||
import { apiURL } from "ente-base/origins";
|
||||
import { CollectionSubType, type Collection } from "ente-media/collection";
|
||||
import { type EnteFile } from "ente-media/file";
|
||||
import { ItemVisibility } from "ente-media/file-metadata";
|
||||
import type { User } from "ente-shared/user/types";
|
||||
import { batch } from "ente-utils/array";
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
import type { User } from "ente-accounts/services/user";
|
||||
import { ensureElectron } from "ente-base/electron";
|
||||
import { joinPath, nameAndExtension } from "ente-base/file-name";
|
||||
import log from "ente-base/log";
|
||||
@@ -20,7 +21,6 @@ import {
|
||||
sanitizeFilename,
|
||||
} from "ente-new/photos/utils/native-fs";
|
||||
import { getData } from "ente-shared/storage/localStorage";
|
||||
import type { User } from "ente-shared/user/types";
|
||||
import { wait } from "ente-utils/promise";
|
||||
import exportService, {
|
||||
getCollectionIDFromFileUID,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { Box, styled } from "@mui/material";
|
||||
|
||||
export const FlexWrapper = styled(Box)`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
`;
|
||||
@@ -1,203 +0,0 @@
|
||||
import { FormHelperText } from "@mui/material";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import { FocusVisibleButton } from "ente-base/components/mui/FocusVisibleButton";
|
||||
import { LoadingButton } from "ente-base/components/mui/LoadingButton";
|
||||
import { ShowHidePasswordInputAdornment } from "ente-base/components/mui/PasswordInputAdornment";
|
||||
import { FlexWrapper } from "ente-shared/components/Container";
|
||||
import { Formik, type FormikHelpers, type FormikState } from "formik";
|
||||
import { t } from "i18next";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import * as Yup from "yup";
|
||||
|
||||
interface formValues {
|
||||
inputValue: string;
|
||||
}
|
||||
export interface SingleInputFormProps {
|
||||
callback: (
|
||||
inputValue: string,
|
||||
setFieldError: (errorMessage: string) => void,
|
||||
resetForm: (nextState?: Partial<FormikState<formValues>>) => void,
|
||||
) => Promise<void>;
|
||||
fieldType: "text" | "email" | "password";
|
||||
/** deprecated: Use realPlaceholder */
|
||||
placeholder?: string;
|
||||
/**
|
||||
* Placeholder
|
||||
*
|
||||
* The existing `placeholder` property uses the placeholder as a label (i.e.
|
||||
* it doesn't appear as the placeholder within the text input area but
|
||||
* rather as the label on top of it). This happens conditionally, so it is
|
||||
* not a matter of simple rename.
|
||||
*
|
||||
* Gradually migrate the existing UI to use this property when we really
|
||||
* want a placeholder, and then create a separate label property for places
|
||||
* that actually want to set the label.
|
||||
*/
|
||||
realPlaceholder?: string;
|
||||
/**
|
||||
* Label to show on top of the text input area.
|
||||
*
|
||||
* Sibling of {@link realPlaceholder}.
|
||||
*/
|
||||
realLabel?: string;
|
||||
buttonText: string;
|
||||
submitButtonProps?: any;
|
||||
initialValue?: string;
|
||||
secondaryButtonAction?: () => void;
|
||||
disableAutoFocus?: boolean;
|
||||
hiddenPreInput?: any;
|
||||
caption?: any;
|
||||
hiddenPostInput?: any;
|
||||
autoComplete?: string;
|
||||
blockButton?: boolean;
|
||||
hiddenLabel?: boolean;
|
||||
disableAutoComplete?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated version, gradually migrate to use the one from ente-base.
|
||||
*/
|
||||
export default function SingleInputForm(props: SingleInputFormProps) {
|
||||
const { submitButtonProps } = props;
|
||||
const { sx: buttonSx, ...restSubmitButtonProps } = submitButtonProps ?? {};
|
||||
|
||||
const [loading, SetLoading] = useState(false);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
const handleToggleShowHidePassword = useCallback(
|
||||
() => setShowPassword((show) => !show),
|
||||
[],
|
||||
);
|
||||
|
||||
const submitForm = async (
|
||||
values: formValues,
|
||||
{ setFieldError, resetForm }: FormikHelpers<formValues>,
|
||||
) => {
|
||||
SetLoading(true);
|
||||
await props.callback(
|
||||
values.inputValue,
|
||||
(message) => setFieldError("inputValue", message),
|
||||
resetForm,
|
||||
);
|
||||
SetLoading(false);
|
||||
};
|
||||
|
||||
const validationSchema = useMemo(() => {
|
||||
switch (props.fieldType) {
|
||||
case "text":
|
||||
return Yup.object().shape({
|
||||
inputValue: Yup.string().required(t("required")),
|
||||
});
|
||||
case "password":
|
||||
return Yup.object().shape({
|
||||
inputValue: Yup.string().required(t("required")),
|
||||
});
|
||||
case "email":
|
||||
return Yup.object().shape({
|
||||
inputValue: Yup.string()
|
||||
.email(t("invalid_email_error"))
|
||||
.required(t("required")),
|
||||
});
|
||||
}
|
||||
}, [props.fieldType]);
|
||||
|
||||
return (
|
||||
<Formik<formValues>
|
||||
initialValues={{ inputValue: props.initialValue ?? "" }}
|
||||
onSubmit={submitForm}
|
||||
validationSchema={validationSchema}
|
||||
validateOnChange={false}
|
||||
validateOnBlur={false}
|
||||
>
|
||||
{({ values, errors, handleChange, handleSubmit }) => (
|
||||
<form noValidate onSubmit={handleSubmit}>
|
||||
{props.hiddenPreInput}
|
||||
<TextField
|
||||
hiddenLabel={props.hiddenLabel}
|
||||
variant="filled"
|
||||
fullWidth
|
||||
type={showPassword ? "text" : props.fieldType}
|
||||
id={props.fieldType}
|
||||
name={props.fieldType}
|
||||
{...(props.hiddenLabel
|
||||
? { placeholder: props.placeholder }
|
||||
: props.realPlaceholder
|
||||
? {
|
||||
placeholder: props.realPlaceholder,
|
||||
label: props.realLabel,
|
||||
}
|
||||
: { label: props.placeholder })}
|
||||
value={values.inputValue}
|
||||
onChange={handleChange("inputValue")}
|
||||
error={Boolean(errors.inputValue)}
|
||||
helperText={errors.inputValue}
|
||||
disabled={loading}
|
||||
autoFocus={!props.disableAutoFocus}
|
||||
autoComplete={props.autoComplete}
|
||||
slotProps={{
|
||||
input: {
|
||||
autoComplete:
|
||||
props.disableAutoComplete ||
|
||||
props.fieldType == "password"
|
||||
? "off"
|
||||
: "on",
|
||||
endAdornment: props.fieldType ===
|
||||
"password" && (
|
||||
<ShowHidePasswordInputAdornment
|
||||
showPassword={showPassword}
|
||||
onToggle={handleToggleShowHidePassword}
|
||||
/>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<FormHelperText
|
||||
sx={{
|
||||
position: "relative",
|
||||
top: errors.inputValue ? "-22px" : "0",
|
||||
float: "right",
|
||||
padding: "0 8px",
|
||||
}}
|
||||
>
|
||||
{props.caption}
|
||||
</FormHelperText>
|
||||
{props.hiddenPostInput}
|
||||
<FlexWrapper
|
||||
justifyContent={"flex-end"}
|
||||
flexWrap={props.blockButton ? "wrap-reverse" : "nowrap"}
|
||||
>
|
||||
{props.secondaryButtonAction && (
|
||||
<FocusVisibleButton
|
||||
onClick={props.secondaryButtonAction}
|
||||
fullWidth
|
||||
color="secondary"
|
||||
sx={{
|
||||
"&&&": {
|
||||
mt: !props.blockButton ? 2 : 0.5,
|
||||
mb: !props.blockButton ? 4 : 0,
|
||||
mr: !props.blockButton ? 1 : 0,
|
||||
...buttonSx,
|
||||
},
|
||||
}}
|
||||
{...restSubmitButtonProps}
|
||||
>
|
||||
{t("cancel")}
|
||||
</FocusVisibleButton>
|
||||
)}
|
||||
<LoadingButton
|
||||
sx={{ "&&&": { mt: 2, ...buttonSx } }}
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="accent"
|
||||
type="submit"
|
||||
loading={loading}
|
||||
{...restSubmitButtonProps}
|
||||
>
|
||||
{props.buttonText}
|
||||
</LoadingButton>
|
||||
</FlexWrapper>
|
||||
</form>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { KeyAttributes } from "ente-accounts/services/user";
|
||||
import { sharedCryptoWorker } from "ente-base/crypto";
|
||||
import { masterKeyFromSession } from "ente-base/session";
|
||||
import { getData, setData, setLSUser } from "ente-shared/storage/localStorage";
|
||||
import { type SessionKey, setKey } from "ente-shared/storage/sessionStorage";
|
||||
import type { KeyAttributes } from "ente-shared/user/types";
|
||||
|
||||
const LOGIN_SUB_KEY_LENGTH = 32;
|
||||
const LOGIN_SUB_KEY_ID = 1;
|
||||
|
||||
@@ -40,7 +40,6 @@ export const CustomError = {
|
||||
BAD_REQUEST: "bad request",
|
||||
SUBSCRIPTION_NEEDED: "subscription not present",
|
||||
NOT_FOUND: "not found ",
|
||||
INCORRECT_PASSWORD: "incorrect password",
|
||||
INCORRECT_PASSWORD_OR_NO_ACCOUNT: "incorrect password or no such account",
|
||||
UPLOAD_CANCELLED: "upload cancelled",
|
||||
UPDATE_EXPORTED_RECORD_FAILED: "update file exported record failed",
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* A structure containing the key related attributes for a user.
|
||||
*/
|
||||
export interface KeyAttributes {
|
||||
kekSalt: string;
|
||||
encryptedKey: string;
|
||||
keyDecryptionNonce: string;
|
||||
opsLimit: number;
|
||||
memLimit: number;
|
||||
publicKey: string;
|
||||
encryptedSecretKey: string;
|
||||
secretKeyDecryptionNonce: string;
|
||||
/** Doesn't change after being initially created. */
|
||||
masterKeyEncryptedWithRecoveryKey?: string;
|
||||
masterKeyDecryptionNonce?: string;
|
||||
/** Doesn't change after being initially created. */
|
||||
recoveryKeyEncryptedWithMasterKey?: string;
|
||||
recoveryKeyDecryptionNonce?: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
email: string;
|
||||
token: string;
|
||||
encryptedToken: string;
|
||||
isTwoFactorEnabled: boolean;
|
||||
twoFactorSessionID: string;
|
||||
}
|
||||
|
||||
export interface KEK {
|
||||
key: string;
|
||||
opsLimit: number;
|
||||
memLimit: number;
|
||||
}
|
||||
Reference in New Issue
Block a user