requested changes

This commit is contained in:
Daniel Hougaard
2024-10-12 07:40:14 +04:00
parent 162005d72f
commit ad2f19658b
15 changed files with 335 additions and 203 deletions

View File

@@ -1,15 +1,9 @@
import { ForbiddenError, subject } from "@casl/ability";
import {
TableName,
TSecretSnapshotFolders,
TSecretSnapshotSecretsV2,
TSecretTagJunctionInsert,
TSecretV2TagJunctionInsert
} from "@app/db/schemas";
import { TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
import { InternalServerError, NotFoundError } from "@app/lib/errors";
import { chunkArray, groupBy } from "@app/lib/fn";
import { groupBy } from "@app/lib/fn";
import { logger } from "@app/lib/logger";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
@@ -247,36 +241,23 @@ export const secretSnapshotServiceFactory = ({
tx
);
const chunkedSnapshotSecrets = chunkArray(secretVersions, 2500);
const chunkedSnapshotFolders = chunkArray(folderVersions, 2500);
const snapshotSecrets: TSecretSnapshotSecretsV2[] = [];
const snapshotFolders: TSecretSnapshotFolders[] = [];
const snapshotSecrets = await snapshotSecretV2BridgeDAL.batchInsert(
secretVersions.map(({ id }) => ({
secretVersionId: id,
envId: folder.environment.envId,
snapshotId: newSnapshot.id
})),
tx
);
for await (const chunk of chunkedSnapshotSecrets) {
const result = await snapshotSecretV2BridgeDAL.insertMany(
chunk.map(({ id }) => ({
secretVersionId: id,
envId: folder.environment.envId,
snapshotId: newSnapshot.id
})),
tx
);
snapshotSecrets.push(...result);
}
for await (const chunk of chunkedSnapshotFolders) {
const result = await snapshotFolderDAL.insertMany(
chunk.map(({ id }) => ({
folderVersionId: id,
envId: folder.environment.envId,
snapshotId: newSnapshot.id
})),
tx
);
snapshotFolders.push(...result);
}
const snapshotFolders = await snapshotFolderDAL.batchInsert(
folderVersions.map(({ id }) => ({
folderVersionId: id,
envId: folder.environment.envId,
snapshotId: newSnapshot.id
})),
tx
);
return { ...newSnapshot, secrets: snapshotSecrets, folder: snapshotFolders };
});

View File

