@open-mercato/core 0.6.6-develop.5612.1.d382eb2f33 → 0.6.6-develop.5619.1.29f01e2c42

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 (86) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/api_key/index.js +2 -0
  3. package/dist/generated/entities/api_key/index.js.map +2 -2
  4. package/dist/generated/entities/attachment_partition/index.js +4 -0
  5. package/dist/generated/entities/attachment_partition/index.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +3 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/api_keys/data/entities.js +9 -2
  9. package/dist/modules/api_keys/data/entities.js.map +2 -2
  10. package/dist/modules/api_keys/migrations/Migration20260523234901.js +17 -0
  11. package/dist/modules/api_keys/migrations/Migration20260523234901.js.map +7 -0
  12. package/dist/modules/api_keys/services/apiKeyService.js +26 -0
  13. package/dist/modules/api_keys/services/apiKeyService.js.map +2 -2
  14. package/dist/modules/attachments/api/partitions/route.js +54 -12
  15. package/dist/modules/attachments/api/partitions/route.js.map +2 -2
  16. package/dist/modules/attachments/data/entities.js +7 -0
  17. package/dist/modules/attachments/data/entities.js.map +2 -2
  18. package/dist/modules/attachments/migrations/Migration20260524000000.js +27 -0
  19. package/dist/modules/attachments/migrations/Migration20260524000000.js.map +7 -0
  20. package/dist/modules/auth/api/roles/route.js +6 -35
  21. package/dist/modules/auth/api/roles/route.js.map +2 -2
  22. package/dist/modules/auth/cli.js +26 -4
  23. package/dist/modules/auth/cli.js.map +2 -2
  24. package/dist/modules/auth/commands/roles.js +34 -11
  25. package/dist/modules/auth/commands/roles.js.map +2 -2
  26. package/dist/modules/auth/lib/roleTenantGuard.js +41 -13
  27. package/dist/modules/auth/lib/roleTenantGuard.js.map +3 -3
  28. package/dist/modules/auth/lib/setup-app.js +88 -26
  29. package/dist/modules/auth/lib/setup-app.js.map +2 -2
  30. package/dist/modules/currencies/api/fetch-configs/route.js +4 -2
  31. package/dist/modules/currencies/api/fetch-configs/route.js.map +2 -2
  32. package/dist/modules/integrations/api/[id]/route.js +5 -1
  33. package/dist/modules/integrations/api/[id]/route.js.map +2 -2
  34. package/dist/modules/integrations/api/route.js +5 -1
  35. package/dist/modules/integrations/api/route.js.map +2 -2
  36. package/dist/modules/integrations/lib/credentials-service.js +6 -4
  37. package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
  38. package/dist/modules/sales/acl.js +6 -0
  39. package/dist/modules/sales/acl.js.map +2 -2
  40. package/dist/modules/sales/api/returns/route.js +43 -3
  41. package/dist/modules/sales/api/returns/route.js.map +2 -2
  42. package/dist/modules/sales/commands/returns.js +473 -213
  43. package/dist/modules/sales/commands/returns.js.map +2 -2
  44. package/dist/modules/sales/commands/shared.js +2 -0
  45. package/dist/modules/sales/commands/shared.js.map +2 -2
  46. package/dist/modules/sales/components/documents/ReturnEditDialog.js +125 -0
  47. package/dist/modules/sales/components/documents/ReturnEditDialog.js.map +7 -0
  48. package/dist/modules/sales/components/documents/ReturnsSection.js +102 -6
  49. package/dist/modules/sales/components/documents/ReturnsSection.js.map +2 -2
  50. package/dist/modules/sales/data/validators.js +13 -0
  51. package/dist/modules/sales/data/validators.js.map +2 -2
  52. package/dist/modules/sales/setup.js +1 -0
  53. package/dist/modules/sales/setup.js.map +2 -2
  54. package/generated/entities/api_key/index.ts +1 -0
  55. package/generated/entities/attachment_partition/index.ts +2 -0
  56. package/generated/entity-fields-registry.ts +3 -0
  57. package/package.json +7 -7
  58. package/src/modules/api_keys/data/entities.ts +15 -1
  59. package/src/modules/api_keys/migrations/.snapshot-open-mercato.json +18 -0
  60. package/src/modules/api_keys/migrations/Migration20260523234901.ts +17 -0
  61. package/src/modules/api_keys/services/apiKeyService.ts +63 -0
  62. package/src/modules/attachments/api/partitions/route.ts +58 -11
  63. package/src/modules/attachments/data/entities.ts +7 -0
  64. package/src/modules/attachments/migrations/.snapshot-open-mercato.json +28 -0
  65. package/src/modules/attachments/migrations/Migration20260524000000.ts +25 -0
  66. package/src/modules/auth/api/roles/route.ts +7 -38
  67. package/src/modules/auth/cli.ts +27 -4
  68. package/src/modules/auth/commands/roles.ts +39 -11
  69. package/src/modules/auth/lib/roleTenantGuard.ts +56 -17
  70. package/src/modules/auth/lib/setup-app.ts +160 -32
  71. package/src/modules/currencies/api/fetch-configs/route.ts +4 -2
  72. package/src/modules/integrations/api/[id]/route.ts +5 -2
  73. package/src/modules/integrations/api/route.ts +5 -2
  74. package/src/modules/integrations/lib/credentials-service.ts +15 -5
  75. package/src/modules/sales/acl.ts +6 -0
  76. package/src/modules/sales/api/returns/route.ts +41 -3
  77. package/src/modules/sales/commands/returns.ts +561 -229
  78. package/src/modules/sales/commands/shared.ts +1 -0
  79. package/src/modules/sales/components/documents/ReturnEditDialog.tsx +157 -0
  80. package/src/modules/sales/components/documents/ReturnsSection.tsx +105 -3
  81. package/src/modules/sales/data/validators.ts +15 -0
  82. package/src/modules/sales/i18n/de.json +11 -0
  83. package/src/modules/sales/i18n/en.json +11 -0
  84. package/src/modules/sales/i18n/es.json +11 -0
  85. package/src/modules/sales/i18n/pl.json +11 -0
  86. package/src/modules/sales/setup.ts +1 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/auth/lib/setup-app.ts"],
