diff --git a/backend/src/services/app-connection/app-connection-service.ts b/backend/src/services/app-connection/app-connection-service.ts index 8e51caf30..04cf20e4a 100644 --- a/backend/src/services/app-connection/app-connection-service.ts +++ b/backend/src/services/app-connection/app-connection-service.ts @@ -144,54 +144,40 @@ export const appConnectionServiceFactory = ({ OrgPermissionSubjects.AppConnections ); - const appConnection = await appConnectionDAL.transaction(async (tx) => { - const isConflictingName = Boolean( - await appConnectionDAL.findOne( - { - name: params.name, - orgId: actor.orgId - }, - tx - ) - ); + const validatedCredentials = await validateAppConnectionCredentials({ + app, + credentials, + method, + orgId: actor.orgId + } as TAppConnectionConfig); - if (isConflictingName) - throw new BadRequestError({ - message: `An App Connection with the name "${params.name}" already exists` - }); + const encryptedCredentials = await encryptAppConnectionCredentials({ + credentials: validatedCredentials, + orgId: actor.orgId, + kmsService + }); - const validatedCredentials = await validateAppConnectionCredentials({ - app, - credentials, - method, - orgId: actor.orgId - } as TAppConnectionConfig); - - const encryptedCredentials = await encryptAppConnectionCredentials({ - credentials: validatedCredentials, + try { + const connection = await appConnectionDAL.create({ orgId: actor.orgId, - kmsService + encryptedCredentials, + method, + app, + ...params }); - const connection = await appConnectionDAL.create( - { - orgId: actor.orgId, - encryptedCredentials, - method, - app, - ...params - }, - tx - ); - return { ...connection, credentialsHash: generateHash(connection.encryptedCredentials), credentials: validatedCredentials - }; - }); + } as TAppConnection; + } catch (err) { + if (err instanceof DatabaseError && (err.error as { code: string })?.code === "23505") { + throw new BadRequestError({ message: `An App Connection with the name "${params.name}" already exists` }); + } - return appConnection as TAppConnection; + throw err; + } }; const updateAppConnection = async ( @@ -215,72 +201,55 @@ export const appConnectionServiceFactory = ({ OrgPermissionSubjects.AppConnections ); - const updatedAppConnection = await appConnectionDAL.transaction(async (tx) => { - if (params.name && appConnection.name !== params.name) { - const isConflictingName = Boolean( - await appConnectionDAL.findOne( - { - name: params.name, - orgId: appConnection.orgId - }, - tx - ) - ); + let encryptedCredentials: undefined | Buffer; - if (isConflictingName) - throw new BadRequestError({ - message: `An App Connection with the name "${params.name}" already exists` - }); - } + if (credentials) { + const { app, method } = appConnection as DiscriminativePick; - let encryptedCredentials: undefined | Buffer; - - if (credentials) { - const { app, method } = appConnection as DiscriminativePick; - - if ( - !VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[app].safeParse({ - method, - credentials - }).success - ) - throw new BadRequestError({ - message: `Invalid credential format for ${ - APP_CONNECTION_NAME_MAP[app] - } Connection with method ${getAppConnectionMethodName(method)}` - }); - - const validatedCredentials = await validateAppConnectionCredentials({ - app, - orgId: actor.orgId, - credentials, - method - } as TAppConnectionConfig); - - if (!validatedCredentials) - throw new BadRequestError({ message: "Unable to validate connection - check credentials" }); - - encryptedCredentials = await encryptAppConnectionCredentials({ - credentials: validatedCredentials, - orgId: actor.orgId, - kmsService + if ( + !VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[app].safeParse({ + method, + credentials + }).success + ) + throw new BadRequestError({ + message: `Invalid credential format for ${ + APP_CONNECTION_NAME_MAP[app] + } Connection with method ${getAppConnectionMethodName(method)}` }); + + const validatedCredentials = await validateAppConnectionCredentials({ + app, + orgId: actor.orgId, + credentials, + method + } as TAppConnectionConfig); + + if (!validatedCredentials) + throw new BadRequestError({ message: "Unable to validate connection - check credentials" }); + + encryptedCredentials = await encryptAppConnectionCredentials({ + credentials: validatedCredentials, + orgId: actor.orgId, + kmsService + }); + } + + try { + const updatedConnection = await appConnectionDAL.updateById(connectionId, { + orgId: actor.orgId, + encryptedCredentials, + ...params + }); + + return await decryptAppConnection(updatedConnection, kmsService); + } catch (err) { + if (err instanceof DatabaseError && (err.error as { code: string })?.code === "23505") { + throw new BadRequestError({ message: `An App Connection with the name "${params.name}" already exists` }); } - const updatedConnection = await appConnectionDAL.updateById( - connectionId, - { - orgId: actor.orgId, - encryptedCredentials, - ...params - }, - tx - ); - - return updatedConnection; - }); - - return decryptAppConnection(updatedAppConnection, kmsService); + throw err; + } }; const deleteAppConnection = async (app: AppConnection, connectionId: string, actor: OrgServiceActor) => { diff --git a/backend/src/services/secret-sync/secret-sync-dal.ts b/backend/src/services/secret-sync/secret-sync-dal.ts index 8d99f8637..0f5733a3a 100644 --- a/backend/src/services/secret-sync/secret-sync-dal.ts +++ b/backend/src/services/secret-sync/secret-sync-dal.ts @@ -123,47 +123,39 @@ export const secretSyncDALFactory = ( }; const create = async (data: Parameters<(typeof secretSyncOrm)["create"]>[0]) => { - try { - const secretSync = (await secretSyncOrm.transaction(async (tx) => { - const sync = await secretSyncOrm.create(data, tx); + const secretSync = (await secretSyncOrm.transaction(async (tx) => { + const sync = await secretSyncOrm.create(data, tx); - return baseSecretSyncQuery({ - filter: { id: sync.id }, - db, - tx - }).first(); - }))!; + return baseSecretSyncQuery({ + filter: { id: sync.id }, + db, + tx + }).first(); + }))!; - // TODO (scott): replace with cached folder path once implemented - const [folderWithPath] = secretSync.folderId - ? await folderDAL.findSecretPathByFolderIds(secretSync.projectId, [secretSync.folderId]) - : []; - return expandSecretSync(secretSync, folderWithPath); - } catch (error) { - throw new DatabaseError({ error, name: "Create - Secret Sync" }); - } + // TODO (scott): replace with cached folder path once implemented + const [folderWithPath] = secretSync.folderId + ? await folderDAL.findSecretPathByFolderIds(secretSync.projectId, [secretSync.folderId]) + : []; + return expandSecretSync(secretSync, folderWithPath); }; const updateById = async (syncId: string, data: Parameters<(typeof secretSyncOrm)["updateById"]>[1]) => { - try { - const secretSync = (await secretSyncOrm.transaction(async (tx) => { - const sync = await secretSyncOrm.updateById(syncId, data, tx); + const secretSync = (await secretSyncOrm.transaction(async (tx) => { + const sync = await secretSyncOrm.updateById(syncId, data, tx); - return baseSecretSyncQuery({ - filter: { id: sync.id }, - db, - tx - }).first(); - }))!; + return baseSecretSyncQuery({ + filter: { id: sync.id }, + db, + tx + }).first(); + }))!; - // TODO (scott): replace with cached folder path once implemented - const [folderWithPath] = secretSync.folderId - ? await folderDAL.findSecretPathByFolderIds(secretSync.projectId, [secretSync.folderId]) - : []; - return expandSecretSync(secretSync, folderWithPath); - } catch (error) { - throw new DatabaseError({ error, name: "Update by ID - Secret Sync" }); - } + // TODO (scott): replace with cached folder path once implemented + const [folderWithPath] = secretSync.folderId + ? await folderDAL.findSecretPathByFolderIds(secretSync.projectId, [secretSync.folderId]) + : []; + return expandSecretSync(secretSync, folderWithPath); }; const findOne = async (filter: Parameters<(typeof secretSyncOrm)["findOne"]>[0], tx?: Knex) => { diff --git a/backend/src/services/secret-sync/secret-sync-service.ts b/backend/src/services/secret-sync/secret-sync-service.ts index 4ce2fd2d5..a855f5dfc 100644 --- a/backend/src/services/secret-sync/secret-sync-service.ts +++ b/backend/src/services/secret-sync/secret-sync-service.ts @@ -8,7 +8,7 @@ import { ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; -import { BadRequestError, NotFoundError } from "@app/lib/errors"; +import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors"; import { OrgServiceActor } from "@app/lib/types"; import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; @@ -197,37 +197,26 @@ export const secretSyncServiceFactory = ({ // validates permission to connect and app is valid for sync destination await appConnectionService.connectAppConnectionById(destinationApp, params.connectionId, actor); - const secretSync = await secretSyncDAL.transaction(async (tx) => { - const isConflictingName = Boolean( - ( - await secretSyncDAL.find( - { - name: params.name, - projectId - }, - tx - ) - ).length - ); - - if (isConflictingName) - throw new BadRequestError({ - message: `A Secret Sync with the name "${params.name}" already exists for the project with ID "${folder.projectId}"` - }); - - const sync = await secretSyncDAL.create({ + try { + const secretSync = await secretSyncDAL.create({ folderId: folder.id, ...params, ...(params.isAutoSyncEnabled && { syncStatus: SecretSyncStatus.Pending }), projectId }); - return sync; - }); + if (secretSync.isAutoSyncEnabled) await secretSyncQueue.queueSecretSyncSyncSecretsById({ syncId: secretSync.id }); - if (secretSync.isAutoSyncEnabled) await secretSyncQueue.queueSecretSyncSyncSecretsById({ syncId: secretSync.id }); + return secretSync as TSecretSync; + } catch (err) { + if (err instanceof DatabaseError && (err.error as { code: string })?.code === "23505") { + throw new BadRequestError({ + message: `A Secret Sync with the name "${params.name}" already exists for the project with ID "${folder.projectId}"` + }); + } - return secretSync as TSecretSync; + throw err; + } }; const updateSecretSync = async ( @@ -260,78 +249,65 @@ export const secretSyncServiceFactory = ({ message: `Secret sync with ID "${secretSync.id}" is not configured for ${SECRET_SYNC_NAME_MAP[destination]}` }); - const updatedSecretSync = await secretSyncDAL.transaction(async (tx) => { - let { folderId } = secretSync; + let { folderId } = secretSync; - if (params.connectionId) { - const destinationApp = SECRET_SYNC_CONNECTION_MAP[secretSync.destination as SecretSync]; + if (params.connectionId) { + const destinationApp = SECRET_SYNC_CONNECTION_MAP[secretSync.destination as SecretSync]; - // validates permission to connect and app is valid for sync destination - await appConnectionService.connectAppConnectionById(destinationApp, params.connectionId, actor); - } + // validates permission to connect and app is valid for sync destination + await appConnectionService.connectAppConnectionById(destinationApp, params.connectionId, actor); + } - if ( - (secretPath && secretPath !== secretSync.folder?.path) || - (environment && environment !== secretSync.environment?.slug) - ) { - const updatedEnvironment = environment ?? secretSync.environment?.slug; - const updatedSecretPath = secretPath ?? secretSync.folder?.path; + if ( + (secretPath && secretPath !== secretSync.folder?.path) || + (environment && environment !== secretSync.environment?.slug) + ) { + const updatedEnvironment = environment ?? secretSync.environment?.slug; + const updatedSecretPath = secretPath ?? secretSync.folder?.path; - if (!updatedEnvironment || !updatedSecretPath) - throw new BadRequestError({ message: "Must specify both source environment and secret path" }); + if (!updatedEnvironment || !updatedSecretPath) + throw new BadRequestError({ message: "Must specify both source environment and secret path" }); - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Secrets, { - environment: updatedEnvironment, - secretPath: updatedSecretPath - }) - ); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Read, + subject(ProjectPermissionSub.Secrets, { + environment: updatedEnvironment, + secretPath: updatedSecretPath + }) + ); - const newFolder = await folderDAL.findBySecretPath(secretSync.projectId, updatedEnvironment, updatedSecretPath); + const newFolder = await folderDAL.findBySecretPath(secretSync.projectId, updatedEnvironment, updatedSecretPath); - if (!newFolder) - throw new BadRequestError({ - message: `Could not find folder with path "${secretPath}" in environment "${environment}" for project with ID "${secretSync.projectId}"` - }); + if (!newFolder) + throw new BadRequestError({ + message: `Could not find folder with path "${secretPath}" in environment "${environment}" for project with ID "${secretSync.projectId}"` + }); - folderId = newFolder.id; - } + folderId = newFolder.id; + } - if (params.name && secretSync.name !== params.name) { - const isConflictingName = Boolean( - ( - await secretSyncDAL.find( - { - name: params.name, - projectId: secretSync.projectId - }, - tx - ) - ).length - ); + const isAutoSyncEnabled = params.isAutoSyncEnabled ?? secretSync.isAutoSyncEnabled; - if (isConflictingName) - throw new BadRequestError({ - message: `A Secret Sync with the name "${params.name}" already exists for project with ID "${secretSync.projectId}"` - }); - } - - const isAutoSyncEnabled = params.isAutoSyncEnabled ?? secretSync.isAutoSyncEnabled; - - const updatedSync = await secretSyncDAL.updateById(syncId, { + try { + const updatedSecretSync = await secretSyncDAL.updateById(syncId, { ...params, ...(isAutoSyncEnabled && folderId && { syncStatus: SecretSyncStatus.Pending }), folderId }); - return updatedSync; - }); + if (updatedSecretSync.isAutoSyncEnabled) + await secretSyncQueue.queueSecretSyncSyncSecretsById({ syncId: secretSync.id }); - if (updatedSecretSync.isAutoSyncEnabled) - await secretSyncQueue.queueSecretSyncSyncSecretsById({ syncId: secretSync.id }); + return updatedSecretSync as TSecretSync; + } catch (err) { + if (err instanceof DatabaseError && (err.error as { code: string })?.code === "23505") { + throw new BadRequestError({ + message: `A Secret Sync with the name "${params.name}" already exists for the project with ID "${secretSync.projectId}"` + }); + } - return updatedSecretSync as TSecretSync; + throw err; + } }; const deleteSecretSync = async (