[web] General code improvements (#6168)

This commit is contained in:
Manav Rathi
2025-06-04 19:15:25 +05:30
committed by GitHub
29 changed files with 274 additions and 380 deletions

View File

@@ -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";

View File

@@ -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) {

View File

@@ -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";

View File

@@ -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";

View File

@@ -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,

View File

@@ -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;

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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 (

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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.

View File

@@ -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";

View File

@@ -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 {

View File

@@ -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}.

View File

@@ -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 */

View File

@@ -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

View File

@@ -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";

View File

@@ -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";
/**

View File

@@ -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,

View File

@@ -1,7 +0,0 @@
import { Box, styled } from "@mui/material";
export const FlexWrapper = styled(Box)`
display: flex;
width: 100%;
align-items: center;
`;

View File

@@ -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>
);
}

View File

@@ -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;

View File

@@ -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",

View File

@@ -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;
}