@open-mercato/enterprise 0.5.1-develop.2691.d8a0934b37 → 0.5.1-develop.2694.732417c5ec

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/modules/record_locks/data/entities.js +2 -1
  2. package/dist/modules/record_locks/data/entities.js.map +2 -2
  3. package/dist/modules/record_locks/lib/recordLockService.js +19 -15
  4. package/dist/modules/record_locks/lib/recordLockService.js.map +2 -2
  5. package/dist/modules/security/data/entities.js +1 -1
  6. package/dist/modules/security/data/entities.js.map +1 -1
  7. package/dist/modules/sso/data/entities.js +1 -1
  8. package/dist/modules/sso/data/entities.js.map +2 -2
  9. package/dist/modules/sso/services/accountLinkingService.js +4 -4
  10. package/dist/modules/sso/services/accountLinkingService.js.map +2 -2
  11. package/dist/modules/sso/services/hrdService.js +3 -2
  12. package/dist/modules/sso/services/hrdService.js.map +2 -2
  13. package/dist/modules/sso/services/scimService.js +7 -7
  14. package/dist/modules/sso/services/scimService.js.map +2 -2
  15. package/dist/modules/sso/services/scimTokenService.js +1 -1
  16. package/dist/modules/sso/services/scimTokenService.js.map +2 -2
  17. package/dist/modules/sso/services/ssoConfigService.js +1 -1
  18. package/dist/modules/sso/services/ssoConfigService.js.map +2 -2
  19. package/dist/modules/sso/setup.js +1 -1
  20. package/dist/modules/sso/setup.js.map +2 -2
  21. package/jest.config.cjs +4 -2
  22. package/package.json +5 -5
  23. package/src/modules/record_locks/data/entities.ts +2 -1
  24. package/src/modules/record_locks/lib/recordLockService.ts +33 -28
  25. package/src/modules/security/data/entities.ts +1 -1
  26. package/src/modules/sso/data/entities.ts +1 -1
  27. package/src/modules/sso/services/accountLinkingService.ts +4 -4
  28. package/src/modules/sso/services/hrdService.ts +10 -7
  29. package/src/modules/sso/services/scimService.ts +7 -7
  30. package/src/modules/sso/services/scimTokenService.ts +1 -1
  31. package/src/modules/sso/services/ssoConfigService.ts +1 -1
  32. package/src/modules/sso/setup.ts +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/sso/services/ssoConfigService.ts"],