4
- "sourcesContent": ["import { hash } from 'bcryptjs'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { Role, RoleAcl, User, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport { Tenant, Organization } from '@open-mercato/core/modules/directory/data/entities'\nimport { rebuildHierarchyForTenant } from '@open-mercato/core/modules/directory/lib/hierarchy'\nimport { normalizeTenantId } from './tenantAccess'\nimport { computeEmailHash } from '@open-mercato/core/modules/auth/lib/emailHash'\nimport { getDefaultEncryptionMaps, type Module } from '@open-mercato/shared/modules/registry'\nimport { isEncryptionDebugEnabled, isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'\nimport { EncryptionMap } from '@open-mercato/core/modules/entities/data/entities'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nconst DEFAULT_ROLE_NAMES = ['employee', 'admin', 'superadmin'] as const\nconst DEMO_SUPERADMIN_EMAIL = 'superadmin@acme.com'\nconst DEFAULT_DERIVED_EMAIL_DOMAIN = DEMO_SUPERADMIN_EMAIL.split('@')[1] ?? 'acme.com'\n\nexport type EnsureRolesOptions = {\n roleNames?: string[]\n tenantId?: string\n}\n\nasync function ensureRolesInContext(\n em: EntityManager,\n roleNames: string[],\n tenantId: string,\n) {\n for (const name of roleNames) {\n const existing = await findOneWithDecryption(em, Role, { name, tenantId }, {}, { tenantId, organizationId: null })\n if (existing) continue\n em.persist(em.create(Role, { name, tenantId, createdAt: new Date() }))\n }\n}\n\nexport async function ensureRoles(em: EntityManager, options: EnsureRolesOptions = {}) {\n const roleNames = options.roleNames ?? [...DEFAULT_ROLE_NAMES]\n const tenantId = normalizeTenantId(options.tenantId ?? null)\n if (!tenantId) {\n throw new Error('ensureRoles requires a tenantId \u2014 global roles are not supported')\n }\n await em.transactional(async (tem) => {\n await ensureRolesInContext(tem, roleNames, tenantId)\n await tem.flush()\n })\n}\n\nasync function findRoleByName(\n em: EntityManager,\n name: string,\n tenantId: string | null,\n): Promise<Role | null> {\n const normalizedTenant = normalizeTenantId(tenantId ?? null) ?? null\n return findOneWithDecryption(em, Role, { name, tenantId: normalizedTenant }, {}, { tenantId: normalizedTenant, organizationId: null })\n}\n\nasync function findRoleByNameOrFail(\n em: EntityManager,\n name: string,\n tenantId: string | null,\n): Promise<Role> {\n const role = await findRoleByName(em, name, tenantId)\n if (!role) throw new Error(`ROLE_NOT_FOUND:${name}`)\n return role\n}\n\ntype PrimaryUserInput = {\n email: string\n password?: string\n hashedPassword?: string | null\n firstName?: string | null\n lastName?: string | null\n displayName?: string | null\n confirm?: boolean\n}\n\nconst DERIVED_EMAIL_ENV = {\n admin: 'OM_INIT_ADMIN_EMAIL',\n employee: 'OM_INIT_EMPLOYEE_EMAIL',\n} as const\n\nexport type SetupInitialTenantOptions = {\n orgName: string\n primaryUser: PrimaryUserInput\n roleNames?: string[]\n includeDerivedUsers?: boolean\n failIfUserExists?: boolean\n primaryUserRoles?: string[]\n includeSuperadminRole?: boolean\n /** Optional list of enabled modules. When provided, module setup hooks are called. */\n modules?: Module[]\n /**\n * Optional global slug to persist on the new Organization. When set, a\n * pre-flight uniqueness check across all tenants throws OrgSlugExistsError\n * on collision so scriptable callers can fail loudly instead of silently\n * reusing or clobbering an existing organization.\n */\n orgSlug?: string\n}\n\nexport class OrgSlugExistsError extends Error {\n constructor(public readonly slug: string) {\n super(`ORG_SLUG_EXISTS: an organization with slug \"${slug}\" already exists`)\n this.name = 'OrgSlugExistsError'\n }\n}\n\nexport type SetupInitialTenantResult = {\n tenantId: string\n organizationId: string\n users: Array<{ user: User; roles: string[]; created: boolean }>\n reusedExistingUser: boolean\n}\n\nexport async function setupInitialTenant(\n em: EntityManager,\n options: SetupInitialTenantOptions,\n): Promise<SetupInitialTenantResult> {\n const {\n primaryUser,\n includeDerivedUsers = true,\n failIfUserExists = false,\n primaryUserRoles,\n includeSuperadminRole = true,\n } = options\n const primaryRolesInput = primaryUserRoles && primaryUserRoles.length ? primaryUserRoles : ['superadmin']\n const primaryRoles = includeSuperadminRole\n ? primaryRolesInput\n : primaryRolesInput.filter((role) => role !== 'superadmin')\n if (primaryRoles.length === 0) {\n throw new Error('PRIMARY_ROLES_REQUIRED')\n }\n const defaultRoleNames = options.roleNames ?? [...DEFAULT_ROLE_NAMES]\n const resolvedRoleNames = includeSuperadminRole\n ? defaultRoleNames\n : defaultRoleNames.filter((role) => role !== 'superadmin')\n const roleNames = Array.from(new Set([...resolvedRoleNames, ...primaryRoles]))\n const resolvedModules = options.modules ?? tryGetModules()\n const defaultEncryptionMaps = getDefaultEncryptionMaps(resolvedModules)\n\n const mainEmail = primaryUser.email\n const existingUser = await findOneWithDecryption(em, User, { email: mainEmail }, {}, { tenantId: null, organizationId: null })\n if (existingUser && failIfUserExists) {\n throw new Error('USER_EXISTS')\n }\n\n if (options.orgSlug) {\n const slugConflict = await findOneWithDecryption(\n em,\n Organization,\n { slug: options.orgSlug },\n {},\n { tenantId: null, organizationId: null },\n )\n if (slugConflict) {\n throw new OrgSlugExistsError(options.orgSlug)\n }\n }\n\n let tenantId: string | undefined\n let organizationId: string | undefined\n let reusedExistingUser = false\n const userSnapshots: Array<{ user: User; roles: string[]; created: boolean }> = []\n\n await em.transactional(async (tem) => {\n if (!existingUser) return\n reusedExistingUser = true\n tenantId = existingUser.tenantId ? String(existingUser.tenantId) : undefined\n organizationId = existingUser.organizationId ? String(existingUser.organizationId) : undefined\n const roleTenantId = normalizeTenantId(existingUser.tenantId ?? null) ?? null\n if (!roleTenantId) {\n throw new Error('Cannot reuse a user without a tenantId \u2014 global roles are not supported')\n }\n\n await ensureRolesInContext(tem, roleNames, roleTenantId)\n await tem.flush()\n\n const requiredRoleSet = new Set([...roleNames, ...primaryRoles])\n const links = await findWithDecryption(\n tem,\n UserRole,\n { user: existingUser },\n { populate: ['role'] },\n { tenantId: roleTenantId, organizationId: null },\n )\n const currentRoles = new Set(links.map((link) => link.role.name))\n for (const roleName of requiredRoleSet) {\n if (!currentRoles.has(roleName)) {\n const role = await findRoleByNameOrFail(tem, roleName, roleTenantId)\n tem.persist(tem.create(UserRole, { user: existingUser, role, createdAt: new Date() }))\n }\n }\n await tem.flush()\n const roles = Array.from(new Set([...currentRoles, ...roleNames]))\n userSnapshots.push({ user: existingUser, roles, created: false })\n })\n\n if (!existingUser) {\n const baseUsers: Array<{\n email: string\n roles: string[]\n name?: string | null\n passwordHash?: string | null\n }> = [\n { email: primaryUser.email, roles: primaryRoles, name: resolvePrimaryName(primaryUser) },\n ]\n if (includeDerivedUsers) {\n const adminOverride = readEnvValue(DERIVED_EMAIL_ENV.admin)\n const employeeOverride = readEnvValue(DERIVED_EMAIL_ENV.employee)\n const adminEmail = adminOverride ?? `admin@${DEFAULT_DERIVED_EMAIL_DOMAIN}`\n const employeeEmail = employeeOverride ?? `employee@${DEFAULT_DERIVED_EMAIL_DOMAIN}`\n const adminPassword = readEnvValue('OM_INIT_ADMIN_PASSWORD') || 'secret'\n const employeePassword = readEnvValue('OM_INIT_EMPLOYEE_PASSWORD') || 'secret'\n const adminPasswordHash = adminPassword ? await resolvePasswordHash({ email: adminEmail, password: adminPassword }) : null\n const employeePasswordHash = employeePassword\n ? await resolvePasswordHash({ email: employeeEmail, password: employeePassword })\n : null\n addUniqueBaseUser(baseUsers, { email: adminEmail, roles: ['admin'], passwordHash: adminPasswordHash })\n addUniqueBaseUser(baseUsers, { email: employeeEmail, roles: ['employee'], passwordHash: employeePasswordHash })\n }\n const passwordHash = await resolvePasswordHash(primaryUser)\n\n await em.transactional(async (tem) => {\n const tenant = tem.create(Tenant, {\n name: `${options.orgName} Tenant`,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n tem.persist(tenant)\n await tem.flush()\n\n const organization = tem.create(Organization, {\n name: options.orgName,\n slug: options.orgSlug ?? null,\n tenant,\n isActive: true,\n depth: 0,\n ancestorIds: [],\n childIds: [],\n descendantIds: [],\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n tem.persist(organization)\n await tem.flush()\n\n tenantId = String(tenant.id)\n organizationId = String(organization.id)\n const roleTenantId = tenantId\n\n if (isTenantDataEncryptionEnabled()) {\n try {\n const kms = createKmsService()\n if (kms.isHealthy()) {\n if (isEncryptionDebugEnabled()) {\n console.info('\uD83D\uDD11 [encryption][setup] provisioning tenant DEK', { tenantId: String(tenant.id) })\n }\n await kms.createTenantDek(String(tenant.id))\n if (isEncryptionDebugEnabled()) {\n console.info('\uD83D\uDD11 [encryption][setup] created tenant DEK during setup', { tenantId: String(tenant.id) })\n }\n } else {\n if (isEncryptionDebugEnabled()) {\n console.warn('\u26A0\uFE0F [encryption][setup] KMS not healthy, skipping tenant DEK creation', { tenantId: String(tenant.id) })\n }\n }\n } catch (err) {\n if (isEncryptionDebugEnabled()) {\n console.warn('\u26A0\uFE0F [encryption][setup] Failed to create tenant DEK', err)\n }\n }\n }\n\n await ensureRolesInContext(tem, roleNames, roleTenantId)\n await tem.flush()\n\n if (isTenantDataEncryptionEnabled()) {\n for (const spec of defaultEncryptionMaps) {\n const existing = await findOneWithDecryption(tem, EncryptionMap, { entityId: spec.entityId, tenantId: tenant.id, organizationId: organization.id, deletedAt: null }, {}, { tenantId: String(tenant.id), organizationId: String(organization.id) })\n if (!existing) {\n tem.persist(tem.create(EncryptionMap, {\n entityId: spec.entityId,\n tenantId: tenant.id,\n organizationId: organization.id,\n fieldsJson: spec.fields,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n }))\n } else {\n existing.fieldsJson = spec.fields\n existing.isActive = true\n }\n }\n await tem.flush()\n }\n })\n\n await em.transactional(async (tem) => {\n if (!tenantId || !organizationId) return\n const roleTenantId = tenantId\n const encryptionService = isTenantDataEncryptionEnabled()\n ? new TenantDataEncryptionService(tem as any, { kms: createKmsService() })\n : null\n if (encryptionService) {\n await encryptionService.invalidateMap('auth:user', String(tenantId), String(organizationId))\n await encryptionService.invalidateMap('auth:user', String(tenantId), null)\n }\n\n for (const base of baseUsers) {\n const resolvedPasswordHash = base.passwordHash ?? passwordHash\n let user = await findOneWithDecryption(tem, User, { email: base.email }, {}, { tenantId: tenantId ?? null, organizationId: organizationId ?? null })\n const confirm = primaryUser.confirm ?? true\n const encryptedPayload = encryptionService\n ? await encryptionService.encryptEntityPayload('auth:user', { email: base.email }, tenantId, organizationId)\n : { email: base.email, emailHash: computeEmailHash(base.email) }\n if (user) {\n user.passwordHash = resolvedPasswordHash\n user.organizationId = organizationId\n user.tenantId = tenantId\n if (isTenantDataEncryptionEnabled()) {\n user.email = encryptedPayload.email as any\n user.emailHash = (encryptedPayload as any).emailHash ?? computeEmailHash(base.email)\n }\n if (base.name) user.name = base.name\n if (confirm) user.isConfirmed = true\n tem.persist(user)\n userSnapshots.push({ user, roles: base.roles, created: false })\n } else {\n user = tem.create(User, {\n email: (encryptedPayload as any).email ?? base.email,\n emailHash: isTenantDataEncryptionEnabled() ? (encryptedPayload as any).emailHash ?? computeEmailHash(base.email) : undefined,\n passwordHash: resolvedPasswordHash,\n organizationId,\n tenantId,\n name: base.name ?? undefined,\n isConfirmed: confirm,\n createdAt: new Date(),\n })\n tem.persist(user)\n userSnapshots.push({ user, roles: base.roles, created: true })\n }\n await tem.flush()\n for (const roleName of base.roles) {\n const role = await findRoleByNameOrFail(tem, roleName, roleTenantId)\n const existingLink = await findOneWithDecryption(tem, UserRole, { user, role }, {}, { tenantId: tenantId ?? null, organizationId: null })\n if (!existingLink) tem.persist(tem.create(UserRole, { user, role, createdAt: new Date() }))\n }\n await tem.flush()\n }\n })\n }\n\n if (!tenantId || !organizationId) {\n throw new Error('SETUP_FAILED')\n }\n\n if (!reusedExistingUser) {\n await rebuildHierarchyForTenant(em, tenantId)\n }\n\n await ensureDefaultRoleAcls(em, tenantId, resolvedModules, { includeSuperadminRole })\n await deactivateDemoSuperAdminIfSelfOnboardingEnabled(em)\n\n // Call module onTenantCreated hooks\n for (const mod of resolvedModules) {\n if (mod.setup?.onTenantCreated) {\n await mod.setup.onTenantCreated({ em, tenantId, organizationId })\n }\n }\n\n return {\n tenantId,\n organizationId,\n users: userSnapshots,\n reusedExistingUser,\n }\n}\n\nfunction resolvePrimaryName(input: PrimaryUserInput): string | null {\n if (input.displayName && input.displayName.trim()) return input.displayName.trim()\n const parts = [input.firstName, input.lastName].map((value) => value?.trim()).filter(Boolean)\n if (parts.length) return parts.join(' ')\n return null\n}\n\nfunction readEnvValue(key: string): string | undefined {\n const value = process.env[key]\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\nfunction addUniqueBaseUser(\n baseUsers: Array<{ email: string; roles: string[]; name?: string | null; passwordHash?: string | null }>,\n entry: { email: string; roles: string[]; name?: string | null; passwordHash?: string | null },\n) {\n if (!entry.email) return\n const normalized = entry.email.toLowerCase()\n if (baseUsers.some((user) => user.email.toLowerCase() === normalized)) return\n baseUsers.push(entry)\n}\n\nfunction isDemoModeEnabled(): boolean {\n const parsed = parseBooleanToken(process.env.DEMO_MODE ?? '')\n return parsed === false ? false : true\n}\n\nfunction shouldKeepDemoSuperadminDuringInit(): boolean {\n if (process.env.OM_INIT_FLOW !== 'true') return false\n if (!readEnvValue('OM_INIT_SUPERADMIN_EMAIL')) return false\n return isDemoModeEnabled()\n}\n\nasync function resolvePasswordHash(input: PrimaryUserInput): Promise<string | null> {\n if (typeof input.hashedPassword === 'string') return input.hashedPassword\n if (input.password) return hash(input.password, 10)\n return null\n}\n\nexport async function ensureDefaultRoleAcls(\n em: EntityManager,\n tenantId: string,\n modules: Module[],\n options: { includeSuperadminRole?: boolean } = {},\n) {\n const includeSuperadminRole = options.includeSuperadminRole ?? true\n const roleTenantId = normalizeTenantId(tenantId) ?? null\n const superadminRole = includeSuperadminRole ? await findRoleByName(em, 'superadmin', roleTenantId) : null\n const adminRole = await findRoleByName(em, 'admin', roleTenantId)\n const employeeRole = await findRoleByName(em, 'employee', roleTenantId)\n\n // Merge features from all enabled modules' setup configs\n const builtInRoles = ['superadmin', 'admin', 'employee'] as const\n const superadminFeatures: string[] = []\n const adminFeatures: string[] = []\n const employeeFeatures: string[] = []\n const customRoleFeatures = new Map<string, string[]>()\n\n for (const mod of modules) {\n const roleFeatures = mod.setup?.defaultRoleFeatures\n if (!roleFeatures) continue\n if (roleFeatures.superadmin) superadminFeatures.push(...roleFeatures.superadmin)\n if (roleFeatures.admin) adminFeatures.push(...roleFeatures.admin)\n if (roleFeatures.employee) employeeFeatures.push(...roleFeatures.employee)\n\n // Collect features for custom roles (any key not in builtInRoles)\n for (const [roleName, features] of Object.entries(roleFeatures)) {\n if ((builtInRoles as readonly string[]).includes(roleName)) continue\n if (!Array.isArray(features)) continue\n const existing = customRoleFeatures.get(roleName) ?? []\n existing.push(...features)\n customRoleFeatures.set(roleName, existing)\n }\n }\n\n console.log('\u2705 Seeded default role features', {\n superadmin: superadminFeatures,\n admin: adminFeatures,\n employee: employeeFeatures,\n ...(customRoleFeatures.size > 0\n ? Object.fromEntries(customRoleFeatures)\n : {}),\n })\n\n if (includeSuperadminRole && superadminRole) {\n await ensureRoleAclFor(em, superadminRole, tenantId, superadminFeatures, { isSuperAdmin: true })\n }\n if (adminRole) {\n await ensureRoleAclFor(em, adminRole, tenantId, adminFeatures)\n }\n if (employeeRole) {\n await ensureRoleAclFor(em, employeeRole, tenantId, employeeFeatures)\n }\n\n // Seed ACLs for custom roles defined by app modules.\n // NOTE: Custom roles may not exist yet if they are created in seedDefaults\n // (which runs after this function). In that case, use ensureCustomRoleAcls()\n // after seedDefaults to pick them up.\n for (const [roleName, features] of customRoleFeatures) {\n const role = await findRoleByName(em, roleName, roleTenantId)\n if (role) {\n await ensureRoleAclFor(em, role, tenantId, features)\n }\n }\n}\n\n/**\n * Seed ACLs for custom roles defined in module defaultRoleFeatures.\n * Call this AFTER seedDefaults to pick up roles created by app modules.\n * Safe to call multiple times \u2014 ensureRoleAclFor merges features idempotently.\n */\nexport async function ensureCustomRoleAcls(\n em: EntityManager,\n tenantId: string,\n modules?: Module[],\n): Promise<void> {\n const resolvedModules = modules ?? tryGetModules()\n const roleTenantId = normalizeTenantId(tenantId) ?? null\n const builtInRoles = ['superadmin', 'admin', 'employee']\n const customRoleFeatures = new Map<string, string[]>()\n\n for (const mod of resolvedModules) {\n const roleFeatures = mod.setup?.defaultRoleFeatures\n if (!roleFeatures) continue\n for (const [roleName, features] of Object.entries(roleFeatures)) {\n if (builtInRoles.includes(roleName)) continue\n if (!Array.isArray(features)) continue\n const existing = customRoleFeatures.get(roleName) ?? []\n existing.push(...features)\n customRoleFeatures.set(roleName, existing)\n }\n }\n\n if (customRoleFeatures.size === 0) return\n\n let seeded = 0\n for (const [roleName, features] of customRoleFeatures) {\n const role = await findRoleByName(em, roleName, roleTenantId)\n if (role) {\n await ensureRoleAclFor(em, role, tenantId, features)\n seeded++\n }\n }\n if (seeded > 0) {\n console.log(`\u2705 Seeded custom role ACLs (${seeded} roles)`)\n }\n}\n\nasync function ensureRoleAclFor(\n em: EntityManager,\n role: Role,\n tenantId: string,\n features: string[],\n options: { isSuperAdmin?: boolean } = {},\n) {\n const existing = await findOneWithDecryption(em, RoleAcl, { role, tenantId }, {}, { tenantId, organizationId: null })\n if (!existing) {\n const acl = em.create(RoleAcl, {\n role,\n tenantId,\n featuresJson: features,\n isSuperAdmin: !!options.isSuperAdmin,\n createdAt: new Date(),\n })\n await em.persist(acl).flush()\n return\n }\n const currentFeatures = Array.isArray(existing.featuresJson) ? existing.featuresJson : []\n const merged = Array.from(new Set([...currentFeatures, ...features]))\n const changed =\n merged.length !== currentFeatures.length ||\n merged.some((value, index) => value !== currentFeatures[index])\n if (changed) existing.featuresJson = merged\n if (options.isSuperAdmin && !existing.isSuperAdmin) {\n existing.isSuperAdmin = true\n }\n if (changed || options.isSuperAdmin) {\n await em.persist(existing).flush()\n }\n}\n\nasync function deactivateDemoSuperAdminIfSelfOnboardingEnabled(em: EntityManager) {\n if (process.env.SELF_SERVICE_ONBOARDING_ENABLED !== 'true') return\n if (shouldKeepDemoSuperadminDuringInit()) return\n try {\n const user = await findOneWithDecryption(em, User, { email: DEMO_SUPERADMIN_EMAIL }, {}, { tenantId: null, organizationId: null })\n if (!user) return\n let dirty = false\n if (user.passwordHash) {\n user.passwordHash = null\n dirty = true\n }\n if (user.isConfirmed !== false) {\n user.isConfirmed = false\n dirty = true\n }\n if (dirty) {\n await em.persist(user).flush()\n }\n } catch (error) {\n console.error('[auth.setup] failed to deactivate demo superadmin user', error)\n }\n}\n\n/** Try to get modules from runtime registry; returns empty array if not yet registered. */\nfunction tryGetModules(): Module[] {\n try {\n const { getModules } = require('@open-mercato/shared/lib/modules/registry')\n return getModules()\n } catch {\n return []\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { hash } from 'bcryptjs'\nimport { randomBytes } from 'node:crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { Role, RoleAcl, User, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport { Tenant, Organization } from '@open-mercato/core/modules/directory/data/entities'\nimport { rebuildHierarchyForTenant } from '@open-mercato/core/modules/directory/lib/hierarchy'\nimport { normalizeTenantId } from './tenantAccess'\nimport { computeEmailHash } from '@open-mercato/core/modules/auth/lib/emailHash'\nimport { getDefaultEncryptionMaps, type Module } from '@open-mercato/shared/modules/registry'\nimport { isEncryptionDebugEnabled, isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'\nimport { EncryptionMap } from '@open-mercato/core/modules/entities/data/entities'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nconst DEFAULT_ROLE_NAMES = ['employee', 'admin', 'superadmin'] as const\nconst DEMO_SUPERADMIN_EMAIL = 'superadmin@acme.com'\nconst DEFAULT_DERIVED_EMAIL_DOMAIN = DEMO_SUPERADMIN_EMAIL.split('@')[1] ?? 'acme.com'\n\nexport type EnsureRolesOptions = {\n roleNames?: string[]\n tenantId?: string\n}\n\nasync function ensureRolesInContext(\n em: EntityManager,\n roleNames: string[],\n tenantId: string,\n) {\n for (const name of roleNames) {\n const existing = await findOneWithDecryption(em, Role, { name, tenantId }, {}, { tenantId, organizationId: null })\n if (existing) continue\n em.persist(em.create(Role, { name, tenantId, createdAt: new Date() }))\n }\n}\n\nexport async function ensureRoles(em: EntityManager, options: EnsureRolesOptions = {}) {\n const roleNames = options.roleNames ?? [...DEFAULT_ROLE_NAMES]\n const tenantId = normalizeTenantId(options.tenantId ?? null)\n if (!tenantId) {\n throw new Error('ensureRoles requires a tenantId \u2014 global roles are not supported')\n }\n await em.transactional(async (tem) => {\n await ensureRolesInContext(tem, roleNames, tenantId)\n await tem.flush()\n })\n}\n\nasync function findRoleByName(\n em: EntityManager,\n name: string,\n tenantId: string | null,\n): Promise<Role | null> {\n const normalizedTenant = normalizeTenantId(tenantId ?? null) ?? null\n return findOneWithDecryption(em, Role, { name, tenantId: normalizedTenant }, {}, { tenantId: normalizedTenant, organizationId: null })\n}\n\nasync function findRoleByNameOrFail(\n em: EntityManager,\n name: string,\n tenantId: string | null,\n): Promise<Role> {\n const role = await findRoleByName(em, name, tenantId)\n if (!role) throw new Error(`ROLE_NOT_FOUND:${name}`)\n return role\n}\n\ntype PrimaryUserInput = {\n email: string\n password?: string\n hashedPassword?: string | null\n firstName?: string | null\n lastName?: string | null\n displayName?: string | null\n confirm?: boolean\n}\n\nconst DERIVED_EMAIL_ENV = {\n admin: 'OM_INIT_ADMIN_EMAIL',\n employee: 'OM_INIT_EMPLOYEE_EMAIL',\n} as const\n\nexport type DemoUserRole = 'superadmin' | 'admin' | 'employee'\nexport type DemoUserEmail = { role: DemoUserRole; email: string }\n\n/**\n * Returns the canonical list of demo user emails the setup path may have\n * seeded. Honors OM_INIT_ADMIN_EMAIL / OM_INIT_EMPLOYEE_EMAIL exactly the\n * same way the derived-user seeding branch does, so the deactivation loop\n * never drifts from the seeding loop.\n *\n * Pure function \u2014 no side effects, safe to unit-test in isolation.\n *\n * @internal Exported for tests; not part of the public auth API.\n */\nexport function resolveDemoUserEmails(): DemoUserEmail[] {\n const adminEmail = readEnvValue(DERIVED_EMAIL_ENV.admin) ?? `admin@${DEFAULT_DERIVED_EMAIL_DOMAIN}`\n const employeeEmail = readEnvValue(DERIVED_EMAIL_ENV.employee) ?? `employee@${DEFAULT_DERIVED_EMAIL_DOMAIN}`\n return [\n { role: 'superadmin', email: DEMO_SUPERADMIN_EMAIL },\n { role: 'admin', email: adminEmail },\n { role: 'employee', email: employeeEmail },\n ]\n}\n\nexport type SetupInitialTenantOptions = {\n orgName: string\n primaryUser: PrimaryUserInput\n roleNames?: string[]\n includeDerivedUsers?: boolean\n failIfUserExists?: boolean\n primaryUserRoles?: string[]\n includeSuperadminRole?: boolean\n /** Optional list of enabled modules. When provided, module setup hooks are called. */\n modules?: Module[]\n /**\n * Optional global slug to persist on the new Organization. When set, a\n * pre-flight uniqueness check across all tenants throws OrgSlugExistsError\n * on collision so scriptable callers can fail loudly instead of silently\n * reusing or clobbering an existing organization.\n */\n orgSlug?: string\n /**\n * Opt-in flag that allows seeding the derived admin/employee accounts when\n * the OM_INIT_ADMIN_PASSWORD / OM_INIT_EMPLOYEE_PASSWORD env vars are unset.\n *\n * - In non-production (`NODE_ENV !== 'production'`): the demo accounts are\n * seeded with the well-known `'secret'` password so local dev workflows\n * (e.g. `mercato init`) stay predictable for developers.\n * - In production: the fallback uses a randomly generated 96-bit password\n * (printed once by the CLI) so an operator who deliberately opts in still\n * avoids the historical hardcoded credential. When this flag is false/unset\n * in production and the env vars are missing, `DerivedUserPasswordRequiredError`\n * is thrown instead of silently seeding any account.\n */\n allowDemoDerivedPasswords?: boolean\n}\n\nexport class OrgSlugExistsError extends Error {\n constructor(public readonly slug: string) {\n super(`ORG_SLUG_EXISTS: an organization with slug \"${slug}\" already exists`)\n this.name = 'OrgSlugExistsError'\n }\n}\n\nexport class DerivedUserPasswordRequiredError extends Error {\n constructor(public readonly missing: string[]) {\n super(\n `DERIVED_USER_PASSWORD_REQUIRED: missing ${missing.join(', ')} (set the env vars, pass allowDemoDerivedPasswords: true / --include-demo-users in non-production, or disable derived user seeding)`,\n )\n this.name = 'DerivedUserPasswordRequiredError'\n }\n}\n\nexport type SetupInitialTenantResult = {\n tenantId: string\n organizationId: string\n users: Array<{ user: User; roles: string[]; created: boolean; generatedPassword?: string | null }>\n reusedExistingUser: boolean\n}\n\nexport async function setupInitialTenant(\n em: EntityManager,\n options: SetupInitialTenantOptions,\n): Promise<SetupInitialTenantResult> {\n const {\n primaryUser,\n includeDerivedUsers = true,\n failIfUserExists = false,\n primaryUserRoles,\n includeSuperadminRole = true,\n } = options\n const primaryRolesInput = primaryUserRoles && primaryUserRoles.length ? primaryUserRoles : ['superadmin']\n const primaryRoles = includeSuperadminRole\n ? primaryRolesInput\n : primaryRolesInput.filter((role) => role !== 'superadmin')\n if (primaryRoles.length === 0) {\n throw new Error('PRIMARY_ROLES_REQUIRED')\n }\n const defaultRoleNames = options.roleNames ?? [...DEFAULT_ROLE_NAMES]\n const resolvedRoleNames = includeSuperadminRole\n ? defaultRoleNames\n : defaultRoleNames.filter((role) => role !== 'superadmin')\n const roleNames = Array.from(new Set([...resolvedRoleNames, ...primaryRoles]))\n const resolvedModules = options.modules ?? tryGetModules()\n const defaultEncryptionMaps = getDefaultEncryptionMaps(resolvedModules)\n\n const mainEmail = primaryUser.email\n const existingUser = await findOneWithDecryption(em, User, { email: mainEmail }, {}, { tenantId: null, organizationId: null })\n if (existingUser && failIfUserExists) {\n throw new Error('USER_EXISTS')\n }\n\n if (options.orgSlug) {\n const slugConflict = await findOneWithDecryption(\n em,\n Organization,\n { slug: options.orgSlug },\n {},\n { tenantId: null, organizationId: null },\n )\n if (slugConflict) {\n throw new OrgSlugExistsError(options.orgSlug)\n }\n }\n\n let tenantId: string | undefined\n let organizationId: string | undefined\n let reusedExistingUser = false\n const userSnapshots: Array<{ user: User; roles: string[]; created: boolean; generatedPassword?: string | null }> = []\n\n await em.transactional(async (tem) => {\n if (!existingUser) return\n reusedExistingUser = true\n tenantId = existingUser.tenantId ? String(existingUser.tenantId) : undefined\n organizationId = existingUser.organizationId ? String(existingUser.organizationId) : undefined\n const roleTenantId = normalizeTenantId(existingUser.tenantId ?? null) ?? null\n if (!roleTenantId) {\n throw new Error('Cannot reuse a user without a tenantId \u2014 global roles are not supported')\n }\n\n await ensureRolesInContext(tem, roleNames, roleTenantId)\n await tem.flush()\n\n const requiredRoleSet = new Set([...roleNames, ...primaryRoles])\n const links = await findWithDecryption(\n tem,\n UserRole,\n { user: existingUser },\n { populate: ['role'] },\n { tenantId: roleTenantId, organizationId: null },\n )\n const currentRoles = new Set(links.map((link) => link.role.name))\n for (const roleName of requiredRoleSet) {\n if (!currentRoles.has(roleName)) {\n const role = await findRoleByNameOrFail(tem, roleName, roleTenantId)\n tem.persist(tem.create(UserRole, { user: existingUser, role, createdAt: new Date() }))\n }\n }\n await tem.flush()\n const roles = Array.from(new Set([...currentRoles, ...roleNames]))\n userSnapshots.push({ user: existingUser, roles, created: false })\n })\n\n if (!existingUser) {\n const baseUsers: Array<{\n email: string\n roles: string[]\n name?: string | null\n passwordHash?: string | null\n generatedPassword?: string | null\n }> = [\n { email: primaryUser.email, roles: primaryRoles, name: resolvePrimaryName(primaryUser) },\n ]\n if (includeDerivedUsers) {\n const adminOverride = readEnvValue(DERIVED_EMAIL_ENV.admin)\n const employeeOverride = readEnvValue(DERIVED_EMAIL_ENV.employee)\n const adminEmail = adminOverride ?? `admin@${DEFAULT_DERIVED_EMAIL_DOMAIN}`\n const employeeEmail = employeeOverride ?? `employee@${DEFAULT_DERIVED_EMAIL_DOMAIN}`\n const envAdminPwd = readEnvValue('OM_INIT_ADMIN_PASSWORD')\n const envEmployeePwd = readEnvValue('OM_INIT_EMPLOYEE_PASSWORD')\n const isProduction = process.env.NODE_ENV === 'production'\n const allowDemo = options.allowDemoDerivedPasswords === true\n if (isProduction && !allowDemo) {\n const missing: string[] = []\n if (!envAdminPwd) missing.push('OM_INIT_ADMIN_PASSWORD')\n if (!envEmployeePwd) missing.push('OM_INIT_EMPLOYEE_PASSWORD')\n if (missing.length) {\n throw new DerivedUserPasswordRequiredError(missing)\n }\n }\n // In non-production, fall back to the well-known DEMO_DERIVED_PASSWORD so\n // local dev workflows stay predictable (admin@/employee@ login with the\n // documented demo password). In production we either have env-supplied\n // values, or \u2014 for opt-in --include-demo-users flows \u2014 we generate a\n // random one-time password and surface it via `generatedPassword` so the\n // operator can capture it. Production callers without env vars and\n // without the opt-in already threw above.\n const fallbackAdminPwd = isProduction ? generateDerivedPassword() : DEMO_DERIVED_PASSWORD\n const fallbackEmployeePwd = isProduction ? generateDerivedPassword() : DEMO_DERIVED_PASSWORD\n const adminPasswordPlain = envAdminPwd ?? fallbackAdminPwd\n const employeePasswordPlain = envEmployeePwd ?? fallbackEmployeePwd\n const adminPasswordHash = await hash(adminPasswordPlain, 10)\n const employeePasswordHash = await hash(employeePasswordPlain, 10)\n addUniqueBaseUser(baseUsers, {\n email: adminEmail,\n roles: ['admin'],\n passwordHash: adminPasswordHash,\n generatedPassword: envAdminPwd ? null : adminPasswordPlain,\n })\n addUniqueBaseUser(baseUsers, {\n email: employeeEmail,\n roles: ['employee'],\n passwordHash: employeePasswordHash,\n generatedPassword: envEmployeePwd ? null : employeePasswordPlain,\n })\n }\n const passwordHash = await resolvePasswordHash(primaryUser)\n\n await em.transactional(async (tem) => {\n const tenant = tem.create(Tenant, {\n name: `${options.orgName} Tenant`,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n tem.persist(tenant)\n await tem.flush()\n\n const organization = tem.create(Organization, {\n name: options.orgName,\n slug: options.orgSlug ?? null,\n tenant,\n isActive: true,\n depth: 0,\n ancestorIds: [],\n childIds: [],\n descendantIds: [],\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n tem.persist(organization)\n await tem.flush()\n\n tenantId = String(tenant.id)\n organizationId = String(organization.id)\n const roleTenantId = tenantId\n\n if (isTenantDataEncryptionEnabled()) {\n try {\n const kms = createKmsService()\n if (kms.isHealthy()) {\n if (isEncryptionDebugEnabled()) {\n console.info('\uD83D\uDD11 [encryption][setup] provisioning tenant DEK', { tenantId: String(tenant.id) })\n }\n await kms.createTenantDek(String(tenant.id))\n if (isEncryptionDebugEnabled()) {\n console.info('\uD83D\uDD11 [encryption][setup] created tenant DEK during setup', { tenantId: String(tenant.id) })\n }\n } else {\n if (isEncryptionDebugEnabled()) {\n console.warn('\u26A0\uFE0F [encryption][setup] KMS not healthy, skipping tenant DEK creation', { tenantId: String(tenant.id) })\n }\n }\n } catch (err) {\n if (isEncryptionDebugEnabled()) {\n console.warn('\u26A0\uFE0F [encryption][setup] Failed to create tenant DEK', err)\n }\n }\n }\n\n await ensureRolesInContext(tem, roleNames, roleTenantId)\n await tem.flush()\n\n if (isTenantDataEncryptionEnabled()) {\n for (const spec of defaultEncryptionMaps) {\n const existing = await findOneWithDecryption(tem, EncryptionMap, { entityId: spec.entityId, tenantId: tenant.id, organizationId: organization.id, deletedAt: null }, {}, { tenantId: String(tenant.id), organizationId: String(organization.id) })\n if (!existing) {\n tem.persist(tem.create(EncryptionMap, {\n entityId: spec.entityId,\n tenantId: tenant.id,\n organizationId: organization.id,\n fieldsJson: spec.fields,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n }))\n } else {\n existing.fieldsJson = spec.fields\n existing.isActive = true\n }\n }\n await tem.flush()\n }\n })\n\n await em.transactional(async (tem) => {\n if (!tenantId || !organizationId) return\n const roleTenantId = tenantId\n const encryptionService = isTenantDataEncryptionEnabled()\n ? new TenantDataEncryptionService(tem as any, { kms: createKmsService() })\n : null\n if (encryptionService) {\n await encryptionService.invalidateMap('auth:user', String(tenantId), String(organizationId))\n await encryptionService.invalidateMap('auth:user', String(tenantId), null)\n }\n\n for (const base of baseUsers) {\n const resolvedPasswordHash = base.passwordHash ?? passwordHash\n let user = await findOneWithDecryption(tem, User, { email: base.email }, {}, { tenantId: tenantId ?? null, organizationId: organizationId ?? null })\n const confirm = primaryUser.confirm ?? true\n const encryptedPayload = encryptionService\n ? await encryptionService.encryptEntityPayload('auth:user', { email: base.email }, tenantId, organizationId)\n : { email: base.email, emailHash: computeEmailHash(base.email) }\n if (user) {\n user.passwordHash = resolvedPasswordHash\n user.organizationId = organizationId\n user.tenantId = tenantId\n if (isTenantDataEncryptionEnabled()) {\n user.email = encryptedPayload.email as any\n user.emailHash = (encryptedPayload as any).emailHash ?? computeEmailHash(base.email)\n }\n if (base.name) user.name = base.name\n if (confirm) user.isConfirmed = true\n tem.persist(user)\n userSnapshots.push({ user, roles: base.roles, created: false, generatedPassword: base.generatedPassword ?? null })\n } else {\n user = tem.create(User, {\n email: (encryptedPayload as any).email ?? base.email,\n emailHash: isTenantDataEncryptionEnabled() ? (encryptedPayload as any).emailHash ?? computeEmailHash(base.email) : undefined,\n passwordHash: resolvedPasswordHash,\n organizationId,\n tenantId,\n name: base.name ?? undefined,\n isConfirmed: confirm,\n createdAt: new Date(),\n })\n tem.persist(user)\n userSnapshots.push({ user, roles: base.roles, created: true, generatedPassword: base.generatedPassword ?? null })\n }\n await tem.flush()\n for (const roleName of base.roles) {\n const role = await findRoleByNameOrFail(tem, roleName, roleTenantId)\n const existingLink = await findOneWithDecryption(tem, UserRole, { user, role }, {}, { tenantId: tenantId ?? null, organizationId: null })\n if (!existingLink) tem.persist(tem.create(UserRole, { user, role, createdAt: new Date() }))\n }\n await tem.flush()\n }\n })\n }\n\n if (!tenantId || !organizationId) {\n throw new Error('SETUP_FAILED')\n }\n\n if (!reusedExistingUser) {\n await rebuildHierarchyForTenant(em, tenantId)\n }\n\n await ensureDefaultRoleAcls(em, tenantId, resolvedModules, { includeSuperadminRole })\n await deactivateDemoUsersIfSelfOnboardingEnabled(em)\n\n // Call module onTenantCreated hooks\n for (const mod of resolvedModules) {\n if (mod.setup?.onTenantCreated) {\n await mod.setup.onTenantCreated({ em, tenantId, organizationId })\n }\n }\n\n return {\n tenantId,\n organizationId,\n users: userSnapshots,\n reusedExistingUser,\n }\n}\n\nfunction resolvePrimaryName(input: PrimaryUserInput): string | null {\n if (input.displayName && input.displayName.trim()) return input.displayName.trim()\n const parts = [input.firstName, input.lastName].map((value) => value?.trim()).filter(Boolean)\n if (parts.length) return parts.join(' ')\n return null\n}\n\nfunction readEnvValue(key: string): string | undefined {\n const value = process.env[key]\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\nfunction addUniqueBaseUser(\n baseUsers: Array<{ email: string; roles: string[]; name?: string | null; passwordHash?: string | null; generatedPassword?: string | null }>,\n entry: { email: string; roles: string[]; name?: string | null; passwordHash?: string | null; generatedPassword?: string | null },\n) {\n if (!entry.email) return\n const normalized = entry.email.toLowerCase()\n if (baseUsers.some((user) => user.email.toLowerCase() === normalized)) return\n baseUsers.push(entry)\n}\n\n/**\n * Well-known demo password seeded for derived admin@/employee@ accounts in\n * non-production (`NODE_ENV !== 'production'`) when no env override is set.\n * Keeps the documented `yarn dev` / `mercato init` DX predictable. Never used\n * in production: the production branch either consumes env-supplied values or\n * falls back to `generateDerivedPassword()` so credentials remain non-guessable.\n */\nconst DEMO_DERIVED_PASSWORD = 'secret'\n\n/**\n * Generate a 16-character base64url password (96 bits of entropy) for a derived\n * demo user in production when no env override is provided AND the caller\n * explicitly opted in via `allowDemoDerivedPasswords`. Surfaced via the\n * `users[].generatedPassword` snapshot so CLI callers can print it to the\n * operator \u2014 there is no other recovery path for these credentials.\n */\nfunction generateDerivedPassword(): string {\n return randomBytes(12).toString('base64url')\n}\n\nfunction isDemoModeEnabled(): boolean {\n const parsed = parseBooleanToken(process.env.DEMO_MODE ?? '')\n return parsed === false ? false : true\n}\n\nfunction shouldKeepDemoSuperadminDuringInit(): boolean {\n if (process.env.OM_INIT_FLOW !== 'true') return false\n if (!readEnvValue('OM_INIT_SUPERADMIN_EMAIL')) return false\n return isDemoModeEnabled()\n}\n\nasync function resolvePasswordHash(input: PrimaryUserInput): Promise<string | null> {\n if (typeof input.hashedPassword === 'string') return input.hashedPassword\n if (input.password) return hash(input.password, 10)\n return null\n}\n\nexport async function ensureDefaultRoleAcls(\n em: EntityManager,\n tenantId: string,\n modules: Module[],\n options: { includeSuperadminRole?: boolean } = {},\n) {\n const includeSuperadminRole = options.includeSuperadminRole ?? true\n const roleTenantId = normalizeTenantId(tenantId) ?? null\n const superadminRole = includeSuperadminRole ? await findRoleByName(em, 'superadmin', roleTenantId) : null\n const adminRole = await findRoleByName(em, 'admin', roleTenantId)\n const employeeRole = await findRoleByName(em, 'employee', roleTenantId)\n\n // Merge features from all enabled modules' setup configs\n const builtInRoles = ['superadmin', 'admin', 'employee'] as const\n const superadminFeatures: string[] = []\n const adminFeatures: string[] = []\n const employeeFeatures: string[] = []\n const customRoleFeatures = new Map<string, string[]>()\n\n for (const mod of modules) {\n const roleFeatures = mod.setup?.defaultRoleFeatures\n if (!roleFeatures) continue\n if (roleFeatures.superadmin) superadminFeatures.push(...roleFeatures.superadmin)\n if (roleFeatures.admin) adminFeatures.push(...roleFeatures.admin)\n if (roleFeatures.employee) employeeFeatures.push(...roleFeatures.employee)\n\n // Collect features for custom roles (any key not in builtInRoles)\n for (const [roleName, features] of Object.entries(roleFeatures)) {\n if ((builtInRoles as readonly string[]).includes(roleName)) continue\n if (!Array.isArray(features)) continue\n const existing = customRoleFeatures.get(roleName) ?? []\n existing.push(...features)\n customRoleFeatures.set(roleName, existing)\n }\n }\n\n console.log('\u2705 Seeded default role features', {\n superadmin: superadminFeatures,\n admin: adminFeatures,\n employee: employeeFeatures,\n ...(customRoleFeatures.size > 0\n ? Object.fromEntries(customRoleFeatures)\n : {}),\n })\n\n if (includeSuperadminRole && superadminRole) {\n await ensureRoleAclFor(em, superadminRole, tenantId, superadminFeatures, { isSuperAdmin: true })\n }\n if (adminRole) {\n await ensureRoleAclFor(em, adminRole, tenantId, adminFeatures)\n }\n if (employeeRole) {\n await ensureRoleAclFor(em, employeeRole, tenantId, employeeFeatures)\n }\n\n // Seed ACLs for custom roles defined by app modules.\n // NOTE: Custom roles may not exist yet if they are created in seedDefaults\n // (which runs after this function). In that case, use ensureCustomRoleAcls()\n // after seedDefaults to pick them up.\n for (const [roleName, features] of customRoleFeatures) {\n const role = await findRoleByName(em, roleName, roleTenantId)\n if (role) {\n await ensureRoleAclFor(em, role, tenantId, features)\n }\n }\n}\n\n/**\n * Seed ACLs for custom roles defined in module defaultRoleFeatures.\n * Call this AFTER seedDefaults to pick up roles created by app modules.\n * Safe to call multiple times \u2014 ensureRoleAclFor merges features idempotently.\n */\nexport async function ensureCustomRoleAcls(\n em: EntityManager,\n tenantId: string,\n modules?: Module[],\n): Promise<void> {\n const resolvedModules = modules ?? tryGetModules()\n const roleTenantId = normalizeTenantId(tenantId) ?? null\n const builtInRoles = ['superadmin', 'admin', 'employee']\n const customRoleFeatures = new Map<string, string[]>()\n\n for (const mod of resolvedModules) {\n const roleFeatures = mod.setup?.defaultRoleFeatures\n if (!roleFeatures) continue\n for (const [roleName, features] of Object.entries(roleFeatures)) {\n if (builtInRoles.includes(roleName)) continue\n if (!Array.isArray(features)) continue\n const existing = customRoleFeatures.get(roleName) ?? []\n existing.push(...features)\n customRoleFeatures.set(roleName, existing)\n }\n }\n\n if (customRoleFeatures.size === 0) return\n\n let seeded = 0\n for (const [roleName, features] of customRoleFeatures) {\n const role = await findRoleByName(em, roleName, roleTenantId)\n if (role) {\n await ensureRoleAclFor(em, role, tenantId, features)\n seeded++\n }\n }\n if (seeded > 0) {\n console.log(`\u2705 Seeded custom role ACLs (${seeded} roles)`)\n }\n}\n\nasync function ensureRoleAclFor(\n em: EntityManager,\n role: Role,\n tenantId: string,\n features: string[],\n options: { isSuperAdmin?: boolean } = {},\n) {\n const existing = await findOneWithDecryption(em, RoleAcl, { role, tenantId }, {}, { tenantId, organizationId: null })\n if (!existing) {\n const acl = em.create(RoleAcl, {\n role,\n tenantId,\n featuresJson: features,\n isSuperAdmin: !!options.isSuperAdmin,\n createdAt: new Date(),\n })\n await em.persist(acl).flush()\n return\n }\n const currentFeatures = Array.isArray(existing.featuresJson) ? existing.featuresJson : []\n const merged = Array.from(new Set([...currentFeatures, ...features]))\n const changed =\n merged.length !== currentFeatures.length ||\n merged.some((value, index) => value !== currentFeatures[index])\n if (changed) existing.featuresJson = merged\n if (options.isSuperAdmin && !existing.isSuperAdmin) {\n existing.isSuperAdmin = true\n }\n if (changed || options.isSuperAdmin) {\n await em.persist(existing).flush()\n }\n}\n\n/**\n * Neutralizes every demo account the setup path may have seeded\n * (superadmin, admin, employee \u2014 honoring OM_INIT_*_EMAIL overrides)\n * when SELF_SERVICE_ONBOARDING_ENABLED is on and the operator did not\n * opt in to keeping demo credentials via shouldKeepDemoSuperadminDuringInit.\n *\n * Each user is processed in its own try/catch so a single failure (e.g.\n * decryption error on a legacy row) does not skip the remaining accounts.\n *\n * @internal Exported for tests; not part of the public auth API.\n */\nexport async function deactivateDemoUsersIfSelfOnboardingEnabled(em: EntityManager) {\n if (process.env.SELF_SERVICE_ONBOARDING_ENABLED !== 'true') return\n if (shouldKeepDemoSuperadminDuringInit()) return\n for (const { role, email } of resolveDemoUserEmails()) {\n try {\n const user = await findOneWithDecryption(\n em,\n User,\n { email },\n {},\n { tenantId: null, organizationId: null },\n )\n if (!user) continue\n let dirty = false\n if (user.passwordHash) {\n user.passwordHash = null\n dirty = true\n }\n if (user.isConfirmed !== false) {\n user.isConfirmed = false\n dirty = true\n }\n if (dirty) {\n await em.persist(user).flush()\n }\n } catch (error) {\n console.error(\n `[auth.setup] failed to deactivate demo ${role} user (${email})`,\n error,\n )\n }\n }\n}\n\n/**\n * @deprecated Renamed to {@link deactivateDemoUsersIfSelfOnboardingEnabled}\n * because the helper now neutralizes admin/employee demo accounts in addition\n * to superadmin (security tracker finding #5). Kept as an internal alias to\n * avoid breaking any out-of-tree caller that imported the old name; will be\n * removed in a future major.\n */\nconst deactivateDemoSuperAdminIfSelfOnboardingEnabled = deactivateDemoUsersIfSelfOnboardingEnabled\n\n/** Try to get modules from runtime registry; returns empty array if not yet registered. */\nfunction tryGetModules(): Module[] {\n try {\n const { getModules } = require('@open-mercato/shared/lib/modules/registry')\n return getModules()\n } catch {\n return []\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -10,8 +10,10 @@ import {
10
10
  } from "../../commands/fetch-configs.js";
