diff --git a/packages/frontend-2/lib/settings/composables/menu.ts b/packages/frontend-2/lib/settings/composables/menu.ts index 8a180b63c..07371bee0 100644 --- a/packages/frontend-2/lib/settings/composables/menu.ts +++ b/packages/frontend-2/lib/settings/composables/menu.ts @@ -264,7 +264,7 @@ export const useSettingsMembersActions = (params: { footerItems.push({ title: 'Remove from workspace...', id: WorkspaceUserActionTypes.RemoveFromWorkspace, - disabled: isOnlyAdmin.value, + disabled: isOnlyAdmin.value && targetUserRole.value === Roles.Workspace.Admin, disabledTooltip: 'There must be at least one admin in this workspace' }) } diff --git a/packages/server/modules/gatekeeper/rest/billing.ts b/packages/server/modules/gatekeeper/rest/billing.ts index f048386ad..7b36d583b 100644 --- a/packages/server/modules/gatekeeper/rest/billing.ts +++ b/packages/server/modules/gatekeeper/rest/billing.ts @@ -120,33 +120,31 @@ export const getBillingRouter = (): Router => { }) logger.info(OperationStatus.start, '[{operationName} ({operationStatus})] ') - // this must use a transaction - - const trx = await db.transaction() - - const completeCheckout = completeCheckoutSessionFactory({ - getCheckoutSession: getCheckoutSessionFactory({ db: trx }), - updateCheckoutSessionStatus: updateCheckoutSessionStatusFactory({ - db: trx - }), - upsertPaidWorkspacePlan: upsertPaidWorkspacePlanFactory({ db: trx }), - upsertWorkspaceSubscription: upsertWorkspaceSubscriptionFactory({ - db: trx - }), - getSubscriptionData: getSubscriptionDataFactory({ - stripe - }), - emitEvent: getEventBus().emit - }) - await withOperationLogging( async () => await withTransaction( - completeCheckout({ - sessionId: session.id, - subscriptionId - }), - trx + async ({ db }) => { + const completeCheckout = completeCheckoutSessionFactory({ + getCheckoutSession: getCheckoutSessionFactory({ db }), + updateCheckoutSessionStatus: updateCheckoutSessionStatusFactory({ + db + }), + upsertPaidWorkspacePlan: upsertPaidWorkspacePlanFactory({ db }), + upsertWorkspaceSubscription: upsertWorkspaceSubscriptionFactory({ + db + }), + getSubscriptionData: getSubscriptionDataFactory({ + stripe + }), + emitEvent: getEventBus().emit + }) + + return completeCheckout({ + sessionId: session.id, + subscriptionId + }) + }, + { db } ), { logger, diff --git a/packages/server/modules/gatekeeper/services/planMigration.ts b/packages/server/modules/gatekeeper/services/planMigration.ts index 5ccef0e4f..7016e4c61 100644 --- a/packages/server/modules/gatekeeper/services/planMigration.ts +++ b/packages/server/modules/gatekeeper/services/planMigration.ts @@ -23,9 +23,7 @@ import { reconcileWorkspaceSubscriptionFactory } from '@/modules/gatekeeper/clie import Stripe from 'stripe' import { cloneDeep } from 'lodash' import { Logger } from '@/observability/logging' - -// get all workspace plan from the DB -// foreach workspace: +import { withTransaction } from '@/modules/shared/helpers/dbHelper' export const migrateOldWorkspacePlans = ({ db, stripe, logger }: { db: Knex; stripe: Stripe; logger: Logger }) => @@ -52,9 +50,14 @@ export const migrateOldWorkspacePlans = for (const oldPlan of oldPlanWorkspaces) { try { - await migrateWorkspacePlan({ db, stripe, logger })({ - workspaceId: oldPlan.workspaceId - }) + await withTransaction( + async ({ db }) => { + await migrateWorkspacePlan({ db, stripe, logger })({ + workspaceId: oldPlan.workspaceId + }) + }, + { db } + ) } catch (err) { logger.error( { err, workspaceId: oldPlan.workspaceId, oldPlan }, @@ -166,10 +169,9 @@ export const migrateWorkspacePlan = 'Migrating {workspaceId} from old plan {workspacePlan} to new plan {newTargetPlan}' ) - const trx = await db.transaction() // add editor seats to everyone - const workspaceMembers = await getWorkspaceRolesFactory({ db: trx })({ + const workspaceMembers = await getWorkspaceRolesFactory({ db })({ workspaceId }) const seats = workspaceMembers.map((m) => ({ @@ -184,7 +186,7 @@ export const migrateWorkspacePlan = 'Inserting {migratedSeatsCount} new seats for the workspace {workspaceId}' ) - await trx('workspace_seats') + await db('workspace_seats') .insert(seats) .onConflict(['workspaceId', 'userId']) .merge() @@ -193,7 +195,7 @@ export const migrateWorkspacePlan = { migratedSeatsCount: seats.length }, 'Workspace {workspaceId} has added {migratedSeatsCount} seats' ) - await upsertWorkspacePlanFactory({ db: trx })({ + await upsertWorkspacePlanFactory({ db })({ //@ts-expect-error the switch above makes sure things are ok workspacePlan: { workspaceId, @@ -220,7 +222,7 @@ export const migrateWorkspacePlan = ) } // if stripe paid plan, convert the stripe sub to use all editor seats - const workspaceSubscription = await getWorkspaceSubscriptionFactory({ db: trx })({ + const workspaceSubscription = await getWorkspaceSubscriptionFactory({ db })({ workspaceId }) if (!workspaceSubscription) @@ -264,7 +266,6 @@ export const migrateWorkspacePlan = prorationBehavior: 'create_prorations' }) } - await trx.commit() log.info('🥳 Workspace plan migration completed for workspace {workspaceId}') // add and editor seat to all workspace members diff --git a/packages/server/modules/multiregion/services/queue.ts b/packages/server/modules/multiregion/services/queue.ts index d02568d04..76ffc150e 100644 --- a/packages/server/modules/multiregion/services/queue.ts +++ b/packages/server/modules/multiregion/services/queue.ts @@ -147,53 +147,87 @@ export const startQueue = async () => { const sourceDb = await getProjectDbClient({ projectId }) const sourceObjectStorage = await getProjectObjectStorage({ projectId }) - const targetDb = await (await getRegionDb({ regionKey })).transaction() + const targetDb = await getRegionDb({ regionKey }) const targetObjectStorage = await getRegionObjectStorage({ regionKey }) - const updateProjectRegion = updateProjectRegionFactory({ - getProject: getProjectFactory({ db: sourceDb }), - getAvailableRegions: getAvailableRegionsFactory({ - getRegions: getRegionsFactory({ db }), - canWorkspaceUseRegions: canWorkspaceUseRegionsFactory({ - getWorkspacePlan: getWorkspacePlanFactory({ db }) - }) - }), - copyWorkspace: copyWorkspaceFactory({ sourceDb, targetDb }), - copyProjects: copyProjectsFactory({ sourceDb, targetDb }), - copyProjectModels: copyProjectModelsFactory({ sourceDb, targetDb }), - copyProjectVersions: copyProjectVersionsFactory({ sourceDb, targetDb }), - copyProjectObjects: copyProjectObjectsFactory({ sourceDb, targetDb }), - copyProjectAutomations: copyProjectAutomationsFactory({ sourceDb, targetDb }), - copyProjectComments: copyProjectCommentsFactory({ sourceDb, targetDb }), - copyProjectWebhooks: copyProjectWebhooksFactory({ sourceDb, targetDb }), - copyProjectBlobs: copyProjectBlobs({ - sourceDb, - sourceObjectStorage, - targetDb, - targetObjectStorage - }), - validateProjectRegionCopy: validateProjectRegionCopyFactory({ - countProjectModels: getStreamBranchCountFactory({ db: sourceDb }), - countProjectVersions: getStreamCommitCountFactory({ db: sourceDb }), - countProjectObjects: getStreamObjectCountFactory({ db: sourceDb }), - countProjectAutomations: getProjectAutomationsTotalCountFactory({ - db: sourceDb - }), - countProjectComments: getStreamCommentCountFactory({ db: sourceDb }), - getProjectWebhooks: getStreamWebhooksFactory({ db: sourceDb }) - }), - updateProjectRegionKey: updateProjectRegionKeyFactory({ - upsertProjectRegionKey: upsertProjectRegionKeyFactory({ db }), - cacheDeleteRegionKey: deleteRegionKeyFromCacheFactory({ - redis: getGenericRedis() - }), - emitEvent: getEventBus().emit - }) - }) - return await withTransaction( - updateProjectRegion({ projectId, regionKey }), - targetDb + async ({ db: targetDbTrx }) => { + const updateProjectRegion = updateProjectRegionFactory({ + getProject: getProjectFactory({ db: sourceDb }), + getAvailableRegions: getAvailableRegionsFactory({ + getRegions: getRegionsFactory({ db }), + canWorkspaceUseRegions: canWorkspaceUseRegionsFactory({ + getWorkspacePlan: getWorkspacePlanFactory({ db }) + }) + }), + copyWorkspace: copyWorkspaceFactory({ + sourceDb, + targetDb: targetDbTrx + }), + copyProjects: copyProjectsFactory({ + sourceDb, + targetDb: targetDbTrx + }), + copyProjectModels: copyProjectModelsFactory({ + sourceDb, + targetDb: targetDbTrx + }), + copyProjectVersions: copyProjectVersionsFactory({ + sourceDb, + targetDb: targetDbTrx + }), + copyProjectObjects: copyProjectObjectsFactory({ + sourceDb, + targetDb: targetDbTrx + }), + copyProjectAutomations: copyProjectAutomationsFactory({ + sourceDb, + targetDb: targetDbTrx + }), + copyProjectComments: copyProjectCommentsFactory({ + sourceDb, + targetDb: targetDbTrx + }), + copyProjectWebhooks: copyProjectWebhooksFactory({ + sourceDb, + targetDb: targetDbTrx + }), + copyProjectBlobs: copyProjectBlobs({ + sourceDb, + sourceObjectStorage, + targetDb: targetDbTrx, + targetObjectStorage + }), + validateProjectRegionCopy: validateProjectRegionCopyFactory({ + countProjectModels: getStreamBranchCountFactory({ + db: sourceDb + }), + countProjectVersions: getStreamCommitCountFactory({ + db: sourceDb + }), + countProjectObjects: getStreamObjectCountFactory({ + db: sourceDb + }), + countProjectAutomations: getProjectAutomationsTotalCountFactory({ + db: sourceDb + }), + countProjectComments: getStreamCommentCountFactory({ + db: sourceDb + }), + getProjectWebhooks: getStreamWebhooksFactory({ db: sourceDb }) + }), + updateProjectRegionKey: updateProjectRegionKeyFactory({ + upsertProjectRegionKey: upsertProjectRegionKeyFactory({ db }), + cacheDeleteRegionKey: deleteRegionKeyFromCacheFactory({ + redis: getGenericRedis() + }), + emitEvent: getEventBus().emit + }) + }) + + return updateProjectRegion({ projectId, regionKey }) + }, + { db: targetDb } ) } case 'delete-project-region-data': diff --git a/packages/server/modules/shared/command.ts b/packages/server/modules/shared/command.ts index 44e216e00..e69eb5a05 100644 --- a/packages/server/modules/shared/command.ts +++ b/packages/server/modules/shared/command.ts @@ -1,6 +1,13 @@ import { EmitArg, EventBus, EventBusEmit } from '@/modules/shared/services/eventBus' import { Knex } from 'knex' +/** + * TODO: Fix api - make operationFactory db arg actually return the trx. Currently many usages of this + * are not working correctly cause they just use the db, skipping the transaction + * + * Also: withOperationLogging and withOperationTransaction could all be merged into this, with + * this having a better name like `operationFactory` + */ export const commandFactory = ) => ReturnType>({ db, diff --git a/packages/server/modules/shared/helpers/dbHelper.ts b/packages/server/modules/shared/helpers/dbHelper.ts index 33f6b5fec..942b72e17 100644 --- a/packages/server/modules/shared/helpers/dbHelper.ts +++ b/packages/server/modules/shared/helpers/dbHelper.ts @@ -3,7 +3,7 @@ import { Knex } from 'knex' import { isString } from 'lodash' import { postgresMaxConnections } from '@/modules/shared/helpers/envHelper' import { EnvironmentResourceError } from '@/modules/shared/errors' -import { isNonNullable } from '@speckle/shared' +import { isNonNullable, MaybeAsync } from '@speckle/shared' export type BatchedSelectOptions = { /** @@ -107,17 +107,20 @@ export const numberOfFreeConnections = (knex: Knex) => { } export const withTransaction = async ( - callback: Promise | T, - trx: Knex.Transaction -): Promise => { + operation: (args: { db: Knex }) => MaybeAsync, + params: { + db: Knex + } +) => { + const { db } = params + const trx = await db.transaction() + try { - return await callback + const result = await operation({ db: trx }) + await trx.commit() + return result } catch (e) { await trx.rollback() throw e - } finally { - if (trx.isTransaction && !trx.isCompleted()) { - await trx.commit() - } } } diff --git a/packages/server/modules/workspaces/events/eventListener.ts b/packages/server/modules/workspaces/events/eventListener.ts index a15e4a626..303eb367c 100644 --- a/packages/server/modules/workspaces/events/eventListener.ts +++ b/packages/server/modules/workspaces/events/eventListener.ts @@ -711,60 +711,87 @@ export const initializeEventListenersFactory = }) }), eventBus.listen(WorkspaceEvents.RoleDeleted, async ({ payload }) => { - const trx = await db.transaction() - const onWorkspaceRoleDeleted = onWorkspaceRoleDeletedFactory({ - queryAllWorkspaceProjects: queryAllWorkspaceProjectsFactory({ getStreams }), - deleteProjectRole: deleteProjectRoleFactory({ db: trx }), - deleteWorkspaceSeat: deleteWorkspaceSeatFactory({ db: trx }) - }) - await withTransaction(onWorkspaceRoleDeleted(payload.acl), trx) + await withTransaction( + async ({ db: trx }) => { + const onWorkspaceRoleDeleted = onWorkspaceRoleDeletedFactory({ + queryAllWorkspaceProjects: queryAllWorkspaceProjectsFactory({ + getStreams + }), + deleteProjectRole: deleteProjectRoleFactory({ db: trx }), + deleteWorkspaceSeat: deleteWorkspaceSeatFactory({ db: trx }) + }) + + return await onWorkspaceRoleDeleted(payload.acl) + }, + { db } + ) }), eventBus.listen(WorkspaceEvents.RoleUpdated, async ({ payload }) => { - const trx = await db.transaction() - const onWorkspaceRoleUpdated = onWorkspaceRoleUpdatedFactory({ - getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db }), - getWorkspaceRoleToDefaultProjectRoleMapping: - getWorkspaceRoleToDefaultProjectRoleMappingFactory({ - getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db }) - }), - queryAllWorkspaceProjects: queryAllWorkspaceProjectsFactory({ getStreams }), - setStreamCollaborator: setStreamCollaboratorFactory({ - getUser: getUserFactory({ db }), - validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }), - emitEvent: eventBus.emit, - grantStreamPermissions: grantStreamPermissionsFactory({ db: trx }), - isStreamCollaborator: isStreamCollaboratorFactory({ - getStream: getStreamFactory({ db }) - }), - revokeStreamPermissions: revokeStreamPermissionsFactory({ db: trx }) - }), - getStreamsCollaboratorCounts: getStreamsCollaboratorCountsFactory({ db }) - }) - await withTransaction(onWorkspaceRoleUpdated(payload), trx) + await withTransaction( + async ({ db: trx }) => { + const onWorkspaceRoleUpdated = onWorkspaceRoleUpdatedFactory({ + getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db }), + getWorkspaceRoleToDefaultProjectRoleMapping: + getWorkspaceRoleToDefaultProjectRoleMappingFactory({ + getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db }) + }), + queryAllWorkspaceProjects: queryAllWorkspaceProjectsFactory({ + getStreams + }), + setStreamCollaborator: setStreamCollaboratorFactory({ + getUser: getUserFactory({ db }), + validateStreamAccess: validateStreamAccessFactory({ + authorizeResolver + }), + emitEvent: eventBus.emit, + grantStreamPermissions: grantStreamPermissionsFactory({ + db: trx + }), + isStreamCollaborator: isStreamCollaboratorFactory({ + getStream: getStreamFactory({ db }) + }), + revokeStreamPermissions: revokeStreamPermissionsFactory({ + db: trx + }) + }), + getStreamsCollaboratorCounts: getStreamsCollaboratorCountsFactory({ db }) + }) + return await onWorkspaceRoleUpdated(payload) + }, + { db } + ) }), eventBus.listen(WorkspaceEvents.SeatUpdated, async (payload) => { - const trx = await db.transaction() - const onWorkspaceSeatUpdated = onWorkspaceSeatUpdatedFactory({ - setStreamCollaborator: setStreamCollaboratorFactory({ - getUser: getUserFactory({ db }), - validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }), - emitEvent: eventBus.emit, - grantStreamPermissions: grantStreamPermissionsFactory({ db: trx }), - isStreamCollaborator: isStreamCollaboratorFactory({ - getStream: getStreamFactory({ db }) - }), - revokeStreamPermissions: revokeStreamPermissionsFactory({ db: trx }) - }), - queryAllWorkspaceProjects: queryAllWorkspaceProjectsFactory({ getStreams }), - getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db }), - getWorkspaceRoleForUser: getWorkspaceRoleForUserFactory({ db }), - getWorkspaceSeatTypeToProjectRoleMapping: - getWorkspaceSeatTypeToProjectRoleMappingFactory({ - getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db }) + await withTransaction( + async ({ db: trx }) => { + const onWorkspaceSeatUpdated = onWorkspaceSeatUpdatedFactory({ + setStreamCollaborator: setStreamCollaboratorFactory({ + getUser: getUserFactory({ db }), + validateStreamAccess: validateStreamAccessFactory({ + authorizeResolver + }), + emitEvent: eventBus.emit, + grantStreamPermissions: grantStreamPermissionsFactory({ db: trx }), + isStreamCollaborator: isStreamCollaboratorFactory({ + getStream: getStreamFactory({ db }) + }), + revokeStreamPermissions: revokeStreamPermissionsFactory({ db: trx }) + }), + queryAllWorkspaceProjects: queryAllWorkspaceProjectsFactory({ + getStreams + }), + getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db }), + getWorkspaceRoleForUser: getWorkspaceRoleForUserFactory({ db }), + getWorkspaceSeatTypeToProjectRoleMapping: + getWorkspaceSeatTypeToProjectRoleMappingFactory({ + getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db }) + }) }) - }) - await withTransaction(onWorkspaceSeatUpdated(payload), trx) + return await onWorkspaceSeatUpdated(payload) + }, + { db } + ) }), eventBus.listen('**', emitWorkspaceGraphqlSubscriptions), eventBus.listen( diff --git a/packages/server/modules/workspaces/graph/resolvers/workspaces.ts b/packages/server/modules/workspaces/graph/resolvers/workspaces.ts index 32c4c31b1..2f5946d72 100644 --- a/packages/server/modules/workspaces/graph/resolvers/workspaces.ts +++ b/packages/server/modules/workspaces/graph/resolvers/workspaces.ts @@ -733,17 +733,19 @@ export = FF_WORKSPACES_MODULE_ENABLED if (!role) { // this is currently not working with the command factory // TODO: include the onWorkspaceRoleDeletedFactory listener service - const trx = await db.transaction() - const deleteWorkspaceRole = deleteWorkspaceRoleFactory({ - deleteWorkspaceRole: repoDeleteWorkspaceRoleFactory({ db: trx }), - getWorkspaceRoles: getWorkspaceRolesFactory({ db: trx }), - emitWorkspaceEvent: getEventBus().emit - }) await withOperationLogging( async () => await withTransaction( - deleteWorkspaceRole({ workspaceId, userId }), - trx + async ({ db: trx }) => { + const deleteWorkspaceRole = deleteWorkspaceRoleFactory({ + deleteWorkspaceRole: repoDeleteWorkspaceRoleFactory({ db: trx }), + getWorkspaceRoles: getWorkspaceRolesFactory({ db: trx }), + emitWorkspaceEvent: getEventBus().emit + }) + + return await deleteWorkspaceRole({ workspaceId, userId }) + }, + { db } ), { logger, @@ -758,18 +760,18 @@ export = FF_WORKSPACES_MODULE_ENABLED const updateWorkspaceRole = commandFactory({ db, eventBus, - operationFactory: ({ db, emit }) => + operationFactory: ({ trx, emit }) => updateWorkspaceRoleFactory({ - upsertWorkspaceRole: upsertWorkspaceRoleFactory({ db }), - getWorkspaceWithDomains: getWorkspaceWithDomainsFactory({ db }), + upsertWorkspaceRole: upsertWorkspaceRoleFactory({ db: trx }), + getWorkspaceWithDomains: getWorkspaceWithDomainsFactory({ db: trx }), findVerifiedEmailsByUserId: findVerifiedEmailsByUserIdFactory({ - db + db: trx }), - getWorkspaceRoles: getWorkspaceRolesFactory({ db }), + getWorkspaceRoles: getWorkspaceRolesFactory({ db: trx }), emitWorkspaceEvent: emit, ensureValidWorkspaceRoleSeat: ensureValidWorkspaceRoleSeatFactory({ - createWorkspaceSeat: createWorkspaceSeatFactory({ db }), - getWorkspaceUserSeat: getWorkspaceUserSeatFactory({ db }), + createWorkspaceSeat: createWorkspaceSeatFactory({ db: trx }), + getWorkspaceUserSeat: getWorkspaceUserSeatFactory({ db: trx }), eventEmit: emit }) }) @@ -946,17 +948,22 @@ export = FF_WORKSPACES_MODULE_ENABLED }) // this is currently not working with the command factory // TODO: include the onWorkspaceRoleDeletedFactory listener service - const trx = await db.transaction() - const deleteWorkspaceRole = deleteWorkspaceRoleFactory({ - deleteWorkspaceRole: repoDeleteWorkspaceRoleFactory({ db: trx }), - getWorkspaceRoles: getWorkspaceRolesFactory({ db: trx }), - emitWorkspaceEvent: getEventBus().emit - }) await withOperationLogging( async () => await withTransaction( - deleteWorkspaceRole({ workspaceId, userId: context.userId! }), - trx + async ({ db: trx }) => { + const deleteWorkspaceRole = deleteWorkspaceRoleFactory({ + deleteWorkspaceRole: repoDeleteWorkspaceRoleFactory({ db: trx }), + getWorkspaceRoles: getWorkspaceRolesFactory({ db: trx }), + emitWorkspaceEvent: getEventBus().emit + }) + + return await deleteWorkspaceRole({ + workspaceId, + userId: context.userId! + }) + }, + { db } ), { logger, diff --git a/packages/server/modules/workspaces/rest/sso.ts b/packages/server/modules/workspaces/rest/sso.ts index d48a3bb91..3cc1664a7 100644 --- a/packages/server/modules/workspaces/rest/sso.ts +++ b/packages/server/modules/workspaces/rest/sso.ts @@ -257,87 +257,99 @@ export const getSsoRouter = (): Router => { query: oidcCallbackRequestQuery }), async (req, res, next) => { - const trx = await db.transaction() - const handleOidcCallback = handleOidcCallbackFactory({ - getWorkspaceRoles: getWorkspaceRolesFactory({ db: trx }), - getWorkspaceBySlug: getWorkspaceBySlugFactory({ db: trx }), - createOidcProvider: createOidcProviderFactory({ - getOIDCProviderValidationRequest: getOIDCProviderValidationRequestFactory({ - redis: getGenericRedis(), - decrypt: getDecryptor() - }), - saveSsoProviderRegistration: saveSsoProviderRegistrationFactory({ - getWorkspaceSsoProvider: getWorkspaceSsoProviderFactory({ - db: trx, - decrypt: getDecryptor() - }), - storeProviderRecord: storeSsoProviderRecordFactory({ - db: trx, - encrypt: getEncryptor() - }), - associateSsoProviderWithWorkspace: associateSsoProviderWithWorkspaceFactory( - { - db: trx - } - ) - }) - }), - getOidcProvider: getOidcProviderFactory({ - getWorkspaceSsoProvider: getWorkspaceSsoProviderFactory({ - db: trx, - decrypt: getDecryptor() - }) - }), - getOidcProviderUserData: getOidcProviderUserDataFactory(), - tryGetSpeckleUserData: tryGetSpeckleUserDataFactory({ - findEmail: findEmailFactory({ db: trx }), - getUser: getUserFactory({ db: trx }), - getUserEmails: findEmailsByUserIdFactory({ db: trx }) - }), - createWorkspaceUserFromSsoProfile: createWorkspaceUserFromSsoProfileFactory({ - createUser: createUserFactory({ - getServerInfo: getServerInfoFactory({ db: trx }), - findEmail: findEmailFactory({ db: trx }), - storeUser: storeUserFactory({ db: trx }), - countAdminUsers: countAdminUsersFactory({ db: trx }), - storeUserAcl: storeUserAclFactory({ db: trx }), - validateAndCreateUserEmail: validateAndCreateUserEmailFactory({ - createUserEmail: createUserEmailFactory({ db: trx }), - ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ - db: trx + try { + await withTransaction( + async ({ db: trx }) => { + const handleOidcCallback = handleOidcCallbackFactory({ + getWorkspaceRoles: getWorkspaceRolesFactory({ db: trx }), + getWorkspaceBySlug: getWorkspaceBySlugFactory({ db: trx }), + createOidcProvider: createOidcProviderFactory({ + getOIDCProviderValidationRequest: + getOIDCProviderValidationRequestFactory({ + redis: getGenericRedis(), + decrypt: getDecryptor() + }), + saveSsoProviderRegistration: saveSsoProviderRegistrationFactory({ + getWorkspaceSsoProvider: getWorkspaceSsoProviderFactory({ + db: trx, + decrypt: getDecryptor() + }), + storeProviderRecord: storeSsoProviderRecordFactory({ + db: trx, + encrypt: getEncryptor() + }), + associateSsoProviderWithWorkspace: + associateSsoProviderWithWorkspaceFactory({ + db: trx + }) + }) }), - findEmail: findEmailFactory({ db: trx }), - updateEmailInvites: finalizeInvitedServerRegistrationFactory({ - deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db: trx }), - updateAllInviteTargets: updateAllInviteTargetsFactory({ db: trx }) + getOidcProvider: getOidcProviderFactory({ + getWorkspaceSsoProvider: getWorkspaceSsoProviderFactory({ + db: trx, + decrypt: getDecryptor() + }) }), - requestNewEmailVerification: requestNewEmailVerificationFactory({ + getOidcProviderUserData: getOidcProviderUserDataFactory(), + tryGetSpeckleUserData: tryGetSpeckleUserDataFactory({ findEmail: findEmailFactory({ db: trx }), getUser: getUserFactory({ db: trx }), - getServerInfo: getServerInfoFactory({ db: trx }), - deleteOldAndInsertNewVerification: - deleteOldAndInsertNewVerificationFactory({ db: trx }), - renderEmail, - sendEmail - }) - }), - emitEvent: getEventBus().emit - }), - upsertWorkspaceRole: upsertWorkspaceRoleFactory({ db: trx }), - findInvite: findInviteFactory({ db: trx }), - deleteInvite: deleteInviteFactory({ db: trx }) - }), - linkUserWithSsoProvider: linkUserWithSsoProviderFactory({ - findEmailsByUserId: findEmailsByUserIdFactory({ db: trx }), - createUserEmail: createUserEmailFactory({ db: trx }), - updateUserEmail: updateUserEmailFactory({ db: trx }), - logger: req.log - }), - upsertUserSsoSession: upsertUserSsoSessionFactory({ db: trx }) - }) + getUserEmails: findEmailsByUserIdFactory({ db: trx }) + }), + createWorkspaceUserFromSsoProfile: + createWorkspaceUserFromSsoProfileFactory({ + createUser: createUserFactory({ + getServerInfo: getServerInfoFactory({ db: trx }), + findEmail: findEmailFactory({ db: trx }), + storeUser: storeUserFactory({ db: trx }), + countAdminUsers: countAdminUsersFactory({ db: trx }), + storeUserAcl: storeUserAclFactory({ db: trx }), + validateAndCreateUserEmail: validateAndCreateUserEmailFactory({ + createUserEmail: createUserEmailFactory({ db: trx }), + ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ + db: trx + }), + findEmail: findEmailFactory({ db: trx }), + updateEmailInvites: finalizeInvitedServerRegistrationFactory({ + deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ + db: trx + }), + updateAllInviteTargets: updateAllInviteTargetsFactory({ + db: trx + }) + }), + requestNewEmailVerification: requestNewEmailVerificationFactory({ + findEmail: findEmailFactory({ db: trx }), + getUser: getUserFactory({ db: trx }), + getServerInfo: getServerInfoFactory({ db: trx }), + deleteOldAndInsertNewVerification: + deleteOldAndInsertNewVerificationFactory({ + db: trx + }), + renderEmail, + sendEmail + }) + }), + emitEvent: getEventBus().emit + }), + upsertWorkspaceRole: upsertWorkspaceRoleFactory({ db: trx }), + findInvite: findInviteFactory({ db: trx }), + deleteInvite: deleteInviteFactory({ db: trx }) + }), + linkUserWithSsoProvider: linkUserWithSsoProviderFactory({ + findEmailsByUserId: findEmailsByUserIdFactory({ db: trx }), + createUserEmail: createUserEmailFactory({ db: trx }), + updateUserEmail: updateUserEmailFactory({ db: trx }), + logger: req.log + }), + upsertUserSsoSession: upsertUserSsoSessionFactory({ db: trx }) + }) + + await handleOidcCallback(req, res, next) + }, + { db } + ) - try { - await withTransaction(handleOidcCallback(req, res, next), trx) return next() } catch (e) { const errorMessage = getErrorMessage(e)