4
- "sourcesContent": ["import type { FilterQuery, RequiredEntityData } from '@mikro-orm/core'\nimport { EntityManager } from '@mikro-orm/postgresql'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { SsoConfig, ScimToken } from '../data/entities'\nimport type { SsoConfigAdminCreateInput, SsoConfigAdminUpdateInput, SsoConfigListQuery } from '../data/validators'\nimport { emitSsoEvent } from '../events'\nimport { validateDomain, normalizeDomain, uniqueDomains, checkDomainLimit } from '../lib/domains'\nimport type { SsoProviderRegistry } from '../lib/registry'\n\nexport interface SsoAdminScope {\n isSuperAdmin: boolean\n organizationId: string | null\n tenantId: string | null\n}\n\nexport interface SsoConfigPublic {\n id: string\n name: string | null\n tenantId: string | null\n organizationId: string\n protocol: string\n issuer: string | null\n clientId: string | null\n hasClientSecret: boolean\n allowedDomains: string[]\n jitEnabled: boolean\n autoLinkByEmail: boolean\n isActive: boolean\n ssoRequired: boolean\n appRoleMappings: Record<string, string>\n createdAt: Date\n updatedAt: Date\n}\n\nexport class SsoConfigService {\n constructor(\n private em: EntityManager,\n private tenantEncryptionService: TenantDataEncryptionService,\n private ssoProviderRegistry: SsoProviderRegistry,\n ) {}\n\n async list(scope: SsoAdminScope, query: SsoConfigListQuery): Promise<{\n items: SsoConfigPublic[]\n total: number\n totalPages: number\n }> {\n const where: FilterQuery<SsoConfig> = { deletedAt: null }\n\n if (!scope.isSuperAdmin) {\n if (!scope.organizationId) {\n throw new SsoConfigError('Organization context is required', 403)\n }\n where.organizationId = scope.organizationId\n } else {\n if (query.organizationId) where.organizationId = query.organizationId\n if (query.tenantId) where.tenantId = query.tenantId\n }\n\n if (query.search) {\n const pattern = `%${escapeLikePattern(query.search)}%`\n where.$or = [\n { name: { $ilike: pattern } },\n { issuer: { $ilike: pattern } },\n { clientId: { $ilike: pattern } },\n ]\n }\n\n const [configs, total] = await this.em.findAndCount(SsoConfig, where, {\n orderBy: { createdAt: 'desc' },\n limit: query.pageSize,\n offset: (query.page - 1) * query.pageSize,\n })\n\n return {\n items: configs.map((c) => this.toPublic(c)),\n total,\n totalPages: Math.ceil(total / query.pageSize) || 1,\n }\n }\n\n async getById(scope: SsoAdminScope, id: string): Promise<SsoConfigPublic | null> {\n const where: FilterQuery<SsoConfig> = { id, deletedAt: null }\n if (!scope.isSuperAdmin) {\n if (!scope.organizationId) throw new SsoConfigError('Organization context is required', 403)\n where.organizationId = scope.organizationId\n }\n\n const config = await this.em.findOne(SsoConfig, where)\n return config ? this.toPublic(config) : null\n }\n\n async create(scope: SsoAdminScope, input: SsoConfigAdminCreateInput): Promise<SsoConfigPublic> {\n const orgId = scope.isSuperAdmin ? input.organizationId : scope.organizationId!\n const tenId = scope.isSuperAdmin ? (input.tenantId ?? null) : scope.tenantId\n\n const existing = await this.em.findOne(SsoConfig, {\n organizationId: orgId,\n deletedAt: null,\n })\n if (existing) {\n throw new SsoConfigError('An SSO configuration already exists for this organization', 409)\n }\n\n const domains = uniqueDomains(input.allowedDomains)\n for (const d of domains) {\n const result = validateDomain(d)\n if (!result.valid) throw new SsoConfigError(`Invalid domain \"${d}\": ${result.error}`, 400)\n }\n\n const encrypted = await this.tenantEncryptionService.encryptEntityPayload(\n 'SsoConfig',\n { clientSecretEnc: input.clientSecret },\n tenId,\n orgId,\n )\n\n const config = this.em.create(SsoConfig, {\n name: input.name,\n tenantId: tenId,\n organizationId: orgId,\n protocol: input.protocol,\n issuer: input.issuer,\n clientId: input.clientId,\n clientSecretEnc: encrypted.clientSecretEnc as string,\n allowedDomains: domains,\n jitEnabled: input.jitEnabled,\n autoLinkByEmail: input.autoLinkByEmail,\n isActive: false,\n ssoRequired: false,\n appRoleMappings: input.appRoleMappings ?? {},\n } as RequiredEntityData<SsoConfig>)\n\n await this.em.persistAndFlush(config)\n\n void emitSsoEvent('sso.config.created', {\n id: config.id,\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n\n return this.toPublic(config)\n }\n\n async update(scope: SsoAdminScope, id: string, input: SsoConfigAdminUpdateInput): Promise<SsoConfigPublic> {\n const config = await this.resolveConfig(scope, id)\n\n if (input.name !== undefined) config.name = input.name\n if (input.protocol !== undefined) config.protocol = input.protocol\n if (input.issuer !== undefined) config.issuer = input.issuer\n if (input.clientId !== undefined) config.clientId = input.clientId\n if (input.jitEnabled !== undefined) {\n if (input.jitEnabled) {\n const activeScimCount = await this.em.count(ScimToken, { ssoConfigId: id, isActive: true })\n if (activeScimCount > 0) {\n throw new SsoConfigError('Cannot enable JIT provisioning while SCIM directory sync is active. Revoke all SCIM tokens first.', 409)\n }\n }\n config.jitEnabled = input.jitEnabled\n }\n if (input.autoLinkByEmail !== undefined) config.autoLinkByEmail = input.autoLinkByEmail\n if (input.appRoleMappings !== undefined) config.appRoleMappings = input.appRoleMappings\n\n if (input.clientSecret !== undefined) {\n const encrypted = await this.tenantEncryptionService.encryptEntityPayload(\n 'SsoConfig',\n { clientSecretEnc: input.clientSecret },\n config.tenantId,\n config.organizationId,\n )\n config.clientSecretEnc = encrypted.clientSecretEnc as string\n }\n\n await this.em.flush()\n\n void emitSsoEvent('sso.config.updated', {\n id: config.id,\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n\n return this.toPublic(config)\n }\n\n async delete(scope: SsoAdminScope, id: string): Promise<void> {\n const config = await this.resolveConfig(scope, id)\n\n if (config.isActive) {\n throw new SsoConfigError('Cannot delete an active SSO configuration \u2014 deactivate it first', 400)\n }\n\n config.deletedAt = new Date()\n await this.em.flush()\n\n void emitSsoEvent('sso.config.deleted', {\n id: config.id,\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n }\n\n async activate(scope: SsoAdminScope, id: string, active: boolean): Promise<SsoConfigPublic> {\n const config = await this.resolveConfig(scope, id)\n\n if (active) {\n if (config.allowedDomains.length === 0) {\n throw new SsoConfigError('Cannot activate SSO configuration with no allowed domains', 400)\n }\n\n const testResult = await this.testConnectionInternal(config)\n if (!testResult.ok) {\n throw new SsoConfigError(`Cannot activate \u2014 discovery failed: ${testResult.error}`, 400)\n }\n }\n\n const wasActive = config.isActive\n config.isActive = active\n await this.em.flush()\n\n if (active && !wasActive) {\n void emitSsoEvent('sso.config.activated', {\n id: config.id,\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n } else if (!active && wasActive) {\n void emitSsoEvent('sso.config.deactivated', {\n id: config.id,\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n }\n\n return this.toPublic(config)\n }\n\n async testConnection(scope: SsoAdminScope, id: string): Promise<{ ok: boolean; error?: string }> {\n const config = await this.resolveConfig(scope, id)\n return this.testConnectionInternal(config)\n }\n\n async addDomain(scope: SsoAdminScope, id: string, domain: string): Promise<SsoConfigPublic> {\n const normalized = normalizeDomain(domain)\n const validation = validateDomain(normalized)\n if (!validation.valid) throw new SsoConfigError(validation.error!, 400)\n\n const config = await this.resolveConfig(scope, id)\n\n if (config.allowedDomains.includes(normalized)) {\n return this.toPublic(config)\n }\n\n const limitCheck = checkDomainLimit(config.allowedDomains.length, 1)\n if (!limitCheck.ok) throw new SsoConfigError(limitCheck.error!, 400)\n\n config.allowedDomains = [...config.allowedDomains, normalized]\n await this.em.flush()\n\n void emitSsoEvent('sso.domain.added', {\n id: config.id,\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n domain: normalized,\n }).catch((e) => console.error('[SSO Event]', e))\n\n return this.toPublic(config)\n }\n\n async removeDomain(scope: SsoAdminScope, id: string, domain: string): Promise<SsoConfigPublic> {\n const normalized = normalizeDomain(domain)\n const config = await this.resolveConfig(scope, id)\n\n config.allowedDomains = config.allowedDomains.filter((d) => d !== normalized)\n await this.em.flush()\n\n void emitSsoEvent('sso.domain.removed', {\n id: config.id,\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n domain: normalized,\n }).catch((e) => console.error('[SSO Event]', e))\n\n return this.toPublic(config)\n }\n\n toPublic(config: SsoConfig): SsoConfigPublic {\n return {\n id: config.id,\n name: config.name ?? null,\n tenantId: config.tenantId ?? null,\n organizationId: config.organizationId,\n protocol: config.protocol,\n issuer: config.issuer ?? null,\n clientId: config.clientId ?? null,\n hasClientSecret: !!config.clientSecretEnc,\n allowedDomains: config.allowedDomains,\n jitEnabled: config.jitEnabled,\n autoLinkByEmail: config.autoLinkByEmail,\n isActive: config.isActive,\n ssoRequired: config.ssoRequired,\n appRoleMappings: config.appRoleMappings ?? {},\n createdAt: config.createdAt,\n updatedAt: config.updatedAt,\n }\n }\n\n private async resolveConfig(scope: SsoAdminScope, id: string): Promise<SsoConfig> {\n const where: FilterQuery<SsoConfig> = { id, deletedAt: null }\n if (!scope.isSuperAdmin) {\n if (!scope.organizationId) throw new SsoConfigError('Organization context is required', 403)\n where.organizationId = scope.organizationId\n }\n\n const config = await this.em.findOne(SsoConfig, where)\n if (!config) throw new SsoConfigError('SSO configuration not found', 404)\n\n return config\n }\n\n private async testConnectionInternal(config: SsoConfig): Promise<{ ok: boolean; error?: string }> {\n const provider = this.ssoProviderRegistry.resolve(config.protocol)\n if (!provider) return { ok: false, error: `No provider for protocol: ${config.protocol}` }\n\n return provider.validateConfig(config)\n }\n}\n\nexport class SsoConfigError extends Error {\n constructor(\n message: string,\n public readonly statusCode: number,\n ) {\n super(message)\n this.name = 'SsoConfigError'\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { FilterQuery, RequiredEntityData } from '@mikro-orm/core'\nimport { EntityManager } from '@mikro-orm/postgresql'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { SsoConfig, ScimToken } from '../data/entities'\nimport type { SsoConfigAdminCreateInput, SsoConfigAdminUpdateInput, SsoConfigListQuery } from '../data/validators'\nimport { emitSsoEvent } from '../events'\nimport { validateDomain, normalizeDomain, uniqueDomains, checkDomainLimit } from '../lib/domains'\nimport type { SsoProviderRegistry } from '../lib/registry'\n\nexport interface SsoAdminScope {\n isSuperAdmin: boolean\n organizationId: string | null\n tenantId: string | null\n}\n\nexport interface SsoConfigPublic {\n id: string\n name: string | null\n tenantId: string | null\n organizationId: string\n protocol: string\n issuer: string | null\n clientId: string | null\n hasClientSecret: boolean\n allowedDomains: string[]\n jitEnabled: boolean\n autoLinkByEmail: boolean\n isActive: boolean\n ssoRequired: boolean\n appRoleMappings: Record<string, string>\n createdAt: Date\n updatedAt: Date\n}\n\nexport class SsoConfigService {\n constructor(\n private em: EntityManager,\n private tenantEncryptionService: TenantDataEncryptionService,\n private ssoProviderRegistry: SsoProviderRegistry,\n ) {}\n\n async list(scope: SsoAdminScope, query: SsoConfigListQuery): Promise<{\n items: SsoConfigPublic[]\n total: number\n totalPages: number\n }> {\n const where: FilterQuery<SsoConfig> = { deletedAt: null }\n\n if (!scope.isSuperAdmin) {\n if (!scope.organizationId) {\n throw new SsoConfigError('Organization context is required', 403)\n }\n where.organizationId = scope.organizationId\n } else {\n if (query.organizationId) where.organizationId = query.organizationId\n if (query.tenantId) where.tenantId = query.tenantId\n }\n\n if (query.search) {\n const pattern = `%${escapeLikePattern(query.search)}%`\n where.$or = [\n { name: { $ilike: pattern } },\n { issuer: { $ilike: pattern } },\n { clientId: { $ilike: pattern } },\n ]\n }\n\n const [configs, total] = await this.em.findAndCount(SsoConfig, where, {\n orderBy: { createdAt: 'desc' },\n limit: query.pageSize,\n offset: (query.page - 1) * query.pageSize,\n })\n\n return {\n items: configs.map((c) => this.toPublic(c)),\n total,\n totalPages: Math.ceil(total / query.pageSize) || 1,\n }\n }\n\n async getById(scope: SsoAdminScope, id: string): Promise<SsoConfigPublic | null> {\n const where: FilterQuery<SsoConfig> = { id, deletedAt: null }\n if (!scope.isSuperAdmin) {\n if (!scope.organizationId) throw new SsoConfigError('Organization context is required', 403)\n where.organizationId = scope.organizationId\n }\n\n const config = await this.em.findOne(SsoConfig, where)\n return config ? this.toPublic(config) : null\n }\n\n async create(scope: SsoAdminScope, input: SsoConfigAdminCreateInput): Promise<SsoConfigPublic> {\n const orgId = scope.isSuperAdmin ? input.organizationId : scope.organizationId!\n const tenId = scope.isSuperAdmin ? (input.tenantId ?? null) : scope.tenantId\n\n const existing = await this.em.findOne(SsoConfig, {\n organizationId: orgId,\n deletedAt: null,\n })\n if (existing) {\n throw new SsoConfigError('An SSO configuration already exists for this organization', 409)\n }\n\n const domains = uniqueDomains(input.allowedDomains)\n for (const d of domains) {\n const result = validateDomain(d)\n if (!result.valid) throw new SsoConfigError(`Invalid domain \"${d}\": ${result.error}`, 400)\n }\n\n const encrypted = await this.tenantEncryptionService.encryptEntityPayload(\n 'SsoConfig',\n { clientSecretEnc: input.clientSecret },\n tenId,\n orgId,\n )\n\n const config = this.em.create(SsoConfig, {\n name: input.name,\n tenantId: tenId,\n organizationId: orgId,\n protocol: input.protocol,\n issuer: input.issuer,\n clientId: input.clientId,\n clientSecretEnc: encrypted.clientSecretEnc as string,\n allowedDomains: domains,\n jitEnabled: input.jitEnabled,\n autoLinkByEmail: input.autoLinkByEmail,\n isActive: false,\n ssoRequired: false,\n appRoleMappings: input.appRoleMappings ?? {},\n } as RequiredEntityData<SsoConfig>)\n\n await this.em.persist(config).flush()\n\n void emitSsoEvent('sso.config.created', {\n id: config.id,\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n\n return this.toPublic(config)\n }\n\n async update(scope: SsoAdminScope, id: string, input: SsoConfigAdminUpdateInput): Promise<SsoConfigPublic> {\n const config = await this.resolveConfig(scope, id)\n\n if (input.name !== undefined) config.name = input.name\n if (input.protocol !== undefined) config.protocol = input.protocol\n if (input.issuer !== undefined) config.issuer = input.issuer\n if (input.clientId !== undefined) config.clientId = input.clientId\n if (input.jitEnabled !== undefined) {\n if (input.jitEnabled) {\n const activeScimCount = await this.em.count(ScimToken, { ssoConfigId: id, isActive: true })\n if (activeScimCount > 0) {\n throw new SsoConfigError('Cannot enable JIT provisioning while SCIM directory sync is active. Revoke all SCIM tokens first.', 409)\n }\n }\n config.jitEnabled = input.jitEnabled\n }\n if (input.autoLinkByEmail !== undefined) config.autoLinkByEmail = input.autoLinkByEmail\n if (input.appRoleMappings !== undefined) config.appRoleMappings = input.appRoleMappings\n\n if (input.clientSecret !== undefined) {\n const encrypted = await this.tenantEncryptionService.encryptEntityPayload(\n 'SsoConfig',\n { clientSecretEnc: input.clientSecret },\n config.tenantId,\n config.organizationId,\n )\n config.clientSecretEnc = encrypted.clientSecretEnc as string\n }\n\n await this.em.flush()\n\n void emitSsoEvent('sso.config.updated', {\n id: config.id,\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n\n return this.toPublic(config)\n }\n\n async delete(scope: SsoAdminScope, id: string): Promise<void> {\n const config = await this.resolveConfig(scope, id)\n\n if (config.isActive) {\n throw new SsoConfigError('Cannot delete an active SSO configuration \u2014 deactivate it first', 400)\n }\n\n config.deletedAt = new Date()\n await this.em.flush()\n\n void emitSsoEvent('sso.config.deleted', {\n id: config.id,\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n }\n\n async activate(scope: SsoAdminScope, id: string, active: boolean): Promise<SsoConfigPublic> {\n const config = await this.resolveConfig(scope, id)\n\n if (active) {\n if (config.allowedDomains.length === 0) {\n throw new SsoConfigError('Cannot activate SSO configuration with no allowed domains', 400)\n }\n\n const testResult = await this.testConnectionInternal(config)\n if (!testResult.ok) {\n throw new SsoConfigError(`Cannot activate \u2014 discovery failed: ${testResult.error}`, 400)\n }\n }\n\n const wasActive = config.isActive\n config.isActive = active\n await this.em.flush()\n\n if (active && !wasActive) {\n void emitSsoEvent('sso.config.activated', {\n id: config.id,\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n } else if (!active && wasActive) {\n void emitSsoEvent('sso.config.deactivated', {\n id: config.id,\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n }\n\n return this.toPublic(config)\n }\n\n async testConnection(scope: SsoAdminScope, id: string): Promise<{ ok: boolean; error?: string }> {\n const config = await this.resolveConfig(scope, id)\n return this.testConnectionInternal(config)\n }\n\n async addDomain(scope: SsoAdminScope, id: string, domain: string): Promise<SsoConfigPublic> {\n const normalized = normalizeDomain(domain)\n const validation = validateDomain(normalized)\n if (!validation.valid) throw new SsoConfigError(validation.error!, 400)\n\n const config = await this.resolveConfig(scope, id)\n\n if (config.allowedDomains.includes(normalized)) {\n return this.toPublic(config)\n }\n\n const limitCheck = checkDomainLimit(config.allowedDomains.length, 1)\n if (!limitCheck.ok) throw new SsoConfigError(limitCheck.error!, 400)\n\n config.allowedDomains = [...config.allowedDomains, normalized]\n await this.em.flush()\n\n void emitSsoEvent('sso.domain.added', {\n id: config.id,\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n domain: normalized,\n }).catch((e) => console.error('[SSO Event]', e))\n\n return this.toPublic(config)\n }\n\n async removeDomain(scope: SsoAdminScope, id: string, domain: string): Promise<SsoConfigPublic> {\n const normalized = normalizeDomain(domain)\n const config = await this.resolveConfig(scope, id)\n\n config.allowedDomains = config.allowedDomains.filter((d) => d !== normalized)\n await this.em.flush()\n\n void emitSsoEvent('sso.domain.removed', {\n id: config.id,\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n domain: normalized,\n }).catch((e) => console.error('[SSO Event]', e))\n\n return this.toPublic(config)\n }\n\n toPublic(config: SsoConfig): SsoConfigPublic {\n return {\n id: config.id,\n name: config.name ?? null,\n tenantId: config.tenantId ?? null,\n organizationId: config.organizationId,\n protocol: config.protocol,\n issuer: config.issuer ?? null,\n clientId: config.clientId ?? null,\n hasClientSecret: !!config.clientSecretEnc,\n allowedDomains: config.allowedDomains,\n jitEnabled: config.jitEnabled,\n autoLinkByEmail: config.autoLinkByEmail,\n isActive: config.isActive,\n ssoRequired: config.ssoRequired,\n appRoleMappings: config.appRoleMappings ?? {},\n createdAt: config.createdAt,\n updatedAt: config.updatedAt,\n }\n }\n\n private async resolveConfig(scope: SsoAdminScope, id: string): Promise<SsoConfig> {\n const where: FilterQuery<SsoConfig> = { id, deletedAt: null }\n if (!scope.isSuperAdmin) {\n if (!scope.organizationId) throw new SsoConfigError('Organization context is required', 403)\n where.organizationId = scope.organizationId\n }\n\n const config = await this.em.findOne(SsoConfig, where)\n if (!config) throw new SsoConfigError('SSO configuration not found', 404)\n\n return config\n }\n\n private async testConnectionInternal(config: SsoConfig): Promise<{ ok: boolean; error?: string }> {\n const provider = this.ssoProviderRegistry.resolve(config.protocol)\n if (!provider) return { ok: false, error: `No provider for protocol: ${config.protocol}` }\n\n return provider.validateConfig(config)\n }\n}\n\nexport class SsoConfigError extends Error {\n constructor(\n message: string,\n public readonly statusCode: number,\n ) {\n super(message)\n this.name = 'SsoConfigError'\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -36,7 +36,7 @@ const setup = {
36
36
  isActive: true,
37
37
  ssoRequired: false
38
38
  });
