merge main and deconflict

This commit is contained in:
Scott Wilson
2025-08-06 18:50:02 -07:00
204 changed files with 5291 additions and 3396 deletions
+32 -111
View File
@@ -1,12 +1,8 @@
import crypto from "crypto";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import jsrp from "jsrp";
import { useServerConfig } from "@app/context";
import { initProjectHelper } from "@app/helpers/project";
import { completeAccountSignup, useSelectOrganization } from "@app/hooks/api/auth/queries";
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
@@ -14,15 +10,9 @@ import { onRequestError } from "@app/hooks/api/reactQuery";
import InputField from "../basic/InputField";
import checkPassword from "../utilities/checks/password/checkPassword";
import Aes256Gcm from "../utilities/cryptography/aes-256-gcm";
import { deriveArgonKey, generateKeyPair } from "../utilities/cryptography/crypto";
import { saveTokenToLocalStorage } from "../utilities/saveTokenToLocalStorage";
import SecurityClient from "../utilities/SecurityClient";
import { Button, Input } from "../v2";
// eslint-disable-next-line new-cap
const client = new jsrp.client();
interface UserInfoStepProps {
incrementStep: () => void;
email: string;
@@ -76,7 +66,6 @@ export default function UserInfoStep({
}: UserInfoStepProps): JSX.Element {
const [nameError, setNameError] = useState(false);
const [organizationNameError, setOrganizationNameError] = useState(false);
const { config } = useServerConfig();
const [errors, setErrors] = useState<Errors>({});
@@ -108,109 +97,41 @@ export default function UserInfoStep({
});
if (!errorCheck) {
// Generate a random pair of a public and a private key
const pair = await generateKeyPair(config.fipsEnabled);
try {
const response = await completeAccountSignup({
email,
password,
firstName: name.split(" ")[0],
lastName: name.split(" ").slice(1).join(" "),
providerAuthToken,
organizationName,
attributionSource
});
localStorage.setItem("PRIVATE_KEY", pair.privateKey);
// unset signup JWT token and set JWT token
SecurityClient.setSignupToken("");
SecurityClient.setToken(response.token);
SecurityClient.setProviderAuthToken("");
client.init(
{
username: email,
password
},
async () => {
client.createVerifier(async (_err: any, result: { salt: string; verifier: string }) => {
try {
// TODO: moduralize into KeyService
const derivedKey = await deriveArgonKey({
password,
salt: result.salt,
mem: 65536,
time: 3,
parallelism: 1,
hashLen: 32
});
if (!derivedKey) throw new Error("Failed to derive key from password");
const key = crypto.randomBytes(32);
// create encrypted private key by encrypting the private
// key with the symmetric key [key]
const {
ciphertext: encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
} = Aes256Gcm.encrypt({
text: pair.privateKey,
secret: key
});
// create the protected key by encrypting the symmetric key
// [key] with the derived key
const {
ciphertext: protectedKey,
iv: protectedKeyIV,
tag: protectedKeyTag
} = Aes256Gcm.encrypt({
text: key.toString("hex"),
secret: Buffer.from(derivedKey.hash)
});
const response = await completeAccountSignup({
email,
password,
firstName: name.split(" ")[0],
lastName: name.split(" ").slice(1).join(" "),
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey: pair.publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
providerAuthToken,
salt: result.salt,
verifier: result.verifier,
organizationName,
attributionSource
});
// unset signup JWT token and set JWT token
SecurityClient.setSignupToken("");
SecurityClient.setToken(response.token);
SecurityClient.setProviderAuthToken("");
if (response.organizationId) {
await selectOrganization({ organizationId: response.organizationId });
}
saveTokenToLocalStorage({
publicKey: pair.publicKey,
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
privateKey: pair.privateKey
});
const userOrgs = await fetchOrganizations();
const orgId = userOrgs[0]?.id;
await initProjectHelper({
projectName: "Example Project"
});
localStorage.setItem("orgData.id", orgId);
incrementStep();
} catch (error) {
onRequestError(error);
setIsLoading(false);
console.error(error);
}
});
if (response.organizationId) {
await selectOrganization({ organizationId: response.organizationId });
}
);
const userOrgs = await fetchOrganizations();
const orgId = userOrgs[0]?.id;
await initProjectHelper({
projectName: "Example Project"
});
localStorage.setItem("orgData.id", orgId);
incrementStep();
} catch (error) {
onRequestError(error);
setIsLoading(false);
console.error(error);
}
} else {
setIsLoading(false);
}
@@ -1,108 +0,0 @@
/* eslint-disable new-cap */
import crypto from "crypto";
import jsrp from "jsrp";
import { changePassword, srp1 } from "@app/hooks/api/auth/queries";
import Aes256Gcm from "./cryptography/aes-256-gcm";
import { deriveArgonKey } from "./cryptography/crypto";
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
const clientOldPassword = new jsrp.client();
const clientNewPassword = new jsrp.client();
type Params = {
email: string;
currentPassword: string;
newPassword: string;
};
const attemptChangePassword = ({ email, currentPassword, newPassword }: Params): Promise<void> => {
return new Promise((resolve, reject) => {
clientOldPassword.init({ username: email, password: currentPassword }, async () => {
let serverPublicKey;
let salt;
try {
const clientPublicKey = clientOldPassword.getPublicKey();
const res = await srp1({ clientPublicKey });
serverPublicKey = res.serverPublicKey;
salt = res.salt;
clientOldPassword.setSalt(salt);
clientOldPassword.setServerPublicKey(serverPublicKey);
const clientProof = clientOldPassword.getProof();
clientNewPassword.init({ username: email, password: newPassword }, async () => {
clientNewPassword.createVerifier(async (_err, result) => {
try {
const derivedKey = await deriveArgonKey({
password: newPassword,
salt: result.salt,
mem: 65536,
time: 3,
parallelism: 1,
hashLen: 32
});
if (!derivedKey) throw new Error("Failed to derive key from password");
const key = crypto.randomBytes(32);
const {
ciphertext: encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
} = Aes256Gcm.encrypt({
text: localStorage.getItem("PRIVATE_KEY") as string,
secret: key
});
const {
ciphertext: protectedKey,
iv: protectedKeyIV,
tag: protectedKeyTag
} = Aes256Gcm.encrypt({
text: key.toString("hex"),
secret: Buffer.from(derivedKey.hash)
});
await changePassword({
password: newPassword,
clientProof,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt: result.salt,
verifier: result.verifier
});
saveTokenToLocalStorage({
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
});
resolve();
} catch (err2) {
console.error(err2);
reject(err2);
}
});
});
} catch (err) {
console.error(err);
reject(err);
}
});
});
};
export default attemptChangePassword;
@@ -1,18 +1,18 @@
/* eslint-disable prefer-destructuring */
import axios from "axios";
import jsrp from "jsrp";
import { decryptPrivateKeyHelper } from "@app/helpers/key";
import { login1, login2 } from "@app/hooks/api/auth/queries";
import { login1, login2, loginV3 } from "@app/hooks/api/auth/queries";
import { createNotification } from "../notifications";
import Telemetry from "./telemetry/Telemetry";
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
import { LoginMode } from "./attemptLogin";
import SecurityClient from "./SecurityClient";
// eslint-disable-next-line new-cap
const client = new jsrp.client();
export interface IsCliLoginSuccessful {
mfaEnabled: boolean;
loginResponse?: {
email: string;
privateKey: string;
@@ -31,14 +31,62 @@ const attemptLogin = async ({
email,
password,
providerAuthToken,
captchaToken
captchaToken,
loginMode = LoginMode.ServerSide
}: {
email: string;
password: string;
providerAuthToken?: string;
captchaToken?: string;
loginMode?: LoginMode;
}): Promise<IsCliLoginSuccessful> => {
const telemetry = new Telemetry().getInstance();
if (loginMode === LoginMode.ServerSide) {
console.log("Attempting login with server side...");
const data = await loginV3({
email,
password,
providerAuthToken,
captchaToken
}).catch((err) => {
if (axios.isAxiosError(err) && err.response?.status === 400) {
if (err.response?.data?.error === "LegacyEncryptionScheme") {
createNotification({
text: "Failed to login without SRP, attempting to authenticate with legacy SRP authentication.",
type: "info"
});
return null;
}
}
throw err;
});
if (data === null) {
return attemptLogin({
email,
password,
providerAuthToken,
captchaToken,
loginMode: LoginMode.LegacySrp
});
}
SecurityClient.setProviderAuthToken("");
SecurityClient.setToken(data.accessToken);
return {
success: true,
loginResponse: {
email,
privateKey: "",
JTWToken: data.accessToken
}
};
}
return new Promise((resolve, reject) => {
client.init(
{
@@ -58,79 +106,26 @@ const attemptLogin = async ({
client.setServerPublicKey(serverPublicKey);
const clientProof = client.getProof(); // called M1
const {
mfaEnabled,
encryptionVersion,
protectedKey,
protectedKeyIV,
protectedKeyTag,
token,
publicKey,
encryptedPrivateKey,
iv,
tag
} = await login2({
const { encryptionVersion, token, encryptedPrivateKey, iv, tag } = await login2({
email,
password,
clientProof,
providerAuthToken,
captchaToken
});
if (mfaEnabled) {
// case: MFA is enabled
// set temporary (MFA) JWT token
SecurityClient.setMfaToken(token);
resolve({
mfaEnabled,
success: true
});
} else if (
!mfaEnabled &&
encryptionVersion &&
encryptedPrivateKey &&
iv &&
tag &&
token
) {
// case: MFA is not enabled
// unset provider auth token in case it was used
if (encryptionVersion && encryptedPrivateKey && iv && tag && token) {
SecurityClient.setProviderAuthToken("");
// set JWT token
SecurityClient.setToken(token);
const privateKey = await decryptPrivateKeyHelper({
encryptionVersion,
encryptedPrivateKey,
iv,
tag,
password,
salt,
protectedKey,
protectedKeyIV,
protectedKeyTag
});
saveTokenToLocalStorage({
publicKey,
encryptedPrivateKey,
iv,
tag,
privateKey
});
if (email) {
telemetry.identify(email, email);
telemetry.capture("User Logged In");
}
resolve({
mfaEnabled: false,
loginResponse: {
email,
privateKey,
privateKey: "",
JTWToken: token
},
success: true
@@ -1,109 +0,0 @@
/* eslint-disable prefer-destructuring */
import jsrp from "jsrp";
import { decryptPrivateKeyHelper } from "@app/helpers/key";
import { login1, verifyMfaToken } from "@app/hooks/api/auth/queries";
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
import SecurityClient from "./SecurityClient";
// eslint-disable-next-line new-cap
const client = new jsrp.client();
interface IsMfaLoginSuccessful {
success: boolean;
loginResponse: {
privateKey: string;
JTWToken: string;
};
}
/**
* Return whether or not MFA-login is successful for user with email [email]
* and MFA token [mfaToken]
* @param {Object} obj
* @param {String} obj.email - email of user
* @param {String} obj.mfaToken - MFA code/token
*/
const attemptLoginMfa = async ({
email,
password,
providerAuthToken,
mfaToken
}: {
email: string;
password: string;
providerAuthToken?: string;
mfaToken: string;
}): Promise<IsMfaLoginSuccessful> => {
return new Promise((resolve, reject) => {
client.init(
{
username: email,
password
},
async () => {
try {
const clientPublicKey = client.getPublicKey();
const { salt } = await login1({
email,
clientPublicKey,
providerAuthToken
});
const {
encryptionVersion,
protectedKey,
protectedKeyIV,
protectedKeyTag,
token,
publicKey,
encryptedPrivateKey,
iv,
tag
} = await verifyMfaToken({
email,
mfaCode: mfaToken
});
// unset temporary (MFA) JWT token and set JWT token
SecurityClient.setMfaToken("");
SecurityClient.setToken(token);
SecurityClient.setProviderAuthToken("");
const privateKey = await decryptPrivateKeyHelper({
encryptionVersion,
encryptedPrivateKey,
iv,
tag,
password,
salt,
protectedKey,
protectedKeyIV,
protectedKeyTag
});
saveTokenToLocalStorage({
publicKey,
encryptedPrivateKey,
iv,
tag,
privateKey
});
resolve({
success: true,
loginResponse: {
privateKey,
JTWToken: token
}
});
} catch (err) {
reject(err);
}
}
);
});
};
export default attemptLoginMfa;
@@ -1,15 +1,19 @@
/* eslint-disable prefer-destructuring */
import axios from "axios";
import jsrp from "jsrp";
import { decryptPrivateKeyHelper } from "@app/helpers/key";
import { login1, login2 } from "@app/hooks/api/auth/queries";
import { login1, login2, loginV3 } from "@app/hooks/api/auth/queries";
import { createNotification } from "../notifications";
import Telemetry from "./telemetry/Telemetry";
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
import SecurityClient from "./SecurityClient";
export enum LoginMode {
LegacySrp = "legacy-srp",
ServerSide = "server-side"
}
interface IsLoginSuccessful {
mfaEnabled: boolean;
success: boolean;
}
@@ -23,14 +27,62 @@ const attemptLogin = async ({
email,
password,
providerAuthToken,
captchaToken
captchaToken,
loginMode = LoginMode.ServerSide
}: {
email: string;
password: string;
providerAuthToken?: string;
captchaToken?: string;
loginMode?: LoginMode;
}): Promise<IsLoginSuccessful> => {
const telemetry = new Telemetry().getInstance();
if (loginMode === LoginMode.ServerSide) {
console.log("Attempting login with server side...");
const data = await loginV3({
email,
password,
providerAuthToken,
captchaToken
}).catch((err) => {
if (axios.isAxiosError(err) && err.response?.status === 400) {
if (err.response?.data?.error === "LegacyEncryptionScheme") {
createNotification({
text: "Failed to login without SRP, attempting to authenticate with legacy SRP authentication.",
type: "info"
});
return null;
}
}
throw err;
});
if (data === null) {
return attemptLogin({
email,
password,
providerAuthToken,
captchaToken,
loginMode: LoginMode.LegacySrp
});
}
SecurityClient.setProviderAuthToken("");
SecurityClient.setToken(data.accessToken);
if (email) {
telemetry.identify(email, email);
telemetry.capture("User Logged In");
}
return {
success: true
};
}
// eslint-disable-next-line new-cap
const client = new jsrp.client();
await new Promise((resolve) => {
@@ -48,18 +100,7 @@ const attemptLogin = async ({
client.setServerPublicKey(serverPublicKey);
const clientProof = client.getProof(); // called M1
const {
mfaEnabled,
encryptionVersion,
protectedKey,
protectedKeyIV,
protectedKeyTag,
token,
publicKey,
encryptedPrivateKey,
iv,
tag
} = await login2({
const { encryptionVersion, token, encryptedPrivateKey, iv, tag } = await login2({
captchaToken,
email,
password,
@@ -67,56 +108,22 @@ const attemptLogin = async ({
providerAuthToken
});
if (mfaEnabled) {
// case: MFA is enabled
// set temporary (MFA) JWT token
SecurityClient.setMfaToken(token);
return {
mfaEnabled,
success: true
};
}
if (!mfaEnabled && encryptionVersion && encryptedPrivateKey && iv && tag && token) {
// case: MFA is not enabled
if (encryptionVersion && encryptedPrivateKey && iv && tag && token) {
// unset provider auth token in case it was used
SecurityClient.setProviderAuthToken("");
// set JWT token
SecurityClient.setToken(token);
const privateKey = await decryptPrivateKeyHelper({
encryptionVersion,
encryptedPrivateKey,
iv,
tag,
password,
salt,
protectedKey,
protectedKeyIV,
protectedKeyTag
});
saveTokenToLocalStorage({
publicKey,
encryptedPrivateKey,
iv,
tag,
privateKey
});
if (email) {
telemetry.identify(email, email);
telemetry.capture("User Logged In");
}
return {
mfaEnabled: false,
success: true
};
}
return { success: false, mfaEnabled: false };
return { success: false };
};
export default attemptLogin;
@@ -1,95 +0,0 @@
/* eslint-disable prefer-destructuring */
import jsrp from "jsrp";
import { decryptPrivateKeyHelper } from "@app/helpers/key";
import { login1, verifyMfaToken } from "@app/hooks/api/auth/queries";
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
import SecurityClient from "./SecurityClient";
// eslint-disable-next-line new-cap
const client = new jsrp.client();
/**
* Return whether or not MFA-login is successful for user with email [email]
* and MFA token [mfaToken]
* @param {Object} obj
* @param {String} obj.email - email of user
* @param {String} obj.mfaToken - MFA code/token
*/
const attemptLoginMfa = async ({
email,
password,
providerAuthToken,
mfaToken
}: {
email: string;
password: string;
providerAuthToken?: string;
mfaToken: string;
}): Promise<boolean> => {
return new Promise((resolve, reject) => {
client.init(
{
username: email,
password
},
async () => {
try {
const clientPublicKey = client.getPublicKey();
const { salt } = await login1({
email,
clientPublicKey,
providerAuthToken
});
const {
encryptionVersion,
protectedKey,
protectedKeyIV,
protectedKeyTag,
token,
publicKey,
encryptedPrivateKey,
iv,
tag
} = await verifyMfaToken({
email,
mfaCode: mfaToken
});
// unset temporary (MFA) JWT token and set JWT token
SecurityClient.setMfaToken("");
SecurityClient.setToken(token);
SecurityClient.setProviderAuthToken("");
const privateKey = await decryptPrivateKeyHelper({
encryptionVersion,
encryptedPrivateKey,
iv,
tag,
password,
salt,
protectedKey,
protectedKeyIV,
protectedKeyTag
});
saveTokenToLocalStorage({
publicKey,
encryptedPrivateKey,
iv,
tag,
privateKey
});
resolve(true);
} catch (err) {
reject(err);
}
}
);
});
};
export default attemptLoginMfa;
@@ -1,114 +0,0 @@
/* eslint-disable new-cap */
import crypto from "crypto";
import jsrp from "jsrp";
import { issueBackupPrivateKey, srp1 } from "@app/hooks/api/auth/queries";
import generateBackupPDF from "../generateBackupPDF";
import Aes256Gcm from "./aes-256-gcm";
const clientPassword = new jsrp.client();
const clientKey = new jsrp.client();
interface BackupKeyProps {
email: string;
password: string;
personalName: string;
setBackupKeyError: (value: boolean) => void;
setBackupKeyIssued: (value: boolean) => void;
}
/**
* This function issue a backup key for a user
* @param {obkect} obj
* @param {string} obj.email - email of a user issuing a backup key
* @param {string} obj.password - password of a user issuing a backup key
* @param {string} obj.personalName - name of a user issuing a backup key
* @param {function} obj.setBackupKeyError - state function that turns true if there is an erorr with a backup key
* @param {function} obj.setBackupKeyIssued - state function that turns true if a backup key was issued correctly
* @returns
*/
const issueBackupKey = async ({
email,
password,
personalName,
setBackupKeyError,
setBackupKeyIssued
}: BackupKeyProps) => {
try {
setBackupKeyError(false);
setBackupKeyIssued(false);
clientPassword.init(
{
username: email,
password
},
async () => {
const clientPublicKey = clientPassword.getPublicKey();
let serverPublicKey;
let salt;
try {
const res = await srp1({
clientPublicKey
});
serverPublicKey = res.serverPublicKey;
salt = res.salt;
} catch (err) {
setBackupKeyError(true);
console.log("Wrong current password", err, 1);
}
clientPassword.setSalt(salt as string);
clientPassword.setServerPublicKey(serverPublicKey as string);
const clientProof = clientPassword.getProof(); // called M1
const generatedKey = crypto.randomBytes(16).toString("hex");
clientKey.init(
{
username: email,
password: generatedKey
},
async () => {
clientKey.createVerifier(
async (_err: any, result: { salt: string; verifier: string }) => {
const { ciphertext, iv, tag } = Aes256Gcm.encrypt({
text: String(localStorage.getItem("PRIVATE_KEY")),
secret: generatedKey
});
try {
await issueBackupPrivateKey({
encryptedPrivateKey: ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
clientProof
});
generateBackupPDF({
personalName,
personalEmail: email,
generatedKey
});
setBackupKeyIssued(true);
} catch {
setBackupKeyError(true);
}
}
);
}
);
}
);
} catch {
setBackupKeyError(true);
console.log("Failed to issue a backup key");
}
return true;
};
export default issueBackupKey;
@@ -1,67 +0,0 @@
interface Props {
protectedKey?: string;
protectedKeyIV?: string;
protectedKeyTag?: string;
publicKey?: string;
encryptedPrivateKey?: string;
iv?: string;
tag?: string;
privateKey?: string;
}
export const saveTokenToLocalStorage = ({
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
iv,
tag,
privateKey
}: Props) => {
try {
if (protectedKey) {
localStorage.removeItem("protectedKey");
localStorage.setItem("protectedKey", protectedKey);
}
if (protectedKeyIV) {
localStorage.removeItem("protectedKeyIV");
localStorage.setItem("protectedKeyIV", protectedKeyIV);
}
if (protectedKeyTag) {
localStorage.removeItem("protectedKeyTag");
localStorage.setItem("protectedKeyTag", protectedKeyTag);
}
if (publicKey) {
localStorage.removeItem("publicKey");
localStorage.setItem("publicKey", publicKey);
}
if (encryptedPrivateKey) {
localStorage.removeItem("encryptedPrivateKey");
localStorage.setItem("encryptedPrivateKey", encryptedPrivateKey);
}
if (iv) {
localStorage.removeItem("iv");
localStorage.setItem("iv", iv);
}
if (tag) {
localStorage.removeItem("tag");
localStorage.setItem("tag", tag);
}
if (privateKey) {
localStorage.removeItem("PRIVATE_KEY");
localStorage.setItem("PRIVATE_KEY", privateKey);
}
} catch (err) {
if (err instanceof Error) {
throw new Error(`Unable to send the tokens in local storage:${err.message}`);
}
}
};