From 777dfd5f58db183410696bec7507b0683097d1b0 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 10 Jun 2024 23:41:35 +0530 Subject: [PATCH 1/8] feat: api changes for srp handover --- .../20240609133400_private-key-handoff.ts | 61 +++++++++++ .../src/db/schemas/user-encryption-keys.ts | 7 +- .../ldap-config/ldap-config-service.ts | 10 +- .../saml-config/saml-config-service.ts | 7 +- backend/src/lib/config/env.ts | 2 +- backend/src/lib/crypto/srp.ts | 25 +++-- backend/src/server/routes/v1/sso-router.ts | 46 ++++++++ backend/src/server/routes/v1/user-router.ts | 29 ++++- backend/src/server/routes/v3/login-router.ts | 6 +- backend/src/server/routes/v3/signup-router.ts | 4 +- backend/src/services/auth/auth-fns.ts | 4 +- .../src/services/auth/auth-login-service.ts | 102 ++++++++++++++++-- backend/src/services/auth/auth-login-type.ts | 8 ++ .../src/services/auth/auth-signup-service.ts | 45 +++++++- backend/src/services/auth/auth-signup-type.ts | 2 + backend/src/services/user/user-service.ts | 20 +++- 16 files changed, 344 insertions(+), 34 deletions(-) create mode 100644 backend/src/db/migrations/20240609133400_private-key-handoff.ts diff --git a/backend/src/db/migrations/20240609133400_private-key-handoff.ts b/backend/src/db/migrations/20240609133400_private-key-handoff.ts new file mode 100644 index 000000000..8c71eab80 --- /dev/null +++ b/backend/src/db/migrations/20240609133400_private-key-handoff.ts @@ -0,0 +1,61 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + const doesPasswordFieldExist = await knex.schema.hasColumn(TableName.UserEncryptionKey, "password"); + const doesPrivateKeyFieldExist = await knex.schema.hasColumn( + TableName.UserEncryptionKey, + "serverEncryptedPrivateKey" + ); + const doesPrivateKeyIVFieldExist = await knex.schema.hasColumn( + TableName.UserEncryptionKey, + "serverEncryptedPrivateKeyIV" + ); + const doesPrivateKeyTagFieldExist = await knex.schema.hasColumn( + TableName.UserEncryptionKey, + "serverEncryptedPrivateKeyTag" + ); + const doesPrivateKeyEncodingFieldExist = await knex.schema.hasColumn( + TableName.UserEncryptionKey, + "serverEncryptedPrivateKeyEncoding" + ); + if (await knex.schema.hasTable(TableName.UserEncryptionKey)) { + await knex.schema.alterTable(TableName.UserEncryptionKey, (t) => { + if (!doesPasswordFieldExist) t.string("password"); + if (!doesPrivateKeyFieldExist) t.text("serverEncryptedPrivateKey"); + if (!doesPrivateKeyIVFieldExist) t.text("serverEncryptedPrivateKeyIV"); + if (!doesPrivateKeyTagFieldExist) t.text("serverEncryptedPrivateKeyTag"); + if (!doesPrivateKeyEncodingFieldExist) t.text("serverEncryptedPrivateKeyEncoding"); + }); + } +} + +export async function down(knex: Knex): Promise { + const doesPasswordFieldExist = await knex.schema.hasColumn(TableName.UserEncryptionKey, "password"); + const doesPrivateKeyFieldExist = await knex.schema.hasColumn( + TableName.UserEncryptionKey, + "serverEncryptedPrivateKey" + ); + const doesPrivateKeyIVFieldExist = await knex.schema.hasColumn( + TableName.UserEncryptionKey, + "serverEncryptedPrivateKeyIV" + ); + const doesPrivateKeyTagFieldExist = await knex.schema.hasColumn( + TableName.UserEncryptionKey, + "serverEncryptedPrivateKeyTag" + ); + const doesPrivateKeyEncodingFieldExist = await knex.schema.hasColumn( + TableName.UserEncryptionKey, + "serverEncryptedPrivateKeyEncoding" + ); + if (await knex.schema.hasTable(TableName.UserEncryptionKey)) { + await knex.schema.alterTable(TableName.UserEncryptionKey, (t) => { + if (doesPasswordFieldExist) t.dropColumn("password"); + if (doesPrivateKeyFieldExist) t.dropColumn("serverEncryptedPrivateKey"); + if (doesPrivateKeyIVFieldExist) t.dropColumn("serverEncryptedPrivateKeyIV"); + if (doesPrivateKeyTagFieldExist) t.dropColumn("serverEncryptedPrivateKeyTag"); + if (doesPrivateKeyEncodingFieldExist) t.dropColumn("serverEncryptedPrivateKeyEncoding"); + }); + } +} diff --git a/backend/src/db/schemas/user-encryption-keys.ts b/backend/src/db/schemas/user-encryption-keys.ts index 693b73b4c..6afab529d 100644 --- a/backend/src/db/schemas/user-encryption-keys.ts +++ b/backend/src/db/schemas/user-encryption-keys.ts @@ -21,7 +21,12 @@ export const UserEncryptionKeysSchema = z.object({ tag: z.string(), salt: z.string(), verifier: z.string(), - userId: z.string().uuid() + userId: z.string().uuid(), + password: z.string().nullable().optional(), + serverEncryptedPrivateKey: z.string().nullable().optional(), + serverEncryptedPrivateKeyIV: z.string().nullable().optional(), + serverEncryptedPrivateKeyTag: z.string().nullable().optional(), + serverEncryptedPrivateKeyEncoding: z.string().nullable().optional() }); export type TUserEncryptionKeys = z.infer; diff --git a/backend/src/ee/services/ldap-config/ldap-config-service.ts b/backend/src/ee/services/ldap-config/ldap-config-service.ts index dd49bd0ae..8027f5907 100644 --- a/backend/src/ee/services/ldap-config/ldap-config-service.ts +++ b/backend/src/ee/services/ldap-config/ldap-config-service.ts @@ -73,7 +73,13 @@ type TLdapConfigServiceFactoryDep = { >; userDAL: Pick< TUserDALFactory, - "create" | "findOne" | "transaction" | "updateById" | "findUserEncKeyByUserIdsBatch" | "find" + | "create" + | "findOne" + | "transaction" + | "updateById" + | "findUserEncKeyByUserIdsBatch" + | "find" + | "findUserEncKeyByUserId" >; userAliasDAL: Pick; permissionService: Pick; @@ -592,12 +598,14 @@ export const ldapConfigServiceFactory = ({ }); const isUserCompleted = Boolean(user.isAccepted); + const userEnc = await userDAL.findUserEncKeyByUserId(user.id); const providerAuthToken = jwt.sign( { authTokenType: AuthTokenType.PROVIDER_TOKEN, userId: user.id, username: user.username, + hasExchangedPrivateKey: Boolean(userEnc?.serverEncryptedPrivateKey), ...(user.email && { email: user.email, isEmailVerified: user.isEmailVerified }), firstName, lastName, diff --git a/backend/src/ee/services/saml-config/saml-config-service.ts b/backend/src/ee/services/saml-config/saml-config-service.ts index 5d7b7ec3b..3cc51e1c2 100644 --- a/backend/src/ee/services/saml-config/saml-config-service.ts +++ b/backend/src/ee/services/saml-config/saml-config-service.ts @@ -41,7 +41,10 @@ import { TCreateSamlCfgDTO, TGetSamlCfgDTO, TSamlLoginDTO, TUpdateSamlCfgDTO } f type TSamlConfigServiceFactoryDep = { samlConfigDAL: Pick; - userDAL: Pick; + userDAL: Pick< + TUserDALFactory, + "create" | "findOne" | "transaction" | "updateById" | "findById" | "findUserEncKeyByUserId" + >; userAliasDAL: Pick; orgDAL: Pick< TOrgDALFactory, @@ -452,6 +455,7 @@ export const samlConfigServiceFactory = ({ await licenseService.updateSubscriptionOrgMemberCount(organization.id); const isUserCompleted = Boolean(user.isAccepted); + const userEnc = await userDAL.findUserEncKeyByUserId(user.id); const providerAuthToken = jwt.sign( { authTokenType: AuthTokenType.PROVIDER_TOKEN, @@ -464,6 +468,7 @@ export const samlConfigServiceFactory = ({ organizationId: organization.id, organizationSlug: organization.slug, authMethod: authProvider, + hasExchangedPrivateKey: Boolean(userEnc?.serverEncryptedPrivateKey), authType: UserAliasType.SAML, isUserCompleted, ...(relayState diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 80a2111fc..f4da71293 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -29,7 +29,7 @@ const envSchema = z DB_USER: zpStr(z.string().describe("Postgres database username").optional()), DB_PASSWORD: zpStr(z.string().describe("Postgres database password").optional()), DB_NAME: zpStr(z.string().describe("Postgres database name").optional()), - + BCRYPT_SALT_ROUND: z.number().default(12), NODE_ENV: z.enum(["development", "test", "production"]).default("production"), SALT_ROUNDS: z.coerce.number().default(10), INITIAL_ORGANIZATION_NAME: zpStr(z.string().optional()), diff --git a/backend/src/lib/crypto/srp.ts b/backend/src/lib/crypto/srp.ts index bc29cdb3f..8d7ea656a 100644 --- a/backend/src/lib/crypto/srp.ts +++ b/backend/src/lib/crypto/srp.ts @@ -6,7 +6,7 @@ import tweetnacl from "tweetnacl-util"; import { TUserEncryptionKeys } from "@app/db/schemas"; -import { decryptSymmetric, encryptAsymmetric, encryptSymmetric } from "./encryption"; +import { decryptSymmetric128BitHexKeyUTF8, encryptAsymmetric, encryptSymmetric } from "./encryption"; export const generateSrpServerKey = async (salt: string, verifier: string) => { // eslint-disable-next-line new-cap @@ -97,7 +97,13 @@ export const generateUserSrpKeys = async (email: string, password: string) => { }; }; -export const getUserPrivateKey = async (password: string, user: TUserEncryptionKeys) => { +export const getUserPrivateKey = async ( + password: string, + user: Pick< + TUserEncryptionKeys, + "protectedKeyTag" | "protectedKey" | "protectedKeyIV" | "encryptedPrivateKey" | "iv" | "salt" | "tag" + > +) => { const derivedKey = await argon2.hash(password, { salt: Buffer.from(user.salt), memoryCost: 65536, @@ -108,17 +114,18 @@ export const getUserPrivateKey = async (password: string, user: TUserEncryptionK raw: true }); if (!derivedKey) throw new Error("Failed to derive key from password"); - const key = decryptSymmetric({ - ciphertext: user.protectedKey!, - iv: user.protectedKeyIV!, - tag: user.protectedKeyTag!, - key: derivedKey.toString("base64") + const key = decryptSymmetric128BitHexKeyUTF8({ + ciphertext: user.protectedKey as string, + iv: user.protectedKeyIV as string, + tag: user.protectedKeyTag as string, + key: derivedKey }); - const privateKey = decryptSymmetric({ + + const privateKey = decryptSymmetric128BitHexKeyUTF8({ ciphertext: user.encryptedPrivateKey, iv: user.iv, tag: user.tag, - key + key: Buffer.from(key, "hex") }); return privateKey; }; diff --git a/backend/src/server/routes/v1/sso-router.ts b/backend/src/server/routes/v1/sso-router.ts index 60bbec7db..3d8d02e6f 100644 --- a/backend/src/server/routes/v1/sso-router.ts +++ b/backend/src/server/routes/v1/sso-router.ts @@ -259,4 +259,50 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { ); } }); + + server.route({ + url: "/token-exchange", + method: "POST", + schema: { + body: z.object({ + providerAuthToken: z.string(), + email: z.string() + }) + }, + handler: async (req, res) => { + const userAgent = req.headers["user-agent"]; + if (!userAgent) throw new Error("user agent header is required"); + + const data = await server.services.login.oauth2TokenExchange({ + email: req.body.email, + ip: req.realIp, + userAgent, + providerAuthToken: req.body.providerAuthToken + }); + + if (data.isMfaEnabled) { + return { mfaEnabled: true, token: data.token } as const; // for discriminated union + } + + void res.setCookie("jid", data.token.refresh, { + httpOnly: true, + path: "/", + sameSite: "strict", + secure: appCfg.HTTPS_ENABLED + }); + + return { + mfaEnabled: false, + encryptionVersion: data.user.encryptionVersion, + token: data.token.access, + publicKey: data.user.publicKey, + encryptedPrivateKey: data.user.encryptedPrivateKey, + iv: data.user.iv, + tag: data.user.tag, + protectedKey: data.user.protectedKey || null, + protectedKeyIV: data.user.protectedKeyIV || null, + protectedKeyTag: data.user.protectedKeyTag || null + } as const; + } + }); }; diff --git a/backend/src/server/routes/v1/user-router.ts b/backend/src/server/routes/v1/user-router.ts index 3d9f531b9..eb2c1004b 100644 --- a/backend/src/server/routes/v1/user-router.ts +++ b/backend/src/server/routes/v1/user-router.ts @@ -19,7 +19,14 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { schema: { response: { 200: z.object({ - user: UsersSchema.merge(UserEncryptionKeysSchema.omit({ verifier: true })) + user: UsersSchema.merge( + UserEncryptionKeysSchema.omit({ + verifier: true, + serverEncryptedPrivateKey: true, + serverEncryptedPrivateKeyIV: true, + serverEncryptedPrivateKeyTag: true + }) + ) }) } }, @@ -30,6 +37,26 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { } }); + server.route({ + method: "GET", + url: "/private-key", + config: { + rateLimit: readLimit + }, + schema: { + response: { + 200: z.object({ + privateKey: z.string() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT], { requireOrg: false }), + handler: async (req) => { + const privateKey = await server.services.user.getUserPrivateKey(req.permission.id); + return { privateKey }; + } + }); + server.route({ method: "GET", url: "/:userId/unlock", diff --git a/backend/src/server/routes/v3/login-router.ts b/backend/src/server/routes/v3/login-router.ts index 4c7df5612..61a0c74e5 100644 --- a/backend/src/server/routes/v3/login-router.ts +++ b/backend/src/server/routes/v3/login-router.ts @@ -81,7 +81,8 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => { email: z.string().trim(), providerAuthToken: z.string().trim().optional(), clientProof: z.string().trim(), - captchaToken: z.string().trim().optional() + captchaToken: z.string().trim().optional(), + password: z.string().optional() }), response: { 200: z.discriminatedUnion("mfaEnabled", [ @@ -112,7 +113,8 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => { ip: req.realIp, userAgent, providerAuthToken: req.body.providerAuthToken, - clientProof: req.body.clientProof + clientProof: req.body.clientProof, + password: req.body.password }); if (data.isMfaEnabled) { diff --git a/backend/src/server/routes/v3/signup-router.ts b/backend/src/server/routes/v3/signup-router.ts index ac43df36d..59131464a 100644 --- a/backend/src/server/routes/v3/signup-router.ts +++ b/backend/src/server/routes/v3/signup-router.ts @@ -102,7 +102,8 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { verifier: z.string().trim(), organizationName: z.string().trim().min(1), providerAuthToken: z.string().trim().optional().nullish(), - attributionSource: z.string().trim().optional() + attributionSource: z.string().trim().optional(), + password: z.string() }), response: { 200: z.object({ @@ -167,6 +168,7 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { schema: { body: z.object({ email: z.string().email().trim(), + password: z.string(), firstName: z.string().trim(), lastName: z.string().trim().optional(), protectedKey: z.string().trim(), diff --git a/backend/src/services/auth/auth-fns.ts b/backend/src/services/auth/auth-fns.ts index ecbf73a48..e8574a8b9 100644 --- a/backend/src/services/auth/auth-fns.ts +++ b/backend/src/services/auth/auth-fns.ts @@ -15,10 +15,10 @@ export const validateProviderAuthToken = (providerToken: string, username?: stri if (decodedToken.username !== username) throw new Error("Invalid auth credentials"); if (decodedToken.organizationId) { - return { orgId: decodedToken.organizationId, authMethod: decodedToken.authMethod }; + return { orgId: decodedToken.organizationId, authMethod: decodedToken.authMethod, userName: decodedToken.username }; } - return { authMethod: decodedToken.authMethod, orgId: null }; + return { authMethod: decodedToken.authMethod, orgId: null, userName: decodedToken.username }; }; export const validateSignUpAuthorization = (token: string, userId: string, validate = true) => { diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index a136508e7..2fecb5857 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -1,3 +1,4 @@ +import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { TUsers, UserDeviceSchema } from "@app/db/schemas"; @@ -5,6 +6,8 @@ import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns"; import { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; import { generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto"; +import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; +import { getUserPrivateKey } from "@app/lib/crypto/srp"; import { BadRequestError, DatabaseError, UnauthorizedError } from "@app/lib/errors"; import { getServerCfg } from "@app/services/super-admin/super-admin-service"; @@ -19,6 +22,7 @@ import { TLoginClientProofDTO, TLoginGenServerPublicKeyDTO, TOauthLoginDTO, + TOauthTokenExchangeDTO, TVerifyMfaTokenDTO } from "./auth-login-type"; import { AuthMethod, AuthModeJwtTokenPayload, AuthModeMfaJwtTokenPayload, AuthTokenType } from "./auth-type"; @@ -101,7 +105,7 @@ export const authLoginServiceFactory = ({ user: TUsers; ip: string; userAgent: string; - organizationId: string | undefined; + organizationId?: string; authMethod: AuthMethod; }) => { const cfg = getConfig(); @@ -178,7 +182,8 @@ export const authLoginServiceFactory = ({ ip, userAgent, providerAuthToken, - captchaToken + captchaToken, + password }: TLoginClientProofDTO) => { const appCfg = getConfig(); @@ -248,14 +253,29 @@ export const authLoginServiceFactory = ({ throw new Error("Failed to authenticate. Try again?"); } - await userDAL.updateUserEncryptionByUserId(userEnc.userId, { - serverPrivateKey: null, - clientPublicKey: null - }); - await userDAL.updateById(userEnc.userId, { consecutiveFailedPasswordAttempts: 0 }); + // from password decrypt the private key + if (password) { + const privateKey = await getUserPrivateKey(password, userEnc); + const hashedPassword = await bcrypt.hash(password, cfg.BCRYPT_SALT_ROUND); + const { iv, tag, ciphertext, encoding } = infisicalSymmetricEncypt(privateKey); + await userDAL.updateUserEncryptionByUserId(userEnc.userId, { + serverPrivateKey: null, + clientPublicKey: null, + password: hashedPassword, + serverEncryptedPrivateKey: ciphertext, + serverEncryptedPrivateKeyIV: iv, + serverEncryptedPrivateKeyTag: tag, + serverEncryptedPrivateKeyEncoding: encoding + }); + } else { + await userDAL.updateUserEncryptionByUserId(userEnc.userId, { + serverPrivateKey: null, + clientPublicKey: null + }); + } // send multi factor auth token if they it enabled if (userEnc.isMfaEnabled && userEnc.email) { @@ -499,8 +519,14 @@ export const authLoginServiceFactory = ({ authMethods: [authMethod], isGhost: false }); + } else { + const isLinkingRequired = !user?.authMethods?.includes(authMethod); + if (isLinkingRequired) { + user = await userDAL.updateById(user.id, { authMethods: [...(user.authMethods || []), authMethod] }); + } } - const isLinkingRequired = !user?.authMethods?.includes(authMethod); + + const userEnc = await userDAL.findUserEncKeyByUserId(user.id); const isUserCompleted = user.isAccepted; const providerAuthToken = jwt.sign( { @@ -511,9 +537,9 @@ export const authLoginServiceFactory = ({ isEmailVerified: user.isEmailVerified, firstName: user.firstName, lastName: user.lastName, + hasExchangedPrivateKey: Boolean(userEnc?.serverEncryptedPrivateKey), authMethod, isUserCompleted, - isLinkingRequired, ...(callbackPort ? { callbackPort @@ -525,10 +551,65 @@ export const authLoginServiceFactory = ({ expiresIn: appCfg.JWT_PROVIDER_AUTH_LIFETIME } ); - return { isUserCompleted, providerAuthToken }; }; + // to login users with oauth2 token used for private key handoff + // The provider token will be given back to client to send back infisical access token + // why not directly sending access token? + // 1. To keep the logic change easier from SRP oauth to simple oauth + // 2. I don't want to attach access token to url as it may get logged the provider token has very short life span + const oauth2TokenExchange = async ({ userAgent, ip, providerAuthToken, email }: TOauthTokenExchangeDTO) => { + const decodedProviderToken = validateProviderAuthToken(providerAuthToken, email); + + const appCfg = getConfig(); + const { authMethod, userName } = decodedProviderToken; + if (!userName) throw new BadRequestError({ message: "Missing user name" }); + const organizationId = + (isAuthMethodSaml(authMethod) || authMethod === AuthMethod.LDAP) && decodedProviderToken.orgId + ? decodedProviderToken.orgId + : undefined; + + const user = await userDAL.findUserEncKeyByUsername({ + username: email + }); + if (!user) throw new BadRequestError({ message: "Invalid token" }); + if (!user.serverEncryptedPrivateKey) throw new BadRequestError({ message: "Private key handoff needs to be done" }); + // send multi factor auth token if they it enabled + if (user.isMfaEnabled && user.email) { + enforceUserLockStatus(Boolean(user.isLocked), user.temporaryLockDateEnd); + + const mfaToken = jwt.sign( + { + authMethod, + authTokenType: AuthTokenType.MFA_TOKEN, + userId: user.userId + }, + appCfg.AUTH_SECRET, + { + expiresIn: appCfg.JWT_MFA_LIFETIME + } + ); + + await sendUserMfaCode({ + userId: user.id, + email: user.email + }); + + return { isMfaEnabled: true, token: mfaToken } as const; + } + + const token = await generateUserTokens({ + user: { ...user, id: user.userId }, + ip, + userAgent, + authMethod, + organizationId + }); + + return { token, isMfaEnabled: false, user } as const; + }; + /* * logout user by incrementing the version by 1 meaning any old session will become invalid * as there number is behind @@ -542,6 +623,7 @@ export const authLoginServiceFactory = ({ loginExchangeClientProof, logout, oauth2Login, + oauth2TokenExchange, resendMfaToken, verifyMfaToken, selectOrganization, diff --git a/backend/src/services/auth/auth-login-type.ts b/backend/src/services/auth/auth-login-type.ts index 4f73ec996..db57d730e 100644 --- a/backend/src/services/auth/auth-login-type.ts +++ b/backend/src/services/auth/auth-login-type.ts @@ -13,6 +13,7 @@ export type TLoginClientProofDTO = { ip: string; userAgent: string; captchaToken?: string; + password?: string; }; export type TVerifyMfaTokenDTO = { @@ -31,3 +32,10 @@ export type TOauthLoginDTO = { authMethod: AuthMethod; callbackPort?: string; }; + +export type TOauthTokenExchangeDTO = { + providerAuthToken: string; + ip: string; + userAgent: string; + email: string; +}; diff --git a/backend/src/services/auth/auth-signup-service.ts b/backend/src/services/auth/auth-signup-service.ts index 528cb44fa..a574c6050 100644 --- a/backend/src/services/auth/auth-signup-service.ts +++ b/backend/src/services/auth/auth-signup-service.ts @@ -1,3 +1,4 @@ +import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { OrgMembershipStatus, TableName } from "@app/db/schemas"; @@ -6,6 +7,8 @@ import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-grou import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns"; import { getConfig } from "@app/lib/config/env"; +import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; +import { getUserPrivateKey } from "@app/lib/crypto/srp"; import { BadRequestError } from "@app/lib/errors"; import { isDisposableEmail } from "@app/lib/validator"; import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal"; @@ -119,6 +122,7 @@ export const authSignupServiceFactory = ({ const completeEmailAccountSignup = async ({ email, + password, firstName, lastName, providerAuthToken, @@ -137,6 +141,7 @@ export const authSignupServiceFactory = ({ userAgent, authorization }: TCompleteAccountSignupDTO) => { + const appCfg = getConfig(); const user = await userDAL.findOne({ username: email }); if (!user || (user && user.isAccepted)) { throw new Error("Failed to complete account for complete user"); @@ -152,6 +157,17 @@ export const authSignupServiceFactory = ({ validateSignUpAuthorization(authorization, user.id); } + const hashedPassword = await bcrypt.hash(password, appCfg.BCRYPT_SALT_ROUND); + const privateKey = await getUserPrivateKey(password, { + salt, + protectedKey, + protectedKeyIV, + protectedKeyTag, + encryptedPrivateKey, + iv: encryptedPrivateKeyIV, + tag: encryptedPrivateKeyTag + }); + const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(privateKey); const updateduser = await authDAL.transaction(async (tx) => { const us = await userDAL.updateById(user.id, { firstName, lastName, isAccepted: true }, tx); if (!us) throw new Error("User not found"); @@ -166,7 +182,12 @@ export const authSignupServiceFactory = ({ protectedKeyTag, encryptedPrivateKey, iv: encryptedPrivateKeyIV, - tag: encryptedPrivateKeyTag + tag: encryptedPrivateKeyTag, + password: hashedPassword, + serverEncryptedPrivateKeyEncoding: encoding, + serverEncryptedPrivateKeyTag: tag, + serverEncryptedPrivateKeyIV: iv, + serverEncryptedPrivateKey: ciphertext }, tx ); @@ -227,7 +248,6 @@ export const authSignupServiceFactory = ({ userId: updateduser.info.id }); if (!tokenSession) throw new Error("Failed to create token"); - const appCfg = getConfig(); const accessToken = jwt.sign( { @@ -265,6 +285,7 @@ export const authSignupServiceFactory = ({ ip, salt, email, + password, verifier, firstName, publicKey, @@ -295,6 +316,18 @@ export const authSignupServiceFactory = ({ name: "complete account invite" }); + const appCfg = getConfig(); + const hashedPassword = await bcrypt.hash(password, appCfg.BCRYPT_SALT_ROUND); + const privateKey = await getUserPrivateKey(password, { + salt, + protectedKey, + protectedKeyIV, + protectedKeyTag, + encryptedPrivateKey, + iv: encryptedPrivateKeyIV, + tag: encryptedPrivateKeyTag + }); + const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(privateKey); const updateduser = await authDAL.transaction(async (tx) => { const us = await userDAL.updateById(user.id, { firstName, lastName, isAccepted: true }, tx); if (!us) throw new Error("User not found"); @@ -310,7 +343,12 @@ export const authSignupServiceFactory = ({ protectedKeyTag, encryptedPrivateKey, iv: encryptedPrivateKeyIV, - tag: encryptedPrivateKeyTag + tag: encryptedPrivateKeyTag, + password: hashedPassword, + serverEncryptedPrivateKeyEncoding: encoding, + serverEncryptedPrivateKeyTag: tag, + serverEncryptedPrivateKeyIV: iv, + serverEncryptedPrivateKey: ciphertext }, tx ); @@ -343,7 +381,6 @@ export const authSignupServiceFactory = ({ userId: updateduser.info.id }); if (!tokenSession) throw new Error("Failed to create token"); - const appCfg = getConfig(); const accessToken = jwt.sign( { diff --git a/backend/src/services/auth/auth-signup-type.ts b/backend/src/services/auth/auth-signup-type.ts index a37a1cd96..9cd70f8c7 100644 --- a/backend/src/services/auth/auth-signup-type.ts +++ b/backend/src/services/auth/auth-signup-type.ts @@ -1,5 +1,6 @@ export type TCompleteAccountSignupDTO = { email: string; + password: string; firstName: string; lastName?: string; protectedKey: string; @@ -21,6 +22,7 @@ export type TCompleteAccountSignupDTO = { export type TCompleteAccountInviteDTO = { email: string; + password: string; firstName: string; lastName?: string; protectedKey: string; diff --git a/backend/src/services/user/user-service.ts b/backend/src/services/user/user-service.ts index 693078fbd..4ee8bdc1f 100644 --- a/backend/src/services/user/user-service.ts +++ b/backend/src/services/user/user-service.ts @@ -1,3 +1,5 @@ +import { SecretKeyEncoding } from "@app/db/schemas"; +import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { BadRequestError } from "@app/lib/errors"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { TokenType } from "@app/services/auth-token/auth-token-types"; @@ -230,6 +232,21 @@ export const userServiceFactory = ({ ); }; + const getUserPrivateKey = async (userId: string) => { + const user = await userDAL.findUserEncKeyByUserId(userId); + if (!user?.serverEncryptedPrivateKey || !user.serverEncryptedPrivateKeyIV || !user.serverEncryptedPrivateKeyTag) { + throw new BadRequestError({ message: "Private key not found. Please login again" }); + } + const privateKey = infisicalSymmetricDecrypt({ + ciphertext: user.serverEncryptedPrivateKey, + tag: user.serverEncryptedPrivateKeyTag, + iv: user.serverEncryptedPrivateKeyIV, + keyEncoding: user.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding + }); + + return privateKey; + }; + return { sendEmailVerificationCode, verifyEmailVerificationCode, @@ -240,6 +257,7 @@ export const userServiceFactory = ({ getMe, createUserAction, getUserAction, - unlockUser + unlockUser, + getUserPrivateKey }; }; From f3ea7b3dfda18d47629a98880973830c32c2f0c4 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 10 Jun 2024 23:42:18 +0530 Subject: [PATCH 2/8] feat: updated ui for srp handover --- .../src/components/signup/UserInfoStep.tsx | 1 + .../components/utilities/attemptCliLogin.ts | 1 + .../src/components/utilities/attemptLogin.ts | 1 + frontend/src/hooks/api/auth/index.tsx | 4 +- frontend/src/hooks/api/auth/queries.tsx | 16 +++ frontend/src/hooks/api/auth/types.ts | 7 + frontend/src/hooks/api/users/queries.tsx | 9 ++ frontend/src/pages/signupinvite.tsx | 1 + .../Login/components/MFAStep/MFAStep.tsx | 76 ++++++++--- .../components/PasswordStep/PasswordStep.tsx | 127 ++++++++++++++---- .../UserInfoSSOStep/UserInfoSSOStep.tsx | 71 +--------- 11 files changed, 202 insertions(+), 112 deletions(-) diff --git a/frontend/src/components/signup/UserInfoStep.tsx b/frontend/src/components/signup/UserInfoStep.tsx index c98998e35..705b490bb 100644 --- a/frontend/src/components/signup/UserInfoStep.tsx +++ b/frontend/src/components/signup/UserInfoStep.tsx @@ -161,6 +161,7 @@ export default function UserInfoStep({ const response = await completeAccountSignup({ email, + password, firstName: name.split(" ")[0], lastName: name.split(" ").slice(1).join(" "), protectedKey, diff --git a/frontend/src/components/utilities/attemptCliLogin.ts b/frontend/src/components/utilities/attemptCliLogin.ts index 8f7c4bb9f..6eba08d61 100644 --- a/frontend/src/components/utilities/attemptCliLogin.ts +++ b/frontend/src/components/utilities/attemptCliLogin.ts @@ -71,6 +71,7 @@ const attemptLogin = async ({ tag } = await login2({ email, + password, clientProof, providerAuthToken, captchaToken diff --git a/frontend/src/components/utilities/attemptLogin.ts b/frontend/src/components/utilities/attemptLogin.ts index b909b1ba7..120ae62ec 100644 --- a/frontend/src/components/utilities/attemptLogin.ts +++ b/frontend/src/components/utilities/attemptLogin.ts @@ -62,6 +62,7 @@ const attemptLogin = async ({ } = await login2({ captchaToken, email, + password, clientProof, providerAuthToken }); diff --git a/frontend/src/hooks/api/auth/index.tsx b/frontend/src/hooks/api/auth/index.tsx index 505f7b05f..66688cbdc 100644 --- a/frontend/src/hooks/api/auth/index.tsx +++ b/frontend/src/hooks/api/auth/index.tsx @@ -1,5 +1,6 @@ export { useGetAuthToken, + useOauthTokenExchange, useResetPassword, useSelectOrganization, useSendMfaToken, @@ -7,4 +8,5 @@ export { useSendVerificationEmail, useVerifyMfaToken, useVerifyPasswordResetCode, - useVerifySignupEmailVerificationCode} from "./queries"; + useVerifySignupEmailVerificationCode +} from "./queries"; diff --git a/frontend/src/hooks/api/auth/queries.tsx b/frontend/src/hooks/api/auth/queries.tsx index cba815fae..fcec4f2ef 100644 --- a/frontend/src/hooks/api/auth/queries.tsx +++ b/frontend/src/hooks/api/auth/queries.tsx @@ -23,6 +23,7 @@ import { SendMfaTokenDTO, SRP1DTO, SRPR1Res, + TOauthTokenExchangeDTO, VerifyMfaTokenDTO, VerifyMfaTokenRes, VerifySignupInviteDTO @@ -92,6 +93,7 @@ export const useLogin2 = () => { mutationFn: async (details: { email: string; clientProof: string; + password: string; providerAuthToken?: string; }) => { return login2(details); @@ -99,6 +101,20 @@ export const useLogin2 = () => { }); }; +export const oauthTokenExchange = async (details: TOauthTokenExchangeDTO) => { + const { data } = await apiRequest.post("/api/v1/sso/token-exchange", details); + return data; +}; + +export const useOauthTokenExchange = () => { + // note: use after srp1 + return useMutation({ + mutationFn: async (details: TOauthTokenExchangeDTO) => { + return oauthTokenExchange(details); + } + }); +}; + export const srp1 = async (details: SRP1DTO) => { const { data } = await apiRequest.post("/api/v1/password/srp1", details); return data; diff --git a/frontend/src/hooks/api/auth/types.ts b/frontend/src/hooks/api/auth/types.ts index ce1b18bc8..a6bd63342 100644 --- a/frontend/src/hooks/api/auth/types.ts +++ b/frontend/src/hooks/api/auth/types.ts @@ -23,6 +23,11 @@ export type VerifyMfaTokenRes = { tag: string; }; +export type TOauthTokenExchangeDTO = { + providerAuthToken: string; + email: string; +}; + export type Login1DTO = { email: string; clientPublicKey: string; @@ -34,6 +39,7 @@ export type Login2DTO = { email: string; clientProof: string; providerAuthToken?: string; + password: string; }; export type Login1Res = { @@ -86,6 +92,7 @@ export type CompleteAccountDTO = { encryptedPrivateKeyTag: string; salt: string; verifier: string; + password: string; }; export type CompleteAccountSignupDTO = CompleteAccountDTO & { diff --git a/frontend/src/hooks/api/users/queries.tsx b/frontend/src/hooks/api/users/queries.tsx index a443c6750..fa0b932ea 100644 --- a/frontend/src/hooks/api/users/queries.tsx +++ b/frontend/src/hooks/api/users/queries.tsx @@ -20,6 +20,7 @@ import { export const userKeys = { getUser: ["user"] as const, + getPrivateKey: ["user"] as const, userAction: ["user-action"] as const, getOrgUsers: (orgId: string) => [{ orgId }, "user"], myIp: ["ip"] as const, @@ -351,3 +352,11 @@ export const useGetMyOrganizationProjects = (orgId: string) => { enabled: true }); }; + +export const fetchMyPrivateKey = async () => { + const { + data: { privateKey } + } = await apiRequest.get<{ privateKey: string }>("/api/v1/user/private-key"); + + return privateKey; +}; diff --git a/frontend/src/pages/signupinvite.tsx b/frontend/src/pages/signupinvite.tsx index 17ac5b4ff..061e1bc70 100644 --- a/frontend/src/pages/signupinvite.tsx +++ b/frontend/src/pages/signupinvite.tsx @@ -149,6 +149,7 @@ export default function SignupInvite() { const { token: jwtToken } = await completeAccountSignupInvite({ email, + password, firstName, lastName, protectedKey, diff --git a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx index b23af1cef..fa443fe78 100644 --- a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx +++ b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx @@ -9,13 +9,12 @@ import Error from "@app/components/basic/Error"; import { createNotification } from "@app/components/notifications"; import attemptCliLoginMfa from "@app/components/utilities/attemptCliLoginMfa"; import attemptLoginMfa from "@app/components/utilities/attemptLoginMfa"; +import SecurityClient from "@app/components/utilities/SecurityClient"; import { Button } from "@app/components/v2"; -import { useUpdateUserAuthMethods } from "@app/hooks/api"; import { useSendMfaToken } from "@app/hooks/api/auth"; -import { useSelectOrganization } from "@app/hooks/api/auth/queries"; +import { useSelectOrganization, verifyMfaToken } from "@app/hooks/api/auth/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; -import { fetchUserDetails } from "@app/hooks/api/users/queries"; -import { AuthMethod } from "@app/hooks/api/users/types"; +import { fetchMyPrivateKey } from "@app/hooks/api/users/queries"; import { navigateUserToOrg, navigateUserToSelectOrg } from "../../Login.utils"; @@ -56,15 +55,62 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => { const { t } = useTranslation(); const sendMfaToken = useSendMfaToken(); - const { mutateAsync: updateUserAuthMethodsMutateAsync } = useUpdateUserAuthMethods(); const { mutateAsync: selectOrganization } = useSelectOrganization(); + // They don't have password + const handleLoginMfaOauth = async (callbackPort: string, organizationId?: string) => { + setIsLoading(true); + if (callbackPort) { + // attemptCliLogin + const { token } = await verifyMfaToken({ + email, + mfaCode + }); + // + // unset temporary (MFA) JWT token and set JWT token + SecurityClient.setMfaToken(""); + SecurityClient.setToken(token); + SecurityClient.setProviderAuthToken(""); + 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 { token: newJwtToken } = await selectOrganization({ organizationId }); + if (callbackPort) { + const cliUrl = `http://127.0.0.1:${callbackPort}/`; + const instance = axios.create(); + await instance.post(cliUrl, { + email, + privateKey, + JTWToken: newJwtToken + }); + } + await navigateUserToOrg(router, organizationId); + } + // case: no organization ID is present -- navigate to the select org page IF the user has any orgs + // if the user has no orgs, navigate to the create org page + else { + const userOrgs = await fetchOrganizations(); + + // case: user has orgs, so we navigate the user to select an org + if (userOrgs.length > 0) { + navigateUserToSelectOrg(router, callbackPort); + } + // case: no orgs found, so we navigate the user to create an org + // cli login will fail in this case + else { + await navigateUserToOrg(router); + } + } + } + }; + const handleLoginMfa = async () => { try { - let isLinkingRequired: undefined | boolean; let callbackPort: undefined | string; - let authMethod: undefined | AuthMethod; let organizationId: undefined | string; + let hasExchangedPrivateKey: undefined | boolean; const queryParams = new URLSearchParams(window.location.search); @@ -73,10 +119,9 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => { if (providerAuthToken) { const decodedToken = jwt_decode(providerAuthToken) as any; - isLinkingRequired = decodedToken.isLinkingRequired; callbackPort = decodedToken.callbackPort; - authMethod = decodedToken.authMethod; organizationId = decodedToken?.organizationId; + hasExchangedPrivateKey = decodedToken?.hasExchangedPrivateKey; } if (mfaCode.length !== 6) { @@ -87,6 +132,11 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => { return; } + if (hasExchangedPrivateKey) { + await handleLoginMfaOauth(callbackPort as string, organizationId); + return; + } + setIsLoading(true); if (callbackPort) { // attemptCliLogin @@ -145,14 +195,6 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => { type: "success" }); - if (isLinkingRequired && authMethod) { - const user = await fetchUserDetails(); - const newAuthMethods = [...user.authMethods, authMethod]; - await updateUserAuthMethodsMutateAsync({ - authMethods: newAuthMethods - }); - } - if (organizationId) { await navigateUserToOrg(router, organizationId); } else { diff --git a/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx b/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx index 2ba90528f..06438a2f8 100644 --- a/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx +++ b/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from "react"; +import { useEffect, useRef,useState } from "react"; import { useTranslation } from "react-i18next"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -10,11 +10,11 @@ import { createNotification } from "@app/components/notifications"; import attemptCliLogin from "@app/components/utilities/attemptCliLogin"; import attemptLogin from "@app/components/utilities/attemptLogin"; import { CAPTCHA_SITE_KEY } from "@app/components/utilities/config"; -import { Button, Input } from "@app/components/v2"; -import { useUpdateUserAuthMethods } from "@app/hooks/api"; -import { useSelectOrganization } from "@app/hooks/api/auth/queries"; +import SecurityClient from "@app/components/utilities/SecurityClient"; +import { Button, Input, Spinner } from "@app/components/v2"; +import { useOauthTokenExchange, useSelectOrganization } from "@app/hooks/api"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; -import { fetchUserDetails } from "@app/hooks/api/users/queries"; +import { fetchMyPrivateKey } from "@app/hooks/api/users/queries"; import { navigateUserToOrg, navigateUserToSelectOrg } from "../../Login.utils"; @@ -36,12 +36,94 @@ export const PasswordStep = ({ const [isLoading, setIsLoading] = useState(false); const { t } = useTranslation(); const router = useRouter(); - const { mutateAsync } = useUpdateUserAuthMethods(); const { mutateAsync: selectOrganization } = useSelectOrganization(); + const { mutateAsync: oauthTokenExchange } = useOauthTokenExchange(); - const { callbackPort, isLinkingRequired, authMethod, organizationId } = jwt_decode( - providerAuthToken - ) as any; + const { callbackPort, organizationId, hasExchangedPrivateKey } = + jwt_decode(providerAuthToken) as any; + + const handleExchange = async () => { + try { + setIsLoading(true); + const oauthLogin = await oauthTokenExchange({ + email, + providerAuthToken + }); + + // attemptCliLogin + if (oauthLogin.mfaEnabled) { + SecurityClient.setMfaToken(oauthLogin.token); + // case: login requires MFA step + setStep(2); + setIsLoading(false); + return; + } + const cliUrl = `http://127.0.0.1:${callbackPort}/`; + + // case: MFA is not enabled + + // unset provider auth token in case it was used + SecurityClient.setProviderAuthToken(""); + // 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 { token: newJwtToken } = await selectOrganization({ organizationId }); + if (callbackPort) { + console.log("organization id was present. new JWT token to be used in CLI:", newJwtToken); + const instance = axios.create(); + await instance.post(cliUrl, { + privateKey, + email, + JTWToken: newJwtToken + }); + } + + await navigateUserToOrg(router, organizationId); + } + // case: no organization ID is present -- navigate to the select org page IF the user has any orgs + // if the user has no orgs, navigate to the create org page + else { + const userOrgs = await fetchOrganizations(); + + // case: user has orgs, so we navigate the user to select an org + if (userOrgs.length > 0) { + navigateUserToSelectOrg(router, callbackPort); + } + // case: no orgs found, so we navigate the user to create an org + else { + await navigateUserToOrg(router); + } + } + } catch (err: any) { + setIsLoading(false); + console.error(err); + + if (err.response.data.error === "User Locked") { + createNotification({ + title: err.response.data.error, + text: err.response.data.message, + type: "error" + }); + return; + } + + createNotification({ + text: "Login unsuccessful. Double-check your master password and try again.", + type: "error" + }); + } + }; + + useEffect(() => { + if (hasExchangedPrivateKey) { + handleExchange(); + } + }, []); const [captchaToken, setCaptchaToken] = useState(""); const [shouldShowCaptcha, setShouldShowCaptcha] = useState(false); @@ -128,14 +210,6 @@ export const PasswordStep = ({ type: "success" }); - if (isLinkingRequired) { - const user = await fetchUserDetails(); - const newAuthMethods = [...user.authMethods, authMethod]; - await mutateAsync({ - authMethods: newAuthMethods - }); - } - // case: organization ID is present from the provider auth token -- navigate directly to the org if (organizationId) { await navigateUserToOrg(router, organizationId); @@ -183,20 +257,21 @@ export const PasswordStep = ({ setCaptchaToken(""); }; + if (hasExchangedPrivateKey) { + return ( +
+ +

Loading, please wait

+
+ ); + } + return (

- {isLinkingRequired ? "Link your account" : "What's your Infisical password?"} + What's your Infisical password?

- {isLinkingRequired && ( -
- - An existing account without this SSO authentication method enabled was found under the - same email. Login with your password to link the account. - -
- )}
diff --git a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx index 69168e0af..e718d9851 100644 --- a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx +++ b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx @@ -2,14 +2,10 @@ import crypto from "crypto"; import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { faInfoCircle, faXmark } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import jsrp from "jsrp"; import nacl from "tweetnacl"; import { encodeBase64 } from "tweetnacl-util"; -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 } from "@app/components/utilities/cryptography/crypto"; import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage"; @@ -32,17 +28,6 @@ type Props = { providerAuthToken?: string; }; -type Errors = { - tooShort?: string; - tooLong?: string; - noLetterChar?: string; - noNumOrSpecialChar?: string; - repeatedChar?: string; - escapeChar?: string; - lowEntropy?: string; - breached?: string; -}; - /** * This is the step of the sign up flow where people provife their name/surname and password * @param {object} obj @@ -69,12 +54,13 @@ export const UserInfoSSOStep = ({ const [organizationName, setOrganizationName] = useState(""); const [organizationNameError, setOrganizationNameError] = useState(false); const [attributionSource, setAttributionSource] = useState(""); - const [errors, setErrors] = useState({}); const [isLoading, setIsLoading] = useState(false); const { t } = useTranslation(); const { mutateAsync: selectOrganization } = useSelectOrganization(); useEffect(() => { + const randomPassword = crypto.randomBytes(32).toString("hex"); + setPassword(randomPassword); if (providerOrganizationName !== undefined) { setOrganizationName(providerOrganizationName); } @@ -98,11 +84,6 @@ export const UserInfoSSOStep = ({ setOrganizationNameError(false); } - errorCheck = await checkPassword({ - password, - setErrors - }); - if (!errorCheck) { // Generate a random pair of a public and a private key const pair = nacl.box.keyPair(); @@ -158,6 +139,7 @@ export const UserInfoSSOStep = ({ const response = await completeAccountSignup({ email: username, + password, firstName: name.split(" ")[0], lastName: name.split(" ").slice(1).join(" "), protectedKey, @@ -272,53 +254,6 @@ export const UserInfoSSOStep = ({ />
)} -
- { - setPassword(pass); - await checkPassword({ - password: pass, - setErrors - }); - }} - type="password" - value={password} - isRequired - error={Object.keys(errors).length > 0} - autoComplete="new-password" - id="new-password" - /> -
- - Infisical Password is used as part of the encryption mechanism so that even the - authentication provider is not able to access your secrets. -
- {Object.keys(errors).length > 0 && ( -
-
- {t("section.password.validate-base")} -
- {Object.keys(errors).map((key) => { - if (errors[key as keyof Errors]) { - return ( -
-
- -
-

{errors[key as keyof Errors]}

-
- ); - } - - return null; - })} -
- )} -