39
- await em.persistAndFlush(config);
39
+ await em.persist(config).flush();
40
40
  }
41
41
  };
42
42
  var setup_default = setup;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/sso/setup.ts"],
4
- "sourcesContent": ["import type { RequiredEntityData } from '@mikro-orm/core'\nimport type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { SsoConfig } from './data/entities'\n\nexport const setup: ModuleSetupConfig = {\n defaultRoleFeatures: {\n superadmin: ['sso.*'],\n admin: ['sso.config.view', 'sso.config.manage', 'sso.scim.manage'],\n },\n\n async seedDefaults({ em, tenantId, organizationId, container }) {\n if (process.env.NODE_ENV !== 'development') return\n if (process.env.SSO_DEV_SEED !== 'true') return\n\n const clientSecret = process.env.SSO_DEV_CLIENT_SECRET\n if (!clientSecret) return\n\n const domains = (process.env.SSO_DEV_ALLOWED_DOMAINS || 'example.com')\n .split(',')\n .map((d) => d.trim())\n .filter(Boolean)\n\n const existing = await em.findOne(SsoConfig, { organizationId })\n if (existing) {\n existing.allowedDomains = domains\n await em.flush()\n return\n }\n\n const encryptionService = container.resolve<TenantDataEncryptionService>('tenantEncryptionService')\n const encrypted = await encryptionService.encryptEntityPayload(\n 'SsoConfig',\n { clientSecretEnc: clientSecret },\n tenantId,\n organizationId,\n )\n\n const config = em.create(SsoConfig, {\n tenantId,\n organizationId,\n protocol: 'oidc',\n issuer: process.env.SSO_DEV_ISSUER || 'http://localhost:8080/realms/open-mercato',\n clientId: process.env.SSO_DEV_CLIENT_ID || 'open-mercato-app',\n clientSecretEnc: encrypted.clientSecretEnc as string,\n allowedDomains: domains,\n jitEnabled: true,\n autoLinkByEmail: true,\n isActive: true,\n ssoRequired: false,\n } as RequiredEntityData<SsoConfig>)\n await em.persistAndFlush(config)\n },\n}\n\nexport default setup\n"],
5
- "mappings": "AAGA,SAAS,iBAAiB;AAEnB,MAAM,QAA2B;AAAA,EACtC,qBAAqB;AAAA,IACnB,YAAY,CAAC,OAAO;AAAA,IACpB,OAAO,CAAC,mBAAmB,qBAAqB,iBAAiB;AAAA,EACnE;AAAA,EAEA,MAAM,aAAa,EAAE,IAAI,UAAU,gBAAgB,UAAU,GAAG;AAC9D,QAAI,QAAQ,IAAI,aAAa,cAAe;AAC5C,QAAI,QAAQ,IAAI,iBAAiB,OAAQ;AAEzC,UAAM,eAAe,QAAQ,IAAI;AACjC,QAAI,CAAC,aAAc;AAEnB,UAAM,WAAW,QAAQ,IAAI,2BAA2B,eACrD,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO;AAEjB,UAAM,WAAW,MAAM,GAAG,QAAQ,WAAW,EAAE,eAAe,CAAC;AAC/D,QAAI,UAAU;AACZ,eAAS,iBAAiB;AAC1B,YAAM,GAAG,MAAM;AACf;AAAA,IACF;AAEA,UAAM,oBAAoB,UAAU,QAAqC,yBAAyB;AAClG,UAAM,YAAY,MAAM,kBAAkB;AAAA,MACxC;AAAA,MACA,EAAE,iBAAiB,aAAa;AAAA,MAChC;AAAA,MACA;AAAA,IACF;AAEA,UAAM,SAAS,GAAG,OAAO,WAAW;AAAA,MAClC;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,QAAQ,QAAQ,IAAI,kBAAkB;AAAA,MACtC,UAAU,QAAQ,IAAI,qBAAqB;AAAA,MAC3C,iBAAiB,UAAU;AAAA,MAC3B,gBAAgB;AAAA,MAChB,YAAY;AAAA,MACZ,iBAAiB;AAAA,MACjB,UAAU;AAAA,MACV,aAAa;AAAA,IACf,CAAkC;AAClC,UAAM,GAAG,gBAAgB,MAAM;AAAA,EACjC;AACF;AAEA,IAAO,gBAAQ;",
4
+ "sourcesContent": ["import type { RequiredEntityData } from '@mikro-orm/core'\nimport type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { SsoConfig } from './data/entities'\n\nexport const setup: ModuleSetupConfig = {\n defaultRoleFeatures: {\n superadmin: ['sso.*'],\n admin: ['sso.config.view', 'sso.config.manage', 'sso.scim.manage'],\n },\n\n async seedDefaults({ em, tenantId, organizationId, container }) {\n if (process.env.NODE_ENV !== 'development') return\n if (process.env.SSO_DEV_SEED !== 'true') return\n\n const clientSecret = process.env.SSO_DEV_CLIENT_SECRET\n if (!clientSecret) return\n\n const domains = (process.env.SSO_DEV_ALLOWED_DOMAINS || 'example.com')\n .split(',')\n .map((d) => d.trim())\n .filter(Boolean)\n\n const existing = await em.findOne(SsoConfig, { organizationId })\n if (existing) {\n existing.allowedDomains = domains\n await em.flush()\n return\n }\n\n const encryptionService = container.resolve<TenantDataEncryptionService>('tenantEncryptionService')\n const encrypted = await encryptionService.encryptEntityPayload(\n 'SsoConfig',\n { clientSecretEnc: clientSecret },\n tenantId,\n organizationId,\n )\n\n const config = em.create(SsoConfig, {\n tenantId,\n organizationId,\n protocol: 'oidc',\n issuer: process.env.SSO_DEV_ISSUER || 'http://localhost:8080/realms/open-mercato',\n clientId: process.env.SSO_DEV_CLIENT_ID || 'open-mercato-app',\n clientSecretEnc: encrypted.clientSecretEnc as string,\n allowedDomains: domains,\n jitEnabled: true,\n autoLinkByEmail: true,\n isActive: true,\n ssoRequired: false,\n } as RequiredEntityData<SsoConfig>)\n await em.persist(config).flush()\n },\n}\n\nexport default setup\n"],
5
+ "mappings": "AAGA,SAAS,iBAAiB;AAEnB,MAAM,QAA2B;AAAA,EACtC,qBAAqB;AAAA,IACnB,YAAY,CAAC,OAAO;AAAA,IACpB,OAAO,CAAC,mBAAmB,qBAAqB,iBAAiB;AAAA,EACnE;AAAA,EAEA,MAAM,aAAa,EAAE,IAAI,UAAU,gBAAgB,UAAU,GAAG;AAC9D,QAAI,QAAQ,IAAI,aAAa,cAAe;AAC5C,QAAI,QAAQ,IAAI,iBAAiB,OAAQ;AAEzC,UAAM,eAAe,QAAQ,IAAI;AACjC,QAAI,CAAC,aAAc;AAEnB,UAAM,WAAW,QAAQ,IAAI,2BAA2B,eACrD,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO;AAEjB,UAAM,WAAW,MAAM,GAAG,QAAQ,WAAW,EAAE,eAAe,CAAC;AAC/D,QAAI,UAAU;AACZ,eAAS,iBAAiB;AAC1B,YAAM,GAAG,MAAM;AACf;AAAA,IACF;AAEA,UAAM,oBAAoB,UAAU,QAAqC,yBAAyB;AAClG,UAAM,YAAY,MAAM,kBAAkB;AAAA,MACxC;AAAA,MACA,EAAE,iBAAiB,aAAa;AAAA,MAChC;AAAA,MACA;AAAA,IACF;AAEA,UAAM,SAAS,GAAG,OAAO,WAAW;AAAA,MAClC;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,QAAQ,QAAQ,IAAI,kBAAkB;AAAA,MACtC,UAAU,QAAQ,IAAI,qBAAqB;AAAA,MAC3C,iBAAiB,UAAU;AAAA,MAC3B,gBAAgB;AAAA,MAChB,YAAY;AAAA,MACZ,iBAAiB;AAAA,MACjB,UAAU;AAAA,MACV,aAAa;AAAA,IACf,CAAkC;AAClC,UAAM,GAAG,QAAQ,MAAM,EAAE,MAAM;AAAA,EACjC;AACF;AAEA,IAAO,gBAAQ;",
6
6
  "names": []