11
11
  import { currencyFetchConfigCreateSchema, currencyFetchConfigUpdateSchema } from "../../data/validators.js";
12
12
  const metadata = {
13
- requireAuth: true,
14
- requireFeatures: ["currencies.fetch.view"]
13
+ GET: { requireAuth: true, requireFeatures: ["currencies.fetch.view"] },
14
+ POST: { requireAuth: true, requireFeatures: ["currencies.fetch.manage"] },
15
+ PUT: { requireAuth: true, requireFeatures: ["currencies.fetch.manage"] },
16
+ DELETE: { requireAuth: true, requireFeatures: ["currencies.fetch.manage"] }
15
17
  };
16
18
  async function GET(req) {
17
19
  const container = await createRequestContainer();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/currencies/api/fetch-configs/route.ts"],
4
- "sourcesContent": ["import { NextRequest, NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { CurrencyFetchConfig } from '../../data/entities'\nimport {\n createFetchConfig,\n updateFetchConfig,\n deleteFetchConfig,\n} from '../../commands/fetch-configs'\nimport { currencyFetchConfigCreateSchema, currencyFetchConfigUpdateSchema } from '../../data/validators'\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['currencies.fetch.view'],\n}\n\nexport async function GET(req: NextRequest) {\n const container = await createRequestContainer()\n\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const em = container.resolve<EntityManager>('em')\n\n const findFilter: Record<string, unknown> = {\n tenantId: auth.tenantId,\n }\n if (auth.orgId) {\n findFilter.organizationId = auth.orgId\n }\n\n const configs = await em.find(\n CurrencyFetchConfig,\n findFilter,\n {\n orderBy: { provider: 'ASC' },\n }\n )\n\n return NextResponse.json({ configs })\n } finally {\n await (container as any).dispose?.()\n }\n}\n\nexport async function POST(req: NextRequest) {\n const container = await createRequestContainer()\n\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const em = container.resolve<EntityManager>('em')\n\n let body\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const config = await createFetchConfig(em, body, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ config }, { status: 201 })\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 400 })\n } finally {\n await (container as any).dispose?.()\n }\n}\n\nexport async function PUT(req: NextRequest) {\n const container = await createRequestContainer()\n\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const em = container.resolve<EntityManager>('em')\n\n let body\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const { id, ...data } = body\n\n if (!id) {\n return NextResponse.json({ error: 'ID required' }, { status: 400 })\n }\n\n const config = await updateFetchConfig(em, id, data, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ config })\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 400 })\n } finally {\n await (container as any).dispose?.()\n }\n}\n\nexport async function DELETE(req: NextRequest) {\n const container = await createRequestContainer()\n\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const em = container.resolve<EntityManager>('em')\n const { searchParams } = new URL(req.url)\n const id = searchParams.get('id')\n\n if (!id) {\n return NextResponse.json({ error: 'ID required' }, { status: 400 })\n }\n\n await deleteFetchConfig(em, id, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ success: true })\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 400 })\n } finally {\n await (container as any).dispose?.()\n }\n}\n\nconst fetchConfigItemSchema = z.object({\n id: z.string().uuid(),\n organizationId: z.string().uuid(),\n tenantId: z.string().uuid(),\n provider: z.string(),\n isEnabled: z.boolean(),\n syncTime: z.string().nullable(),\n lastSyncAt: z.string().nullable().optional(),\n lastSyncStatus: z.string().nullable().optional(),\n lastSyncMessage: z.string().nullable().optional(),\n lastSyncCount: z.number().nullable().optional(),\n config: z.record(z.string(), z.unknown()).nullable().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n})\n\nconst errorSchema = z.object({ error: z.string() })\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Manage currency fetch configurations',\n description: 'Endpoints for managing currency rate fetch configurations from external providers.',\n methods: {\n GET: {\n operationId: 'listCurrencyFetchConfigs',\n summary: 'List currency fetch configurations',\n description: 'Returns all currency fetch configurations scoped to the authenticated organization.',\n responses: [\n {\n status: 200,\n description: 'A list of currency fetch configurations',\n schema: z.object({\n configs: z.array(fetchConfigItemSchema),\n }),\n },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n POST: {\n operationId: 'createCurrencyFetchConfig',\n summary: 'Create currency fetch configuration',\n description: 'Creates a new currency fetch configuration.',\n requestBody: {\n schema: currencyFetchConfigCreateSchema,\n contentType: 'application/json',\n },\n responses: [\n {\n status: 201,\n description: 'Currency fetch configuration created successfully',\n schema: z.object({\n config: fetchConfigItemSchema,\n }),\n },\n ],\n errors: [\n { status: 400, description: 'Bad request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n PUT: {\n operationId: 'updateCurrencyFetchConfig',\n summary: 'Update currency fetch configuration',\n description: 'Updates an existing currency fetch configuration by id.',\n requestBody: {\n schema: currencyFetchConfigUpdateSchema.extend({\n id: z.string().uuid(),\n }),\n contentType: 'application/json',\n },\n responses: [\n {\n status: 200,\n description: 'Currency fetch configuration updated successfully',\n schema: z.object({\n config: fetchConfigItemSchema,\n }),\n },\n ],\n errors: [\n { status: 400, description: 'Bad request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n DELETE: {\n operationId: 'deleteCurrencyFetchConfig',\n summary: 'Delete currency fetch configuration',\n description: 'Deletes a currency fetch configuration by id.',\n query: z.object({\n id: z.string().uuid().describe('Currency fetch configuration identifier to delete'),\n }),\n responses: [\n {\n status: 200,\n description: 'Currency fetch configuration deleted successfully',\n schema: z.object({ success: z.literal(true) }),\n },\n ],\n errors: [\n { status: 400, description: 'Bad request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextRequest, NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { CurrencyFetchConfig } from '../../data/entities'\nimport {\n createFetchConfig,\n updateFetchConfig,\n deleteFetchConfig,\n} from '../../commands/fetch-configs'\nimport { currencyFetchConfigCreateSchema, currencyFetchConfigUpdateSchema } from '../../data/validators'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['currencies.fetch.view'] },\n POST: { requireAuth: true, requireFeatures: ['currencies.fetch.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['currencies.fetch.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['currencies.fetch.manage'] },\n}\n\nexport async function GET(req: NextRequest) {\n const container = await createRequestContainer()\n\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const em = container.resolve<EntityManager>('em')\n\n const findFilter: Record<string, unknown> = {\n tenantId: auth.tenantId,\n }\n if (auth.orgId) {\n findFilter.organizationId = auth.orgId\n }\n\n const configs = await em.find(\n CurrencyFetchConfig,\n findFilter,\n {\n orderBy: { provider: 'ASC' },\n }\n )\n\n return NextResponse.json({ configs })\n } finally {\n await (container as any).dispose?.()\n }\n}\n\nexport async function POST(req: NextRequest) {\n const container = await createRequestContainer()\n\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const em = container.resolve<EntityManager>('em')\n\n let body\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const config = await createFetchConfig(em, body, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ config }, { status: 201 })\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 400 })\n } finally {\n await (container as any).dispose?.()\n }\n}\n\nexport async function PUT(req: NextRequest) {\n const container = await createRequestContainer()\n\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const em = container.resolve<EntityManager>('em')\n\n let body\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const { id, ...data } = body\n\n if (!id) {\n return NextResponse.json({ error: 'ID required' }, { status: 400 })\n }\n\n const config = await updateFetchConfig(em, id, data, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ config })\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 400 })\n } finally {\n await (container as any).dispose?.()\n }\n}\n\nexport async function DELETE(req: NextRequest) {\n const container = await createRequestContainer()\n\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const em = container.resolve<EntityManager>('em')\n const { searchParams } = new URL(req.url)\n const id = searchParams.get('id')\n\n if (!id) {\n return NextResponse.json({ error: 'ID required' }, { status: 400 })\n }\n\n await deleteFetchConfig(em, id, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ success: true })\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 400 })\n } finally {\n await (container as any).dispose?.()\n }\n}\n\nconst fetchConfigItemSchema = z.object({\n id: z.string().uuid(),\n organizationId: z.string().uuid(),\n tenantId: z.string().uuid(),\n provider: z.string(),\n isEnabled: z.boolean(),\n syncTime: z.string().nullable(),\n lastSyncAt: z.string().nullable().optional(),\n lastSyncStatus: z.string().nullable().optional(),\n lastSyncMessage: z.string().nullable().optional(),\n lastSyncCount: z.number().nullable().optional(),\n config: z.record(z.string(), z.unknown()).nullable().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n})\n\nconst errorSchema = z.object({ error: z.string() })\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Manage currency fetch configurations',\n description: 'Endpoints for managing currency rate fetch configurations from external providers.',\n methods: {\n GET: {\n operationId: 'listCurrencyFetchConfigs',\n summary: 'List currency fetch configurations',\n description: 'Returns all currency fetch configurations scoped to the authenticated organization.',\n responses: [\n {\n status: 200,\n description: 'A list of currency fetch configurations',\n schema: z.object({\n configs: z.array(fetchConfigItemSchema),\n }),\n },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n POST: {\n operationId: 'createCurrencyFetchConfig',\n summary: 'Create currency fetch configuration',\n description: 'Creates a new currency fetch configuration.',\n requestBody: {\n schema: currencyFetchConfigCreateSchema,\n contentType: 'application/json',\n },\n responses: [\n {\n status: 201,\n description: 'Currency fetch configuration created successfully',\n schema: z.object({\n config: fetchConfigItemSchema,\n }),\n },\n ],\n errors: [\n { status: 400, description: 'Bad request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n PUT: {\n operationId: 'updateCurrencyFetchConfig',\n summary: 'Update currency fetch configuration',\n description: 'Updates an existing currency fetch configuration by id.',\n requestBody: {\n schema: currencyFetchConfigUpdateSchema.extend({\n id: z.string().uuid(),\n }),\n contentType: 'application/json',\n },\n responses: [\n {\n status: 200,\n description: 'Currency fetch configuration updated successfully',\n schema: z.object({\n config: fetchConfigItemSchema,\n }),\n },\n ],\n errors: [\n { status: 400, description: 'Bad request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n DELETE: {\n operationId: 'deleteCurrencyFetchConfig',\n summary: 'Delete currency fetch configuration',\n description: 'Deletes a currency fetch configuration by id.',\n query: z.object({\n id: z.string().uuid().describe('Currency fetch configuration identifier to delete'),\n }),\n responses: [\n {\n status: 200,\n description: 'Currency fetch configuration deleted successfully',\n schema: z.object({ success: z.literal(true) }),\n },\n ],\n errors: [\n { status: 400, description: 'Bad request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAsB,oBAAoB;AAE1C,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,iCAAiC,uCAAuC;AAE1E,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,uBAAuB,EAAE;AAAA,EACrE,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,yBAAyB,EAAE;AAAA,EACxE,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,yBAAyB,EAAE;AAAA,EACvE,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,yBAAyB,EAAE;AAC5E;AAEA,eAAsB,IAAI,KAAkB;AAC1C,QAAM,YAAY,MAAM,uBAAuB;AAE/C,MAAI;AACF,UAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,QAAI,CAAC,QAAQ,CAAC,KAAK,YAAa,CAAC,KAAK,SAAS,CAAC,KAAK,cAAe;AAClE,aAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AAEA,UAAM,KAAK,UAAU,QAAuB,IAAI;AAEhD,UAAM,aAAsC;AAAA,MAC1C,UAAU,KAAK;AAAA,IACjB;AACA,QAAI,KAAK,OAAO;AACd,iBAAW,iBAAiB,KAAK;AAAA,IACnC;AAEA,UAAM,UAAU,MAAM,GAAG;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,QACE,SAAS,EAAE,UAAU,MAAM;AAAA,MAC7B;AAAA,IACF;AAEA,WAAO,aAAa,KAAK,EAAE,QAAQ,CAAC;AAAA,EACtC,UAAE;AACA,UAAO,UAAkB,UAAU;AAAA,EACrC;AACF;AAEA,eAAsB,KAAK,KAAkB;AAC3C,QAAM,YAAY,MAAM,uBAAuB;AAE/C,MAAI;AACF,UAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,QAAI,CAAC,QAAQ,CAAC,KAAK,YAAY,CAAC,KAAK,OAAO;AAC1C,aAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AAEA,UAAM,KAAK,UAAU,QAAuB,IAAI;AAEhD,QAAI;AACJ,QAAI;AACF,aAAO,MAAM,IAAI,KAAK;AAAA,IACxB,QAAQ;AACN,aAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AAEA,UAAM,SAAS,MAAM,kBAAkB,IAAI,MAAM;AAAA,MAC/C,UAAU,KAAK;AAAA,MACf,gBAAgB,KAAK;AAAA,MACrB,QAAQ,KAAK;AAAA,IACf,CAAC;AAED,WAAO,aAAa,KAAK,EAAE,OAAO,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACtD,SAAS,KAAU;AACjB,WAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClE,UAAE;AACA,UAAO,UAAkB,UAAU;AAAA,EACrC;AACF;AAEA,eAAsB,IAAI,KAAkB;AAC1C,QAAM,YAAY,MAAM,uBAAuB;AAE/C,MAAI;AACF,UAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,QAAI,CAAC,QAAQ,CAAC,KAAK,YAAY,CAAC,KAAK,OAAO;AAC1C,aAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AAEA,UAAM,KAAK,UAAU,QAAuB,IAAI;AAEhD,QAAI;AACJ,QAAI;AACF,aAAO,MAAM,IAAI,KAAK;AAAA,IACxB,QAAQ;AACN,aAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AAEA,UAAM,EAAE,IAAI,GAAG,KAAK,IAAI;AAExB,QAAI,CAAC,IAAI;AACP,aAAO,aAAa,KAAK,EAAE,OAAO,cAAc,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpE;AAEA,UAAM,SAAS,MAAM,kBAAkB,IAAI,IAAI,MAAM;AAAA,MACnD,UAAU,KAAK;AAAA,MACf,gBAAgB,KAAK;AAAA,MACrB,QAAQ,KAAK;AAAA,IACf,CAAC;AAED,WAAO,aAAa,KAAK,EAAE,OAAO,CAAC;AAAA,EACrC,SAAS,KAAU;AACjB,WAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClE,UAAE;AACA,UAAO,UAAkB,UAAU;AAAA,EACrC;AACF;AAEA,eAAsB,OAAO,KAAkB;AAC7C,QAAM,YAAY,MAAM,uBAAuB;AAE/C,MAAI;AACF,UAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,QAAI,CAAC,QAAQ,CAAC,KAAK,YAAY,CAAC,KAAK,OAAO;AAC1C,aAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AAEA,UAAM,KAAK,UAAU,QAAuB,IAAI;AAChD,UAAM,EAAE,aAAa,IAAI,IAAI,IAAI,IAAI,GAAG;AACxC,UAAM,KAAK,aAAa,IAAI,IAAI;AAEhC,QAAI,CAAC,IAAI;AACP,aAAO,aAAa,KAAK,EAAE,OAAO,cAAc,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpE;AAEA,UAAM,kBAAkB,IAAI,IAAI;AAAA,MAC9B,UAAU,KAAK;AAAA,MACf,gBAAgB,KAAK;AAAA,MACrB,QAAQ,KAAK;AAAA,IACf,CAAC;AAED,WAAO,aAAa,KAAK,EAAE,SAAS,KAAK,CAAC;AAAA,EAC5C,SAAS,KAAU;AACjB,WAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClE,UAAE;AACA,UAAO,UAAkB,UAAU;AAAA,EACrC;AACF;AAEA,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,gBAAgB,EAAE,OAAO,EAAE,KAAK;AAAA,EAChC,UAAU,EAAE,OAAO,EAAE,KAAK;AAAA,EAC1B,UAAU,EAAE,OAAO;AAAA,EACnB,WAAW,EAAE,QAAQ;AAAA,EACrB,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC3C,gBAAgB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC/C,iBAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAChD,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC9C,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,SAAS;AAAA,EAC9D,WAAW,EAAE,OAAO;AAAA,EACpB,WAAW,EAAE,OAAO;AACtB,CAAC;AAED,MAAM,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAE3C,MAAM,UAA2B;AAAA,EACtC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,SAAS;AAAA,IACP,KAAK;AAAA,MACH,aAAa;AAAA,MACb,SAAS;AAAA,MACT,aAAa;AAAA,MACb,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO;AAAA,YACf,SAAS,EAAE,MAAM,qBAAqB;AAAA,UACxC,CAAC;AAAA,QACH;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,YAAY;AAAA,MAClE;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,aAAa;AAAA,MACb,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACX,QAAQ;AAAA,QACR,aAAa;AAAA,MACf;AAAA,MACA,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO;AAAA,YACf,QAAQ;AAAA,UACV,CAAC;AAAA,QACH;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,eAAe,QAAQ,YAAY;AAAA,QAC/D,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,YAAY;AAAA,MAClE;AAAA,IACF;AAAA,IACA,KAAK;AAAA,MACH,aAAa;AAAA,MACb,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACX,QAAQ,gCAAgC,OAAO;AAAA,UAC7C,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,QACtB,CAAC;AAAA,QACD,aAAa;AAAA,MACf;AAAA,MACA,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO;AAAA,YACf,QAAQ;AAAA,UACV,CAAC;AAAA,QACH;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,eAAe,QAAQ,YAAY;AAAA,QAC/D,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,YAAY;AAAA,MAClE;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,MACT,aAAa;AAAA,MACb,OAAO,EAAE,OAAO;AAAA,QACd,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,mDAAmD;AAAA,MACpF,CAAC;AAAA,MACD,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,IAAI,EAAE,CAAC;AAAA,QAC/C;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,eAAe,QAAQ,YAAY;AAAA,QAC/D,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,YAAY;AAAA,MAClE;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
4
4
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
5
5
  import { getBundle, getBundleIntegrations, getIntegration } from "@open-mercato/shared/modules/integrations/types";
6
+ import { CredentialsEncryptionUnavailableError } from "../../lib/credentials-service.js";
6
7
  import { deriveIntegrationHealthStatus, getEffectiveHealthCheckConfig } from "../../lib/health-service.js";
7
8
  import {
8
9
  finalizeIntegrationsReadResponse,
@@ -46,7 +47,10 @@ async function GET(req, ctx) {
46
47
  const logService = container.resolve("integrationLogService");
47
48
  const scope = { organizationId: auth.orgId, tenantId: auth.tenantId };
48
49
  const [credentials, state, analyticsMap] = await Promise.all([
49
- credentialsService.resolve(integration.id, scope),
50
+ credentialsService.resolve(integration.id, scope).catch((err) => {
51
+ if (err instanceof CredentialsEncryptionUnavailableError) return null;
52
+ throw err;
53
+ }),
50
54
  stateService.resolveState(integration.id, scope),
51
55
  logService.aggregateAnalytics([integration.id], scope, 30)
52
56
  ]);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/integrations/api/%5Bid%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getBundle, getBundleIntegrations, getIntegration } from '@open-mercato/shared/modules/integrations/types'\nimport type { CredentialsService } from '../../lib/credentials-service'\nimport type { IntegrationStateService } from '../../lib/state-service'\nimport type { IntegrationLogService } from '../../lib/log-service'\nimport { deriveIntegrationHealthStatus, getEffectiveHealthCheckConfig } from '../../lib/health-service'\nimport {\n finalizeIntegrationsReadResponse,\n integrationApiRoutePaths,\n runIntegrationsReadBeforeInterceptors,\n} from '../umes-read'\n\nconst idParamsSchema = z.object({ id: z.string().min(1) })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['integrations.view'] },\n}\n\nexport const openApi = {\n tags: ['Integrations'],\n summary: 'Get integration detail',\n}\n\nexport async function GET(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')\n ? await (ctx.params as Promise<{ id?: string }>)\n : (ctx.params as { id?: string } | undefined)\n\n const parsedParams = idParamsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })\n }\n\n const integration = getIntegration(parsedParams.data.id)\n if (!integration) {\n return NextResponse.json({ error: 'Integration not found' }, { status: 404 })\n }\n\n const container = await createRequestContainer()\n const beforeInterceptors = await runIntegrationsReadBeforeInterceptors({\n routePath: integrationApiRoutePaths.detail,\n request: req,\n auth,\n container,\n })\n if (!beforeInterceptors.ok) {\n return NextResponse.json(beforeInterceptors.body, { status: beforeInterceptors.statusCode })\n }\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const stateService = container.resolve('integrationStateService') as IntegrationStateService\n const logService = container.resolve('integrationLogService') as IntegrationLogService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const [credentials, state, analyticsMap] = await Promise.all([\n credentialsService.resolve(integration.id, scope),\n stateService.resolveState(integration.id, scope),\n logService.aggregateAnalytics([integration.id], scope, 30),\n ])\n\n const hasCredentials = credentials != null && Object.keys(credentials).length > 0\n const healthConfig = getEffectiveHealthCheckConfig(integration.id)\n const hasHealthCheck = Boolean(healthConfig?.service)\n const healthStatus = deriveIntegrationHealthStatus({\n hasHealthCheck,\n hasCredentials,\n lastHealthStatus: state.lastHealthStatus,\n lastHealthCheckedAt: state.lastHealthCheckedAt,\n })\n\n const analytics = analyticsMap.get(integration.id) ?? {\n lastActivityAt: null,\n totalCount: 0,\n errorCount: 0,\n errorRate: 0,\n dailyCounts: Array.from({ length: 30 }, () => 0),\n }\n\n const bundle = integration.bundleId ? getBundle(integration.bundleId) : undefined\n const bundleIntegrations = integration.bundleId\n ? await Promise.all(\n getBundleIntegrations(integration.bundleId).map(async (item) => {\n const resolvedState = await stateService.resolveState(item.id, scope)\n return {\n ...item,\n isEnabled: resolvedState.isEnabled,\n state: {\n isEnabled: resolvedState.isEnabled,\n apiVersion: resolvedState.apiVersion,\n reauthRequired: resolvedState.reauthRequired,\n lastHealthStatus: resolvedState.lastHealthStatus,\n lastHealthCheckedAt: resolvedState.lastHealthCheckedAt?.toISOString() ?? null,\n lastHealthLatencyMs: resolvedState.lastHealthLatencyMs,\n enabledAt: resolvedState.enabledAt?.toISOString() ?? null,\n },\n }\n }),\n )\n : []\n\n return finalizeIntegrationsReadResponse({\n routePath: integrationApiRoutePaths.detail,\n request: req,\n auth,\n container,\n interceptorRequest: beforeInterceptors.request,\n beforeMetadata: beforeInterceptors.metadataByInterceptor,\n enrich: {\n targetEntity: 'integrations.integration',\n recordKeys: ['integration'],\n listKeys: ['bundleIntegrations'],\n },\n body: {\n integration,\n bundle,\n bundleIntegrations,\n state: {\n isEnabled: state.isEnabled,\n apiVersion: state.apiVersion,\n reauthRequired: state.reauthRequired,\n lastHealthStatus: state.lastHealthStatus,\n lastHealthCheckedAt: state.lastHealthCheckedAt?.toISOString() ?? null,\n lastHealthLatencyMs: state.lastHealthLatencyMs,\n enabledAt: state.enabledAt?.toISOString() ?? null,\n },\n hasCredentials,\n healthStatus,\n analytics,\n },\n })\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getBundle, getBundleIntegrations, getIntegration } from '@open-mercato/shared/modules/integrations/types'\nimport { CredentialsEncryptionUnavailableError, type CredentialsService } from '../../lib/credentials-service'\nimport type { IntegrationStateService } from '../../lib/state-service'\nimport type { IntegrationLogService } from '../../lib/log-service'\nimport { deriveIntegrationHealthStatus, getEffectiveHealthCheckConfig } from '../../lib/health-service'\nimport {\n finalizeIntegrationsReadResponse,\n integrationApiRoutePaths,\n runIntegrationsReadBeforeInterceptors,\n} from '../umes-read'\n\nconst idParamsSchema = z.object({ id: z.string().min(1) })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['integrations.view'] },\n}\n\nexport const openApi = {\n tags: ['Integrations'],\n summary: 'Get integration detail',\n}\n\nexport async function GET(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')\n ? await (ctx.params as Promise<{ id?: string }>)\n : (ctx.params as { id?: string } | undefined)\n\n const parsedParams = idParamsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })\n }\n\n const integration = getIntegration(parsedParams.data.id)\n if (!integration) {\n return NextResponse.json({ error: 'Integration not found' }, { status: 404 })\n }\n\n const container = await createRequestContainer()\n const beforeInterceptors = await runIntegrationsReadBeforeInterceptors({\n routePath: integrationApiRoutePaths.detail,\n request: req,\n auth,\n container,\n })\n if (!beforeInterceptors.ok) {\n return NextResponse.json(beforeInterceptors.body, { status: beforeInterceptors.statusCode })\n }\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const stateService = container.resolve('integrationStateService') as IntegrationStateService\n const logService = container.resolve('integrationLogService') as IntegrationLogService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const [credentials, state, analyticsMap] = await Promise.all([\n credentialsService.resolve(integration.id, scope).catch((err) => {\n if (err instanceof CredentialsEncryptionUnavailableError) return null\n throw err\n }),\n stateService.resolveState(integration.id, scope),\n logService.aggregateAnalytics([integration.id], scope, 30),\n ])\n\n const hasCredentials = credentials != null && Object.keys(credentials).length > 0\n const healthConfig = getEffectiveHealthCheckConfig(integration.id)\n const hasHealthCheck = Boolean(healthConfig?.service)\n const healthStatus = deriveIntegrationHealthStatus({\n hasHealthCheck,\n hasCredentials,\n lastHealthStatus: state.lastHealthStatus,\n lastHealthCheckedAt: state.lastHealthCheckedAt,\n })\n\n const analytics = analyticsMap.get(integration.id) ?? {\n lastActivityAt: null,\n totalCount: 0,\n errorCount: 0,\n errorRate: 0,\n dailyCounts: Array.from({ length: 30 }, () => 0),\n }\n\n const bundle = integration.bundleId ? getBundle(integration.bundleId) : undefined\n const bundleIntegrations = integration.bundleId\n ? await Promise.all(\n getBundleIntegrations(integration.bundleId).map(async (item) => {\n const resolvedState = await stateService.resolveState(item.id, scope)\n return {\n ...item,\n isEnabled: resolvedState.isEnabled,\n state: {\n isEnabled: resolvedState.isEnabled,\n apiVersion: resolvedState.apiVersion,\n reauthRequired: resolvedState.reauthRequired,\n lastHealthStatus: resolvedState.lastHealthStatus,\n lastHealthCheckedAt: resolvedState.lastHealthCheckedAt?.toISOString() ?? null,\n lastHealthLatencyMs: resolvedState.lastHealthLatencyMs,\n enabledAt: resolvedState.enabledAt?.toISOString() ?? null,\n },\n }\n }),\n )\n : []\n\n return finalizeIntegrationsReadResponse({\n routePath: integrationApiRoutePaths.detail,\n request: req,\n auth,\n container,\n interceptorRequest: beforeInterceptors.request,\n beforeMetadata: beforeInterceptors.metadataByInterceptor,\n enrich: {\n targetEntity: 'integrations.integration',\n recordKeys: ['integration'],\n listKeys: ['bundleIntegrations'],\n },\n body: {\n integration,\n bundle,\n bundleIntegrations,\n state: {\n isEnabled: state.isEnabled,\n apiVersion: state.apiVersion,\n reauthRequired: state.reauthRequired,\n lastHealthStatus: state.lastHealthStatus,\n lastHealthCheckedAt: state.lastHealthCheckedAt?.toISOString() ?? null,\n lastHealthLatencyMs: state.lastHealthLatencyMs,\n enabledAt: state.enabledAt?.toISOString() ?? null,\n },\n hasCredentials,\n healthStatus,\n analytics,\n },\n })\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
