@open-mercato/enterprise 0.6.6-develop.6465.1.019f0fb26f → 0.6.6-develop.6471.1.9ab5bdd79a

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 (66) hide show
  1. package/dist/modules/record_locks/subscribers/conflict-detected-notification.js +3 -1
  2. package/dist/modules/record_locks/subscribers/conflict-detected-notification.js.map +2 -2
  3. package/dist/modules/record_locks/subscribers/conflict-resolved-notification.js +3 -1
  4. package/dist/modules/record_locks/subscribers/conflict-resolved-notification.js.map +2 -2
  5. package/dist/modules/record_locks/subscribers/lock-contended-notification.js +3 -1
  6. package/dist/modules/record_locks/subscribers/lock-contended-notification.js.map +2 -2
  7. package/dist/modules/record_locks/subscribers/lock-force-released-notification.js +3 -1
  8. package/dist/modules/record_locks/subscribers/lock-force-released-notification.js.map +2 -2
  9. package/dist/modules/record_locks/widgets/injection/record-locking/widget.client.js +4 -2
  10. package/dist/modules/record_locks/widgets/injection/record-locking/widget.client.js.map +2 -2
  11. package/dist/modules/security/api/enforcement/_shared.js +3 -1
  12. package/dist/modules/security/api/enforcement/_shared.js.map +2 -2
  13. package/dist/modules/security/api/mfa/_shared.js +3 -1
  14. package/dist/modules/security/api/mfa/_shared.js.map +2 -2
  15. package/dist/modules/security/api/profile/password/route.js +3 -1
  16. package/dist/modules/security/api/profile/password/route.js.map +2 -2
  17. package/dist/modules/security/api/sudo/_shared.js +3 -1
  18. package/dist/modules/security/api/sudo/_shared.js.map +2 -2
  19. package/dist/modules/security/api/users/_shared.js +3 -1
  20. package/dist/modules/security/api/users/_shared.js.map +2 -2
  21. package/dist/modules/sso/api/callback/oidc/route.js +5 -3
  22. package/dist/modules/sso/api/callback/oidc/route.js.map +2 -2
  23. package/dist/modules/sso/api/error-handler.js +4 -2
  24. package/dist/modules/sso/api/error-handler.js.map +2 -2
  25. package/dist/modules/sso/api/hrd/route.js +3 -1
  26. package/dist/modules/sso/api/hrd/route.js.map +2 -2
  27. package/dist/modules/sso/api/initiate/route.js +4 -2
  28. package/dist/modules/sso/api/initiate/route.js.map +2 -2
  29. package/dist/modules/sso/backend/sso/config/[id]/page.js +2 -2
  30. package/dist/modules/sso/backend/sso/config/[id]/page.js.map +2 -2
  31. package/dist/modules/sso/i18n/de.json +2 -2
  32. package/dist/modules/sso/i18n/en.json +2 -2
  33. package/dist/modules/sso/i18n/es.json +2 -2
  34. package/dist/modules/sso/i18n/pl.json +2 -2
  35. package/dist/modules/sso/services/accountLinkingService.js +9 -46
  36. package/dist/modules/sso/services/accountLinkingService.js.map +2 -2
  37. package/dist/modules/sso/services/ssoConfigService.js +9 -7
  38. package/dist/modules/sso/services/ssoConfigService.js.map +2 -2
  39. package/dist/modules/sso/services/ssoService.js +4 -2
  40. package/dist/modules/sso/services/ssoService.js.map +2 -2
  41. package/dist/modules/system_status_overlays/subscribers/application-bootstrap-enterprise-warning.js +4 -2
  42. package/dist/modules/system_status_overlays/subscribers/application-bootstrap-enterprise-warning.js.map +2 -2
  43. package/package.json +5 -5
  44. package/src/modules/record_locks/subscribers/conflict-detected-notification.ts +4 -1
  45. package/src/modules/record_locks/subscribers/conflict-resolved-notification.ts +4 -1
  46. package/src/modules/record_locks/subscribers/lock-contended-notification.ts +4 -1
  47. package/src/modules/record_locks/subscribers/lock-force-released-notification.ts +4 -1
  48. package/src/modules/record_locks/widgets/injection/record-locking/widget.client.tsx +5 -2
  49. package/src/modules/security/api/enforcement/_shared.ts +4 -1
  50. package/src/modules/security/api/mfa/_shared.ts +4 -1
  51. package/src/modules/security/api/profile/password/route.ts +4 -1
  52. package/src/modules/security/api/sudo/_shared.ts +4 -1
  53. package/src/modules/security/api/users/_shared.ts +4 -1
  54. package/src/modules/sso/api/callback/oidc/route.ts +6 -3
  55. package/src/modules/sso/api/error-handler.ts +5 -2
  56. package/src/modules/sso/api/hrd/route.ts +4 -1
  57. package/src/modules/sso/api/initiate/route.ts +5 -2
  58. package/src/modules/sso/backend/sso/config/[id]/page.tsx +2 -2
  59. package/src/modules/sso/i18n/de.json +2 -2
  60. package/src/modules/sso/i18n/en.json +2 -2
  61. package/src/modules/sso/i18n/es.json +2 -2
  62. package/src/modules/sso/i18n/pl.json +2 -2
  63. package/src/modules/sso/services/accountLinkingService.ts +10 -52
  64. package/src/modules/sso/services/ssoConfigService.ts +10 -7
  65. package/src/modules/sso/services/ssoService.ts +5 -2
  66. package/src/modules/system_status_overlays/subscribers/application-bootstrap-enterprise-warning.ts +6 -2
@@ -3,6 +3,8 @@ import { escapeLikePattern } from "@open-mercato/shared/lib/db/escapeLikePattern
3
3
  import { SsoConfig, ScimToken } from "../data/entities.js";
4
4
  import { emitSsoEvent } from "../events.js";
5
5
  import { validateDomain, normalizeDomain, uniqueDomains, checkDomainLimit } from "../lib/domains.js";