@@ -1206,8 +1206,16 @@ export const registerRoutes = async (
const externalMigrationQueue = externalMigrationQueueFactory({
orgService,
projectEnvService,
projectDAL,
projectService,
smtpService,
kmsService,
projectEnvDAL,
secretVersionDAL: secretVersionV2BridgeDAL,
secretTagDAL,
secretVersionTagDAL: secretVersionTagV2BridgeDAL,
folderDAL,
secretDAL: secretV2BridgeDAL,
queueService,
secretV2BridgeService
});

View File

@@ -4,19 +4,39 @@ import sjcl from "sjcl";
import tweetnacl from "tweetnacl";
import tweetnaclUtil from "tweetnacl-util";
import { OrgMembershipRole, ProjectMembershipRole } from "@app/db/schemas";
import { BadRequestError } from "@app/lib/errors";
import { OrgMembershipRole, ProjectMembershipRole, SecretType } from "@app/db/schemas";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { chunkArray } from "@app/lib/fn";
import { logger } from "@app/lib/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TKmsServiceFactory } from "../kms/kms-service";
import { KmsDataKey } from "../kms/kms-types";
import { TOrgServiceFactory } from "../org/org-service";
import { TProjectDALFactory } from "../project/project-dal";
import { TProjectServiceFactory } from "../project/project-service";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
import { fnSecretBulkInsert, getAllNestedSecretReferences } from "../secret-v2-bridge/secret-v2-bridge-fns";
import type { TSecretV2BridgeServiceFactory } from "../secret-v2-bridge/secret-v2-bridge-service";
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
import { InfisicalImportData, TEnvKeyExportJSON, TImportInfisicalDataCreate } from "./external-migration-types";
export type TImportDataIntoInfisicalDTO = {
projectDAL: Pick<TProjectDALFactory, "transaction">;
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findLastEnvPosition" | "create" | "findOne">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys">;
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath">;
projectService: Pick<TProjectServiceFactory, "createProject">;
orgService: Pick<TOrgServiceFactory, "inviteUserToOrganization">;
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
@@ -91,9 +111,15 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
export const importDataIntoInfisicalFn = async ({
projectService,
projectEnvDAL,
projectDAL,
orgService,
projectEnvService,
secretV2BridgeService,
secretDAL,
kmsService,
secretVersionDAL,
secretTagDAL,
secretVersionTagDAL,
folderDAL,
input: { data, actor, actorId, actorOrgId, actorAuthMethod }
}: TImportDataIntoInfisicalDTO) => {
// Import data to infisical
@@ -104,113 +130,152 @@ export const importDataIntoInfisicalFn = async ({
const originalToNewProjectId = new Map<string, string>();
const originalToNewEnvironmentId = new Map<string, string>();
for await (const project of data.projects) {
const newProject = await projectService
.createProject({
actor,
actorId,
actorOrgId,
actorAuthMethod,
workspaceName: project.name,
createDefaultEnvs: false
})
.catch(() => {
throw new BadRequestError({ message: `Failed to import to project [name:${project.name}` });
});
originalToNewProjectId.set(project.id, newProject.id);
}
// Invite user importing projects
const invites = await orgService.inviteUserToOrganization({
actorAuthMethod,
actorId,
actorOrgId,
actor,
inviteeEmails: [],
orgId: actorOrgId,
organizationRoleSlug: OrgMembershipRole.NoAccess,
projects: Array.from(originalToNewProjectId.values()).map((project) => ({
id: project,
projectRoleSlug: [ProjectMembershipRole.Member]
}))
});
if (!invites) {
throw new BadRequestError({ message: `Failed to invite user to projects: [userId:${actorId}]` });
}
// Import environments
if (data.environments) {
for await (const environment of data.environments) {
try {
const newEnvironment = await projectEnvService.createEnvironment({
await projectDAL.transaction(async (tx) => {
for await (const project of data.projects) {
const newProject = await projectService
.createProject({
actor,
actorId,
actorOrgId,
actorAuthMethod,
name: environment.name,
projectId: originalToNewProjectId.get(environment.projectId)!,
slug: slugify(`${environment.name}-${alphaNumericNanoId(4)}`)
workspaceName: project.name,
createDefaultEnvs: false,
tx
})
.catch((e) => {
logger.error(e, `Failed to import to project [name:${project.name}]`);
throw new BadRequestError({ message: `Failed to import to project [name:${project.name}]` });
});
if (!newEnvironment) {
logger.error(`Failed to import environment: [name:${environment.name}]`);
originalToNewProjectId.set(project.id, newProject.id);
}
// Invite user importing projects
const invites = await orgService.inviteUserToOrganization({
verifyPermissions: false,
actorAuthMethod,
actorId,
actorOrgId,
actor,
inviteeEmails: [],
orgId: actorOrgId,
organizationRoleSlug: OrgMembershipRole.NoAccess,
projects: Array.from(originalToNewProjectId.values()).map((project) => ({
id: project,
projectRoleSlug: [ProjectMembershipRole.Member]
})),
tx
});
if (!invites) {
throw new BadRequestError({ message: `Failed to invite user to projects: [userId:${actorId}]` });
}
// Import environments
if (data.environments) {
for await (const environment of data.environments) {
const projectId = originalToNewProjectId.get(environment.projectId)!;
const slug = slugify(`${environment.name}-${alphaNumericNanoId(4)}`);
const existingEnv = await projectEnvDAL.findOne({ projectId, slug }, tx);
if (existingEnv) {
throw new BadRequestError({
message: `Failed to import environment: [name:${environment.name}]`
message: `Environment with slug '${slug}' already exist`,
name: "CreateEnvironment"
});
}
originalToNewEnvironmentId.set(environment.id, newEnvironment.slug);
} catch (error) {
throw new BadRequestError({
message: `Failed to import environment: ${environment.name}]`,
name: "EnvKeyMigrationImportEnvironment"
const lastPos = await projectEnvDAL.findLastEnvPosition(projectId, tx);
const doc = await projectEnvDAL.create({ slug, name: environment.name, projectId, position: lastPos + 1 }, tx);
await folderDAL.create({ name: "root", parentId: null, envId: doc.id, version: 1 }, tx);
originalToNewEnvironmentId.set(environment.id, doc.slug);
}
}
if (data.secrets && data.secrets.length > 0) {
const mappedToEnvironmentId = new Map<
string,
{
secretKey: string;
secretValue: string;
}[]
>();
for (const secret of data.secrets) {
if (!mappedToEnvironmentId.has(secret.environmentId)) {
mappedToEnvironmentId.set(secret.environmentId, []);
}
mappedToEnvironmentId.get(secret.environmentId)!.push({
secretKey: secret.name,
secretValue: secret.value || ""
});
}
}
}
if (data.secrets && data.secrets.length > 0) {
const mappedToEnvironmentId = new Map<
string,
{
secretKey: string;
secretValue: string;
}[]
>();
// for each of the mappedEnvironmentId
for await (const [envId, secrets] of mappedToEnvironmentId) {
const environment = data.environments.find((env) => env.id === envId);
const projectId = originalToNewProjectId.get(environment?.projectId as string)!;
for (const secret of data.secrets) {
if (!mappedToEnvironmentId.has(secret.environmentId)) {
mappedToEnvironmentId.set(secret.environmentId, []);
}
mappedToEnvironmentId.get(secret.environmentId)!.push({
secretKey: secret.name,
secretValue: secret.value || ""
});
}
if (!projectId) {
throw new BadRequestError({ message: `Failed to import secret, project not found` });
}
// for each of the mappedEnvironmentId
for await (const [envId, secrets] of mappedToEnvironmentId) {
const environment = data.environments.find((env) => env.id === envId);
const projectId = environment?.projectId;
const { encryptor: secretManagerEncrypt } = await kmsService.createCipherPairWithDataKey(
{
type: KmsDataKey.SecretManager,
projectId
},
tx
);
if (!projectId) {
throw new BadRequestError({ message: `Failed to import secret, project not found` });
}
const envSlug = originalToNewEnvironmentId.get(envId)!;
const folder = await folderDAL.findBySecretPath(projectId, envSlug, "/", tx);
if (!folder)
throw new NotFoundError({
message: `Folder not found for the given environment slug (${envSlug}) & secret path (/)`,
name: "Create secret"
});
const secretBatches = chunkArray(secrets, 2500);
const secretsByKeys = await secretDAL.findBySecretKeys(
folder.id,
secrets.map((el) => ({
key: el.secretKey,
type: SecretType.Shared
})),
tx
);
if (secretsByKeys.length) {
throw new BadRequestError({
message: `Secret already exist: ${secretsByKeys.map((el) => el.key).join(",")}`
});
}
for await (const secretBatch of secretBatches) {
await secretV2BridgeService.createManySecret({
actorId,
actor,
actorOrgId,
environment: originalToNewEnvironmentId.get(envId)!,
actorAuthMethod,
projectId: originalToNewProjectId.get(projectId)!,
secretPath: "/",
secrets: secretBatch
});
const secretBatches = chunkArray(secrets, 2500);
for await (const secretBatch of secretBatches) {
await fnSecretBulkInsert({
inputSecrets: secretBatch.map((el) => {
const references = getAllNestedSecretReferences(el.secretValue);
return {
version: 1,
encryptedValue: el.secretValue
? secretManagerEncrypt({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob
: undefined,
key: el.secretKey,
references,
type: SecretType.Shared
};
}),
folderId: folder.id,
secretDAL,
secretVersionDAL,
secretTagDAL,
secretVersionTagDAL,
tx
});
}
}
}
}
});
};

View File

@@ -3,22 +3,40 @@ import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
import { TKmsServiceFactory } from "../kms/kms-service";
import { TOrgServiceFactory } from "../org/org-service";
import { TProjectDALFactory } from "../project/project-dal";
import { TProjectServiceFactory } from "../project/project-service";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
import { TSecretV2BridgeServiceFactory } from "../secret-v2-bridge/secret-v2-bridge-service";
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
import { importDataIntoInfisicalFn } from "./external-migration-fns";
import { ExternalPlatforms, TImportInfisicalDataCreate } from "./external-migration-types";
export type TExternalMigrationQueueFactoryDep = {
smtpService: TSmtpService;
queueService: TQueueServiceFactory;
projectDAL: Pick<TProjectDALFactory, "transaction">;
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findLastEnvPosition" | "create" | "findOne">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys">;
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath">;
projectService: Pick<TProjectServiceFactory, "createProject">;
orgService: Pick<TOrgServiceFactory, "inviteUserToOrganization">;
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
secretV2BridgeService: Pick<TSecretV2BridgeServiceFactory, "createManySecret">;
smtpService: TSmtpService;
queueService: TQueueServiceFactory;
};
export type TExternalMigrationQueueFactory = ReturnType<typeof externalMigrationQueueFactory>;
@@ -28,8 +46,16 @@ export const externalMigrationQueueFactory = ({
projectService,
orgService,
smtpService,
projectDAL,
projectEnvService,
secretV2BridgeService
secretV2BridgeService,
kmsService,
projectEnvDAL,
secretDAL,
secretVersionDAL,
secretTagDAL,
secretVersionTagDAL,
folderDAL
}: TExternalMigrationQueueFactoryDep) => {
const startImport = async (dto: {
actorEmail: string;
@@ -76,6 +102,14 @@ export const externalMigrationQueueFactory = ({
await importDataIntoInfisicalFn({
input: decryptedJson,
projectDAL,
projectEnvDAL,
secretDAL,
secretVersionDAL,
secretTagDAL,
secretVersionTagDAL,
folderDAL,
kmsService,
projectService,
orgService,
projectEnvService,

View File

@@ -160,8 +160,8 @@ export const kmsServiceFactory = ({
* In mean time the rest of the request will wait until creation is finished followed by getting the created on
* In real time this would be milliseconds
*/
const getOrgKmsKeyId = async (orgId: string) => {
let org = await orgDAL.findById(orgId);
const getOrgKmsKeyId = async (orgId: string, trx?: Knex) => {
let org = await orgDAL.findById(orgId, trx);
if (!org) {
throw new NotFoundError({ message: "Org not found" });
@@ -180,9 +180,9 @@ export const kmsServiceFactory = ({
waitingCb: () => logger.info("KMS. Waiting for org key to be created")
});
org = await orgDAL.findById(orgId);
org = await orgDAL.findById(orgId, trx);
} else {
const keyId = await orgDAL.transaction(async (tx) => {
const keyId = await (trx || orgDAL).transaction(async (tx) => {
org = await orgDAL.findById(orgId, tx);
if (org.kmsDefaultKeyId) {
return org.kmsDefaultKeyId;
@@ -240,11 +240,12 @@ export const kmsServiceFactory = ({
const decryptWithKmsKey = async ({
kmsId,
depth = 0
}: Omit<TDecryptWithKmsDTO, "cipherTextBlob"> & { depth?: number }) => {
depth = 0,
tx
}: Omit<TDecryptWithKmsDTO, "cipherTextBlob"> & { depth?: number; tx?: Knex }) => {
if (depth > 2) throw new BadRequestError({ message: "KMS depth max limit" });
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId);
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId, tx);
if (!kmsDoc) {
throw new NotFoundError({ message: "KMS ID not found" });
}
@@ -261,7 +262,8 @@ export const kmsServiceFactory = ({
// we put a limit of depth to avoid too many cycles
const orgKmsDecryptor = await decryptWithKmsKey({
kmsId: kmsDoc.orgKms.id,
depth: depth + 1
depth: depth + 1,
tx
});
const orgKmsDataKey = await orgKmsDecryptor({
@@ -375,9 +377,9 @@ export const kmsServiceFactory = ({
};
};
const $getOrgKmsDataKey = async (orgId: string) => {
const kmsKeyId = await getOrgKmsKeyId(orgId);
let org = await orgDAL.findById(orgId);
const $getOrgKmsDataKey = async (orgId: string, trx?: Knex) => {
const kmsKeyId = await getOrgKmsKeyId(orgId, trx);
let org = await orgDAL.findById(orgId, trx);
if (!org) {
throw new NotFoundError({ message: "Org not found" });
@@ -396,9 +398,9 @@ export const kmsServiceFactory = ({
waitingCb: () => logger.info("KMS. Waiting for org data key to be created")
});
org = await orgDAL.findById(orgId);
org = await orgDAL.findById(orgId, trx);
} else {
const orgDataKey = await orgDAL.transaction(async (tx) => {
const orgDataKey = await (trx || orgDAL).transaction(async (tx) => {
org = await orgDAL.findById(orgId, tx);
if (org.kmsEncryptedDataKey) {
return;
@@ -455,8 +457,8 @@ export const kmsServiceFactory = ({
});
};
const getProjectSecretManagerKmsKeyId = async (projectId: string) => {
let project = await projectDAL.findById(projectId);
const getProjectSecretManagerKmsKeyId = async (projectId: string, trx?: Knex) => {
let project = await projectDAL.findById(projectId, trx);
if (!project) {
throw new NotFoundError({ message: "Project not found" });
}
@@ -477,7 +479,7 @@ export const kmsServiceFactory = ({
project = await projectDAL.findById(projectId);
} else {
const kmsKeyId = await projectDAL.transaction(async (tx) => {
const kmsKeyId = await (trx || projectDAL).transaction(async (tx) => {
project = await projectDAL.findById(projectId, tx);
if (project.kmsSecretManagerKeyId) {
return project.kmsSecretManagerKeyId;
@@ -520,9 +522,9 @@ export const kmsServiceFactory = ({
return project.kmsSecretManagerKeyId;
};
const $getProjectSecretManagerKmsDataKey = async (projectId: string) => {
const kmsKeyId = await getProjectSecretManagerKmsKeyId(projectId);
let project = await projectDAL.findById(projectId);
const $getProjectSecretManagerKmsDataKey = async (projectId: string, trx?: Knex) => {
const kmsKeyId = await getProjectSecretManagerKmsKeyId(projectId, trx);
let project = await projectDAL.findById(projectId, trx);
if (!project.kmsSecretManagerEncryptedDataKey) {
const lock = await keyStore
@@ -538,18 +540,21 @@ export const kmsServiceFactory = ({
delay: 500
});
project = await projectDAL.findById(projectId);
project = await projectDAL.findById(projectId, trx);
} else {
const projectDataKey = await projectDAL.transaction(async (tx) => {
const projectDataKey = await (trx || projectDAL).transaction(async (tx) => {
project = await projectDAL.findById(projectId, tx);
if (project.kmsSecretManagerEncryptedDataKey) {
return;
}
const dataKey = randomSecureBytes();
const kmsEncryptor = await encryptWithKmsKey({
kmsId: kmsKeyId
});
const kmsEncryptor = await encryptWithKmsKey(
{
kmsId: kmsKeyId
},
tx
);
const { cipherTextBlob } = await kmsEncryptor({
plainText: dataKey
@@ -585,7 +590,8 @@ export const kmsServiceFactory = ({
}
const kmsDecryptor = await decryptWithKmsKey({
kmsId: kmsKeyId
kmsId: kmsKeyId,
tx: trx
});
return kmsDecryptor({
@@ -593,13 +599,13 @@ export const kmsServiceFactory = ({
});
};
const $getDataKey = async (dto: TEncryptWithKmsDataKeyDTO) => {
const $getDataKey = async (dto: TEncryptWithKmsDataKeyDTO, trx?: Knex) => {
switch (dto.type) {
case KmsDataKey.SecretManager: {
return $getProjectSecretManagerKmsDataKey(dto.projectId);
return $getProjectSecretManagerKmsDataKey(dto.projectId, trx);
}
default: {
return $getOrgKmsDataKey(dto.orgId);
return $getOrgKmsDataKey(dto.orgId, trx);
}
}
};
@@ -607,8 +613,9 @@ export const kmsServiceFactory = ({
// by keeping the decrypted data key in inner scope
// none of the entities outside can interact directly or expose the data key
// NOTICE: If changing here update migrations/utils/kms
const createCipherPairWithDataKey = async (encryptionContext: TEncryptWithKmsDataKeyDTO) => {
const dataKey = await $getDataKey(encryptionContext);
const createCipherPairWithDataKey = async (encryptionContext: TEncryptWithKmsDataKeyDTO, trx?: Knex) => {
const dataKey = await $getDataKey(encryptionContext, trx);
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
return {

View File

@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ForbiddenError, MongoAbility } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import { MongoQuery } from "@ucast/mongo2js";
import crypto from "crypto";
import jwt from "jsonwebtoken";
import { Knex } from "knex";
@@ -19,7 +20,11 @@ import { TProjects } from "@app/db/schemas/projects";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
OrgPermissionActions,
OrgPermissionSet,
OrgPermissionSubjects
} from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
@@ -487,11 +492,17 @@ export const orgServiceFactory = ({
organizationRoleSlug,
projects: invitedProjects,
actorAuthMethod,
actorOrgId
actorOrgId,
tx: trx,
verifyPermissions = true
}: TInviteUserToOrgDTO) => {
const appCfg = getConfig();
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
let permission: MongoAbility<OrgPermissionSet, MongoQuery> | undefined;
if (verifyPermissions) {
permission = (await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId))
.permission;
}
const org = await orgDAL.findOrgById(orgId);
@@ -512,12 +523,15 @@ export const orgServiceFactory = ({
}
const projectsToInvite = invitedProjects?.length
? await projectDAL.find({
orgId,
$in: {
id: invitedProjects?.map(({ id }) => id)
}
})
? await projectDAL.find(
{
orgId,
$in: {
id: invitedProjects?.map(({ id }) => id)
}
},
{ tx: trx }
)
: [];
if (projectsToInvite.length !== invitedProjects?.length) {
throw new ForbiddenRequestError({
@@ -534,7 +548,7 @@ export const orgServiceFactory = ({
const mailsForOrgInvitation: { email: string; userId: string; firstName: string; lastName: string }[] = [];
const mailsForProjectInvitation: { email: string[]; projectName: string }[] = [];
const newProjectMemberships: TProjectMemberships[] = [];
await orgDAL.transaction(async (tx) => {
await (trx || orgDAL).transaction(async (tx) => {
const users: Pick<TUsers, "id" | "firstName" | "lastName" | "email" | "username">[] = [];
for await (const inviteeEmail of inviteeEmails) {
@@ -620,8 +634,16 @@ export const orgServiceFactory = ({
});
}
// as its used by project invite also
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
if (verifyPermissions) {
if (!permission) {
throw new ForbiddenRequestError({
name: "InviteUser",
message: "Failed to invite user because no permission was found"
});
}
// as its used by project invite also
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
}
let roleId;
const orgRole = isCustomOrgRole ? OrgMembershipRole.Custom : organizationRoleSlug;
if (isCustomOrgRole) {
@@ -662,17 +684,19 @@ export const orgServiceFactory = ({
// if there exist no project membership we set is as given by the request
for await (const project of projectsToInvite) {
const projectId = project.id;
const { permission: projectPermission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(projectPermission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Member
);
if (verifyPermissions) {
const { permission: projectPermission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(projectPermission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Member
);
}
const existingMembers = await projectMembershipDAL.find(
{
projectId: project.id,

View File

@@ -1,3 +1,5 @@
import { Knex } from "knex";
import { TOrgPermission } from "@app/lib/types";
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
@@ -26,18 +28,15 @@ export type TDeleteOrgMembershipDTO = {
};
export type TInviteUserToOrgDTO = {
actorId: string;
actor: ActorType;
orgId: string;
actorOrgId: string | undefined;
actorAuthMethod: ActorAuthMethod;
inviteeEmails: string[];
organizationRoleSlug: string;
tx?: Knex;
verifyPermissions?: boolean;
projects?: {
id: string;
projectRoleSlug?: string[];
}[];
};
} & TOrgPermission;
export type TVerifyUserToOrgDTO = {
email: string;

View File

@@ -39,7 +39,8 @@ export const projectEnvServiceFactory = ({
actorAuthMethod,
position,
name,
slug
slug,
tx: trx
}: TCreateEnvDTO) => {
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -83,7 +84,7 @@ export const projectEnvServiceFactory = ({
});
}
const env = await projectEnvDAL.transaction(async (tx) => {
const env = await (trx || projectEnvDAL).transaction(async (tx) => {
if (position !== undefined) {
// Check if there's an environment at the specified position
const existingEnvWithPosition = await projectEnvDAL.findOne({ projectId, position }, tx);

View File

@@ -1,9 +1,12 @@
import { Knex } from "knex";
import { TProjectPermission } from "@app/lib/types";
export type TCreateEnvDTO = {
name: string;
slug: string;
position?: number;
tx?: Knex;
} & TProjectPermission;
export type TUpdateEnvDTO = {

View File

@@ -147,6 +147,7 @@ export const projectServiceFactory = ({
workspaceName,
slug: projectSlug,
kmsKeyId,
tx: trx,
createDefaultEnvs = true
}: TCreateProjectDTO) => {
const organization = await orgDAL.findOne({ id: actorOrgId });
@@ -169,7 +170,7 @@ export const projectServiceFactory = ({
});
}
const results = await projectDAL.transaction(async (tx) => {
const results = await (trx || projectDAL).transaction(async (tx) => {
const ghostUser = await orgService.addGhostUser(organization.id, tx);
if (kmsKeyId) {

View File

@@ -1,3 +1,5 @@
import { Knex } from "knex";
import { TProjectKeys } from "@app/db/schemas";
import { TProjectPermission } from "@app/lib/types";
@@ -30,6 +32,7 @@ export type TCreateProjectDTO = {
slug?: string;
kmsKeyId?: string;
createDefaultEnvs?: boolean;
tx?: Knex;
};
export type TDeleteProjectBySlugDTO = {

View File

@@ -82,7 +82,10 @@ export const fnSecretBulkInsert = async ({
})
);
const newSecrets = await secretDAL.insertMany(sanitizedInputSecrets.map((el) => ({ ...el, folderId })));
const newSecrets = await secretDAL.insertMany(
sanitizedInputSecrets.map((el) => ({ ...el, folderId })),
tx
);
const newSecretGroupedByKeyName = groupBy(newSecrets, (item) => item.key);
const newSecretTags = inputSecrets.flatMap(({ tagIds: secretTags = [], key }) =>
secretTags.map((tag) => ({

View File

@@ -911,7 +911,8 @@ export const secretV2BridgeServiceFactory = ({
actorOrgId,
environment,
projectId,
secrets: inputSecrets
secrets: inputSecrets,
tx: trx
}: TCreateManySecretDTO) => {
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -951,7 +952,7 @@ export const secretV2BridgeServiceFactory = ({
const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } =
await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, projectId });
const newSecrets = await secretDAL.transaction(async (tx) =>
const newSecrets = await (trx || secretDAL).transaction(async (tx) =>
fnSecretBulkInsert({
inputSecrets: inputSecrets.map((el) => {
const references = getAllNestedSecretReferences(el.secretValue);

View File

@@ -81,6 +81,7 @@ export type TCreateManySecretDTO = Omit<TProjectPermission, "projectId"> & {
secretPath: string;
projectId: string;
environment: string;
tx?: Knex;
secrets: {
secretKey: string;
secretValue: string;

View File

@@ -54,8 +54,9 @@ export const EnvKeyPlatformModal = ({ onClose }: Props) => {
decryptionKey: data.encryptionKey
});
createNotification({
text: "Data imported successfully.",
type: "success"
title: "Import started",
text: "Your data is being imported. You will receive an email when the import is complete or if the import fails. This may take up to 10 minutes.",
type: "info"
});
onClose();