feat: api changes for srp handover

This commit is contained in:
=
2024-06-10 23:41:35 +05:30
parent a3a1c9d2e5
commit 777dfd5f58
16 changed files with 344 additions and 34 deletions

View File

@@ -0,0 +1,61 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
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<void> {
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");
});
}
}

View File

@@ -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<typeof UserEncryptionKeysSchema>;

View File

@@ -73,7 +73,13 @@ type TLdapConfigServiceFactoryDep = {
>;
userDAL: Pick<
TUserDALFactory,
"create" | "findOne" | "transaction" | "updateById" | "findUserEncKeyByUserIdsBatch" | "find"
| "create"
| "findOne"
| "transaction"
| "updateById"
| "findUserEncKeyByUserIdsBatch"
| "find"
| "findUserEncKeyByUserId"
>;
userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
@@ -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,

View File

@@ -41,7 +41,10 @@ import { TCreateSamlCfgDTO, TGetSamlCfgDTO, TSamlLoginDTO, TUpdateSamlCfgDTO } f
type TSamlConfigServiceFactoryDep = {
samlConfigDAL: Pick<TSamlConfigDALFactory, "create" | "findOne" | "update" | "findById">;
userDAL: Pick<TUserDALFactory, "create" | "findOne" | "transaction" | "updateById" | "findById">;
userDAL: Pick<
TUserDALFactory,
"create" | "findOne" | "transaction" | "updateById" | "findById" | "findUserEncKeyByUserId"
>;
userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">;
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

View File

@@ -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()),

View File

@@ -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;
};

View File

@@ -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;
}
});
};

View File

@@ -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",

View File

@@ -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) {

View File

@@ -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(),

View File

@@ -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) => {

View File

@@ -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,

View File

@@ -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;
};

View File

@@ -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(
{

View File

@@ -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;

View File

@@ -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
};
};