6
+ import { createLogger } from "@open-mercato/shared/lib/logger";
7
+ const logger = createLogger("sso").child({ component: "config" });
6
8
  class SsoConfigService {
7
9
  constructor(em, tenantEncryptionService, ssoProviderRegistry) {
8
10
  this.em = em;
@@ -89,7 +91,7 @@ class SsoConfigService {
89
91
  id: config.id,
90
92
  tenantId: config.tenantId,
91
93
  organizationId: config.organizationId
92
- }).catch((e) => console.error("[SSO Event]", e));
94
+ }).catch((eventError) => logger.error("SSO event emit failed", { err: eventError }));
93
95
  return this.toPublic(config);
94
96
  }
95
97
  async update(scope, id, input) {
@@ -123,7 +125,7 @@ class SsoConfigService {
123
125
  id: config.id,
124
126
  tenantId: config.tenantId,
125
127
  organizationId: config.organizationId
126
- }).catch((e) => console.error("[SSO Event]", e));
128
+ }).catch((eventError) => logger.error("SSO event emit failed", { err: eventError }));
127
129
  return this.toPublic(config);
128
130
  }
129
131
  async delete(scope, id) {
@@ -137,7 +139,7 @@ class SsoConfigService {
137
139
  id: config.id,
138
140
  tenantId: config.tenantId,
139
141
  organizationId: config.organizationId
140
- }).catch((e) => console.error("[SSO Event]", e));
142
+ }).catch((eventError) => logger.error("SSO event emit failed", { err: eventError }));
141
143
  }
142
144
  async activate(scope, id, active) {
143
145
  return this.em.transactional(async (txEm) => {
@@ -163,13 +165,13 @@ class SsoConfigService {
163
165
  id: config.id,
164
166
  tenantId: config.tenantId,
165
167
  organizationId: config.organizationId
166
- }).catch((e) => console.error("[SSO Event]", e));
168
+ }).catch((eventError) => logger.error("SSO event emit failed", { err: eventError }));
167
169
  } else if (!active && wasActive) {
168
170
  void emitSsoEvent("sso.config.deactivated", {
169
171
  id: config.id,
170
172
  tenantId: config.tenantId,
171
173
  organizationId: config.organizationId
172
- }).catch((e) => console.error("[SSO Event]", e));
174
+ }).catch((eventError) => logger.error("SSO event emit failed", { err: eventError }));
173
175
  }
174
176
  return this.toPublic(config);
175
177
  });
@@ -201,7 +203,7 @@ class SsoConfigService {
201
203
  tenantId: config.tenantId,
202
204
  organizationId: config.organizationId,
203
205
  domain: normalized
204
- }).catch((e) => console.error("[SSO Event]", e));
206
+ }).catch((eventError) => logger.error("SSO event emit failed", { err: eventError }));
205
207
  return this.toPublic(config);
206
208
  });
207
209
  }
@@ -215,7 +217,7 @@ class SsoConfigService {
215
217
  tenantId: config.tenantId,
216
218
  organizationId: config.organizationId,
217
219
  domain: normalized
218
- }).catch((e) => console.error("[SSO Event]", e));
220
+ }).catch((eventError) => logger.error("SSO event emit failed", { err: eventError }));
219
221
  return this.toPublic(config);
220
222
  }