7
7
  }
package/jest.config.cjs CHANGED
@@ -1,6 +1,5 @@
1
1
  /** @type {import('jest').Config} */
2
2
  module.exports = {
3
- preset: 'ts-jest',
4
3
  testEnvironment: 'node',
5
4
  watchman: false,
6
5
  rootDir: '.',
@@ -14,7 +13,7 @@ module.exports = {
14
13
  },
15
14
  transform: {
16
15
  '^.+\\.(t|j)sx?$': [
17
- 'ts-jest',
16
+ '<rootDir>/../../scripts/jest-mikroorm-transformer.cjs',
18
17
  {
19
18
  tsconfig: {
20
19
  jsx: 'react-jsx',
@@ -22,6 +21,9 @@ module.exports = {
22
21
  },
23
22
  ],
24
23
  },
24
+ transformIgnorePatterns: [
25
+ 'node_modules/(?!(@mikro-orm)/)',
26
+ ],
25
27
  testMatch: ['<rootDir>/src/**/__tests__/**/*.test.(ts|tsx)'],
26
28
  passWithNoTests: true,
27
29
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/enterprise",
3
- "version": "0.5.1-develop.2691.d8a0934b37",
3
+ "version": "0.5.1-develop.2694.732417c5ec",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -64,8 +64,8 @@
64
64
  }
65
65
  },
66
66
  "dependencies": {
67
- "@open-mercato/core": "0.5.1-develop.2691.d8a0934b37",
68
- "@open-mercato/ui": "0.5.1-develop.2691.d8a0934b37",
67
+ "@open-mercato/core": "0.5.1-develop.2694.732417c5ec",
68
+ "@open-mercato/ui": "0.5.1-develop.2694.732417c5ec",
69
69
  "@simplewebauthn/browser": "^13.3.0",
70
70
  "@simplewebauthn/server": "^13.3.0",
71
71
  "@simplewebauthn/types": "^12.0.0",
@@ -75,10 +75,10 @@
75
75
  "qrcode": "^1.5.4"
76
76
  },
77
77
  "peerDependencies": {
78
- "@open-mercato/shared": "0.5.1-develop.2691.d8a0934b37"
78
+ "@open-mercato/shared": "0.5.1-develop.2694.732417c5ec"
79
79
  },
80
80
  "devDependencies": {
81
- "@open-mercato/shared": "0.5.1-develop.2691.d8a0934b37",
81
+ "@open-mercato/shared": "0.5.1-develop.2694.732417c5ec",
82
82
  "@types/jest": "^30.0.0",
83
83
  "jest": "^30.3.0",
84
84
  "ts-jest": "^29.4.9"
@@ -1,4 +1,5 @@
1
- import { Entity, Index, OptionalProps, PrimaryKey, Property, Unique } from '@mikro-orm/core'
1
+ import { OptionalProps } from '@mikro-orm/core'
2
+ import { Entity, Index, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'
2
3
 
3
4
  export type RecordLockStatus = 'active' | 'released' | 'expired' | 'force_released'
4
5
  export type RecordLockStrategy = 'optimistic' | 'pessimistic'
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from 'crypto'
2
2
  import { UniqueConstraintViolationException, type FilterQuery } from '@mikro-orm/core'
3
3
  import type { EntityManager } from '@mikro-orm/postgresql'
4
- import type { Knex } from 'knex'
4
+ import { type Kysely, sql } from 'kysely'
5
5
  import type { ModuleConfigService } from '@open-mercato/core/modules/configs/lib/module-config-service'
6
6
  import { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'
7
7
  import type { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'
@@ -268,8 +268,8 @@ function isActiveLockScopeUniqueViolation(error: unknown): boolean {
268
268
  return false
269
269
  }
270
270
 
271
- function getKnex(em: EntityManager): Knex {
272
- return (em.getConnection() as unknown as { getKnex: () => Knex }).getKnex()
271
+ function getKysely(em: EntityManager): Kysely<any> {
272
+ return (em as unknown as { getKysely: () => Kysely<any> }).getKysely()
273
273
  }
274
274
 
275
275
  const SKIPPED_CONFLICT_FIELDS = new Set([
@@ -1260,39 +1260,44 @@ export class RecordLockService {
1260
1260
 
1261
1261
  private async cleanupHistoricalRecords(tenantId: string): Promise<void> {
1262
1262
  try {
1263
- const knex = getKnex(this.em)
1263
+ const db = getKysely(this.em)
1264
1264
  const now = Date.now()
1265
1265
  const lockCutoff = new Date(now - LOCK_RETENTION_MS)
1266
1266
  const resolvedConflictCutoff = new Date(now - RESOLVED_CONFLICT_RETENTION_MS)
1267
1267
  const pendingConflictCutoff = new Date(now - PENDING_CONFLICT_RETENTION_MS)
1268
1268
  const deletedAt = new Date(now)
1269
1269
 
1270
- await knex('record_locks')
1271
- .where({ tenant_id: tenantId })
1272
- .whereNull('deleted_at')
1273
- .whereNot('status', ACTIVE_LOCK_STATUS)
1274
- .andWhere('updated_at', '<', lockCutoff)
1275
- .update({
1270
+ await db
1271
+ .updateTable('record_locks' as any)
1272
+ .set({
1276
1273
  deleted_at: deletedAt,
1277
1274
  updated_at: deletedAt,
1278
- })
1279
-
1280
- await knex('record_lock_conflicts')
1281
- .where({ tenant_id: tenantId })
1282
- .whereNull('deleted_at')
1283
- .andWhere((query) => {
1284
- query
1285
- .where((pending) => {
1286
- pending.where('status', 'pending').andWhere('created_at', '<', pendingConflictCutoff)
1287
- })
1288
- .orWhere((resolved) => {
1289
- resolved.whereNot('status', 'pending').andWhere('updated_at', '<', resolvedConflictCutoff)
1290
- })
1291
- })
1292
- .update({
1275
+ } as any)
1276
+ .where('tenant_id' as any, '=', tenantId)
1277
+ .where('deleted_at' as any, 'is', null as any)
1278
+ .where('status' as any, '!=', ACTIVE_LOCK_STATUS)
1279
+ .where('updated_at' as any, '<', lockCutoff)
1280
+ .execute()
1281
+
1282
+ await db
1283
+ .updateTable('record_lock_conflicts' as any)
1284
+ .set({
1293
1285
  deleted_at: deletedAt,
1294
1286
  updated_at: deletedAt,
1295
- })
1287
+ } as any)
1288
+ .where('tenant_id' as any, '=', tenantId)
1289
+ .where('deleted_at' as any, 'is', null as any)
1290
+ .where((eb: any) => eb.or([
1291
+ eb.and([
1292
+ eb('status' as any, '=', 'pending'),
1293
+ eb('created_at' as any, '<', pendingConflictCutoff),
1294
+ ]),
1295
+ eb.and([
1296
+ eb('status' as any, '!=', 'pending'),
1297
+ eb('updated_at' as any, '<', resolvedConflictCutoff),
1298
+ ]),
1299
+ ]))
1300
+ .execute()
1296
1301
  } catch {
1297
1302
  // Best-effort cleanup must never fail lock workflows.
1298
1303
  }
@@ -1644,8 +1649,8 @@ export class RecordLockService {
1644
1649
 
1645
1650
  const result = await this.em.transactional(async (tx) => {
1646
1651
  try {
1647
- const knex = getKnex(tx as EntityManager)
1648
- await knex.raw('select pg_advisory_xact_lock(hashtext(?))', [dedupeKey])
1652
+ const db = getKysely(tx as EntityManager)
1653
+ await sql`select pg_advisory_xact_lock(hashtext(${dedupeKey}))`.execute(db)
1649
1654
  } catch {
1650
1655
  // Best-effort lock; fallback to find-first behavior below.
1651
1656
  }
@@ -1,4 +1,4 @@
1
- import { Entity, Index, PrimaryKey, Property } from '@mikro-orm/core'
1
+ import { Entity, Index, PrimaryKey, Property } from '@mikro-orm/decorators/legacy'
2
2
  import {
3
3
  ChallengeMethod,
4
4
  EnforcementScope,
@@ -1,4 +1,4 @@
1
- import { Entity, PrimaryKey, Property, Unique, Index } from '@mikro-orm/core'
1
+ import { Entity, Index, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'
2
2
 
3
3
  @Entity({ tableName: 'sso_configs' })
4
4
  // Unique index on organization_id (partial: WHERE deleted_at IS NULL) — managed by migration
@@ -120,7 +120,7 @@ export class AccountLinkingService {
120
120
  createdAt: now,
121
121
  updatedAt: now,
122
122
  } as RequiredEntityData<SsoIdentity>)
123
- await this.em.persistAndFlush(identity)
123
+ await this.em.persist(identity).flush()
124
124
 
125
125
  void emitSsoEvent('sso.identity.linked', {
126
126
  id: identity.id,
@@ -147,7 +147,7 @@ export class AccountLinkingService {
147
147
  isConfirmed: true,
148
148
  createdAt: new Date(),
149
149
  })
150
- await txEm.persistAndFlush(user)
150
+ await txEm.persist(user).flush()
151
151
 
152
152
  await this.assignRolesFromSso(txEm, user, config, tenantId, idpPayload.groups)
153
153
 
@@ -167,7 +167,7 @@ export class AccountLinkingService {
167
167
  createdAt: now,
168
168
  updatedAt: now,
169
169
  } as RequiredEntityData<SsoIdentity>)
170
- await txEm.persistAndFlush(identity)
170
+ await txEm.persist(identity).flush()
171
171
 
172
172
  void emitSsoEvent('sso.identity.created', {
173
173
  id: identity.id,
@@ -290,7 +290,7 @@ export class AccountLinkingService {
290
290
  if (existingLink) return
291
291
 
292
292
  const userRole = em.create(UserRole, { user, role, createdAt: new Date() })
293
- await em.persistAndFlush(userRole)
293
+ await em.persist(userRole).flush()
294
294
  }
295
295
  }
296
296
 
@@ -1,4 +1,5 @@
1
1
  import { EntityManager } from '@mikro-orm/postgresql'
2
+ import { type Kysely, sql } from 'kysely'
2
3
  import { SsoConfig } from '../data/entities'
3
4
 
4
5
  export class HrdService {
@@ -8,15 +9,17 @@ export class HrdService {
8
9
  const domain = email.split('@')[1]?.toLowerCase()
9
10
  if (!domain) return null
10
11
 
11
- const knex = this.em.getKnex()
12
- const row = await knex('sso_configs')
13
- .whereRaw("allowed_domains @> ?::jsonb", [JSON.stringify([domain])])
14
- .where('is_active', true)
15
- .whereNull('deleted_at')
16
- .first()
12
+ const db = (this.em as any).getKysely() as Kysely<any>
13
+ const row = await db
14
+ .selectFrom('sso_configs' as any)
15
+ .selectAll()
16
+ .where(sql<boolean>`allowed_domains @> ${JSON.stringify([domain])}::jsonb`)
17
+ .where('is_active' as any, '=', true)
18
+ .where('deleted_at' as any, 'is', null as any)
19
+ .executeTakeFirst()
17
20
 
18
21
  if (!row) return null
19
22
 
20
- return this.em.map(SsoConfig, row)
23
+ return this.em.map(SsoConfig, row as Record<string, unknown>)
21
24
  }
22
25
  }
@@ -92,7 +92,7 @@ export class ScimService {
92
92
  createdAt: now,
93
93
  updatedAt: now,
94
94
  } as RequiredEntityData<SsoIdentity>)
95
- await this.em.persistAndFlush(identity)
95
+ await this.em.persist(identity).flush()
96
96
 
97
97
  const deactivation = parsed.active === false
98
98
  ? await this.createDeactivation(existingUser.id, scope)
@@ -117,7 +117,7 @@ export class ScimService {
117
117
  isConfirmed: true,
118
118
  createdAt: new Date(),
119
119
  })
120
- await txEm.persistAndFlush(user)
120
+ await txEm.persist(user).flush()
121
121
 
122
122
  const now = new Date()
123
123
  const identity = txEm.create(SsoIdentity, {
@@ -134,7 +134,7 @@ export class ScimService {
134
134
  createdAt: now,
135
135
  updatedAt: now,
136
136
  } as RequiredEntityData<SsoIdentity>)
137
- await txEm.persistAndFlush(identity)
137
+ await txEm.persist(identity).flush()
138
138
 
139
139
  const deactivation = parsed.active === false
140
140
  ? await this.createDeactivationTx(txEm, user.id, scope)
@@ -384,7 +384,7 @@ export class ScimService {
384
384
  ssoConfigId: scope.ssoConfigId,
385
385
  deactivatedAt: new Date(),
386
386
  } as RequiredEntityData<SsoUserDeactivation>)
387
- await this.em.persistAndFlush(deactivation)
387
+ await this.em.persist(deactivation).flush()
388
388
  return deactivation
389
389
  }
390
390
 
@@ -396,7 +396,7 @@ export class ScimService {
396
396
  ssoConfigId: scope.ssoConfigId,
397
397
  deactivatedAt: new Date(),
398
398
  } as RequiredEntityData<SsoUserDeactivation>)
399
- await txEm.persistAndFlush(deactivation)
399
+ await txEm.persist(deactivation).flush()
400
400
  return deactivation
401
401
  }
402
402
 
@@ -419,7 +419,7 @@ export class ScimService {
419
419
  responseStatus,
420
420
  errorMessage: errorMessage ?? null,
421
421
  } as RequiredEntityData<ScimProvisioningLog>)
422
- await this.em.persistAndFlush(entry)
422
+ await this.em.persist(entry).flush()
423
423
  }
424
424
 
425
425
  private async logTx(
@@ -440,7 +440,7 @@ export class ScimService {
440
440
  scimExternalId: externalId ?? null,
441
441
  responseStatus,
442
442
  } as RequiredEntityData<ScimProvisioningLog>)
443
- await txEm.persistAndFlush(entry)
443
+ await txEm.persist(entry).flush()
444
444
  }
445
445
  }
446
446
 
@@ -58,7 +58,7 @@ export class ScimTokenService {
58
58
  organizationId: scope.organizationId!,
59
59
  } as RequiredEntityData<ScimToken>)
60
60
 
61
- await this.em.persistAndFlush(token)
61
+ await this.em.persist(token).flush()
62
62
 
63
63
  return { id: token.id, token: raw, prefix: tokenPrefix, name }
64
64
  }
@@ -132,7 +132,7 @@ export class SsoConfigService {
132
132
  appRoleMappings: input.appRoleMappings ?? {},
133
133
  } as RequiredEntityData<SsoConfig>)
134
134
 
135
- await this.em.persistAndFlush(config)
135
+ await this.em.persist(config).flush()
136
136
 
137
137
  void emitSsoEvent('sso.config.created', {
138
138
  id: config.id,
@@ -49,7 +49,7 @@ export const setup: ModuleSetupConfig = {
49
49
  isActive: true,
50
50
  ssoRequired: false,
51
51
  } as RequiredEntityData<SsoConfig>)
52
- await em.persistAndFlush(config)
52
+ await em.persist(config).flush()
53
53
  },
54
54
  }
55
55