mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 15:27:27 +00:00
feat: srp handover for admin and minor bug fix in mfa
This commit is contained in:
@@ -79,6 +79,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
email: z.string().email().trim(),
|
email: z.string().email().trim(),
|
||||||
|
password: z.string().trim(),
|
||||||
firstName: z.string().trim(),
|
firstName: z.string().trim(),
|
||||||
lastName: z.string().trim().optional(),
|
lastName: z.string().trim().optional(),
|
||||||
protectedKey: z.string().trim(),
|
protectedKey: z.string().trim(),
|
||||||
|
|||||||
@@ -570,20 +570,21 @@ export const authLoginServiceFactory = ({
|
|||||||
? decodedProviderToken.orgId
|
? decodedProviderToken.orgId
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const user = await userDAL.findUserEncKeyByUsername({
|
const userEnc = await userDAL.findUserEncKeyByUsername({
|
||||||
username: email
|
username: email
|
||||||
});
|
});
|
||||||
if (!user) throw new BadRequestError({ message: "Invalid token" });
|
if (!userEnc) throw new BadRequestError({ message: "Invalid token" });
|
||||||
if (!user.serverEncryptedPrivateKey) throw new BadRequestError({ message: "Private key handoff needs to be done" });
|
if (!userEnc.serverEncryptedPrivateKey)
|
||||||
|
throw new BadRequestError({ message: "Private key handoff needs to be done" });
|
||||||
// send multi factor auth token if they it enabled
|
// send multi factor auth token if they it enabled
|
||||||
if (user.isMfaEnabled && user.email) {
|
if (userEnc.isMfaEnabled && userEnc.email) {
|
||||||
enforceUserLockStatus(Boolean(user.isLocked), user.temporaryLockDateEnd);
|
enforceUserLockStatus(Boolean(userEnc.isLocked), userEnc.temporaryLockDateEnd);
|
||||||
|
|
||||||
const mfaToken = jwt.sign(
|
const mfaToken = jwt.sign(
|
||||||
{
|
{
|
||||||
authMethod,
|
authMethod,
|
||||||
authTokenType: AuthTokenType.MFA_TOKEN,
|
authTokenType: AuthTokenType.MFA_TOKEN,
|
||||||
userId: user.userId
|
userId: userEnc.userId
|
||||||
},
|
},
|
||||||
appCfg.AUTH_SECRET,
|
appCfg.AUTH_SECRET,
|
||||||
{
|
{
|
||||||
@@ -592,22 +593,22 @@ export const authLoginServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
await sendUserMfaCode({
|
await sendUserMfaCode({
|
||||||
userId: user.id,
|
userId: userEnc.userId,
|
||||||
email: user.email
|
email: userEnc.email
|
||||||
});
|
});
|
||||||
|
|
||||||
return { isMfaEnabled: true, token: mfaToken } as const;
|
return { isMfaEnabled: true, token: mfaToken } as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = await generateUserTokens({
|
const token = await generateUserTokens({
|
||||||
user: { ...user, id: user.userId },
|
user: { ...userEnc, id: userEnc.userId },
|
||||||
ip,
|
ip,
|
||||||
userAgent,
|
userAgent,
|
||||||
authMethod,
|
authMethod,
|
||||||
organizationId
|
organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
return { token, isMfaEnabled: false, user } as const;
|
return { token, isMfaEnabled: false, user: userEnc } as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
import bcrypt from "bcrypt";
|
||||||
|
|
||||||
import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
|
import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
|
||||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
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 { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
import { TAuthLoginFactory } from "../auth/auth-login-service";
|
import { TAuthLoginFactory } from "../auth/auth-login-service";
|
||||||
@@ -77,6 +81,7 @@ export const superAdminServiceFactory = ({
|
|||||||
firstName,
|
firstName,
|
||||||
salt,
|
salt,
|
||||||
email,
|
email,
|
||||||
|
password,
|
||||||
verifier,
|
verifier,
|
||||||
publicKey,
|
publicKey,
|
||||||
protectedKey,
|
protectedKey,
|
||||||
@@ -92,6 +97,17 @@ export const superAdminServiceFactory = ({
|
|||||||
const existingUser = await userDAL.findOne({ email });
|
const existingUser = await userDAL.findOne({ email });
|
||||||
if (existingUser) throw new BadRequestError({ name: "Admin sign up", message: "User already exist" });
|
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 userInfo = await userDAL.transaction(async (tx) => {
|
||||||
const newUser = await userDAL.create(
|
const newUser = await userDAL.create(
|
||||||
{
|
{
|
||||||
@@ -119,7 +135,12 @@ export const superAdminServiceFactory = ({
|
|||||||
iv: encryptedPrivateKeyIV,
|
iv: encryptedPrivateKeyIV,
|
||||||
tag: encryptedPrivateKeyTag,
|
tag: encryptedPrivateKeyTag,
|
||||||
verifier,
|
verifier,
|
||||||
userId: newUser.id
|
userId: newUser.id,
|
||||||
|
password: hashedPassword,
|
||||||
|
serverEncryptedPrivateKey: ciphertext,
|
||||||
|
serverEncryptedPrivateKeyIV: iv,
|
||||||
|
serverEncryptedPrivateKeyTag: tag,
|
||||||
|
serverEncryptedPrivateKeyEncoding: encoding
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export type TAdminSignUpDTO = {
|
export type TAdminSignUpDTO = {
|
||||||
email: string;
|
email: string;
|
||||||
|
password: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
salt: string;
|
salt: string;
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export type TServerConfig = {
|
|||||||
|
|
||||||
export type TCreateAdminUserDTO = {
|
export type TCreateAdminUserDTO = {
|
||||||
email: string;
|
email: string;
|
||||||
|
password: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
protectedKey: string;
|
protectedKey: string;
|
||||||
|
|||||||
@@ -60,48 +60,45 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => {
|
|||||||
// They don't have password
|
// They don't have password
|
||||||
const handleLoginMfaOauth = async (callbackPort: string, organizationId?: string) => {
|
const handleLoginMfaOauth = async (callbackPort: string, organizationId?: string) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
if (callbackPort) {
|
const { token } = await verifyMfaToken({
|
||||||
// attemptCliLogin
|
email,
|
||||||
const { token } = await verifyMfaToken({
|
mfaCode
|
||||||
email,
|
});
|
||||||
mfaCode
|
//
|
||||||
});
|
// unset temporary (MFA) JWT token and set JWT token
|
||||||
//
|
SecurityClient.setMfaToken("");
|
||||||
// unset temporary (MFA) JWT token and set JWT token
|
SecurityClient.setToken(token);
|
||||||
SecurityClient.setMfaToken("");
|
SecurityClient.setProviderAuthToken("");
|
||||||
SecurityClient.setToken(token);
|
const privateKey = await fetchMyPrivateKey();
|
||||||
SecurityClient.setProviderAuthToken("");
|
localStorage.setItem("PRIVATE_KEY", privateKey);
|
||||||
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
|
// 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) {
|
if (organizationId) {
|
||||||
const { token: newJwtToken } = await selectOrganization({ organizationId });
|
const { token: newJwtToken } = await selectOrganization({ organizationId });
|
||||||
if (callbackPort) {
|
if (callbackPort) {
|
||||||
const cliUrl = `http://127.0.0.1:${callbackPort}/`;
|
const cliUrl = `http://127.0.0.1:${callbackPort}/`;
|
||||||
const instance = axios.create();
|
const instance = axios.create();
|
||||||
await instance.post(cliUrl, {
|
await instance.post(cliUrl, {
|
||||||
email,
|
email,
|
||||||
privateKey,
|
privateKey,
|
||||||
JTWToken: newJwtToken
|
JTWToken: newJwtToken
|
||||||
});
|
});
|
||||||
}
|
|
||||||
await navigateUserToOrg(router, organizationId);
|
|
||||||
}
|
}
|
||||||
// case: no organization ID is present -- navigate to the select org page IF the user has any orgs
|
await navigateUserToOrg(router, organizationId);
|
||||||
// if the user has no orgs, navigate to the create org page
|
}
|
||||||
else {
|
// case: no organization ID is present -- navigate to the select org page IF the user has any orgs
|
||||||
const userOrgs = await fetchOrganizations();
|
// 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
|
// case: user has orgs, so we navigate the user to select an org
|
||||||
if (userOrgs.length > 0) {
|
if (userOrgs.length > 0) {
|
||||||
navigateUserToSelectOrg(router, callbackPort);
|
navigateUserToSelectOrg(router, callbackPort);
|
||||||
}
|
}
|
||||||
// case: no orgs found, so we navigate the user to create an org
|
// case: no orgs found, so we navigate the user to create an org
|
||||||
// cli login will fail in this case
|
// cli login will fail in this case
|
||||||
else {
|
else {
|
||||||
await navigateUserToOrg(router);
|
await navigateUserToOrg(router);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export const SignUpPage = () => {
|
|||||||
const { privateKey, ...userPass } = await generateUserPassKey(email, password);
|
const { privateKey, ...userPass } = await generateUserPassKey(email, password);
|
||||||
const res = await createAdminUser({
|
const res = await createAdminUser({
|
||||||
email,
|
email,
|
||||||
|
password,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
...userPass
|
...userPass
|
||||||
|
|||||||
Reference in New Issue
Block a user