3
3
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
+ import { CredentialsEncryptionUnavailableError } from "../lib/credentials-service.js";
4
5
  import { deriveIntegrationHealthStatus, getEffectiveHealthCheckConfig } from "../lib/health-service.js";
5
6
  import { getAllBundles, getAllIntegrations } from "@open-mercato/shared/modules/integrations/types";
6
7
  import { listIntegrationsQuerySchema } from "../data/validators.js";
@@ -61,7 +62,10 @@ async function GET(req) {
61
62
  const baseRows = await Promise.all(
62
63
  getAllIntegrations().map(async (integration) => {
63
64
  const [resolvedCredentials, state] = await Promise.all([
64
- credentialsService.resolve(integration.id, scope),
65
+ credentialsService.resolve(integration.id, scope).catch((err) => {
66
+ if (err instanceof CredentialsEncryptionUnavailableError) return null;
67
+ throw err;
68
+ }),
65
69
  stateService.resolveState(integration.id, scope)
66
70
  ]);
67
71
  const hasCredentials = resolvedCredentials != null && Object.keys(resolvedCredentials).length > 0;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/integrations/api/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CredentialsService } from '../lib/credentials-service'\nimport type { IntegrationStateService } from '../lib/state-service'\nimport type { IntegrationLogService } from '../lib/log-service'\nimport { deriveIntegrationHealthStatus, getEffectiveHealthCheckConfig } from '../lib/health-service'\nimport type { IntegrationHealthDisplayStatus } from '../lib/health-service'\nimport { getAllBundles, getAllIntegrations } from '@open-mercato/shared/modules/integrations/types'\nimport { listIntegrationsQuerySchema } from '../data/validators'\nimport { buildIntegrationsCrudOpenApi, integrationsListResponseSchema } from './openapi'\nimport {\n finalizeIntegrationsReadResponse,\n integrationApiRoutePaths,\n runIntegrationsReadBeforeInterceptors,\n} from './umes-read'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['integrations.view'] },\n}\n\nexport const openApi = buildIntegrationsCrudOpenApi({\n resourceName: 'Integration',\n pluralName: 'Integrations',\n listResponseSchema: integrationsListResponseSchema,\n querySchema: listIntegrationsQuerySchema,\n})\n\nconst HEALTH_SORT_RANK: Record<IntegrationHealthDisplayStatus, number> = {\n healthy: 0,\n degraded: 1,\n unhealthy: 2,\n unconfigured: 3,\n}\n\nfunction matchesSearchQuery(\n integration: { title: string; description?: string; tags?: string[] },\n queryNormalized: string,\n): boolean {\n if (!queryNormalized) return true\n if (integration.title.toLowerCase().includes(queryNormalized)) return true\n if (integration.description?.toLowerCase().includes(queryNormalized)) return true\n const tags = integration.tags ?? []\n return tags.some((tag) => tag.toLowerCase().includes(queryNormalized))\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsedQuery = listIntegrationsQuerySchema.safeParse(Object.fromEntries(url.searchParams.entries()))\n if (!parsedQuery.success) {\n return NextResponse.json({ error: 'Invalid query', details: parsedQuery.error.flatten() }, { status: 400 })\n }\n const query = parsedQuery.data\n\n const container = await createRequestContainer()\n const beforeInterceptors = await runIntegrationsReadBeforeInterceptors({\n routePath: integrationApiRoutePaths.list,\n request: req,\n auth,\n container,\n })\n if (!beforeInterceptors.ok) {\n return NextResponse.json(beforeInterceptors.body, { status: beforeInterceptors.statusCode })\n }\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const stateService = container.resolve('integrationStateService') as IntegrationStateService\n const logService = container.resolve('integrationLogService') as IntegrationLogService\n\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId as string }\n const searchNeedle = query.q?.trim().toLowerCase() ?? ''\n\n type ListRow = {\n id: string\n title: string\n description: string | null\n category: string | null\n tags: string[]\n hub: string | null\n providerKey: string | null\n bundleId: string | null\n author: string | null\n company: string | null\n version: string | null\n hasCredentials: boolean\n isEnabled: boolean\n apiVersion: string | null\n healthStatus: IntegrationHealthDisplayStatus\n lastHealthCheckedAt: string | null\n lastHealthLatencyMs: number | null\n enabledAt: string | null\n sortEnabledAtMs: number\n sortTitle: string\n sortCategory: string\n }\n\n const baseRows: ListRow[] = await Promise.all(\n getAllIntegrations().map(async (integration) => {\n const [resolvedCredentials, state] = await Promise.all([\n credentialsService.resolve(integration.id, scope),\n stateService.resolveState(integration.id, scope),\n ])\n\n const hasCredentials =\n resolvedCredentials != null && Object.keys(resolvedCredentials).length > 0\n const healthConfig = getEffectiveHealthCheckConfig(integration.id)\n const hasHealthCheck = Boolean(healthConfig?.service)\n const healthStatus = deriveIntegrationHealthStatus({\n hasHealthCheck,\n hasCredentials,\n lastHealthStatus: state.lastHealthStatus,\n lastHealthCheckedAt: state.lastHealthCheckedAt,\n })\n\n const enabledAtMs = state.enabledAt?.getTime() ?? 0\n\n return {\n id: integration.id,\n title: integration.title,\n description: integration.description ?? null,\n category: integration.category ?? null,\n tags: integration.tags ?? [],\n hub: integration.hub ?? null,\n providerKey: integration.providerKey ?? null,\n bundleId: integration.bundleId ?? null,\n author: integration.author ?? null,\n company: integration.company ?? null,\n version: integration.version ?? null,\n hasCredentials,\n isEnabled: state.isEnabled,\n apiVersion: state.apiVersion,\n healthStatus,\n lastHealthCheckedAt: state.lastHealthCheckedAt?.toISOString() ?? null,\n lastHealthLatencyMs: state.lastHealthLatencyMs,\n enabledAt: state.enabledAt?.toISOString() ?? null,\n sortEnabledAtMs: enabledAtMs,\n sortTitle: integration.title.toLowerCase(),\n sortCategory: (integration.category ?? '').toLowerCase(),\n }\n }),\n )\n\n let filtered = baseRows.filter((row) => matchesSearchQuery(\n { title: row.title, description: row.description ?? undefined, tags: row.tags },\n searchNeedle,\n ))\n\n if (query.category) {\n filtered = filtered.filter((row) => row.category === query.category)\n }\n if (query.bundleId) {\n filtered = filtered.filter((row) => row.bundleId === query.bundleId)\n }\n if (query.isEnabled !== undefined) {\n filtered = filtered.filter((row) => row.isEnabled === query.isEnabled)\n }\n if (query.healthStatus) {\n filtered = filtered.filter((row) => row.healthStatus === query.healthStatus)\n }\n\n const sortKey = query.sort ?? 'title'\n const orderSign = query.order === 'desc' ? -1 : 1\n\n const sorted = [...filtered].sort((rowA, rowB) => {\n let cmp = 0\n if (sortKey === 'title') {\n cmp = rowA.sortTitle.localeCompare(rowB.sortTitle)\n } else if (sortKey === 'category') {\n cmp = rowA.sortCategory.localeCompare(rowB.sortCategory)\n if (cmp === 0) cmp = rowA.sortTitle.localeCompare(rowB.sortTitle)\n } else if (sortKey === 'enabledAt') {\n cmp = rowA.sortEnabledAtMs - rowB.sortEnabledAtMs\n if (cmp === 0) cmp = rowA.sortTitle.localeCompare(rowB.sortTitle)\n } else if (sortKey === 'healthStatus') {\n cmp = HEALTH_SORT_RANK[rowA.healthStatus] - HEALTH_SORT_RANK[rowB.healthStatus]\n if (cmp === 0) cmp = rowA.sortTitle.localeCompare(rowB.sortTitle)\n }\n return cmp * orderSign\n })\n\n const total = sorted.length\n const pageSize = query.pageSize\n const page = query.page\n const totalPages = Math.max(1, Math.ceil(total / pageSize) || 1)\n const safePage = Math.min(page, totalPages)\n const offset = (safePage - 1) * pageSize\n const pageSlice = sorted.slice(offset, offset + pageSize)\n\n const analyticsMap = await logService.aggregateAnalytics(\n pageSlice.map((row) => row.id),\n scope,\n 30,\n )\n\n const items = pageSlice.map((row) => {\n const analytics = analyticsMap.get(row.id)\n return {\n id: row.id,\n title: row.title,\n description: row.description,\n category: row.category,\n tags: row.tags,\n hub: row.hub,\n providerKey: row.providerKey,\n bundleId: row.bundleId,\n author: row.author,\n company: row.company,\n version: row.version,\n hasCredentials: row.hasCredentials,\n isEnabled: row.isEnabled,\n apiVersion: row.apiVersion,\n healthStatus: row.healthStatus,\n lastHealthCheckedAt: row.lastHealthCheckedAt,\n lastHealthLatencyMs: row.lastHealthLatencyMs,\n enabledAt: row.enabledAt,\n analytics: analytics ?? {\n lastActivityAt: null,\n totalCount: 0,\n errorCount: 0,\n errorRate: 0,\n dailyCounts: Array.from({ length: 30 }, () => 0),\n },\n }\n })\n\n const bundles = getAllBundles().map((bundle) => {\n const bundleIntegrations = filtered.filter((row) => row.bundleId === bundle.id)\n const enabledCount = bundleIntegrations.reduce((count, integration) => count + (integration.isEnabled ? 1 : 0), 0)\n\n return {\n id: bundle.id,\n title: bundle.title,\n description: bundle.description,\n icon: bundle.icon ?? null,\n integrationCount: bundleIntegrations.length,\n enabledCount,\n }\n })\n\n return finalizeIntegrationsReadResponse({\n routePath: integrationApiRoutePaths.list,\n request: req,\n auth,\n container,\n interceptorRequest: beforeInterceptors.request,\n beforeMetadata: beforeInterceptors.metadataByInterceptor,\n enrich: {\n targetEntity: 'integrations.integration',\n listKeys: ['items'],\n },\n body: {\n items,\n bundles,\n total,\n page: safePage,\n pageSize,\n totalPages,\n },\n })\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAIvC,SAAS,+BAA+B,qCAAqC;AAE7E,SAAS,eAAe,0BAA0B;AAClD,SAAS,mCAAmC;AAC5C,SAAS,8BAA8B,sCAAsC;AAC7E;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACnE;AAEO,MAAM,UAAU,6BAA6B;AAAA,EAClD,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,oBAAoB;AAAA,EACpB,aAAa;AACf,CAAC;AAED,MAAM,mBAAmE;AAAA,EACvE,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EACX,cAAc;AAChB;AAEA,SAAS,mBACP,aACA,iBACS;AACT,MAAI,CAAC,gBAAiB,QAAO;AAC7B,MAAI,YAAY,MAAM,YAAY,EAAE,SAAS,eAAe,EAAG,QAAO;AACtE,MAAI,YAAY,aAAa,YAAY,EAAE,SAAS,eAAe,EAAG,QAAO;AAC7E,QAAM,OAAO,YAAY,QAAQ,CAAC;AAClC,SAAO,KAAK,KAAK,CAAC,QAAQ,IAAI,YAAY,EAAE,SAAS,eAAe,CAAC;AACvE;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,YAAY,CAAC,KAAK,OAAO;AAClC,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AAEA,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,QAAM,cAAc,4BAA4B,UAAU,OAAO,YAAY,IAAI,aAAa,QAAQ,CAAC,CAAC;AACxG,MAAI,CAAC,YAAY,SAAS;AACxB,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,SAAS,YAAY,MAAM,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC5G;AACA,QAAM,QAAQ,YAAY;AAE1B,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,qBAAqB,MAAM,sCAAsC;AAAA,IACrE,WAAW,yBAAyB;AAAA,IACpC,SAAS;AAAA,IACT;AAAA,IACA;AAAA,EACF,CAAC;AACD,MAAI,CAAC,mBAAmB,IAAI;AAC1B,WAAO,aAAa,KAAK,mBAAmB,MAAM,EAAE,QAAQ,mBAAmB,WAAW,CAAC;AAAA,EAC7F;AACA,QAAM,qBAAqB,UAAU,QAAQ,+BAA+B;AAC5E,QAAM,eAAe,UAAU,QAAQ,yBAAyB;AAChE,QAAM,aAAa,UAAU,QAAQ,uBAAuB;AAE5D,QAAM,QAAQ,EAAE,gBAAgB,KAAK,OAAiB,UAAU,KAAK,SAAmB;AACxF,QAAM,eAAe,MAAM,GAAG,KAAK,EAAE,YAAY,KAAK;AA0BtD,QAAM,WAAsB,MAAM,QAAQ;AAAA,IACxC,mBAAmB,EAAE,IAAI,OAAO,gBAAgB;AAC9C,YAAM,CAAC,qBAAqB,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,QACrD,mBAAmB,QAAQ,YAAY,IAAI,KAAK;AAAA,QAChD,aAAa,aAAa,YAAY,IAAI,KAAK;AAAA,MACjD,CAAC;AAED,YAAM,iBACJ,uBAAuB,QAAQ,OAAO,KAAK,mBAAmB,EAAE,SAAS;AAC3E,YAAM,eAAe,8BAA8B,YAAY,EAAE;AACjE,YAAM,iBAAiB,QAAQ,cAAc,OAAO;AACpD,YAAM,eAAe,8BAA8B;AAAA,QACjD;AAAA,QACA;AAAA,QACA,kBAAkB,MAAM;AAAA,QACxB,qBAAqB,MAAM;AAAA,MAC7B,CAAC;AAED,YAAM,cAAc,MAAM,WAAW,QAAQ,KAAK;AAElD,aAAO;AAAA,QACL,IAAI,YAAY;AAAA,QAChB,OAAO,YAAY;AAAA,QACnB,aAAa,YAAY,eAAe;AAAA,QACxC,UAAU,YAAY,YAAY;AAAA,QAClC,MAAM,YAAY,QAAQ,CAAC;AAAA,QAC3B,KAAK,YAAY,OAAO;AAAA,QACxB,aAAa,YAAY,eAAe;AAAA,QACxC,UAAU,YAAY,YAAY;AAAA,QAClC,QAAQ,YAAY,UAAU;AAAA,QAC9B,SAAS,YAAY,WAAW;AAAA,QAChC,SAAS,YAAY,WAAW;AAAA,QAChC;AAAA,QACA,WAAW,MAAM;AAAA,QACjB,YAAY,MAAM;AAAA,QAClB;AAAA,QACA,qBAAqB,MAAM,qBAAqB,YAAY,KAAK;AAAA,QACjE,qBAAqB,MAAM;AAAA,QAC3B,WAAW,MAAM,WAAW,YAAY,KAAK;AAAA,QAC7C,iBAAiB;AAAA,QACjB,WAAW,YAAY,MAAM,YAAY;AAAA,QACzC,eAAe,YAAY,YAAY,IAAI,YAAY;AAAA,MACzD;AAAA,IACF,CAAC;AAAA,EACH;AAEA,MAAI,WAAW,SAAS,OAAO,CAAC,QAAQ;AAAA,IACtC,EAAE,OAAO,IAAI,OAAO,aAAa,IAAI,eAAe,QAAW,MAAM,IAAI,KAAK;AAAA,IAC9E;AAAA,EACF,CAAC;AAED,MAAI,MAAM,UAAU;AAClB,eAAW,SAAS,OAAO,CAAC,QAAQ,IAAI,aAAa,MAAM,QAAQ;AAAA,EACrE;AACA,MAAI,MAAM,UAAU;AAClB,eAAW,SAAS,OAAO,CAAC,QAAQ,IAAI,aAAa,MAAM,QAAQ;AAAA,EACrE;AACA,MAAI,MAAM,cAAc,QAAW;AACjC,eAAW,SAAS,OAAO,CAAC,QAAQ,IAAI,cAAc,MAAM,SAAS;AAAA,EACvE;AACA,MAAI,MAAM,cAAc;AACtB,eAAW,SAAS,OAAO,CAAC,QAAQ,IAAI,iBAAiB,MAAM,YAAY;AAAA,EAC7E;AAEA,QAAM,UAAU,MAAM,QAAQ;AAC9B,QAAM,YAAY,MAAM,UAAU,SAAS,KAAK;AAEhD,QAAM,SAAS,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,MAAM,SAAS;AAChD,QAAI,MAAM;AACV,QAAI,YAAY,SAAS;AACvB,YAAM,KAAK,UAAU,cAAc,KAAK,SAAS;AAAA,IACnD,WAAW,YAAY,YAAY;AACjC,YAAM,KAAK,aAAa,cAAc,KAAK,YAAY;AACvD,UAAI,QAAQ,EAAG,OAAM,KAAK,UAAU,cAAc,KAAK,SAAS;AAAA,IAClE,WAAW,YAAY,aAAa;AAClC,YAAM,KAAK,kBAAkB,KAAK;AAClC,UAAI,QAAQ,EAAG,OAAM,KAAK,UAAU,cAAc,KAAK,SAAS;AAAA,IAClE,WAAW,YAAY,gBAAgB;AACrC,YAAM,iBAAiB,KAAK,YAAY,IAAI,iBAAiB,KAAK,YAAY;AAC9E,UAAI,QAAQ,EAAG,OAAM,KAAK,UAAU,cAAc,KAAK,SAAS;AAAA,IAClE;AACA,WAAO,MAAM;AAAA,EACf,CAAC;AAED,QAAM,QAAQ,OAAO;AACrB,QAAM,WAAW,MAAM;AACvB,QAAM,OAAO,MAAM;AACnB,QAAM,aAAa,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,QAAQ,KAAK,CAAC;AAC/D,QAAM,WAAW,KAAK,IAAI,MAAM,UAAU;AAC1C,QAAM,UAAU,WAAW,KAAK;AAChC,QAAM,YAAY,OAAO,MAAM,QAAQ,SAAS,QAAQ;AAExD,QAAM,eAAe,MAAM,WAAW;AAAA,IACpC,UAAU,IAAI,CAAC,QAAQ,IAAI,EAAE;AAAA,IAC7B;AAAA,IACA;AAAA,EACF;AAEA,QAAM,QAAQ,UAAU,IAAI,CAAC,QAAQ;AACnC,UAAM,YAAY,aAAa,IAAI,IAAI,EAAE;AACzC,WAAO;AAAA,MACL,IAAI,IAAI;AAAA,MACR,OAAO,IAAI;AAAA,MACX,aAAa,IAAI;AAAA,MACjB,UAAU,IAAI;AAAA,MACd,MAAM,IAAI;AAAA,MACV,KAAK,IAAI;AAAA,MACT,aAAa,IAAI;AAAA,MACjB,UAAU,IAAI;AAAA,MACd,QAAQ,IAAI;AAAA,MACZ,SAAS,IAAI;AAAA,MACb,SAAS,IAAI;AAAA,MACb,gBAAgB,IAAI;AAAA,MACpB,WAAW,IAAI;AAAA,MACf,YAAY,IAAI;AAAA,MAChB,cAAc,IAAI;AAAA,MAClB,qBAAqB,IAAI;AAAA,MACzB,qBAAqB,IAAI;AAAA,MACzB,WAAW,IAAI;AAAA,MACf,WAAW,aAAa;AAAA,QACtB,gBAAgB;AAAA,QAChB,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,WAAW;AAAA,QACX,aAAa,MAAM,KAAK,EAAE,QAAQ,GAAG,GAAG,MAAM,CAAC;AAAA,MACjD;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,UAAU,cAAc,EAAE,IAAI,CAAC,WAAW;AAC9C,UAAM,qBAAqB,SAAS,OAAO,CAAC,QAAQ,IAAI,aAAa,OAAO,EAAE;AAC9E,UAAM,eAAe,mBAAmB,OAAO,CAAC,OAAO,gBAAgB,SAAS,YAAY,YAAY,IAAI,IAAI,CAAC;AAEjH,WAAO;AAAA,MACL,IAAI,OAAO;AAAA,MACX,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,MACpB,MAAM,OAAO,QAAQ;AAAA,MACrB,kBAAkB,mBAAmB;AAAA,MACrC;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,iCAAiC;AAAA,IACtC,WAAW,yBAAyB;AAAA,IACpC,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA,oBAAoB,mBAAmB;AAAA,IACvC,gBAAgB,mBAAmB;AAAA,IACnC,QAAQ;AAAA,MACN,cAAc;AAAA,MACd,UAAU,CAAC,OAAO;AAAA,IACpB;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AACH;",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CredentialsEncryptionUnavailableError, type CredentialsService } from '../lib/credentials-service'\nimport type { IntegrationStateService } from '../lib/state-service'\nimport type { IntegrationLogService } from '../lib/log-service'\nimport { deriveIntegrationHealthStatus, getEffectiveHealthCheckConfig } from '../lib/health-service'\nimport type { IntegrationHealthDisplayStatus } from '../lib/health-service'\nimport { getAllBundles, getAllIntegrations } from '@open-mercato/shared/modules/integrations/types'\nimport { listIntegrationsQuerySchema } from '../data/validators'\nimport { buildIntegrationsCrudOpenApi, integrationsListResponseSchema } from './openapi'\nimport {\n finalizeIntegrationsReadResponse,\n integrationApiRoutePaths,\n runIntegrationsReadBeforeInterceptors,\n} from './umes-read'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['integrations.view'] },\n}\n\nexport const openApi = buildIntegrationsCrudOpenApi({\n resourceName: 'Integration',\n pluralName: 'Integrations',\n listResponseSchema: integrationsListResponseSchema,\n querySchema: listIntegrationsQuerySchema,\n})\n\nconst HEALTH_SORT_RANK: Record<IntegrationHealthDisplayStatus, number> = {\n healthy: 0,\n degraded: 1,\n unhealthy: 2,\n unconfigured: 3,\n}\n\nfunction matchesSearchQuery(\n integration: { title: string; description?: string; tags?: string[] },\n queryNormalized: string,\n): boolean {\n if (!queryNormalized) return true\n if (integration.title.toLowerCase().includes(queryNormalized)) return true\n if (integration.description?.toLowerCase().includes(queryNormalized)) return true\n const tags = integration.tags ?? []\n return tags.some((tag) => tag.toLowerCase().includes(queryNormalized))\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsedQuery = listIntegrationsQuerySchema.safeParse(Object.fromEntries(url.searchParams.entries()))\n if (!parsedQuery.success) {\n return NextResponse.json({ error: 'Invalid query', details: parsedQuery.error.flatten() }, { status: 400 })\n }\n const query = parsedQuery.data\n\n const container = await createRequestContainer()\n const beforeInterceptors = await runIntegrationsReadBeforeInterceptors({\n routePath: integrationApiRoutePaths.list,\n request: req,\n auth,\n container,\n })\n if (!beforeInterceptors.ok) {\n return NextResponse.json(beforeInterceptors.body, { status: beforeInterceptors.statusCode })\n }\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const stateService = container.resolve('integrationStateService') as IntegrationStateService\n const logService = container.resolve('integrationLogService') as IntegrationLogService\n\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId as string }\n const searchNeedle = query.q?.trim().toLowerCase() ?? ''\n\n type ListRow = {\n id: string\n title: string\n description: string | null\n category: string | null\n tags: string[]\n hub: string | null\n providerKey: string | null\n bundleId: string | null\n author: string | null\n company: string | null\n version: string | null\n hasCredentials: boolean\n isEnabled: boolean\n apiVersion: string | null\n healthStatus: IntegrationHealthDisplayStatus\n lastHealthCheckedAt: string | null\n lastHealthLatencyMs: number | null\n enabledAt: string | null\n sortEnabledAtMs: number\n sortTitle: string\n sortCategory: string\n }\n\n const baseRows: ListRow[] = await Promise.all(\n getAllIntegrations().map(async (integration) => {\n const [resolvedCredentials, state] = await Promise.all([\n credentialsService.resolve(integration.id, scope).catch((err) => {\n if (err instanceof CredentialsEncryptionUnavailableError) return null\n throw err\n }),\n stateService.resolveState(integration.id, scope),\n ])\n\n const hasCredentials =\n resolvedCredentials != null && Object.keys(resolvedCredentials).length > 0\n const healthConfig = getEffectiveHealthCheckConfig(integration.id)\n const hasHealthCheck = Boolean(healthConfig?.service)\n const healthStatus = deriveIntegrationHealthStatus({\n hasHealthCheck,\n hasCredentials,\n lastHealthStatus: state.lastHealthStatus,\n lastHealthCheckedAt: state.lastHealthCheckedAt,\n })\n\n const enabledAtMs = state.enabledAt?.getTime() ?? 0\n\n return {\n id: integration.id,\n title: integration.title,\n description: integration.description ?? null,\n category: integration.category ?? null,\n tags: integration.tags ?? [],\n hub: integration.hub ?? null,\n providerKey: integration.providerKey ?? null,\n bundleId: integration.bundleId ?? null,\n author: integration.author ?? null,\n company: integration.company ?? null,\n version: integration.version ?? null,\n hasCredentials,\n isEnabled: state.isEnabled,\n apiVersion: state.apiVersion,\n healthStatus,\n lastHealthCheckedAt: state.lastHealthCheckedAt?.toISOString() ?? null,\n lastHealthLatencyMs: state.lastHealthLatencyMs,\n enabledAt: state.enabledAt?.toISOString() ?? null,\n sortEnabledAtMs: enabledAtMs,\n sortTitle: integration.title.toLowerCase(),\n sortCategory: (integration.category ?? '').toLowerCase(),\n }\n }),\n )\n\n let filtered = baseRows.filter((row) => matchesSearchQuery(\n { title: row.title, description: row.description ?? undefined, tags: row.tags },\n searchNeedle,\n ))\n\n if (query.category) {\n filtered = filtered.filter((row) => row.category === query.category)\n }\n if (query.bundleId) {\n filtered = filtered.filter((row) => row.bundleId === query.bundleId)\n }\n if (query.isEnabled !== undefined) {\n filtered = filtered.filter((row) => row.isEnabled === query.isEnabled)\n }\n if (query.healthStatus) {\n filtered = filtered.filter((row) => row.healthStatus === query.healthStatus)\n }\n\n const sortKey = query.sort ?? 'title'\n const orderSign = query.order === 'desc' ? -1 : 1\n\n const sorted = [...filtered].sort((rowA, rowB) => {\n let cmp = 0\n if (sortKey === 'title') {\n cmp = rowA.sortTitle.localeCompare(rowB.sortTitle)\n } else if (sortKey === 'category') {\n cmp = rowA.sortCategory.localeCompare(rowB.sortCategory)\n if (cmp === 0) cmp = rowA.sortTitle.localeCompare(rowB.sortTitle)\n } else if (sortKey === 'enabledAt') {\n cmp = rowA.sortEnabledAtMs - rowB.sortEnabledAtMs\n if (cmp === 0) cmp = rowA.sortTitle.localeCompare(rowB.sortTitle)\n } else if (sortKey === 'healthStatus') {\n cmp = HEALTH_SORT_RANK[rowA.healthStatus] - HEALTH_SORT_RANK[rowB.healthStatus]\n if (cmp === 0) cmp = rowA.sortTitle.localeCompare(rowB.sortTitle)\n }\n return cmp * orderSign\n })\n\n const total = sorted.length\n const pageSize = query.pageSize\n const page = query.page\n const totalPages = Math.max(1, Math.ceil(total / pageSize) || 1)\n const safePage = Math.min(page, totalPages)\n const offset = (safePage - 1) * pageSize\n const pageSlice = sorted.slice(offset, offset + pageSize)\n\n const analyticsMap = await logService.aggregateAnalytics(\n pageSlice.map((row) => row.id),\n scope,\n 30,\n )\n\n const items = pageSlice.map((row) => {\n const analytics = analyticsMap.get(row.id)\n return {\n id: row.id,\n title: row.title,\n description: row.description,\n category: row.category,\n tags: row.tags,\n hub: row.hub,\n providerKey: row.providerKey,\n bundleId: row.bundleId,\n author: row.author,\n company: row.company,\n version: row.version,\n hasCredentials: row.hasCredentials,\n isEnabled: row.isEnabled,\n apiVersion: row.apiVersion,\n healthStatus: row.healthStatus,\n lastHealthCheckedAt: row.lastHealthCheckedAt,\n lastHealthLatencyMs: row.lastHealthLatencyMs,\n enabledAt: row.enabledAt,\n analytics: analytics ?? {\n lastActivityAt: null,\n totalCount: 0,\n errorCount: 0,\n errorRate: 0,\n dailyCounts: Array.from({ length: 30 }, () => 0),\n },\n }\n })\n\n const bundles = getAllBundles().map((bundle) => {\n const bundleIntegrations = filtered.filter((row) => row.bundleId === bundle.id)\n const enabledCount = bundleIntegrations.reduce((count, integration) => count + (integration.isEnabled ? 1 : 0), 0)\n\n return {\n id: bundle.id,\n title: bundle.title,\n description: bundle.description,\n icon: bundle.icon ?? null,\n integrationCount: bundleIntegrations.length,\n enabledCount,\n }\n })\n\n return finalizeIntegrationsReadResponse({\n routePath: integrationApiRoutePaths.list,\n request: req,\n auth,\n container,\n interceptorRequest: beforeInterceptors.request,\n beforeMetadata: beforeInterceptors.metadataByInterceptor,\n enrich: {\n targetEntity: 'integrations.integration',\n listKeys: ['items'],\n },\n body: {\n items,\n bundles,\n total,\n page: safePage,\n pageSize,\n totalPages,\n },\n })\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -10,10 +10,12 @@ import {
10
10
  import { EncryptionMap } from "../../entities/data/entities.js";
11
11
  import { IntegrationCredentials } from "../data/entities.js";
12
12
  const ENCRYPTED_CREDENTIALS_BLOB_KEY = "__om_encrypted_credentials_blob_v1";
13
- const CREDENTIALS_ENCRYPTION_UNAVAILABLE_MESSAGE = "Integration credentials encryption key is unavailable. Configure Vault KMS or TENANT_DATA_ENCRYPTION_FALLBACK_KEY.";
14
13
  class CredentialsEncryptionUnavailableError extends Error {
15
- constructor() {
16
- super(CREDENTIALS_ENCRYPTION_UNAVAILABLE_MESSAGE);
14
+ constructor(tenantId) {
15
+ super(
16
+ `Cannot encrypt or decrypt integration credentials for tenant ${tenantId}: no tenant DEK is available. Configure Vault (VAULT_ADDR/VAULT_TOKEN) or set TENANT_DATA_ENCRYPTION_FALLBACK_KEY in the environment.`
17
+ );
18
+ this.code = "CREDENTIALS_ENCRYPTION_UNAVAILABLE";
17
19
  this.name = "CredentialsEncryptionUnavailableError";
18
20
  }
19
21
  }
@@ -80,7 +82,7 @@ function createCredentialsService(em) {
80
82
  if (existing?.key) return existing.key;
81
83
  const created = await kms.createTenantDek(scope.tenantId);
82
84
  if (created?.key) return created.key;
83
- throw new CredentialsEncryptionUnavailableError();
85
+ throw new CredentialsEncryptionUnavailableError(scope.tenantId);
84
86
  }
85
87
  async function encryptCredentialsBlob(credentials, scope) {
86
88
  const dek = await resolveCredentialsDek(scope);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/integrations/lib/credentials-service.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { decryptWithAesGcm, encryptWithAesGcm } from '@open-mercato/shared/lib/encryption/aes'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport {\n getBundle,\n getIntegration,\n resolveIntegrationCredentialsSchema,\n type IntegrationScope,\n} from '@open-mercato/shared/modules/integrations/types'\nimport { EncryptionMap } from '../../entities/data/entities'\nimport { IntegrationCredentials } from '../data/entities'\n\nconst ENCRYPTED_CREDENTIALS_BLOB_KEY = '__om_encrypted_credentials_blob_v1'\nconst CREDENTIALS_ENCRYPTION_UNAVAILABLE_MESSAGE =\n 'Integration credentials encryption key is unavailable. Configure Vault KMS or TENANT_DATA_ENCRYPTION_FALLBACK_KEY.'\n\nexport class CredentialsEncryptionUnavailableError extends Error {\n constructor() {\n super(CREDENTIALS_ENCRYPTION_UNAVAILABLE_MESSAGE)\n this.name = 'CredentialsEncryptionUnavailableError'\n }\n}\n\nexport function isCredentialsEncryptionUnavailableError(error: unknown): error is CredentialsEncryptionUnavailableError {\n return error instanceof CredentialsEncryptionUnavailableError\n}\n\nfunction isRecordValue(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction normalizeCredentialsRecord(value: unknown): Record<string, unknown> {\n if (isRecordValue(value)) return value\n if (typeof value !== 'string') return {}\n\n const parsed = parseDecryptedFieldValue(value)\n return isRecordValue(parsed) ? parsed : {}\n}\n\n/**\n * Build the where-filter for credential lookups.\n *\n * Per-user scoping (added 2026-05-26): when `scope.userId` is set, the filter\n * matches the row owned by that user \u2014 different users on the same tenant get\n * their OWN row for the same provider. When `scope.userId` is `undefined` /\n * `null`, the filter matches tenant-wide credentials (existing behaviour,\n * e.g. shared Stripe/Akeneo API keys).\n *\n * The partial unique index `integration_credentials_user_lookup_idx` enforces\n * uniqueness across `(integration_id, organization_id, tenant_id, user_id)`\n * when `user_id IS NOT NULL`.\n */\nexport function buildCredentialsFilter(integrationId: string, scope: IntegrationScope) {\n const base = {\n integrationId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n } as Record<string, unknown>\n if (scope.userId) {\n base.userId = scope.userId\n } else {\n base.userId = null\n }\n return base\n}\n\nexport function createCredentialsService(em: EntityManager) {\n const credentialsEncryptionSpec = [{ field: 'credentials' }]\n\n async function ensureCredentialsEncryptionMap(scope: IntegrationScope): Promise<void> {\n const existing = await findOneWithDecryption(\n em,\n EncryptionMap,\n {\n entityId: 'integrations:integration_credentials',\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n\n if (!existing) {\n const created = em.create(EncryptionMap, {\n entityId: 'integrations:integration_credentials',\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n fieldsJson: credentialsEncryptionSpec,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(created)\n return\n }\n\n existing.fieldsJson = credentialsEncryptionSpec\n existing.isActive = true\n }\n\n async function resolveCredentialsDek(scope: IntegrationScope): Promise<string> {\n const kms = createKmsService()\n const existing = await kms.getTenantDek(scope.tenantId)\n if (existing?.key) return existing.key\n\n const created = await kms.createTenantDek(scope.tenantId)\n if (created?.key) return created.key\n\n throw new CredentialsEncryptionUnavailableError()\n }\n\n async function encryptCredentialsBlob(\n credentials: Record<string, unknown>,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const dek = await resolveCredentialsDek(scope)\n const payload = encryptWithAesGcm(JSON.stringify(credentials), dek)\n return { [ENCRYPTED_CREDENTIALS_BLOB_KEY]: payload.value }\n }\n\n async function decryptCredentialsBlob(\n credentialsInput: unknown,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const credentials = normalizeCredentialsRecord(credentialsInput)\n const encrypted = credentials[ENCRYPTED_CREDENTIALS_BLOB_KEY]\n if (typeof encrypted !== 'string' || !encrypted) return credentials\n\n const dek = await resolveCredentialsDek(scope)\n const decryptedRaw = decryptWithAesGcm(encrypted, dek)\n if (!decryptedRaw) return {}\n\n try {\n const parsed = JSON.parse(decryptedRaw) as unknown\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {}\n } catch {\n return {}\n }\n }\n\n return {\n async getRaw(integrationId: string, scope: IntegrationScope): Promise<Record<string, unknown> | null> {\n let row = await findOneWithDecryption(\n em,\n IntegrationCredentials,\n buildCredentialsFilter(integrationId, scope),\n undefined,\n scope,\n )\n // Spec 2026-05-21 (email-integration-foundation) \"Hub credentials store\":\n // per-user secrets resolve as `WHERE user_id = currentUser.id OR user_id IS NULL`.\n // A user-scoped read of a TENANT-WIDE integration (sync_excel, Stripe, Akeneo,\n // S3, the channel OAuth *client* config) MUST still find the shared\n // `user_id = NULL` row \u2014 the per-user row takes precedence, and we only fall\n // back to the tenant-wide row when the user has none of their own. Writes stay\n // strict (`save` uses the unmodified filter) so a per-user save never clobbers\n // the shared credential.\n if (!row && scope.userId) {\n row = await findOneWithDecryption(\n em,\n IntegrationCredentials,\n buildCredentialsFilter(integrationId, { ...scope, userId: null }),\n undefined,\n scope,\n )\n }\n if (!row) return null\n return decryptCredentialsBlob(row.credentials, scope)\n },\n\n async resolve(integrationId: string, scope: IntegrationScope): Promise<Record<string, unknown> | null> {\n const direct = await this.getRaw(integrationId, scope)\n if (direct) return direct\n\n const definition = getIntegration(integrationId)\n if (!definition?.bundleId) return null\n return this.getRaw(definition.bundleId, scope)\n },\n\n async save(integrationId: string, credentials: Record<string, unknown>, scope: IntegrationScope): Promise<void> {\n const encryptedCredentials = await encryptCredentialsBlob(credentials, scope)\n await ensureCredentialsEncryptionMap(scope)\n\n const row = await findOneWithDecryption(\n em,\n IntegrationCredentials,\n buildCredentialsFilter(integrationId, scope),\n undefined,\n scope,\n )\n\n if (row) {\n row.credentials = encryptedCredentials\n await em.flush()\n return\n }\n\n const created = em.create(IntegrationCredentials, {\n integrationId,\n credentials: encryptedCredentials,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n ...(scope.userId ? { userId: scope.userId } : {}),\n })\n await em.persist(created).flush()\n },\n\n async saveField(\n integrationId: string,\n fieldKey: string,\n value: unknown,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const current = (await this.getRaw(integrationId, scope)) ?? {}\n const updated = { ...current, [fieldKey]: value }\n await this.save(integrationId, updated, scope)\n return updated\n },\n\n getSchema(integrationId: string) {\n const definition = getIntegration(integrationId)\n if (!definition) return undefined\n\n if (definition.bundleId) {\n const bundle = getBundle(definition.bundleId)\n return bundle?.credentials ?? resolveIntegrationCredentialsSchema(integrationId)\n }\n\n return definition.credentials ?? resolveIntegrationCredentialsSchema(integrationId)\n },\n }\n}\n\nexport type CredentialsService = ReturnType<typeof createCredentialsService>\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { decryptWithAesGcm, encryptWithAesGcm } from '@open-mercato/shared/lib/encryption/aes'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport {\n getBundle,\n getIntegration,\n resolveIntegrationCredentialsSchema,\n type IntegrationScope,\n} from '@open-mercato/shared/modules/integrations/types'\nimport { EncryptionMap } from '../../entities/data/entities'\nimport { IntegrationCredentials } from '../data/entities'\n\nconst ENCRYPTED_CREDENTIALS_BLOB_KEY = '__om_encrypted_credentials_blob_v1'\n\n/**\n * Raised when integration credentials cannot be encrypted or decrypted because\n * no tenant DEK is available \u2014 typically a production deployment with neither\n * Vault nor `TENANT_DATA_ENCRYPTION_FALLBACK_KEY` (or equivalent env vars)\n * configured. The credentials path deliberately fails closed instead of using\n * a hardcoded fallback secret; see security tracker finding #7.\n */\nexport class CredentialsEncryptionUnavailableError extends Error {\n readonly code = 'CREDENTIALS_ENCRYPTION_UNAVAILABLE'\n constructor(tenantId: string) {\n super(\n `Cannot encrypt or decrypt integration credentials for tenant ${tenantId}: ` +\n `no tenant DEK is available. Configure Vault (VAULT_ADDR/VAULT_TOKEN) or ` +\n `set TENANT_DATA_ENCRYPTION_FALLBACK_KEY in the environment.`,\n )\n this.name = 'CredentialsEncryptionUnavailableError'\n }\n}\n\nexport function isCredentialsEncryptionUnavailableError(error: unknown): error is CredentialsEncryptionUnavailableError {\n return error instanceof CredentialsEncryptionUnavailableError\n}\n\nfunction isRecordValue(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction normalizeCredentialsRecord(value: unknown): Record<string, unknown> {\n if (isRecordValue(value)) return value\n if (typeof value !== 'string') return {}\n\n const parsed = parseDecryptedFieldValue(value)\n return isRecordValue(parsed) ? parsed : {}\n}\n\n/**\n * Build the where-filter for credential lookups.\n *\n * Per-user scoping (added 2026-05-26): when `scope.userId` is set, the filter\n * matches the row owned by that user \u2014 different users on the same tenant get\n * their OWN row for the same provider. When `scope.userId` is `undefined` /\n * `null`, the filter matches tenant-wide credentials (existing behaviour,\n * e.g. shared Stripe/Akeneo API keys).\n *\n * The partial unique index `integration_credentials_user_lookup_idx` enforces\n * uniqueness across `(integration_id, organization_id, tenant_id, user_id)`\n * when `user_id IS NOT NULL`.\n */\nexport function buildCredentialsFilter(integrationId: string, scope: IntegrationScope) {\n const base = {\n integrationId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n } as Record<string, unknown>\n if (scope.userId) {\n base.userId = scope.userId\n } else {\n base.userId = null\n }\n return base\n}\n\nexport function createCredentialsService(em: EntityManager) {\n const credentialsEncryptionSpec = [{ field: 'credentials' }]\n\n async function ensureCredentialsEncryptionMap(scope: IntegrationScope): Promise<void> {\n const existing = await findOneWithDecryption(\n em,\n EncryptionMap,\n {\n entityId: 'integrations:integration_credentials',\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n\n if (!existing) {\n const created = em.create(EncryptionMap, {\n entityId: 'integrations:integration_credentials',\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n fieldsJson: credentialsEncryptionSpec,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(created)\n return\n }\n\n existing.fieldsJson = credentialsEncryptionSpec\n existing.isActive = true\n }\n\n async function resolveCredentialsDek(scope: IntegrationScope): Promise<string> {\n const kms = createKmsService()\n const existing = await kms.getTenantDek(scope.tenantId)\n if (existing?.key) return existing.key\n\n const created = await kms.createTenantDek(scope.tenantId)\n if (created?.key) return created.key\n\n throw new CredentialsEncryptionUnavailableError(scope.tenantId)\n }\n\n async function encryptCredentialsBlob(\n credentials: Record<string, unknown>,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const dek = await resolveCredentialsDek(scope)\n const payload = encryptWithAesGcm(JSON.stringify(credentials), dek)\n return { [ENCRYPTED_CREDENTIALS_BLOB_KEY]: payload.value }\n }\n\n async function decryptCredentialsBlob(\n credentialsInput: unknown,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const credentials = normalizeCredentialsRecord(credentialsInput)\n const encrypted = credentials[ENCRYPTED_CREDENTIALS_BLOB_KEY]\n if (typeof encrypted !== 'string' || !encrypted) return credentials\n\n const dek = await resolveCredentialsDek(scope)\n const decryptedRaw = decryptWithAesGcm(encrypted, dek)\n if (!decryptedRaw) return {}\n\n try {\n const parsed = JSON.parse(decryptedRaw) as unknown\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {}\n } catch {\n return {}\n }\n }\n\n return {\n async getRaw(integrationId: string, scope: IntegrationScope): Promise<Record<string, unknown> | null> {\n let row = await findOneWithDecryption(\n em,\n IntegrationCredentials,\n buildCredentialsFilter(integrationId, scope),\n undefined,\n scope,\n )\n // Spec 2026-05-21 (email-integration-foundation) \"Hub credentials store\":\n // per-user secrets resolve as `WHERE user_id = currentUser.id OR user_id IS NULL`.\n // A user-scoped read of a TENANT-WIDE integration (sync_excel, Stripe, Akeneo,\n // S3, the channel OAuth *client* config) MUST still find the shared\n // `user_id = NULL` row \u2014 the per-user row takes precedence, and we only fall\n // back to the tenant-wide row when the user has none of their own. Writes stay\n // strict (`save` uses the unmodified filter) so a per-user save never clobbers\n // the shared credential.\n if (!row && scope.userId) {\n row = await findOneWithDecryption(\n em,\n IntegrationCredentials,\n buildCredentialsFilter(integrationId, { ...scope, userId: null }),\n undefined,\n scope,\n )\n }\n if (!row) return null\n return decryptCredentialsBlob(row.credentials, scope)\n },\n\n async resolve(integrationId: string, scope: IntegrationScope): Promise<Record<string, unknown> | null> {\n const direct = await this.getRaw(integrationId, scope)\n if (direct) return direct\n\n const definition = getIntegration(integrationId)\n if (!definition?.bundleId) return null\n return this.getRaw(definition.bundleId, scope)\n },\n\n async save(integrationId: string, credentials: Record<string, unknown>, scope: IntegrationScope): Promise<void> {\n const encryptedCredentials = await encryptCredentialsBlob(credentials, scope)\n await ensureCredentialsEncryptionMap(scope)\n\n const row = await findOneWithDecryption(\n em,\n IntegrationCredentials,\n buildCredentialsFilter(integrationId, scope),\n undefined,\n scope,\n )\n\n if (row) {\n row.credentials = encryptedCredentials\n await em.flush()\n return\n }\n\n const created = em.create(IntegrationCredentials, {\n integrationId,\n credentials: encryptedCredentials,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n ...(scope.userId ? { userId: scope.userId } : {}),\n })\n await em.persist(created).flush()\n },\n\n async saveField(\n integrationId: string,\n fieldKey: string,\n value: unknown,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const current = (await this.getRaw(integrationId, scope)) ?? {}\n const updated = { ...current, [fieldKey]: value }\n await this.save(integrationId, updated, scope)\n return updated\n },\n\n getSchema(integrationId: string) {\n const definition = getIntegration(integrationId)\n if (!definition) return undefined\n\n if (definition.bundleId) {\n const bundle = getBundle(definition.bundleId)\n return bundle?.credentials ?? resolveIntegrationCredentialsSchema(integrationId)\n }\n\n return definition.credentials ?? resolveIntegrationCredentialsSchema(integrationId)\n },\n }\n}\n\nexport type CredentialsService = ReturnType<typeof createCredentialsService>\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -82,6 +82,12 @@ const features = [
82
82
  module: "sales",
83
83
  dependsOn: ["sales.returns.view", "sales.orders.manage"]
84
84
  },
85
+ {
86
+ id: "sales.returns.manage",
87
+ title: "Edit and delete order returns",
88
+ module: "sales",
89
+ dependsOn: ["sales.returns.create"]
90
+ },
85
91
  {
86
92
  id: "sales.invoices.manage",
87
93
  title: "Manage sales invoices",