Merge pull request #4270 from Infisical/daniel/srp-removal-round-2

feat: srp removal
This commit is contained in:
Daniel Hougaard
2025-08-05 23:47:43 +04:00
committed by GitHub
63 changed files with 917 additions and 2354 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}`);
}
}
};
+7 -48
View File
@@ -8,26 +8,24 @@ import { organizationKeys } from "../organization/queries";
import { setAuthToken } from "../reactQuery";
import { workspaceKeys } from "../workspace";
import {
ChangePasswordDTO,
CompleteAccountDTO,
CompleteAccountSignupDTO,
GetAuthTokenAPI,
GetBackupEncryptedPrivateKeyDTO,
IssueBackupPrivateKeyDTO,
Login1DTO,
Login1Res,
Login2DTO,
Login2Res,
LoginLDAPDTO,
LoginLDAPRes,
LoginV3DTO,
LoginV3Res,
MfaMethod,
ResetPasswordDTO,
ResetPasswordV2DTO,
ResetUserPasswordV2DTO,
SendMfaTokenDTO,
SetupPasswordDTO,
SRP1DTO,
SRPR1Res,
TOauthTokenExchangeDTO,
UserAgentType,
UserEncryptionVersion,
@@ -50,21 +48,14 @@ export const login2 = async (loginDetails: Login2DTO) => {
return data;
};
export const loginLDAPRedirect = async (loginLDAPDetails: LoginLDAPDTO) => {
const { data } = await apiRequest.post<LoginLDAPRes>("/api/v1/ldap/login", loginLDAPDetails); // return if account is complete or not + provider auth token
export const loginV3 = async (loginDetails: LoginV3DTO) => {
const { data } = await apiRequest.post<LoginV3Res>("/api/v3/auth/login", loginDetails);
return data;
};
export const useLogin1 = () => {
return useMutation({
mutationFn: async (details: {
email: string;
clientPublicKey: string;
providerAuthToken?: string;
}) => {
return login1(details);
}
});
export const loginLDAPRedirect = async (loginLDAPDetails: LoginLDAPDTO) => {
const { data } = await apiRequest.post<LoginLDAPRes>("/api/v1/ldap/login", loginLDAPDetails); // return if account is complete or not + provider auth token
return data;
};
export const selectOrganization = async (data: {
@@ -143,11 +134,6 @@ export const useOauthTokenExchange = () => {
});
};
export const srp1 = async (details: SRP1DTO) => {
const { data } = await apiRequest.post<SRPR1Res>("/api/v1/password/srp1", details);
return data;
};
export const completeAccountSignup = async (details: CompleteAccountSignupDTO) => {
const { data } = await apiRequest.post("/api/v3/signup/complete-account/signup", details);
return data;
@@ -158,14 +144,6 @@ export const completeAccountSignupInvite = async (details: CompleteAccountDTO) =
return data;
};
export const useCompleteAccountSignup = () => {
return useMutation({
mutationFn: async (details: CompleteAccountSignupDTO) => {
return completeAccountSignup(details);
}
});
};
export const useSendMfaToken = () => {
return useMutation<object, object, SendMfaTokenDTO>({
mutationFn: async ({ email }) => {
@@ -263,11 +241,6 @@ export const useVerifyPasswordResetCode = () => {
});
};
export const issueBackupPrivateKey = async (details: IssueBackupPrivateKeyDTO) => {
const { data } = await apiRequest.post("/api/v1/password/backup-private-key", details);
return data;
};
export const getBackupEncryptedPrivateKey = async ({
verificationToken
}: GetBackupEncryptedPrivateKeyDTO) => {
@@ -328,20 +301,6 @@ export const useResetUserPasswordV2 = () => {
});
};
export const changePassword = async (details: ChangePasswordDTO) => {
const { data } = await apiRequest.post("/api/v1/password/change-password", details);
return data;
};
export const useChangePassword = () => {
// note: use after srp1
return useMutation({
mutationFn: async (details: ChangePasswordDTO) => {
return changePassword(details);
}
});
};
// Refresh token is set as cookie when logged in
// Using that we fetch the auth bearer token needed for auth calls
export const fetchAuthToken = async () => {
+13 -49
View File
@@ -49,13 +49,19 @@ export type Login2DTO = {
password: string;
};
export type LoginV3DTO = {
email: string;
password: string;
providerAuthToken?: string;
captchaToken?: string;
};
export type Login1Res = {
serverPublicKey: string;
salt: string;
};
export type Login2Res = {
mfaEnabled: boolean;
token: string;
encryptionVersion?: number;
protectedKey?: string;
@@ -67,6 +73,11 @@ export type Login2Res = {
tag?: string;
};
export type LoginV3Res = {
accessToken: string;
mfaEnabled: boolean;
};
export type LoginLDAPDTO = {
organizationSlug: string;
username: string;
@@ -77,28 +88,10 @@ export type LoginLDAPRes = {
nextUrl: string;
};
export type SRP1DTO = {
clientPublicKey: string;
};
export type SRPR1Res = {
serverPublicKey: string;
salt: string;
};
export type CompleteAccountDTO = {
email: string;
firstName: string;
lastName: string;
protectedKey: string;
protectedKeyIV: string;
protectedKeyTag: string;
publicKey: string;
encryptedPrivateKey: string;
encryptedPrivateKeyIV: string;
encryptedPrivateKeyTag: string;
salt: string;
verifier: string;
password: string;
tokenMetadata?: string;
};
@@ -116,19 +109,6 @@ export type VerifySignupInviteDTO = {
organizationId: string;
};
export type ChangePasswordDTO = {
password: string;
clientProof: string;
protectedKey: string;
protectedKeyIV: string;
protectedKeyTag: string;
encryptedPrivateKey: string;
encryptedPrivateKeyIV: string;
encryptedPrivateKeyTag: string;
salt: string;
verifier: string;
};
export type ResetPasswordDTO = {
protectedKey: string;
protectedKeyIV: string;
@@ -153,27 +133,11 @@ export type ResetUserPasswordV2DTO = {
};
export type SetupPasswordDTO = {
protectedKey: string;
protectedKeyIV: string;
protectedKeyTag: string;
encryptedPrivateKey: string;
encryptedPrivateKeyIV: string;
encryptedPrivateKeyTag: string;
salt: string;
verifier: string;
email: string;
token: string;
password: string;
};
export type IssueBackupPrivateKeyDTO = {
encryptedPrivateKey: string;
iv: string;
tag: string;
salt: string;
verifier: string;
clientProof: string;
};
export type GetBackupEncryptedPrivateKeyDTO = {
verificationToken: string;
};
-8
View File
@@ -495,14 +495,6 @@ export const useGetMyOrganizationProjects = (orgId: string) => {
});
};
export const fetchMyPrivateKey = async () => {
const {
data: { privateKey }
} = await apiRequest.get<{ privateKey: string }>("/api/v1/user/private-key");
return privateKey;
};
export const useListUserGroupMemberships = (username: string) => {
return useQuery({
queryKey: userKeys.listUserGroupMemberships(username),
-47
View File
@@ -4,53 +4,6 @@ import jsrp from "jsrp";
import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm";
import { deriveArgonKey, generateKeyPair } from "@app/components/utilities/cryptography/crypto";
import { issueBackupPrivateKey, srp1 } from "@app/hooks/api/auth/queries";
export const generateUserBackupKey = async (email: string, password: string) => {
// eslint-disable-next-line new-cap
const clientKey = new jsrp.client();
// eslint-disable-next-line new-cap
const clientPassword = new jsrp.client();
await new Promise((resolve) => {
clientPassword.init({ username: email, password }, () => resolve(null));
});
const clientPublicKey = clientPassword.getPublicKey();
const srpKeys = await srp1({ clientPublicKey });
clientPassword.setSalt(srpKeys.salt);
clientPassword.setServerPublicKey(srpKeys.serverPublicKey);
const clientProof = clientPassword.getProof(); // called M1
const generatedKey = crypto.randomBytes(16).toString("hex");
await new Promise((resolve) => {
clientKey.init({ username: email, password: generatedKey }, () => resolve(null));
});
const { salt, verifier } = await new Promise<{ salt: string; verifier: string }>(
(resolve, reject) => {
clientKey.createVerifier((err, res) => {
if (err) return reject(err);
return resolve(res);
});
}
);
const { ciphertext, iv, tag } = Aes256Gcm.encrypt({
text: String(localStorage.getItem("PRIVATE_KEY")),
secret: generatedKey
});
await issueBackupPrivateKey({
encryptedPrivateKey: ciphertext,
iv,
tag,
salt,
verifier,
clientProof
});
return generatedKey;
};
export const generateUserPassKey = async (
email: string,
@@ -8,7 +8,6 @@ import { z } from "zod";
import { createNotification } from "@app/components/notifications";
// TODO(akhilmhdh): rewrite this into module functions in lib
import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage";
import SecurityClient from "@app/components/utilities/SecurityClient";
import { Button, ContentLoader, FormControl, Input } from "@app/components/v2";
import { useServerConfig } from "@app/context";
@@ -63,13 +62,6 @@ export const SignUpPage = () => {
});
SecurityClient.setToken(res.token);
saveTokenToLocalStorage({
publicKey: userPass.publicKey,
encryptedPrivateKey: userPass.encryptedPrivateKey,
iv: userPass.encryptedPrivateKeyIV,
tag: userPass.encryptedPrivateKeyTag,
privateKey
});
await selectOrganization({ organizationId: res.organization.id });
// TODO(akhilmhdh): This is such a confusing pattern and too unreliable
@@ -18,7 +18,7 @@ import { useToggle } from "@app/hooks";
import { useOauthTokenExchange, useSelectOrganization } from "@app/hooks/api";
import { MfaMethod } from "@app/hooks/api/auth/types";
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
import { fetchMyPrivateKey, fetchUserDuplicateAccounts } from "@app/hooks/api/users/queries";
import { fetchUserDuplicateAccounts } from "@app/hooks/api/users/queries";
import { EmailDuplicationConfirmation } from "@app/pages/auth/SelectOrgPage/EmailDuplicationConfirmation";
import { navigateUserToOrg, useNavigateToSelectOrganization } from "../../Login.utils";
@@ -70,9 +70,6 @@ export const PasswordStep = ({
// set JWT token
SecurityClient.setToken(oauthLogin.token);
const privateKey = await fetchMyPrivateKey();
localStorage.setItem("PRIVATE_KEY", privateKey);
// case: organization ID is present from the provider auth token -- select the org and use the new jwt token in the CLI, then navigate to the org
if (organizationId) {
const finishWithOrgWorkflow = async () => {
@@ -92,7 +89,7 @@ export const PasswordStep = ({
console.log("organization id was present. new JWT token to be used in CLI:", token);
const instance = axios.create();
const payload = {
privateKey,
privateKey: "", // note(daniel): no longer needed by the CLI, because the CLI only uses the private key to create service tokens, and the private key isn't used anymore when creating service tokens.
email,
JTWToken: token
};
@@ -1,22 +1,14 @@
import crypto from "crypto";
import { FormEvent, useState } from "react";
import { faCheck, faEye, faEyeSlash, faKey, faX } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useSearch } from "@tanstack/react-router";
import jsrp from "jsrp";
import { createNotification } from "@app/components/notifications";
import passwordCheck from "@app/components/utilities/checks/password/PasswordCheck";
import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm";
import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto";
import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes";
import { useSetupPassword } from "@app/hooks/api/auth/queries";
// eslint-disable-next-line new-cap
const client = new jsrp.client();
export const PasswordSetupPage = () => {
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
@@ -65,83 +57,31 @@ export const PasswordSetupPage = () => {
setPasswordsMatch(true);
if (!errorCheck) {
client.init(
{
username: email,
try {
await setupPassword.mutateAsync({
email,
token,
password
},
async () => {
client.createVerifier(async (_err: any, result: { salt: string; verifier: string }) => {
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");
setIsRedirecting(true);
const key = crypto.randomBytes(32);
createNotification({
type: "success",
title: "Password successfully set",
text: "Redirecting to login..."
});
// create encrypted private key by encrypting the private
// key with the symmetric key [key]
const {
ciphertext: encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
} = Aes256Gcm.encrypt({
text: localStorage.getItem("PRIVATE_KEY") as string,
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)
});
try {
await setupPassword.mutateAsync({
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt: result.salt,
verifier: result.verifier,
token,
password
});
setIsRedirecting(true);
createNotification({
type: "success",
title: "Password successfully set",
text: "Redirecting to login..."
});
setTimeout(() => {
window.location.href = "/login";
}, 3000);
} catch (error) {
createNotification({
type: "error",
text: (error as Error).message ?? "Error setting password"
});
navigate({ to: "/personal-settings" });
}
});
}
);
setTimeout(() => {
window.location.href = "/login";
}, 3000);
} catch (error) {
createNotification({
type: "error",
text: (error as Error).message ?? "Error setting password"
});
navigate({ to: "/personal-settings" });
}
}
};
@@ -123,11 +123,8 @@ export const SelectOrganizationSection = () => {
}
if (callbackPort) {
const privateKey = localStorage.getItem("PRIVATE_KEY");
let error: string | null = null;
if (!privateKey) error = "Private key not found";
if (!user?.email) error = "User email not found";
if (!token) error = "No token found";
@@ -142,7 +139,7 @@ export const SelectOrganizationSection = () => {
const payload = {
JTWToken: token,
email: user?.email,
privateKey
privateKey: ""
} as IsCliLoginSuccessful["loginResponse"];
// send request to server endpoint
@@ -1,23 +1,14 @@
/* eslint-disable no-nested-ternary */
/* eslint-disable @typescript-eslint/no-unused-vars */
import crypto from "crypto";
import { useState } from "react";
import { Helmet } from "react-helmet";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useNavigate, useSearch } from "@tanstack/react-router";
import jsrp from "jsrp";
import { Mfa } from "@app/components/auth/Mfa";
import InputField from "@app/components/basic/InputField";
import checkPassword from "@app/components/utilities/checks/password/checkPassword";
import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm";
import { deriveArgonKey, generateKeyPair } from "@app/components/utilities/cryptography/crypto";
import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage";
import SecurityClient from "@app/components/utilities/SecurityClient";
import { Button } from "@app/components/v2";
import { useServerConfig } from "@app/context";
import { useToggle } from "@app/hooks";
import {
completeAccountSignupInvite,
@@ -28,9 +19,6 @@ import { MfaMethod } from "@app/hooks/api/auth/types";
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
import { isLoggedIn } from "@app/hooks/api/reactQuery";
// eslint-disable-next-line new-cap
const client = new jsrp.client();
type Errors = {
tooShort?: string;
tooLong?: string;
@@ -67,7 +55,6 @@ export const SignupInvitePage = () => {
const metadata = queryParams.get("metadata") || undefined;
const { mutateAsync: selectOrganization } = useSelectOrganization();
const { config } = useServerConfig();
const loggedIn = isLoggedIn();
@@ -94,123 +81,56 @@ export const SignupInvitePage = () => {
}
if (!errorCheck) {
// Generate a random pair of a public and a private key
const { publicKey, privateKey } = await generateKeyPair(config.fipsEnabled);
try {
const { token: jwtToken } = await completeAccountSignupInvite({
email,
password,
firstName,
lastName,
tokenMetadata: metadata
});
localStorage.setItem("PRIVATE_KEY", privateKey);
// unset temporary signup JWT token and set JWT token
SecurityClient.setSignupToken("");
SecurityClient.setToken(jwtToken);
client.init(
{
username: email,
password
},
async () => {
client.createVerifier(async (_err, result) => {
try {
const derivedKey = await deriveArgonKey({
password,
salt: result.salt,
mem: 65536,
time: 3,
parallelism: 1,
hashLen: 32
});
const userOrgs = await fetchOrganizations();
if (!derivedKey) throw new Error("Failed to derive key from password");
const orgId = userOrgs[0].id;
const key = crypto.randomBytes(32);
if (!orgId) throw new Error("You are not part of any organization");
// create encrypted private key by encrypting the private
// key with the symmetric key [key]
const {
ciphertext: encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
} = Aes256Gcm.encrypt({
text: 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 { token: jwtToken } = await completeAccountSignupInvite({
email,
password,
firstName,
lastName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt: result.salt,
verifier: result.verifier,
tokenMetadata: metadata
});
// unset temporary signup JWT token and set JWT token
SecurityClient.setSignupToken("");
SecurityClient.setToken(jwtToken);
saveTokenToLocalStorage({
publicKey,
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
privateKey
});
const userOrgs = await fetchOrganizations();
const orgId = userOrgs[0].id;
if (!orgId) throw new Error("You are not part of any organization");
const completeSignupFlow = async () => {
const {
token: mfaToken,
isMfaEnabled,
mfaMethod
} = await selectOrganization({
organizationId: orgId
});
if (isMfaEnabled) {
SecurityClient.setMfaToken(mfaToken);
if (mfaMethod) {
setRequiredMfaMethod(mfaMethod);
}
toggleShowMfa.on();
setMfaSuccessCallback(() => completeSignupFlow);
return;
}
localStorage.setItem("orgData.id", orgId);
navigate({
to: "/organization/projects"
});
};
await completeSignupFlow();
} catch (error) {
setIsLoading(false);
console.error(error);
}
const completeSignupFlow = async () => {
const {
token: mfaToken,
isMfaEnabled,
mfaMethod
} = await selectOrganization({
organizationId: orgId
});
}
);
if (isMfaEnabled) {
SecurityClient.setMfaToken(mfaToken);
if (mfaMethod) {
setRequiredMfaMethod(mfaMethod);
}
toggleShowMfa.on();
setMfaSuccessCallback(() => completeSignupFlow);
return;
}
localStorage.setItem("orgData.id", orgId);
navigate({
to: "/organization/projects"
});
};
await completeSignupFlow();
} catch (error) {
setIsLoading(false);
console.error(error);
}
} else {
setIsLoading(false);
}
@@ -3,24 +3,16 @@ import crypto from "crypto";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "@tanstack/react-router";
import jsrp from "jsrp";
import { Mfa } from "@app/components/auth/Mfa";
import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm";
import { deriveArgonKey, generateKeyPair } from "@app/components/utilities/cryptography/crypto";
import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage";
import SecurityClient from "@app/components/utilities/SecurityClient";
import { Button, Input } from "@app/components/v2";
import { useServerConfig } from "@app/context";
import { initProjectHelper } from "@app/helpers/project";
import { useToggle } from "@app/hooks";
import { completeAccountSignup, useSelectOrganization } from "@app/hooks/api/auth/queries";
import { MfaMethod } from "@app/hooks/api/auth/types";
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
// eslint-disable-next-line new-cap
const client = new jsrp.client();
type Props = {
username: string;
password: string;
@@ -64,7 +56,6 @@ export const UserInfoSSOStep = ({
const { mutateAsync: selectOrganization } = useSelectOrganization();
const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {});
const navigate = useNavigate();
const { config } = useServerConfig();
useEffect(() => {
const randomPassword = crypto.randomBytes(32).toString("hex");
@@ -93,131 +84,64 @@ export const UserInfoSSOStep = ({
}
if (!errorCheck) {
// Generate a random pair of a public and a private key
const { publicKey, privateKey } = await generateKeyPair(config.fipsEnabled);
localStorage.setItem("PRIVATE_KEY", privateKey);
try {
const response = await completeAccountSignup({
email: username,
password,
firstName: name.split(" ")[0],
lastName: name.split(" ").slice(1).join(" "),
providerAuthToken,
organizationName,
attributionSource,
useDefaultOrg: forceDefaultOrg
});
client.init(
{
username,
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
});
// unset signup JWT token and set JWT token
SecurityClient.setSignupToken("");
SecurityClient.setToken(response.token);
SecurityClient.setProviderAuthToken("");
if (!derivedKey) throw new Error("Failed to derive key from password");
const userOrgs = await fetchOrganizations();
const orgId = userOrgs[0]?.id;
const key = crypto.randomBytes(32);
const completeSignupFlow = async () => {
try {
const { isMfaEnabled, token, mfaMethod } = await selectOrganization({
organizationId: orgId
});
// create encrypted private key by encrypting the private
// key with the symmetric key [key]
const {
ciphertext: encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
} = Aes256Gcm.encrypt({
text: 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: username,
password,
firstName: name.split(" ")[0],
lastName: name.split(" ").slice(1).join(" "),
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
providerAuthToken,
salt: result.salt,
verifier: result.verifier,
organizationName,
attributionSource,
useDefaultOrg: forceDefaultOrg
});
// unset signup JWT token and set JWT token
SecurityClient.setSignupToken("");
SecurityClient.setToken(response.token);
SecurityClient.setProviderAuthToken("");
saveTokenToLocalStorage({
publicKey,
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
privateKey
});
const userOrgs = await fetchOrganizations();
const orgId = userOrgs[0]?.id;
const completeSignupFlow = async () => {
try {
const { isMfaEnabled, token, mfaMethod } = await selectOrganization({
organizationId: orgId
});
if (isMfaEnabled) {
SecurityClient.setMfaToken(token);
if (mfaMethod) {
setRequiredMfaMethod(mfaMethod);
}
toggleShowMfa.on();
setMfaSuccessCallback(() => completeSignupFlow);
return;
}
// only create example project if not joining existing org
if (!providerOrganizationName) {
await initProjectHelper({
projectName: "Example Project"
});
}
localStorage.setItem("orgData.id", orgId);
navigate({
to: "/organization/projects"
});
} catch (error) {
setIsLoading(false);
console.error(error);
}
};
await completeSignupFlow();
} catch (error) {
setIsLoading(false);
console.error(error);
if (isMfaEnabled) {
SecurityClient.setMfaToken(token);
if (mfaMethod) {
setRequiredMfaMethod(mfaMethod);
}
toggleShowMfa.on();
setMfaSuccessCallback(() => completeSignupFlow);
return;
}
});
}
);
// only create example project if not joining existing org
if (!providerOrganizationName) {
await initProjectHelper({
projectName: "Example Project"
});
}
localStorage.setItem("orgData.id", orgId);
navigate({
to: "/organization/projects"
});
} catch (error) {
setIsLoading(false);
console.error(error);
}
};
await completeSignupFlow();
} catch (error) {
setIsLoading(false);
console.error(error);
}
} else {
setIsLoading(false);
}
@@ -8,12 +8,12 @@ import { useNavigate } from "@tanstack/react-router";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import attemptChangePassword from "@app/components/utilities/attemptChangePassword";
import checkPassword from "@app/components/utilities/checks/password/checkPassword";
import { Button, FormControl, Input } from "@app/components/v2";
import { useUser } from "@app/context";
import { useResetUserPasswordV2, useSendPasswordSetupEmail } from "@app/hooks/api/auth/queries";
import { UserEncryptionVersion } from "@app/hooks/api/auth/types";
import { clearSession } from "@app/hooks/api/users/queries";
type Errors = {
tooShort?: string;
@@ -62,19 +62,22 @@ export const ChangePasswordSection = () => {
if (errorCheck) return;
setIsLoading(true);
if (user.encryptionVersion === UserEncryptionVersion.V2) {
await resetPasswordV2({
oldPassword,
newPassword
});
} else {
await attemptChangePassword({
email: user.username,
currentPassword: oldPassword,
newPassword
if (user.encryptionVersion !== UserEncryptionVersion.V2) {
createNotification({
text: "Legacy encryption scheme not supported for changing password. Please contact support.",
type: "error"
});
setIsLoading(false);
return;
}
await resetPasswordV2({
oldPassword,
newPassword
});
clearSession();
setIsLoading(false);
createNotification({
text: "Successfully changed password",
@@ -1,80 +0,0 @@
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import issueBackupKey from "@app/components/utilities/cryptography/issueBackupKey";
import { Button, FormControl, Input } from "@app/components/v2";
import { useUser } from "@app/context";
const schema = z
.object({
password: z.string().describe("Password is required")
})
.required();
export type FormData = z.infer<typeof schema>;
export const EmergencyKitSection = () => {
const { user } = useUser();
const { reset, control, handleSubmit } = useForm({
defaultValues: {
password: ""
},
resolver: zodResolver(schema)
});
const onFormSubmit = ({ password }: FormData) => {
try {
if (!user?.email) return;
issueBackupKey({
email: user.email,
password,
personalName: `${user.firstName} ${user.lastName}`,
setBackupKeyError: () => {},
setBackupKeyIssued: () => {}
});
reset();
} catch (err) {
console.error(err);
createNotification({
text: "Failed to download emergency kit",
type: "error"
});
}
};
return (
<form
onSubmit={handleSubmit(onFormSubmit)}
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
>
<h2 className="flex-1 text-xl font-semibold text-mineshaft-100">Emergency Kit</h2>
<p className="mb-8 text-gray-400">
The kit contains information you can use to recover your account.
</p>
<div className="max-w-md">
<Controller
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message}>
<Input
placeholder="Password"
type="password"
{...field}
className="bg-mineshaft-800"
/>
</FormControl>
)}
control={control}
name="password"
/>
</div>
<Button type="submit" colorSchema="secondary" isLoading={false}>
Save
</Button>
</form>
);
};
@@ -1 +0,0 @@
export { EmergencyKitSection } from "./EmergencyKitSection";
@@ -1,20 +1,12 @@
import { useUser } from "@app/context";
import { UserEncryptionVersion } from "@app/hooks/api/auth/types";
import { DeleteAccountSection } from "../DeleteAccountSection";
import { EmergencyKitSection } from "../EmergencyKitSection";
import { SessionsSection } from "../SessionsSection";
import { UserNameSection } from "../UserNameSection";
export const PersonalGeneralTab = () => {
const { user } = useUser();
const encryptionVersion = user?.encryptionVersion ?? UserEncryptionVersion.V2;
return (
<div>
<UserNameSection />
<SessionsSection />
{encryptionVersion === UserEncryptionVersion.V1 && <EmergencyKitSection />}
<DeleteAccountSection />
</div>
);