221
223
  toPublic(config) {
@@ -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.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 return this.em.transactional(async (txEm) => {\n const tx = txEm as EntityManager\n await this.lockActiveDomainMutation(tx, id)\n const config = await this.resolveConfig(scope, id, tx)\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 await this.lockActiveDomains(tx, config.allowedDomains)\n await this.assertActiveDomainAvailability(tx, config.allowedDomains, config.id)\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 tx.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\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 return this.em.transactional(async (txEm) => {\n const tx = txEm as EntityManager\n await this.lockActiveDomainMutation(tx, id, [normalized])\n const config = await this.resolveConfig(scope, id, tx)\n\n if (config.allowedDomains.some((d) => normalizeDomain(d) === 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 if (config.isActive) {\n await this.assertActiveDomainAvailability(tx, [...config.allowedDomains, normalized], config.id)\n }\n\n config.allowedDomains = [...config.allowedDomains, normalized]\n await tx.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\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, em: EntityManager = this.em): 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 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 private async lockActiveDomainMutation(em: EntityManager, configId: string, domains: string[] = []): Promise<void> {\n const lockKeys = [\n `sso:sso_config:${configId}`,\n ...uniqueDomains(domains)\n .sort((a, b) => a.localeCompare(b))\n .map((domain) => `sso:allowed_domain:${domain}`),\n ]\n\n await this.lockKeys(em, lockKeys)\n }\n\n private async lockActiveDomains(em: EntityManager, domains: string[]): Promise<void> {\n const lockKeys = uniqueDomains(domains)\n .sort((a, b) => a.localeCompare(b))\n .map((domain) => `sso:allowed_domain:${domain}`)\n await this.lockKeys(em, lockKeys)\n }\n\n private async lockKeys(em: EntityManager, lockKeys: string[]): Promise<void> {\n for (const lockKey of lockKeys) {\n await em.getConnection().execute('select pg_advisory_xact_lock(hashtext(?::text))', [lockKey])\n }\n }\n\n private async assertActiveDomainAvailability(\n em: EntityManager,\n domains: string[],\n currentConfigId: string,\n ): Promise<void> {\n const requestedDomains = new Set(uniqueDomains(domains))\n if (requestedDomains.size === 0) return\n\n const activeConfigs = await findWithDecryption(\n em,\n SsoConfig,\n { isActive: true, deletedAt: null },\n {},\n { tenantId: null },\n )\n const conflictDomain = activeConfigs\n .filter((config) => config.id !== currentConfigId)\n .flatMap((config) => config.allowedDomains.map(normalizeDomain))\n .find((domain) => requestedDomains.has(domain))\n\n if (conflictDomain) {\n throw new SsoConfigError(\n `SSO domain \"${conflictDomain}\" is already claimed by another active SSO configuration`,\n 409,\n )\n }\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'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('sso').child({ component: 'config' })\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((eventError) => logger.error('SSO event emit failed', { err: eventError }))\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((eventError) => logger.error('SSO event emit failed', { err: eventError }))\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((eventError) => logger.error('SSO event emit failed', { err: eventError }))\n }\n\n async activate(scope: SsoAdminScope, id: string, active: boolean): Promise<SsoConfigPublic> {\n return this.em.transactional(async (txEm) => {\n const tx = txEm as EntityManager\n await this.lockActiveDomainMutation(tx, id)\n const config = await this.resolveConfig(scope, id, tx)\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 await this.lockActiveDomains(tx, config.allowedDomains)\n await this.assertActiveDomainAvailability(tx, config.allowedDomains, config.id)\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 tx.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((eventError) => logger.error('SSO event emit failed', { err: eventError }))\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((eventError) => logger.error('SSO event emit failed', { err: eventError }))\n }\n\n return this.toPublic(config)\n })\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 return this.em.transactional(async (txEm) => {\n const tx = txEm as EntityManager\n await this.lockActiveDomainMutation(tx, id, [normalized])\n const config = await this.resolveConfig(scope, id, tx)\n\n if (config.allowedDomains.some((d) => normalizeDomain(d) === 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 if (config.isActive) {\n await this.assertActiveDomainAvailability(tx, [...config.allowedDomains, normalized], config.id)\n }\n\n config.allowedDomains = [...config.allowedDomains, normalized]\n await tx.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((eventError) => logger.error('SSO event emit failed', { err: eventError }))\n\n return this.toPublic(config)\n })\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((eventError) => logger.error('SSO event emit failed', { err: eventError }))\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, em: EntityManager = this.em): 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 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 private async lockActiveDomainMutation(em: EntityManager, configId: string, domains: string[] = []): Promise<void> {\n const lockKeys = [\n `sso:sso_config:${configId}`,\n ...uniqueDomains(domains)\n .sort((a, b) => a.localeCompare(b))\n .map((domain) => `sso:allowed_domain:${domain}`),\n ]\n\n await this.lockKeys(em, lockKeys)\n }\n\n private async lockActiveDomains(em: EntityManager, domains: string[]): Promise<void> {\n const lockKeys = uniqueDomains(domains)\n .sort((a, b) => a.localeCompare(b))\n .map((domain) => `sso:allowed_domain:${domain}`)\n await this.lockKeys(em, lockKeys)\n }\n\n private async lockKeys(em: EntityManager, lockKeys: string[]): Promise<void> {\n for (const lockKey of lockKeys) {\n await em.getConnection().execute('select pg_advisory_xact_lock(hashtext(?::text))', [lockKey])\n }\n }\n\n private async assertActiveDomainAvailability(\n em: EntityManager,\n domains: string[],\n currentConfigId: string,\n ): Promise<void> {\n const requestedDomains = new Set(uniqueDomains(domains))\n if (requestedDomains.size === 0) return\n\n const activeConfigs = await findWithDecryption(\n em,\n SsoConfig,\n { isActive: true, deletedAt: null },\n {},\n { tenantId: null },\n )\n const conflictDomain = activeConfigs\n .filter((config) => config.id !== currentConfigId)\n .flatMap((config) => config.allowedDomains.map(normalizeDomain))\n .find((domain) => requestedDomains.has(domain))\n\n if (conflictDomain) {\n throw new SsoConfigError(\n `SSO domain \"${conflictDomain}\" is already claimed by another active SSO configuration`,\n 409,\n )\n }\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
  }
@@ -4,6 +4,8 @@ import { signJwt } from "@open-mercato/shared/lib/auth/jwt";
4
4
  import { SsoConfig } from "../data/entities.js";
5
5
  import { encryptStateCookie, decryptStateCookie, createFlowState } from "../lib/state-cookie.js";
6
6
  import { emitSsoEvent } from "../events.js";
7
+ import { createLogger } from "@open-mercato/shared/lib/logger";
8
+ const logger = createLogger("sso").child({ component: "service" });
7
9
  class SsoService {
8
10
  constructor(em, ssoProviderRegistry, accountLinkingService, tenantEncryptionService, authService, rbacService) {
9
11
  this.em = em;
@@ -42,7 +44,7 @@ class SsoService {
42
44
  void emitSsoEvent("sso.login.initiated", {
43
45
  tenantId: config.tenantId,
44
46
  organizationId: config.organizationId
45
- }).catch((e) => console.error("[SSO Event]", e));
47
+ }).catch((eventError) => logger.error("SSO event emit failed", { err: eventError }));
46
48
  const authUrl = await provider.buildAuthUrl(config, {
47
49
  state: state.state,
48
50
  nonce: state.nonce,
@@ -100,7 +102,7 @@ class SsoService {
100
102
  id: String(user.id),
101
103
  tenantId: config.tenantId,
102
104
  organizationId: config.organizationId
103
- }).catch((e) => console.error("[SSO Event]", e));
105
+ }).catch((eventError) => logger.error("SSO event emit failed", { err: eventError }));
104
106
  return {
105
107
  token,
106
108
  sessionToken: sessionRefreshToken,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/sso/services/ssoService.ts"],
4
- "sourcesContent": ["import crypto from 'node:crypto'\nimport { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { signJwt } from '@open-mercato/shared/lib/auth/jwt'\nimport type { AuthService } from '@open-mercato/core/modules/auth/services/authService'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { SsoConfig } from '../data/entities'\nimport type { SsoProviderRegistry } from '../lib/registry'\nimport type { AccountLinkingService } from './accountLinkingService'\nimport { encryptStateCookie, decryptStateCookie, createFlowState } from '../lib/state-cookie'\nimport { emitSsoEvent } from '../events'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\n\nexport class SsoService {\n constructor(\n private em: EntityManager,\n private ssoProviderRegistry: SsoProviderRegistry,\n private accountLinkingService: AccountLinkingService,\n private tenantEncryptionService: TenantDataEncryptionService,\n private authService: AuthService,\n private rbacService: RbacService,\n ) {}\n\n async findConfigByEmail(email: string): Promise<SsoConfig | null> {\n const domain = email.split('@')[1]?.toLowerCase()\n if (!domain) return null\n\n const configs = await findWithDecryption(\n this.em,\n SsoConfig,\n { isActive: true, deletedAt: null },\n {},\n { tenantId: null },\n )\n const matches = configs.filter((c) => c.allowedDomains.some((d) => d.toLowerCase() === domain))\n return matches.length === 1 ? matches[0]! : null\n }\n\n async initiateLogin(\n configId: string,\n returnUrl: string,\n redirectUri: string,\n ): Promise<{ redirectUrl: string; stateCookie: string }> {\n const config = await findOneWithDecryption(\n this.em,\n SsoConfig,\n { id: configId, isActive: true, deletedAt: null },\n {},\n { tenantId: null },\n )\n if (!config) throw new Error('SSO configuration not found or inactive')\n\n const provider = this.ssoProviderRegistry.resolve(config.protocol)\n if (!provider) throw new Error(`No provider registered for protocol: ${config.protocol}`)\n\n const clientSecret = await this.decryptClientSecret(config)\n\n const { state } = createFlowState({ configId, returnUrl })\n\n void emitSsoEvent('sso.login.initiated', {\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n\n const authUrl = await provider.buildAuthUrl(config, {\n state: state.state,\n nonce: state.nonce,\n redirectUri,\n codeVerifier: state.codeVerifier,\n clientSecret,\n })\n\n const stateCookie = encryptStateCookie(state)\n return { redirectUrl: authUrl, stateCookie }\n }\n\n async handleOidcCallback(\n callbackParams: Record<string, string>,\n stateCookie: string,\n redirectUri: string,\n ): Promise<{\n token: string\n sessionToken: string\n sessionExpiresAt: Date\n redirectUrl: string\n tenantId: string | null\n organizationId: string\n }> {\n const flowState = decryptStateCookie(stateCookie)\n if (!flowState) throw new Error('Invalid or expired SSO state')\n\n const receivedState = Buffer.from(callbackParams.state || '')\n const expectedState = Buffer.from(flowState.state)\n if (receivedState.length !== expectedState.length || !crypto.timingSafeEqual(receivedState, expectedState)) {\n throw new Error('State mismatch \u2014 possible CSRF attack')\n }\n\n const config = await findOneWithDecryption(\n this.em,\n SsoConfig,\n { id: flowState.configId, isActive: true, deletedAt: null },\n {},\n { tenantId: null },\n )\n if (!config) throw new Error('SSO configuration no longer active')\n\n const provider = this.ssoProviderRegistry.resolve(config.protocol)\n if (!provider) throw new Error(`No provider for protocol: ${config.protocol}`)\n\n const clientSecret = await this.decryptClientSecret(config)\n\n const idpPayload = await provider.handleCallback(config, {\n callbackParams,\n redirectUri,\n expectedState: flowState.state,\n expectedNonce: flowState.nonce,\n codeVerifier: flowState.codeVerifier,\n clientSecret,\n })\n\n const tenantId = config.tenantId ?? ''\n const { user } = await this.accountLinkingService.resolveUser(config, idpPayload, tenantId)\n\n await this.rbacService.invalidateUserCache(String(user.id))\n\n const roles = await this.authService.getUserRoles(user, tenantId || null)\n\n await this.authService.updateLastLoginAt(user)\n\n const days = Number(process.env.REMEMBER_ME_DAYS || '30')\n const sessionExpiresAt = new Date(Date.now() + days * 24 * 60 * 60 * 1000)\n const { session, token: sessionRefreshToken } = await this.authService.createSession(user, sessionExpiresAt)\n\n const token = signJwt({\n sub: String(user.id),\n sid: String(session.id),\n tenantId: tenantId || null,\n orgId: user.organizationId ? String(user.organizationId) : null,\n email: user.email,\n roles,\n })\n\n void emitSsoEvent('sso.login.completed', {\n id: String(user.id),\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n\n return {\n token,\n sessionToken: sessionRefreshToken,\n sessionExpiresAt,\n redirectUrl: flowState.returnUrl || '/backend',\n tenantId: config.tenantId ?? null,\n organizationId: config.organizationId,\n }\n }\n\n private async decryptClientSecret(config: SsoConfig): Promise<string | undefined> {\n if (!config.clientSecretEnc) return undefined\n\n const decrypted = await this.tenantEncryptionService.decryptEntityPayload(\n config.id,\n { clientSecretEnc: config.clientSecretEnc },\n config.tenantId,\n config.organizationId,\n )\n return (decrypted.clientSecretEnc as string) ?? undefined\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import crypto from 'node:crypto'\nimport { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { signJwt } from '@open-mercato/shared/lib/auth/jwt'\nimport type { AuthService } from '@open-mercato/core/modules/auth/services/authService'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { SsoConfig } from '../data/entities'\nimport type { SsoProviderRegistry } from '../lib/registry'\nimport type { AccountLinkingService } from './accountLinkingService'\nimport { encryptStateCookie, decryptStateCookie, createFlowState } from '../lib/state-cookie'\nimport { emitSsoEvent } from '../events'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('sso').child({ component: 'service' })\n\nexport class SsoService {\n constructor(\n private em: EntityManager,\n private ssoProviderRegistry: SsoProviderRegistry,\n private accountLinkingService: AccountLinkingService,\n private tenantEncryptionService: TenantDataEncryptionService,\n private authService: AuthService,\n private rbacService: RbacService,\n ) {}\n\n async findConfigByEmail(email: string): Promise<SsoConfig | null> {\n const domain = email.split('@')[1]?.toLowerCase()\n if (!domain) return null\n\n const configs = await findWithDecryption(\n this.em,\n SsoConfig,\n { isActive: true, deletedAt: null },\n {},\n { tenantId: null },\n )\n const matches = configs.filter((c) => c.allowedDomains.some((d) => d.toLowerCase() === domain))\n return matches.length === 1 ? matches[0]! : null\n }\n\n async initiateLogin(\n configId: string,\n returnUrl: string,\n redirectUri: string,\n ): Promise<{ redirectUrl: string; stateCookie: string }> {\n const config = await findOneWithDecryption(\n this.em,\n SsoConfig,\n { id: configId, isActive: true, deletedAt: null },\n {},\n { tenantId: null },\n )\n if (!config) throw new Error('SSO configuration not found or inactive')\n\n const provider = this.ssoProviderRegistry.resolve(config.protocol)\n if (!provider) throw new Error(`No provider registered for protocol: ${config.protocol}`)\n\n const clientSecret = await this.decryptClientSecret(config)\n\n const { state } = createFlowState({ configId, returnUrl })\n\n void emitSsoEvent('sso.login.initiated', {\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n }).catch((eventError) => logger.error('SSO event emit failed', { err: eventError }))\n\n const authUrl = await provider.buildAuthUrl(config, {\n state: state.state,\n nonce: state.nonce,\n redirectUri,\n codeVerifier: state.codeVerifier,\n clientSecret,\n })\n\n const stateCookie = encryptStateCookie(state)\n return { redirectUrl: authUrl, stateCookie }\n }\n\n async handleOidcCallback(\n callbackParams: Record<string, string>,\n stateCookie: string,\n redirectUri: string,\n ): Promise<{\n token: string\n sessionToken: string\n sessionExpiresAt: Date\n redirectUrl: string\n tenantId: string | null\n organizationId: string\n }> {\n const flowState = decryptStateCookie(stateCookie)\n if (!flowState) throw new Error('Invalid or expired SSO state')\n\n const receivedState = Buffer.from(callbackParams.state || '')\n const expectedState = Buffer.from(flowState.state)\n if (receivedState.length !== expectedState.length || !crypto.timingSafeEqual(receivedState, expectedState)) {\n throw new Error('State mismatch \u2014 possible CSRF attack')\n }\n\n const config = await findOneWithDecryption(\n this.em,\n SsoConfig,\n { id: flowState.configId, isActive: true, deletedAt: null },\n {},\n { tenantId: null },\n )\n if (!config) throw new Error('SSO configuration no longer active')\n\n const provider = this.ssoProviderRegistry.resolve(config.protocol)\n if (!provider) throw new Error(`No provider for protocol: ${config.protocol}`)\n\n const clientSecret = await this.decryptClientSecret(config)\n\n const idpPayload = await provider.handleCallback(config, {\n callbackParams,\n redirectUri,\n expectedState: flowState.state,\n expectedNonce: flowState.nonce,\n codeVerifier: flowState.codeVerifier,\n clientSecret,\n })\n\n const tenantId = config.tenantId ?? ''\n const { user } = await this.accountLinkingService.resolveUser(config, idpPayload, tenantId)\n\n await this.rbacService.invalidateUserCache(String(user.id))\n\n const roles = await this.authService.getUserRoles(user, tenantId || null)\n\n await this.authService.updateLastLoginAt(user)\n\n const days = Number(process.env.REMEMBER_ME_DAYS || '30')\n const sessionExpiresAt = new Date(Date.now() + days * 24 * 60 * 60 * 1000)\n const { session, token: sessionRefreshToken } = await this.authService.createSession(user, sessionExpiresAt)\n\n const token = signJwt({\n sub: String(user.id),\n sid: String(session.id),\n tenantId: tenantId || null,\n orgId: user.organizationId ? String(user.organizationId) : null,\n email: user.email,\n roles,\n })\n\n void emitSsoEvent('sso.login.completed', {\n id: String(user.id),\n tenantId: config.tenantId,\n organizationId: config.organizationId,\n }).catch((eventError) => logger.error('SSO event emit failed', { err: eventError }))\n\n return {\n token,\n sessionToken: sessionRefreshToken,\n sessionExpiresAt,\n redirectUrl: flowState.returnUrl || '/backend',\n tenantId: config.tenantId ?? null,\n organizationId: config.organizationId,\n }\n }\n\n private async decryptClientSecret(config: SsoConfig): Promise<string | undefined> {\n if (!config.clientSecretEnc) return undefined\n\n const decrypted = await this.tenantEncryptionService.decryptEntityPayload(\n config.id,\n { clientSecretEnc: config.clientSecretEnc },\n config.tenantId,\n config.organizationId,\n )\n return (decrypted.clientSecretEnc as string) ?? undefined\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,5 @@
1
+ import { createLogger } from "@open-mercato/shared/lib/logger";
2
+ const logger = createLogger("system_status_overlays");
1
3
  const APP_BOOTSTRAP_EVENT = "application.bootstrap.completed";
2
4
  const ENTERPRISE_WARNING_GLOBAL_KEY = "__openMercatoEnterpriseLicenseWarningShown__";
3
5
  const metadata = {
@@ -8,8 +10,8 @@ const metadata = {
8
10
  async function handle() {
9
11
  if (globalThis[ENTERPRISE_WARNING_GLOBAL_KEY] === true) return;
10
12
  globalThis[ENTERPRISE_WARNING_GLOBAL_KEY] = true;
11
- console.warn(
12
- "[enterprise] Enterprise modules are enabled. Developer preview is free, but production usage requires a commercial enterprise license. See: https://github.com/open-mercato/open-mercato/blob/main/packages/enterprise/README.md"
13
+ logger.warn(
14
+ "Enterprise modules are enabled. Developer preview is free, but production usage requires a commercial enterprise license. See: https://github.com/open-mercato/open-mercato/blob/main/packages/enterprise/README.md"
13
15
  );
14
16
  }
15
17
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/system_status_overlays/subscribers/application-bootstrap-enterprise-warning.ts"],
4
- "sourcesContent": ["const APP_BOOTSTRAP_EVENT = 'application.bootstrap.completed'\nconst ENTERPRISE_WARNING_GLOBAL_KEY = '__openMercatoEnterpriseLicenseWarningShown__'\n\nexport const metadata = {\n event: APP_BOOTSTRAP_EVENT,\n persistent: false,\n id: 'system_status_overlays:application-bootstrap-enterprise-warning',\n}\n\nexport default async function handle() {\n if ((globalThis as Record<string, unknown>)[ENTERPRISE_WARNING_GLOBAL_KEY] === true) return\n\n ;(globalThis as Record<string, unknown>)[ENTERPRISE_WARNING_GLOBAL_KEY] = true\n console.warn(\n '[enterprise] Enterprise modules are enabled. Developer preview is free, but production usage requires a commercial enterprise license. See: https://github.com/open-mercato/open-mercato/blob/main/packages/enterprise/README.md',\n )\n}\n"],
5
- "mappings": "AAAA,MAAM,sBAAsB;AAC5B,MAAM,gCAAgC;AAE/B,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAEA,eAAO,SAAgC;AACrC,MAAK,WAAuC,6BAA6B,MAAM,KAAM;AAEpF,EAAC,WAAuC,6BAA6B,IAAI;AAC1E,UAAQ;AAAA,IACN;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('system_status_overlays')\n\nconst APP_BOOTSTRAP_EVENT = 'application.bootstrap.completed'\nconst ENTERPRISE_WARNING_GLOBAL_KEY = '__openMercatoEnterpriseLicenseWarningShown__'\n\nexport const metadata = {\n event: APP_BOOTSTRAP_EVENT,\n persistent: false,\n id: 'system_status_overlays:application-bootstrap-enterprise-warning',\n}\n\nexport default async function handle() {\n if ((globalThis as Record<string, unknown>)[ENTERPRISE_WARNING_GLOBAL_KEY] === true) return\n\n ;(globalThis as Record<string, unknown>)[ENTERPRISE_WARNING_GLOBAL_KEY] = true\n logger.warn(\n 'Enterprise modules are enabled. Developer preview is free, but production usage requires a commercial enterprise license. See: https://github.com/open-mercato/open-mercato/blob/main/packages/enterprise/README.md',\n )\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,wBAAwB;AAEpD,MAAM,sBAAsB;AAC5B,MAAM,gCAAgC;AAE/B,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAEA,eAAO,SAAgC;AACrC,MAAK,WAAuC,6BAA6B,MAAM,KAAM;AAEpF,EAAC,WAAuC,6BAA6B,IAAI;AAC1E,SAAO;AAAA,IACL;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/enterprise",
3
- "version": "0.6.6-develop.6465.1.019f0fb26f",
3
+ "version": "0.6.6-develop.6471.1.9ab5bdd79a",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -65,8 +65,8 @@
65
65
  }
66
66
  },
67
67
  "dependencies": {
68
- "@open-mercato/core": "0.6.6-develop.6465.1.019f0fb26f",
69
- "@open-mercato/ui": "0.6.6-develop.6465.1.019f0fb26f",
68
+ "@open-mercato/core": "0.6.6-develop.6471.1.9ab5bdd79a",
69
+ "@open-mercato/ui": "0.6.6-develop.6471.1.9ab5bdd79a",
70
70
  "@simplewebauthn/browser": "^13.3.0",
71
71
  "@simplewebauthn/server": "^13.3.2",
72
72
  "@simplewebauthn/types": "^12.0.0",
@@ -76,12 +76,12 @@
76
76
  "qrcode": "^1.5.4"
77
77
  },
78
78
  "peerDependencies": {
79
- "@open-mercato/shared": "0.6.6-develop.6465.1.019f0fb26f",
79
+ "@open-mercato/shared": "0.6.6-develop.6471.1.9ab5bdd79a",
80
80
  "react": "^19.0.0",
81
81
  "react-dom": "^19.0.0"
82
82
  },
83
83
  "devDependencies": {
84
- "@open-mercato/shared": "0.6.6-develop.6465.1.019f0fb26f",
84
+ "@open-mercato/shared": "0.6.6-develop.6471.1.9ab5bdd79a",
85
85
  "@types/jest": "^30.0.0",
86
86
  "@types/react": "^19.2.17",
87
87
  "@types/react-dom": "^19.2.3",
@@ -9,6 +9,9 @@ import {
9
9
  resolveRecordLockNotificationType,
10
10
  resolveRecordResourceLink,
11
11
  } from '../lib/notificationHelpers'
12
+ import { createLogger } from '@open-mercato/shared/lib/logger'
13
+
14
+ const logger = createLogger('record_locks').child({ subscriber: 'conflict-detected-notification' })
12
15
 
13
16
  export const metadata = {
14
17
  event: 'record_locks.conflict.detected',
@@ -109,6 +112,6 @@ export default async function handle(payload: Payload, ctx: ResolverContext) {
109
112
  organizationId: payload.organizationId ?? null,
110
113
  })
111
114
  } catch (error) {
112
- console.error('[record_locks:conflict-detected-notification] Failed to create notification:', error)
115
+ logger.error('Failed to create notification', { err: error })
113
116
  }
114
117
  }
@@ -5,6 +5,9 @@ import {
5
5
  resolveRecordLockNotificationType,
6
6
  resolveRecordResourceLink,
7
7
  } from '../lib/notificationHelpers'
8
+ import { createLogger } from '@open-mercato/shared/lib/logger'
9
+
10
+ const logger = createLogger('record_locks').child({ subscriber: 'conflict-resolved-notification' })
8
11
 
9
12
  export const metadata = {
10
13
  event: 'record_locks.conflict.resolved',
@@ -59,6 +62,6 @@ export default async function handle(payload: Payload, ctx: ResolverContext) {
59
62
  organizationId: payload.organizationId ?? null,
60
63
  })
61
64
  } catch (error) {
62
- console.error('[record_locks:conflict-resolved-notification] Failed to create notification:', error)
65
+ logger.error('Failed to create notification', { err: error })
63
66
  }
64
67
  }
@@ -1,6 +1,9 @@
1
1
  import { buildNotificationFromType } from '@open-mercato/core/modules/notifications/lib/notificationBuilder'
2
2
  import { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'
3
3
  import { resolveRecordLockNotificationType, resolveRecordResourceLink } from '../lib/notificationHelpers'
4
+ import { createLogger } from '@open-mercato/shared/lib/logger'
5
+
6
+ const logger = createLogger('record_locks').child({ subscriber: 'lock-contended-notification' })
4
7
 
5
8
  export const metadata = {
6
9
  event: 'record_locks.lock.contended',
@@ -47,6 +50,6 @@ export default async function handle(payload: Payload, ctx: ResolverContext) {
47
50
  organizationId: payload.organizationId ?? null,
48
51
  })
49
52
  } catch (error) {
50
- console.error('[record_locks:lock-contended-notification] Failed to create notification:', error)
53
+ logger.error('Failed to create notification', { err: error })
51
54
  }
52
55
  }
@@ -1,6 +1,9 @@
1
1
  import { buildNotificationFromType } from '@open-mercato/core/modules/notifications/lib/notificationBuilder'
2
2
  import { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'
3
3
  import { resolveRecordLockNotificationType, resolveRecordResourceLink } from '../lib/notificationHelpers'
4
+ import { createLogger } from '@open-mercato/shared/lib/logger'
5
+
6
+ const logger = createLogger('record_locks').child({ subscriber: 'lock-force-released-notification' })
4
7
 
5
8
  export const metadata = {
6
9
  event: 'record_locks.lock.force_released',
@@ -46,6 +49,6 @@ export default async function handle(payload: Payload, ctx: ResolverContext) {
46
49
  organizationId: payload.organizationId ?? null,
47
50
  })
48
51
  } catch (error) {
49
- console.error('[record_locks:lock-force-released-notification] Failed to create notification:', error)
52
+ logger.error('Failed to create notification', { err: error })
50
53
  }
51
54
  }
@@ -34,6 +34,9 @@ import {
34
34
  } from '@open-mercato/enterprise/modules/record_locks/lib/clientLockStore'
35
35
  import { isUuid, resolveConflictId, runAcceptIncoming } from './conflictResolution'
36
36
  import { isOptimisticLockFloorConflict } from '@open-mercato/enterprise/modules/record_locks/lib/optimisticLockFloor'
37
+ import { createLogger } from '@open-mercato/shared/lib/logger'
38
+
39
+ const logger = createLogger('record_locks').child({ component: 'record-locking-widget' })
37
40
 
38
41
  type CrudInjectionContext = {
39
42
  formId?: string
@@ -556,7 +559,7 @@ function releaseLockWithKeepalive(state: {
556
559
  keepalive: true,
557
560
  credentials: 'include',
558
561
  }).catch((error) => {
559
- console.warn('[RecordLockingWidget] Failed to release lock with keepalive fallback', error)
562
+ logger.warn('Failed to release lock with keepalive fallback', { err: error })
560
563
  })
561
564
  }
562
565
 
@@ -661,7 +664,7 @@ export default function RecordLockingWidget({
661
664
  token: current.lock.token,
662
665
  reason: 'cancelled',
663
666
  }).catch((error) => {
664
- console.warn('[RecordLockingWidget] Failed to release lock while demoting owner', error)
667
+ logger.warn('Failed to release lock while demoting owner', { err: error })
665
668
  })
666
669
  clearRecordLockFormState(formId)
667
670
  }, [formId, isPrimaryInstance])
@@ -14,6 +14,9 @@ import type {
14
14
  MfaEnforcementService,
15
15
  } from '../../services/MfaEnforcementService'
16
16
  import { localizeSecurityApiBody, securityApiError } from '../i18n'
17
+ import { createLogger } from '@open-mercato/shared/lib/logger'
18
+
19
+ const logger = createLogger('security').child({ component: 'enforcement' })
17
20
 
18
21
  type RequestContainer = Awaited<ReturnType<typeof createRequestContainer>>
19
22
  type Auth = NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>
@@ -128,7 +131,7 @@ export async function mapEnforcementError(error: unknown): Promise<NextResponse>
128
131
  if (isMfaEnforcementServiceError(error)) {
129
132
  return securityApiError(error.statusCode, error.message)
130
133
  }
131
- console.error('security.enforcement.route failure', error)
134
+ logger.error('Enforcement route failure', { err: error })
132
135
  return securityApiError(500, 'Failed to process enforcement request.')
133
136
  }
134
137
 
@@ -8,6 +8,9 @@ import { signJwt } from '@open-mercato/shared/lib/auth/jwt'
8
8
  import type { MfaService, MfaServiceError } from '../../services/MfaService'
9
9
  import type { MfaVerificationService, MfaVerificationServiceError } from '../../services/MfaVerificationService'
10
10
  import { localizeSecurityApiBody, securityApiError } from '../i18n'
11
+ import { createLogger } from '@open-mercato/shared/lib/logger'
12
+
13
+ const logger = createLogger('security').child({ component: 'mfa' })
11
14
 
12
15
  const jsonRecordSchema = z.record(z.string(), z.unknown())
13
16
 
@@ -70,7 +73,7 @@ export async function mapMfaError(error: unknown): Promise<NextResponse> {
70
73
  if (isMfaServiceError(error) || isMfaVerificationServiceError(error)) {
71
74
  return securityApiError(error.statusCode, error.message)
72
75
  }
73
- console.error('security.mfa.route failure', error)
76
+ logger.error('MFA route failure', { err: error })
74
77
  return securityApiError(500, 'Failed to process MFA request.')
75
78
  }
76
79
 
@@ -8,6 +8,9 @@ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
8
8
  import { changePasswordSchema } from '../../../data/validators'
9
9
  import { buildSecurityOpenApi, securityErrorSchema } from '../../openapi'
10
10
  import { localizeSecurityApiBody } from '../../i18n'
11
+ import { createLogger } from '@open-mercato/shared/lib/logger'
12
+
13
+ const logger = createLogger('security').child({ component: 'profile-password' })
11
14
 
12
15
  const changePasswordResponseSchema = z.object({
13
16
  ok: z.literal(true),
@@ -68,7 +71,7 @@ export async function PUT(req: Request) {
68
71
  if (error instanceof CrudHttpError) {
69
72
  return NextResponse.json(await localizeSecurityApiBody(error.body), { status: error.status })
70
73
  }
71
- console.error('security.profile.password.update failed', error)
74
+ logger.error('Profile password update failed', { err: error })
72
75
  return NextResponse.json(
73
76
  { error: translate('security.profile.password.form.errors.save', 'Failed to update password.') },
74
77
  { status: 400 },
@@ -13,6 +13,9 @@ import type {
13
13
  } from '../../services/SudoChallengeService'
14
14
  import { isSudoRequiredError } from '../../lib/sudo-middleware'
15
15
  import { localizeSecurityApiBody, securityApiError } from '../i18n'
16
+ import { createLogger } from '@open-mercato/shared/lib/logger'
17
+
18
+ const logger = createLogger('security').child({ component: 'sudo' })
16
19
 
17
20
  type RequestContainer = Awaited<ReturnType<typeof createRequestContainer>>
18
21
  type Auth = NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>
@@ -135,7 +138,7 @@ export async function mapSudoError(error: unknown): Promise<NextResponse> {
135
138
  if (isSudoChallengeServiceError(error)) {
136
139
  return securityApiError(error.statusCode, error.message)
137
140
  }
138
- console.error('security.sudo.route failure', error)
141
+ logger.error('Sudo route failure', { err: error })
139
142
  return securityApiError(500, 'Failed to process sudo request.')
140
143
  }
141
144
 
@@ -11,6 +11,9 @@ import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacS
11
11
  import { isSudoRequiredError } from '../../lib/sudo-middleware'
12
12
  import type { MfaAdminService, MfaAdminServiceError } from '../../services/MfaAdminService'
13
13
  import { localizeSecurityApiBody, securityApiError } from '../i18n'
14
+ import { createLogger } from '@open-mercato/shared/lib/logger'
15
+
16
+ const logger = createLogger('security').child({ component: 'users' })
14
17
 
15
18
  type RequestContainer = Awaited<ReturnType<typeof createRequestContainer>>
16
19
  type Auth = NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>
@@ -120,7 +123,7 @@ export async function mapSecurityUsersError(error: unknown): Promise<NextRespons
120
123
  return securityApiError(error.statusCode, error.message)
121
124
  }
122
125
 
123
- console.error('security.users.route failure', error)
126
+ logger.error('Users route failure', { err: error })
124
127
  return securityApiError(500, 'Failed to process user security request.')
125
128
  }
126
129
 
@@ -5,6 +5,9 @@ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
5
  import { SsoService } from '../../../services/ssoService'
6
6
  import { emitSsoEvent } from '../../../events'
7
7
  import { resolveSsoCallbackErrorCode } from '../../../lib/errors'
8
+ import { createLogger } from '@open-mercato/shared/lib/logger'
9
+
10
+ const logger = createLogger('sso').child({ component: 'callback' })
8
11
 
9
12
  export const metadata = {
10
13
  GET: { requireAuth: false },
@@ -43,7 +46,7 @@ async function handleCallback(req: Request): Promise<NextResponse> {
43
46
  if (callbackParams.error) {
44
47
  void emitSsoEvent('sso.login.failed', {
45
48
  reason: callbackParams.error,
46
- }).catch((e) => console.error('[SSO Event]', e))
49
+ }).catch((eventError) => logger.error('SSO event emit failed', { err: eventError }))
47
50
  return NextResponse.redirect(toAbsoluteUrl(req, '/login?error=sso_idp_error'))
48
51
  }
49
52
 
@@ -79,10 +82,10 @@ async function handleCallback(req: Request): Promise<NextResponse> {
79
82
 
80
83
  return res
81
84
  } catch (err) {
82
- console.error('[SSO Callback] Error:', err)
85
+ logger.error('SSO callback error', { err })
83
86
  void emitSsoEvent('sso.login.failed', {
84
87
  reason: err instanceof Error ? err.message : 'callback_failed',
85
- }).catch((e) => console.error('[SSO Event]', e))
88
+ }).catch((eventError) => logger.error('SSO event emit failed', { err: eventError }))
86
89
  const errorCode = resolveSsoCallbackErrorCode(err)
87
90
  return NextResponse.redirect(toAbsoluteUrl(req, `/login?error=${errorCode}`))
88
91
  }
@@ -4,6 +4,9 @@ import { SsoConfigError } from '../services/ssoConfigService'
4
4
  import { ScimTokenError } from '../services/scimTokenService'
5
5
  import { ScimServiceError } from '../services/scimService'
6
6
  import { scimJson, buildScimError } from '../lib/scim-response'
7
+ import { createLogger } from '@open-mercato/shared/lib/logger'
8
+
9
+ const logger = createLogger('sso')
7
10
 
8
11
  interface SsoHttpError {
9
12
  message: string
@@ -23,7 +26,7 @@ export function handleSsoAdminApiError(err: unknown, label: string): NextRespons
23
26
  if (isSsoHttpError(err)) {
24
27
  return NextResponse.json({ error: err.message }, { status: err.statusCode })
25
28
  }
26
- console.error(`[${label}] Error:`, err)
29
+ logger.error('SSO admin API error', { label, err })
27
30
  return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
28
31
  }
29
32
 
@@ -31,6 +34,6 @@ export function handleScimApiError(err: unknown, label: string): Response {
31
34
  if (err instanceof ScimServiceError) {
32
35
  return scimJson(buildScimError(err.statusCode, err.message), err.statusCode)
33
36
  }
34
- console.error(`[${label}] Error:`, err)
37
+ logger.error('SCIM API error', { label, err })
35
38
  return scimJson(buildScimError(500, 'Internal server error'), 500)
36
39
  }
@@ -3,6 +3,9 @@ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
3
3
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
4
4
  import { HrdService } from '../../services/hrdService'
5
5
  import { hrdRequestSchema } from '../../data/validators'
6
+ import { createLogger } from '@open-mercato/shared/lib/logger'
7
+
8
+ const logger = createLogger('sso').child({ component: 'hrd' })
6
9
 
7
10
  export const metadata = {
8
11
  POST: { requireAuth: false },
@@ -31,7 +34,7 @@ export async function POST(req: Request) {
31
34
  protocol: config.protocol,
32
35
  })
33
36
  } catch (err) {
34
- console.error('[SSO HRD] Error:', err)
37
+ logger.error('SSO HRD error', { err })
35
38
  return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
36
39
  }
37
40
  }