From 736f067178c827400aab041b311276786de7c5b4 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 11 Jun 2024 00:34:07 +0530 Subject: [PATCH] feat: srp handover for admin and minor bug fix in mfa --- backend/src/server/routes/v1/admin-router.ts | 1 + .../src/services/auth/auth-login-service.ts | 21 +++--- .../super-admin/super-admin-service.ts | 23 +++++- .../services/super-admin/super-admin-types.ts | 1 + frontend/src/hooks/api/admin/types.ts | 1 + .../Login/components/MFAStep/MFAStep.tsx | 75 +++++++++---------- .../src/views/admin/SignUpPage/SignUpPage.tsx | 1 + 7 files changed, 73 insertions(+), 50 deletions(-) diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index 572409d9b..97c3449d9 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -79,6 +79,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { schema: { body: z.object({ email: z.string().email().trim(), + password: z.string().trim(), firstName: z.string().trim(), lastName: z.string().trim().optional(), protectedKey: z.string().trim(), diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index 2fecb5857..8778e0a6c 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -570,20 +570,21 @@ export const authLoginServiceFactory = ({ ? decodedProviderToken.orgId : undefined; - const user = await userDAL.findUserEncKeyByUsername({ + const userEnc = 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" }); + if (!userEnc) throw new BadRequestError({ message: "Invalid token" }); + if (!userEnc.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); + if (userEnc.isMfaEnabled && userEnc.email) { + enforceUserLockStatus(Boolean(userEnc.isLocked), userEnc.temporaryLockDateEnd); const mfaToken = jwt.sign( { authMethod, authTokenType: AuthTokenType.MFA_TOKEN, - userId: user.userId + userId: userEnc.userId }, appCfg.AUTH_SECRET, { @@ -592,22 +593,22 @@ export const authLoginServiceFactory = ({ ); await sendUserMfaCode({ - userId: user.id, - email: user.email + userId: userEnc.userId, + email: userEnc.email }); return { isMfaEnabled: true, token: mfaToken } as const; } const token = await generateUserTokens({ - user: { ...user, id: user.userId }, + user: { ...userEnc, id: userEnc.userId }, ip, userAgent, authMethod, organizationId }); - return { token, isMfaEnabled: false, user } as const; + return { token, isMfaEnabled: false, user: userEnc } as const; }; /* diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index bec8f3f37..199f97bce 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -1,6 +1,10 @@ +import bcrypt from "bcrypt"; + import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas"; import { TKeyStoreFactory } from "@app/keystore/keystore"; 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 { TAuthLoginFactory } from "../auth/auth-login-service"; @@ -77,6 +81,7 @@ export const superAdminServiceFactory = ({ firstName, salt, email, + password, verifier, publicKey, protectedKey, @@ -92,6 +97,17 @@ export const superAdminServiceFactory = ({ const existingUser = await userDAL.findOne({ email }); if (existingUser) throw new BadRequestError({ name: "Admin sign up", message: "User already exist" }); + const privateKey = await getUserPrivateKey(password, { + salt, + protectedKey, + protectedKeyIV, + protectedKeyTag, + encryptedPrivateKey, + iv: encryptedPrivateKeyIV, + tag: encryptedPrivateKeyTag + }); + const hashedPassword = await bcrypt.hash(password, appCfg.BCRYPT_SALT_ROUND); + const { iv, tag, ciphertext, encoding } = infisicalSymmetricEncypt(privateKey); const userInfo = await userDAL.transaction(async (tx) => { const newUser = await userDAL.create( { @@ -119,7 +135,12 @@ export const superAdminServiceFactory = ({ iv: encryptedPrivateKeyIV, tag: encryptedPrivateKeyTag, verifier, - userId: newUser.id + userId: newUser.id, + password: hashedPassword, + serverEncryptedPrivateKey: ciphertext, + serverEncryptedPrivateKeyIV: iv, + serverEncryptedPrivateKeyTag: tag, + serverEncryptedPrivateKeyEncoding: encoding }, tx ); diff --git a/backend/src/services/super-admin/super-admin-types.ts b/backend/src/services/super-admin/super-admin-types.ts index e586946f2..e444c8843 100644 --- a/backend/src/services/super-admin/super-admin-types.ts +++ b/backend/src/services/super-admin/super-admin-types.ts @@ -1,5 +1,6 @@ export type TAdminSignUpDTO = { email: string; + password: string; publicKey: string; salt: string; lastName?: string; diff --git a/frontend/src/hooks/api/admin/types.ts b/frontend/src/hooks/api/admin/types.ts index 6a42e6ed0..8d5f59ede 100644 --- a/frontend/src/hooks/api/admin/types.ts +++ b/frontend/src/hooks/api/admin/types.ts @@ -10,6 +10,7 @@ export type TServerConfig = { export type TCreateAdminUserDTO = { email: string; + password: string; firstName: string; lastName?: string; protectedKey: string; diff --git a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx index fa443fe78..5e613008d 100644 --- a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx +++ b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx @@ -60,48 +60,45 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => { // 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); + 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: 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 + }); } - // 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(); + 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); - } + // 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); } } }; diff --git a/frontend/src/views/admin/SignUpPage/SignUpPage.tsx b/frontend/src/views/admin/SignUpPage/SignUpPage.tsx index bdbc8ac86..2db9041f8 100644 --- a/frontend/src/views/admin/SignUpPage/SignUpPage.tsx +++ b/frontend/src/views/admin/SignUpPage/SignUpPage.tsx @@ -73,6 +73,7 @@ export const SignUpPage = () => { const { privateKey, ...userPass } = await generateUserPassKey(email, password); const res = await createAdminUser({ email, + password, firstName, lastName, ...userPass