@open-mercato/core 0.4.2-canary-e2aeb1a7bf → 0.4.2-canary-601795d9cd

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.
@@ -15,6 +15,7 @@ import { env } from "process";
15
15
  import crypto from "node:crypto";
16
16
  import { formatPasswordRequirements, getPasswordPolicy, validatePassword } from "@open-mercato/shared/lib/auth/passwordPolicy";
17
17
  import { parseBooleanToken } from "@open-mercato/shared/lib/boolean";
18
+ import { getCliModules } from "@open-mercato/shared/modules/registry";
18
19
  const addUser = {
19
20
  command: "add-user",
20
21
  async run(rest) {
@@ -396,7 +397,8 @@ const setupApp = {
396
397
  orgName,
397
398
  roleNames,
398
399
  primaryUser: { email, password, confirm: true },
399
- includeDerivedUsers: true
400
+ includeDerivedUsers: true,
401
+ modules: getCliModules()
400
402
  });
401
403
  if (result.reusedExistingUser) {
402
404
  console.log("\u26A0\uFE0F Existing initial user detected during setup.");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/auth/cli.ts"],
4
- "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { hash } from 'bcryptjs'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { User, Role, 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 { ensureRoles, setupInitialTenant } from './lib/setup-app'\nimport { normalizeTenantId } from './lib/tenantAccess'\nimport { computeEmailHash } from './lib/emailHash'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { decryptWithAesGcm } from '@open-mercato/shared/lib/encryption/aes'\nimport { env } from 'process'\nimport type { KmsService, TenantDek } from '@open-mercato/shared/lib/encryption/kms'\nimport crypto from 'node:crypto'\nimport { formatPasswordRequirements, getPasswordPolicy, validatePassword } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nconst addUser: ModuleCli = {\n command: 'add-user',\n async run(rest) {\n const args: Record<string, string> = {}\n for (let i = 0; i < rest.length; i += 2) {\n const k = rest[i]?.replace(/^--/, '')\n const v = rest[i + 1]\n if (k) args[k] = v\n }\n const email = args.email\n const password = args.password\n const organizationId = String(args.organizationId ?? args.orgId ?? args.org)\n const rolesCsv = (args.roles ?? '').trim()\n if (!email || !password || !organizationId) {\n console.error('Usage: mercato auth add-user --email <email> --password <password> --organizationId <id> [--roles customer,employee]')\n return\n }\n if (!ensurePasswordPolicy(password)) return\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n const org =\n (await findOneWithDecryption(\n em,\n Organization,\n { id: organizationId },\n { populate: ['tenant'] },\n { tenantId: null, organizationId },\n )) ?? null\n if (!org) throw new Error('Organization not found')\n const orgTenantId = org.tenant?.id ? String(org.tenant.id) : null\n const normalizedTenantId = normalizeTenantId(orgTenantId ?? null) ?? null\n const u = em.create(User, {\n email,\n emailHash: computeEmailHash(email),\n passwordHash: await hash(password, 10),\n isConfirmed: true,\n organizationId: org.id,\n tenantId: org.tenant.id,\n })\n await em.persistAndFlush(u)\n if (rolesCsv) {\n const names = rolesCsv.split(',').map(s => s.trim()).filter(Boolean)\n for (const name of names) {\n let role = await em.findOne(Role, { name, tenantId: normalizedTenantId })\n if (!role && normalizedTenantId !== null) {\n role = await em.findOne(Role, { name, tenantId: null })\n }\n if (!role) {\n role = em.create(Role, { name, tenantId: normalizedTenantId, createdAt: new Date() })\n await em.persistAndFlush(role)\n } else if (normalizedTenantId !== null && role.tenantId !== normalizedTenantId) {\n role.tenantId = normalizedTenantId\n await em.persistAndFlush(role)\n }\n const link = em.create(UserRole, { user: u, role })\n await em.persistAndFlush(link)\n }\n }\n console.log('User created with id', u.id)\n },\n}\n\nfunction parseArgs(rest: string[]) {\n const args: Record<string, string | boolean> = {}\n for (let i = 0; i < rest.length; i++) {\n const a = rest[i]\n if (!a) continue\n if (a.startsWith('--')) {\n const [k, v] = a.replace(/^--/, '').split('=')\n if (v !== undefined) args[k] = v\n else if (rest[i + 1] && !rest[i + 1]!.startsWith('--')) { args[k] = rest[i + 1]!; i++ }\n else args[k] = true\n }\n }\n return args\n}\n\nfunction normalizeKeyInput(value: string): string {\n return value.trim().replace(/^['\"]|['\"]$/g, '')\n}\n\nfunction hashSecret(value: string | null | undefined): string | null {\n if (!value) return null\n return crypto.createHash('sha256').update(normalizeKeyInput(value)).digest('hex').slice(0, 12)\n}\n\nfunction ensurePasswordPolicy(password: string): boolean {\n const policy = getPasswordPolicy()\n const result = validatePassword(password, policy)\n if (result.ok) return true\n const requirements = formatPasswordRequirements(policy, (_key, fallback) => fallback)\n const suffix = requirements ? `: ${requirements}` : ''\n console.error(`Password does not meet the requirements${suffix}.`)\n return false\n}\n\nasync function withEncryptionDebugDisabled<T>(fn: () => Promise<T>): Promise<T> {\n const previous = process.env.TENANT_DATA_ENCRYPTION_DEBUG\n process.env.TENANT_DATA_ENCRYPTION_DEBUG = 'no'\n try {\n return await fn()\n } finally {\n if (previous === undefined) {\n delete process.env.TENANT_DATA_ENCRYPTION_DEBUG\n } else {\n process.env.TENANT_DATA_ENCRYPTION_DEBUG = previous\n }\n }\n}\n\nclass DerivedKeyKmsService implements KmsService {\n private root: Buffer\n constructor(secret: string) {\n this.root = crypto.createHash('sha256').update(normalizeKeyInput(secret)).digest()\n }\n\n isHealthy(): boolean {\n return true\n }\n\n private deriveKey(tenantId: string): string {\n const iterations = 310_000\n const keyLength = 32\n const derived = crypto.pbkdf2Sync(this.root, tenantId, iterations, keyLength, 'sha512')\n return derived.toString('base64')\n }\n\n async getTenantDek(tenantId: string): Promise<TenantDek | null> {\n if (!tenantId) return null\n return { tenantId, key: this.deriveKey(tenantId), fetchedAt: Date.now() }\n }\n\n async createTenantDek(tenantId: string): Promise<TenantDek | null> {\n return this.getTenantDek(tenantId)\n }\n}\n\nfunction fingerprintDek(dek: TenantDek | null): string | null {\n if (!dek?.key) return null\n return crypto.createHash('sha256').update(dek.key).digest('hex').slice(0, 12)\n}\n\nfunction decryptWithOldKey(\n payload: string,\n dek: TenantDek | null,\n): string | null {\n if (!dek?.key) return null\n return decryptWithAesGcm(payload, dek.key)\n}\n\nconst seedRoles: ModuleCli = {\n command: 'seed-roles',\n async run(rest) {\n const args: Record<string, string> = {}\n for (let i = 0; i < rest.length; i += 2) {\n const key = rest[i]?.replace(/^--/, '')\n if (!key) continue\n const value = rest[i + 1]\n if (value) args[key] = value\n }\n const tenantId = args.tenantId ?? args.tenant ?? args.tenant_id ?? null\n const { resolve } = await createRequestContainer()\n const em = resolve<EntityManager>('em')\n if (tenantId) {\n await ensureRoles(em, { tenantId })\n console.log('\uD83D\uDEE1\uFE0F Roles ensured for tenant', tenantId)\n return\n }\n const tenants = await em.find(Tenant, {})\n if (!tenants.length) {\n console.log('No tenants found; nothing to seed.')\n return\n }\n for (const tenant of tenants) {\n const id = tenant.id ? String(tenant.id) : null\n if (!id) continue\n await ensureRoles(em, { tenantId: id })\n console.log('\uD83D\uDEE1\uFE0F Roles ensured for tenant', id)\n }\n },\n}\n\nconst rotateEncryptionKey: ModuleCli = {\n command: 'rotate-encryption-key',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = (args.tenantId as string) ?? (args.tenant as string) ?? (args.tenant_id as string) ?? null\n const organizationId = (args.organizationId as string) ?? (args.orgId as string) ?? (args.org as string) ?? null\n const oldKey = (args['old-key'] as string) ?? (args.oldKey as string) ?? null\n const dryRun = Boolean(args['dry-run'] || args.dry)\n const debug = Boolean(args.debug)\n const rotate = Boolean(oldKey)\n if (rotate && !tenantId) {\n console.warn(\n '\u26A0\uFE0F Rotating with --old-key across all tenants. A single old key should normally target one tenant; consider --tenant.',\n )\n }\n if (!isTenantDataEncryptionEnabled()) {\n console.error('TENANT_DATA_ENCRYPTION is disabled; aborting.')\n return\n }\n const { resolve } = await createRequestContainer()\n const em = resolve<EntityManager>('em')\n const encryptionService = new TenantDataEncryptionService(em as any, { kms: createKmsService() })\n const oldKms = rotate && oldKey ? new DerivedKeyKmsService(oldKey) : null\n if (debug) {\n console.log('[rotate-encryption-key]', {\n hasOldKey: Boolean(oldKey),\n rotate,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n })\n console.log('[rotate-encryption-key] key fingerprints', {\n oldKey: hashSecret(oldKey),\n currentKey: hashSecret(process.env.TENANT_DATA_ENCRYPTION_FALLBACK_KEY),\n })\n }\n if (!encryptionService.isEnabled()) {\n console.error('Encryption service is not enabled (KMS unhealthy or no DEK). Aborting.')\n return\n }\n const conn: any = (em as any).getConnection?.()\n if (!conn || typeof conn.execute !== 'function') {\n console.error('Unable to access raw connection; aborting.')\n return\n }\n const meta = (em as any)?.getMetadata?.()?.get?.(User)\n const tableName = meta?.tableName || 'users'\n const schema = meta?.schema\n const qualifiedTable = schema ? `\"${schema}\".\"${tableName}\"` : `\"${tableName}\"`\n const isEncryptedPayload = (value: unknown): boolean => {\n if (typeof value !== 'string') return false\n const parts = value.split(':')\n return parts.length === 4 && parts[3] === 'v1'\n }\n const printedDek = new Set<string>()\n const oldDekCache = new Map<string, TenantDek | null>()\n const processScope = async (scopeTenantId: string, scopeOrganizationId: string): Promise<number> => {\n if (debug && !printedDek.has(scopeTenantId)) {\n printedDek.add(scopeTenantId)\n const [oldDek, newDek] = await Promise.all([\n oldKms?.getTenantDek(scopeTenantId) ?? Promise.resolve(null),\n encryptionService.getDek(scopeTenantId),\n ])\n console.log('[rotate-encryption-key] dek fingerprints', {\n tenantId: scopeTenantId,\n oldKey: fingerprintDek(oldDek),\n currentKey: fingerprintDek(newDek),\n })\n }\n const rawRows = await conn.execute(\n `select id, email, email_hash from ${qualifiedTable} where tenant_id = ? and organization_id = ?`,\n [scopeTenantId, scopeOrganizationId],\n )\n const rows = Array.isArray(rawRows) ? rawRows : []\n const pending = rotate\n ? rows\n : rows.filter((row: any) => !isEncryptedPayload(row?.email))\n if (!pending.length) return 0\n console.log(\n `Found ${pending.length} auth user records to process for org=${scopeOrganizationId}${dryRun ? ' (dry-run)' : ''}.`\n )\n if (dryRun) return 0\n const ids = pending.map((row: any) => String(row.id))\n const users = rotate\n ? await em.find(\n User,\n { id: { $in: ids }, tenantId: scopeTenantId, organizationId: scopeOrganizationId },\n )\n : await findWithDecryption(\n em,\n User,\n { id: { $in: ids }, tenantId: scopeTenantId, organizationId: scopeOrganizationId },\n {},\n { tenantId: scopeTenantId, organizationId: scopeOrganizationId, encryptionService },\n )\n const usersById = new Map(users.map((user) => [String(user.id), user]))\n let updated = 0\n for (const row of pending) {\n const user = usersById.get(String(row.id))\n if (!user) continue\n const rawEmail = typeof row.email === 'string' ? row.email : String(row.email ?? '')\n if (!rawEmail) continue\n if (rotate && (!isEncryptedPayload(rawEmail) || !oldKms)) {\n continue\n }\n let plainEmail = rawEmail\n if (rotate && isEncryptedPayload(rawEmail) && oldKms) {\n if (debug) {\n console.log('[rotate-encryption-key] decrypting', {\n userId: row.id,\n tenantId: scopeTenantId,\n organizationId: scopeOrganizationId,\n })\n }\n let oldDek = oldDekCache.get(scopeTenantId) ?? null\n if (!oldDekCache.has(scopeTenantId)) {\n oldDek = await oldKms.getTenantDek(scopeTenantId)\n oldDekCache.set(scopeTenantId, oldDek)\n }\n const maybeEmail = decryptWithOldKey(rawEmail, oldDek)\n if (typeof maybeEmail !== 'string' || isEncryptedPayload(maybeEmail)) continue\n plainEmail = maybeEmail\n }\n if (!plainEmail) continue\n const encrypted = await encryptionService.encryptEntityPayload(\n 'auth:user',\n { email: plainEmail },\n scopeTenantId,\n scopeOrganizationId,\n )\n const nextEmail = encrypted.email as string | undefined\n if (nextEmail && nextEmail !== user.email) {\n user.email = nextEmail as any\n user.emailHash = (encrypted as any).emailHash ?? computeEmailHash(plainEmail)\n em.persist(user)\n updated += 1\n }\n }\n if (updated > 0) {\n await em.flush()\n }\n return updated\n }\n\n if (tenantId && organizationId) {\n const updated = await processScope(String(tenantId), String(organizationId))\n if (!updated) {\n console.log('All auth user emails already encrypted for the selected scope.')\n } else {\n console.log(`Encrypted ${updated} auth user email(s).`)\n }\n return\n }\n\n const organizations = await em.find(Organization, {})\n if (!organizations.length) {\n console.log('No organizations found; nothing to encrypt.')\n return\n }\n let total = 0\n for (const org of organizations) {\n const scopeTenantId = org.tenant?.id ? String(org.tenant.id) : org.tenant.id ? String(org.tenant.id) : null\n const scopeOrganizationId = org.id ? String(org.id) : null\n if (!scopeTenantId || !scopeOrganizationId) continue\n total += await processScope(scopeTenantId, scopeOrganizationId)\n }\n if (total > 0) {\n console.log(`Encrypted ${total} auth user email(s) across all organizations.`)\n } else {\n console.log('All auth user emails already encrypted across all organizations.')\n }\n },\n}\n\n// will be exported at the bottom with all commands\n\nconst addOrganization: ModuleCli = {\n command: 'add-org',\n async run(rest) {\n const args: Record<string, string> = {}\n for (let i = 0; i < rest.length; i += 2) {\n const k = rest[i]?.replace(/^--/, '')\n const v = rest[i + 1]\n if (k) args[k] = v\n }\n const name = args.name || args.orgName\n if (!name) {\n console.error('Usage: mercato auth add-org --name <organization name>')\n return\n }\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n // Create tenant implicitly for simplicity\n const tenant = em.create(Tenant, { name: `${name} Tenant` })\n await em.persistAndFlush(tenant)\n const org = em.create(Organization, { name, tenant })\n await em.persistAndFlush(org)\n await rebuildHierarchyForTenant(em, String(tenant.id))\n console.log('Organization created with id', org.id, 'in tenant', tenant.id)\n },\n}\n\nconst setupApp: ModuleCli = {\n command: 'setup',\n async run(rest) {\n const args = parseArgs(rest)\n const orgName = typeof args.orgName === 'string'\n ? args.orgName\n : typeof args.name === 'string'\n ? args.name\n : undefined\n const email = typeof args.email === 'string' ? args.email : undefined\n const password = typeof args.password === 'string' ? args.password : undefined\n const rolesCsv = typeof args.roles === 'string'\n ? args.roles.trim()\n : 'superadmin,admin,employee'\n const skipPasswordPolicyRaw =\n args['skip-password-policy'] ??\n args.skipPasswordPolicy ??\n args['allow-weak-password'] ??\n args.allowWeakPassword\n const skipPasswordPolicy = typeof skipPasswordPolicyRaw === 'boolean'\n ? skipPasswordPolicyRaw\n : parseBooleanToken(typeof skipPasswordPolicyRaw === 'string' ? skipPasswordPolicyRaw : null) ?? false\n if (!orgName || !email || !password) {\n console.error('Usage: mercato auth setup --orgName <name> --email <email> --password <password> [--roles superadmin,admin,employee] [--skip-password-policy]')\n return\n }\n if (!skipPasswordPolicy && !ensurePasswordPolicy(password)) return\n if (skipPasswordPolicy) {\n console.warn('\u26A0\uFE0F Password policy validation skipped for setup.')\n }\n const { resolve } = await createRequestContainer()\n const em = resolve<EntityManager>('em')\n const roleNames = rolesCsv\n ? rolesCsv.split(',').map((s) => s.trim()).filter(Boolean)\n : undefined\n\n try {\n const result = await setupInitialTenant(em, {\n orgName,\n roleNames,\n primaryUser: { email, password, confirm: true },\n includeDerivedUsers: true,\n })\n\n if (result.reusedExistingUser) {\n console.log('\u26A0\uFE0F Existing initial user detected during setup.')\n console.log(`\u26A0\uFE0F Email: ${email}`)\n console.log('\u26A0\uFE0F Updated roles if missing and reused tenant/organization.')\n }\n\n if(env.NODE_ENV !== 'test') { \n for (const snapshot of result.users) {\n if (snapshot.created) {\n if (snapshot.user.email === email && password) {\n console.log('\uD83C\uDF89 Created user', snapshot.user.email, 'password:', password)\n } else {\n console.log('\uD83C\uDF89 Created user', snapshot.user.email)\n }\n } else {\n console.log(`Updated user ${snapshot.user.email}`)\n }\n }\n }\n\n if(env.NODE_ENV !== 'test') console.log('\u2705 Setup complete:', { tenantId: result.tenantId, organizationId: result.organizationId })\n } catch (err) {\n if (err instanceof Error && err.message === 'USER_EXISTS') {\n console.error('Setup aborted: user already exists with the provided email.')\n return\n }\n throw err\n }\n },\n}\n\nconst listOrganizations: ModuleCli = {\n command: 'list-orgs',\n async run() {\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n const orgs = await findWithDecryption(\n em,\n Organization,\n {},\n { populate: ['tenant'] },\n { tenantId: null, organizationId: null },\n )\n \n if (orgs.length === 0) {\n console.log('No organizations found')\n return\n }\n \n console.log(`Found ${orgs.length} organization(s):`)\n console.log('')\n console.log('ID | Name | Tenant ID | Created')\n console.log('-------------------------------------|-------------------------|-------------------------------------|-------------------')\n \n for (const org of orgs) {\n const created = org.createdAt ? new Date(org.createdAt).toLocaleDateString() : 'N/A'\n const id = org.id || 'N/A'\n const tenantId = org.tenant?.id || 'N/A'\n const name = (org.name || 'Unnamed').padEnd(23)\n console.log(`${id.padEnd(35)} | ${name} | ${tenantId.padEnd(35)} | ${created}`)\n }\n },\n}\n\nconst listTenants: ModuleCli = {\n command: 'list-tenants',\n async run() {\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n const tenants = await em.find(Tenant, {})\n \n if (tenants.length === 0) {\n console.log('No tenants found')\n return\n }\n \n console.log(`Found ${tenants.length} tenant(s):`)\n console.log('')\n console.log('ID | Name | Created')\n console.log('-------------------------------------|-------------------------|-------------------')\n \n for (const tenant of tenants) {\n const created = tenant.createdAt ? new Date(tenant.createdAt).toLocaleDateString() : 'N/A'\n const id = tenant.id || 'N/A'\n const name = (tenant.name || 'Unnamed').padEnd(23)\n console.log(`${id.padEnd(35)} | ${name} | ${created}`)\n }\n },\n}\n\nconst listUsers: ModuleCli = {\n command: 'list-users',\n async run(rest) {\n const args: Record<string, string> = {}\n for (let i = 0; i < rest.length; i += 2) {\n const k = rest[i]?.replace(/^--/, '')\n const v = rest[i + 1]\n if (k) args[k] = v\n }\n \n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n \n // Build query with optional filters\n const where: any = {}\n if (args.organizationId || args.orgId || args.org) {\n where.organizationId = args.organizationId || args.orgId || args.org\n }\n if (args.tenantId || args.tenant) {\n where.tenantId = args.tenantId || args.tenant\n }\n \n const users = await em.find(User, where)\n \n if (users.length === 0) {\n console.log('No users found')\n return\n }\n \n console.log(`Found ${users.length} user(s):`)\n console.log('')\n console.log('ID | Email | Name | Organization ID | Tenant ID | Roles')\n console.log('-------------------------------------|-------------------------|-------------------------|---------------------|---------------------|-------------------')\n \n for (const user of users) {\n // Get user roles separately\n const userRoles = await findWithDecryption(\n em,\n UserRole,\n { user: user.id },\n { populate: ['role'] },\n { tenantId: user.tenantId ?? null, organizationId: user.organizationId ?? null },\n )\n const roles = userRoles.map((ur: any) => ur.role?.name).filter(Boolean).join(', ') || 'None'\n \n // Get organization and tenant names if IDs exist\n let orgName = 'N/A'\n let tenantName = 'N/A'\n \n if (user.organizationId) {\n const org = await em.findOne(Organization, { id: user.organizationId })\n orgName = org?.name?.substring(0, 19) + '...' || user.organizationId.substring(0, 8) + '...'\n }\n \n if (user.tenantId) {\n const tenant = await em.findOne(Tenant, { id: user.tenantId })\n tenantName = tenant?.name?.substring(0, 19) + '...' || user.tenantId.substring(0, 8) + '...'\n }\n \n const id = user.id || 'N/A'\n const email = (user.email || 'N/A').padEnd(23)\n const name = (user.name || 'Unnamed').padEnd(23)\n \n console.log(`${id.padEnd(35)} | ${email} | ${name} | ${orgName.padEnd(19)} | ${tenantName.padEnd(19)} | ${roles}`)\n }\n },\n}\n\nconst setPassword: ModuleCli = {\n command: 'set-password',\n async run(rest) {\n const args: Record<string, string> = {}\n for (let i = 0; i < rest.length; i += 2) {\n const k = rest[i]?.replace(/^--/, '')\n const v = rest[i + 1]\n if (k) args[k] = v\n }\n \n const email = args.email\n const password = args.password\n \n if (!email || !password) {\n console.error('Usage: mercato auth set-password --email <email> --password <newPassword>')\n return\n }\n if (!ensurePasswordPolicy(password)) return\n \n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n const emailHash = computeEmailHash(email)\n const user = await em.findOne(User, { $or: [{ email }, { emailHash }] })\n \n if (!user) {\n console.error(`User with email \"${email}\" not found`)\n return\n }\n \n user.passwordHash = await hash(password, 10)\n await em.persistAndFlush(user)\n \n console.log(`\u2705 Password updated successfully for user: ${email}`)\n },\n}\n\n// Export the full CLI list\nexport default [addUser, seedRoles, rotateEncryptionKey, addOrganization, setupApp, listOrganizations, listTenants, listUsers, setPassword]\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { hash } from 'bcryptjs'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { User, Role, 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 { ensureRoles, setupInitialTenant } from './lib/setup-app'\nimport { normalizeTenantId } from './lib/tenantAccess'\nimport { computeEmailHash } from './lib/emailHash'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { decryptWithAesGcm } from '@open-mercato/shared/lib/encryption/aes'\nimport { env } from 'process'\nimport type { KmsService, TenantDek } from '@open-mercato/shared/lib/encryption/kms'\nimport crypto from 'node:crypto'\nimport { formatPasswordRequirements, getPasswordPolicy, validatePassword } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { getCliModules } from '@open-mercato/shared/modules/registry'\n\nconst addUser: ModuleCli = {\n command: 'add-user',\n async run(rest) {\n const args: Record<string, string> = {}\n for (let i = 0; i < rest.length; i += 2) {\n const k = rest[i]?.replace(/^--/, '')\n const v = rest[i + 1]\n if (k) args[k] = v\n }\n const email = args.email\n const password = args.password\n const organizationId = String(args.organizationId ?? args.orgId ?? args.org)\n const rolesCsv = (args.roles ?? '').trim()\n if (!email || !password || !organizationId) {\n console.error('Usage: mercato auth add-user --email <email> --password <password> --organizationId <id> [--roles customer,employee]')\n return\n }\n if (!ensurePasswordPolicy(password)) return\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n const org =\n (await findOneWithDecryption(\n em,\n Organization,\n { id: organizationId },\n { populate: ['tenant'] },\n { tenantId: null, organizationId },\n )) ?? null\n if (!org) throw new Error('Organization not found')\n const orgTenantId = org.tenant?.id ? String(org.tenant.id) : null\n const normalizedTenantId = normalizeTenantId(orgTenantId ?? null) ?? null\n const u = em.create(User, {\n email,\n emailHash: computeEmailHash(email),\n passwordHash: await hash(password, 10),\n isConfirmed: true,\n organizationId: org.id,\n tenantId: org.tenant.id,\n })\n await em.persistAndFlush(u)\n if (rolesCsv) {\n const names = rolesCsv.split(',').map(s => s.trim()).filter(Boolean)\n for (const name of names) {\n let role = await em.findOne(Role, { name, tenantId: normalizedTenantId })\n if (!role && normalizedTenantId !== null) {\n role = await em.findOne(Role, { name, tenantId: null })\n }\n if (!role) {\n role = em.create(Role, { name, tenantId: normalizedTenantId, createdAt: new Date() })\n await em.persistAndFlush(role)\n } else if (normalizedTenantId !== null && role.tenantId !== normalizedTenantId) {\n role.tenantId = normalizedTenantId\n await em.persistAndFlush(role)\n }\n const link = em.create(UserRole, { user: u, role })\n await em.persistAndFlush(link)\n }\n }\n console.log('User created with id', u.id)\n },\n}\n\nfunction parseArgs(rest: string[]) {\n const args: Record<string, string | boolean> = {}\n for (let i = 0; i < rest.length; i++) {\n const a = rest[i]\n if (!a) continue\n if (a.startsWith('--')) {\n const [k, v] = a.replace(/^--/, '').split('=')\n if (v !== undefined) args[k] = v\n else if (rest[i + 1] && !rest[i + 1]!.startsWith('--')) { args[k] = rest[i + 1]!; i++ }\n else args[k] = true\n }\n }\n return args\n}\n\nfunction normalizeKeyInput(value: string): string {\n return value.trim().replace(/^['\"]|['\"]$/g, '')\n}\n\nfunction hashSecret(value: string | null | undefined): string | null {\n if (!value) return null\n return crypto.createHash('sha256').update(normalizeKeyInput(value)).digest('hex').slice(0, 12)\n}\n\nfunction ensurePasswordPolicy(password: string): boolean {\n const policy = getPasswordPolicy()\n const result = validatePassword(password, policy)\n if (result.ok) return true\n const requirements = formatPasswordRequirements(policy, (_key, fallback) => fallback)\n const suffix = requirements ? `: ${requirements}` : ''\n console.error(`Password does not meet the requirements${suffix}.`)\n return false\n}\n\nasync function withEncryptionDebugDisabled<T>(fn: () => Promise<T>): Promise<T> {\n const previous = process.env.TENANT_DATA_ENCRYPTION_DEBUG\n process.env.TENANT_DATA_ENCRYPTION_DEBUG = 'no'\n try {\n return await fn()\n } finally {\n if (previous === undefined) {\n delete process.env.TENANT_DATA_ENCRYPTION_DEBUG\n } else {\n process.env.TENANT_DATA_ENCRYPTION_DEBUG = previous\n }\n }\n}\n\nclass DerivedKeyKmsService implements KmsService {\n private root: Buffer\n constructor(secret: string) {\n this.root = crypto.createHash('sha256').update(normalizeKeyInput(secret)).digest()\n }\n\n isHealthy(): boolean {\n return true\n }\n\n private deriveKey(tenantId: string): string {\n const iterations = 310_000\n const keyLength = 32\n const derived = crypto.pbkdf2Sync(this.root, tenantId, iterations, keyLength, 'sha512')\n return derived.toString('base64')\n }\n\n async getTenantDek(tenantId: string): Promise<TenantDek | null> {\n if (!tenantId) return null\n return { tenantId, key: this.deriveKey(tenantId), fetchedAt: Date.now() }\n }\n\n async createTenantDek(tenantId: string): Promise<TenantDek | null> {\n return this.getTenantDek(tenantId)\n }\n}\n\nfunction fingerprintDek(dek: TenantDek | null): string | null {\n if (!dek?.key) return null\n return crypto.createHash('sha256').update(dek.key).digest('hex').slice(0, 12)\n}\n\nfunction decryptWithOldKey(\n payload: string,\n dek: TenantDek | null,\n): string | null {\n if (!dek?.key) return null\n return decryptWithAesGcm(payload, dek.key)\n}\n\nconst seedRoles: ModuleCli = {\n command: 'seed-roles',\n async run(rest) {\n const args: Record<string, string> = {}\n for (let i = 0; i < rest.length; i += 2) {\n const key = rest[i]?.replace(/^--/, '')\n if (!key) continue\n const value = rest[i + 1]\n if (value) args[key] = value\n }\n const tenantId = args.tenantId ?? args.tenant ?? args.tenant_id ?? null\n const { resolve } = await createRequestContainer()\n const em = resolve<EntityManager>('em')\n if (tenantId) {\n await ensureRoles(em, { tenantId })\n console.log('\uD83D\uDEE1\uFE0F Roles ensured for tenant', tenantId)\n return\n }\n const tenants = await em.find(Tenant, {})\n if (!tenants.length) {\n console.log('No tenants found; nothing to seed.')\n return\n }\n for (const tenant of tenants) {\n const id = tenant.id ? String(tenant.id) : null\n if (!id) continue\n await ensureRoles(em, { tenantId: id })\n console.log('\uD83D\uDEE1\uFE0F Roles ensured for tenant', id)\n }\n },\n}\n\nconst rotateEncryptionKey: ModuleCli = {\n command: 'rotate-encryption-key',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = (args.tenantId as string) ?? (args.tenant as string) ?? (args.tenant_id as string) ?? null\n const organizationId = (args.organizationId as string) ?? (args.orgId as string) ?? (args.org as string) ?? null\n const oldKey = (args['old-key'] as string) ?? (args.oldKey as string) ?? null\n const dryRun = Boolean(args['dry-run'] || args.dry)\n const debug = Boolean(args.debug)\n const rotate = Boolean(oldKey)\n if (rotate && !tenantId) {\n console.warn(\n '\u26A0\uFE0F Rotating with --old-key across all tenants. A single old key should normally target one tenant; consider --tenant.',\n )\n }\n if (!isTenantDataEncryptionEnabled()) {\n console.error('TENANT_DATA_ENCRYPTION is disabled; aborting.')\n return\n }\n const { resolve } = await createRequestContainer()\n const em = resolve<EntityManager>('em')\n const encryptionService = new TenantDataEncryptionService(em as any, { kms: createKmsService() })\n const oldKms = rotate && oldKey ? new DerivedKeyKmsService(oldKey) : null\n if (debug) {\n console.log('[rotate-encryption-key]', {\n hasOldKey: Boolean(oldKey),\n rotate,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n })\n console.log('[rotate-encryption-key] key fingerprints', {\n oldKey: hashSecret(oldKey),\n currentKey: hashSecret(process.env.TENANT_DATA_ENCRYPTION_FALLBACK_KEY),\n })\n }\n if (!encryptionService.isEnabled()) {\n console.error('Encryption service is not enabled (KMS unhealthy or no DEK). Aborting.')\n return\n }\n const conn: any = (em as any).getConnection?.()\n if (!conn || typeof conn.execute !== 'function') {\n console.error('Unable to access raw connection; aborting.')\n return\n }\n const meta = (em as any)?.getMetadata?.()?.get?.(User)\n const tableName = meta?.tableName || 'users'\n const schema = meta?.schema\n const qualifiedTable = schema ? `\"${schema}\".\"${tableName}\"` : `\"${tableName}\"`\n const isEncryptedPayload = (value: unknown): boolean => {\n if (typeof value !== 'string') return false\n const parts = value.split(':')\n return parts.length === 4 && parts[3] === 'v1'\n }\n const printedDek = new Set<string>()\n const oldDekCache = new Map<string, TenantDek | null>()\n const processScope = async (scopeTenantId: string, scopeOrganizationId: string): Promise<number> => {\n if (debug && !printedDek.has(scopeTenantId)) {\n printedDek.add(scopeTenantId)\n const [oldDek, newDek] = await Promise.all([\n oldKms?.getTenantDek(scopeTenantId) ?? Promise.resolve(null),\n encryptionService.getDek(scopeTenantId),\n ])\n console.log('[rotate-encryption-key] dek fingerprints', {\n tenantId: scopeTenantId,\n oldKey: fingerprintDek(oldDek),\n currentKey: fingerprintDek(newDek),\n })\n }\n const rawRows = await conn.execute(\n `select id, email, email_hash from ${qualifiedTable} where tenant_id = ? and organization_id = ?`,\n [scopeTenantId, scopeOrganizationId],\n )\n const rows = Array.isArray(rawRows) ? rawRows : []\n const pending = rotate\n ? rows\n : rows.filter((row: any) => !isEncryptedPayload(row?.email))\n if (!pending.length) return 0\n console.log(\n `Found ${pending.length} auth user records to process for org=${scopeOrganizationId}${dryRun ? ' (dry-run)' : ''}.`\n )\n if (dryRun) return 0\n const ids = pending.map((row: any) => String(row.id))\n const users = rotate\n ? await em.find(\n User,\n { id: { $in: ids }, tenantId: scopeTenantId, organizationId: scopeOrganizationId },\n )\n : await findWithDecryption(\n em,\n User,\n { id: { $in: ids }, tenantId: scopeTenantId, organizationId: scopeOrganizationId },\n {},\n { tenantId: scopeTenantId, organizationId: scopeOrganizationId, encryptionService },\n )\n const usersById = new Map(users.map((user) => [String(user.id), user]))\n let updated = 0\n for (const row of pending) {\n const user = usersById.get(String(row.id))\n if (!user) continue\n const rawEmail = typeof row.email === 'string' ? row.email : String(row.email ?? '')\n if (!rawEmail) continue\n if (rotate && (!isEncryptedPayload(rawEmail) || !oldKms)) {\n continue\n }\n let plainEmail = rawEmail\n if (rotate && isEncryptedPayload(rawEmail) && oldKms) {\n if (debug) {\n console.log('[rotate-encryption-key] decrypting', {\n userId: row.id,\n tenantId: scopeTenantId,\n organizationId: scopeOrganizationId,\n })\n }\n let oldDek = oldDekCache.get(scopeTenantId) ?? null\n if (!oldDekCache.has(scopeTenantId)) {\n oldDek = await oldKms.getTenantDek(scopeTenantId)\n oldDekCache.set(scopeTenantId, oldDek)\n }\n const maybeEmail = decryptWithOldKey(rawEmail, oldDek)\n if (typeof maybeEmail !== 'string' || isEncryptedPayload(maybeEmail)) continue\n plainEmail = maybeEmail\n }\n if (!plainEmail) continue\n const encrypted = await encryptionService.encryptEntityPayload(\n 'auth:user',\n { email: plainEmail },\n scopeTenantId,\n scopeOrganizationId,\n )\n const nextEmail = encrypted.email as string | undefined\n if (nextEmail && nextEmail !== user.email) {\n user.email = nextEmail as any\n user.emailHash = (encrypted as any).emailHash ?? computeEmailHash(plainEmail)\n em.persist(user)\n updated += 1\n }\n }\n if (updated > 0) {\n await em.flush()\n }\n return updated\n }\n\n if (tenantId && organizationId) {\n const updated = await processScope(String(tenantId), String(organizationId))\n if (!updated) {\n console.log('All auth user emails already encrypted for the selected scope.')\n } else {\n console.log(`Encrypted ${updated} auth user email(s).`)\n }\n return\n }\n\n const organizations = await em.find(Organization, {})\n if (!organizations.length) {\n console.log('No organizations found; nothing to encrypt.')\n return\n }\n let total = 0\n for (const org of organizations) {\n const scopeTenantId = org.tenant?.id ? String(org.tenant.id) : org.tenant.id ? String(org.tenant.id) : null\n const scopeOrganizationId = org.id ? String(org.id) : null\n if (!scopeTenantId || !scopeOrganizationId) continue\n total += await processScope(scopeTenantId, scopeOrganizationId)\n }\n if (total > 0) {\n console.log(`Encrypted ${total} auth user email(s) across all organizations.`)\n } else {\n console.log('All auth user emails already encrypted across all organizations.')\n }\n },\n}\n\n// will be exported at the bottom with all commands\n\nconst addOrganization: ModuleCli = {\n command: 'add-org',\n async run(rest) {\n const args: Record<string, string> = {}\n for (let i = 0; i < rest.length; i += 2) {\n const k = rest[i]?.replace(/^--/, '')\n const v = rest[i + 1]\n if (k) args[k] = v\n }\n const name = args.name || args.orgName\n if (!name) {\n console.error('Usage: mercato auth add-org --name <organization name>')\n return\n }\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n // Create tenant implicitly for simplicity\n const tenant = em.create(Tenant, { name: `${name} Tenant` })\n await em.persistAndFlush(tenant)\n const org = em.create(Organization, { name, tenant })\n await em.persistAndFlush(org)\n await rebuildHierarchyForTenant(em, String(tenant.id))\n console.log('Organization created with id', org.id, 'in tenant', tenant.id)\n },\n}\n\nconst setupApp: ModuleCli = {\n command: 'setup',\n async run(rest) {\n const args = parseArgs(rest)\n const orgName = typeof args.orgName === 'string'\n ? args.orgName\n : typeof args.name === 'string'\n ? args.name\n : undefined\n const email = typeof args.email === 'string' ? args.email : undefined\n const password = typeof args.password === 'string' ? args.password : undefined\n const rolesCsv = typeof args.roles === 'string'\n ? args.roles.trim()\n : 'superadmin,admin,employee'\n const skipPasswordPolicyRaw =\n args['skip-password-policy'] ??\n args.skipPasswordPolicy ??\n args['allow-weak-password'] ??\n args.allowWeakPassword\n const skipPasswordPolicy = typeof skipPasswordPolicyRaw === 'boolean'\n ? skipPasswordPolicyRaw\n : parseBooleanToken(typeof skipPasswordPolicyRaw === 'string' ? skipPasswordPolicyRaw : null) ?? false\n if (!orgName || !email || !password) {\n console.error('Usage: mercato auth setup --orgName <name> --email <email> --password <password> [--roles superadmin,admin,employee] [--skip-password-policy]')\n return\n }\n if (!skipPasswordPolicy && !ensurePasswordPolicy(password)) return\n if (skipPasswordPolicy) {\n console.warn('\u26A0\uFE0F Password policy validation skipped for setup.')\n }\n const { resolve } = await createRequestContainer()\n const em = resolve<EntityManager>('em')\n const roleNames = rolesCsv\n ? rolesCsv.split(',').map((s) => s.trim()).filter(Boolean)\n : undefined\n\n try {\n const result = await setupInitialTenant(em, {\n orgName,\n roleNames,\n primaryUser: { email, password, confirm: true },\n includeDerivedUsers: true,\n modules: getCliModules(),\n })\n\n if (result.reusedExistingUser) {\n console.log('\u26A0\uFE0F Existing initial user detected during setup.')\n console.log(`\u26A0\uFE0F Email: ${email}`)\n console.log('\u26A0\uFE0F Updated roles if missing and reused tenant/organization.')\n }\n\n if(env.NODE_ENV !== 'test') { \n for (const snapshot of result.users) {\n if (snapshot.created) {\n if (snapshot.user.email === email && password) {\n console.log('\uD83C\uDF89 Created user', snapshot.user.email, 'password:', password)\n } else {\n console.log('\uD83C\uDF89 Created user', snapshot.user.email)\n }\n } else {\n console.log(`Updated user ${snapshot.user.email}`)\n }\n }\n }\n\n if(env.NODE_ENV !== 'test') console.log('\u2705 Setup complete:', { tenantId: result.tenantId, organizationId: result.organizationId })\n } catch (err) {\n if (err instanceof Error && err.message === 'USER_EXISTS') {\n console.error('Setup aborted: user already exists with the provided email.')\n return\n }\n throw err\n }\n },\n}\n\nconst listOrganizations: ModuleCli = {\n command: 'list-orgs',\n async run() {\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n const orgs = await findWithDecryption(\n em,\n Organization,\n {},\n { populate: ['tenant'] },\n { tenantId: null, organizationId: null },\n )\n \n if (orgs.length === 0) {\n console.log('No organizations found')\n return\n }\n \n console.log(`Found ${orgs.length} organization(s):`)\n console.log('')\n console.log('ID | Name | Tenant ID | Created')\n console.log('-------------------------------------|-------------------------|-------------------------------------|-------------------')\n \n for (const org of orgs) {\n const created = org.createdAt ? new Date(org.createdAt).toLocaleDateString() : 'N/A'\n const id = org.id || 'N/A'\n const tenantId = org.tenant?.id || 'N/A'\n const name = (org.name || 'Unnamed').padEnd(23)\n console.log(`${id.padEnd(35)} | ${name} | ${tenantId.padEnd(35)} | ${created}`)\n }\n },\n}\n\nconst listTenants: ModuleCli = {\n command: 'list-tenants',\n async run() {\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n const tenants = await em.find(Tenant, {})\n \n if (tenants.length === 0) {\n console.log('No tenants found')\n return\n }\n \n console.log(`Found ${tenants.length} tenant(s):`)\n console.log('')\n console.log('ID | Name | Created')\n console.log('-------------------------------------|-------------------------|-------------------')\n \n for (const tenant of tenants) {\n const created = tenant.createdAt ? new Date(tenant.createdAt).toLocaleDateString() : 'N/A'\n const id = tenant.id || 'N/A'\n const name = (tenant.name || 'Unnamed').padEnd(23)\n console.log(`${id.padEnd(35)} | ${name} | ${created}`)\n }\n },\n}\n\nconst listUsers: ModuleCli = {\n command: 'list-users',\n async run(rest) {\n const args: Record<string, string> = {}\n for (let i = 0; i < rest.length; i += 2) {\n const k = rest[i]?.replace(/^--/, '')\n const v = rest[i + 1]\n if (k) args[k] = v\n }\n \n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n \n // Build query with optional filters\n const where: any = {}\n if (args.organizationId || args.orgId || args.org) {\n where.organizationId = args.organizationId || args.orgId || args.org\n }\n if (args.tenantId || args.tenant) {\n where.tenantId = args.tenantId || args.tenant\n }\n \n const users = await em.find(User, where)\n \n if (users.length === 0) {\n console.log('No users found')\n return\n }\n \n console.log(`Found ${users.length} user(s):`)\n console.log('')\n console.log('ID | Email | Name | Organization ID | Tenant ID | Roles')\n console.log('-------------------------------------|-------------------------|-------------------------|---------------------|---------------------|-------------------')\n \n for (const user of users) {\n // Get user roles separately\n const userRoles = await findWithDecryption(\n em,\n UserRole,\n { user: user.id },\n { populate: ['role'] },\n { tenantId: user.tenantId ?? null, organizationId: user.organizationId ?? null },\n )\n const roles = userRoles.map((ur: any) => ur.role?.name).filter(Boolean).join(', ') || 'None'\n \n // Get organization and tenant names if IDs exist\n let orgName = 'N/A'\n let tenantName = 'N/A'\n \n if (user.organizationId) {\n const org = await em.findOne(Organization, { id: user.organizationId })\n orgName = org?.name?.substring(0, 19) + '...' || user.organizationId.substring(0, 8) + '...'\n }\n \n if (user.tenantId) {\n const tenant = await em.findOne(Tenant, { id: user.tenantId })\n tenantName = tenant?.name?.substring(0, 19) + '...' || user.tenantId.substring(0, 8) + '...'\n }\n \n const id = user.id || 'N/A'\n const email = (user.email || 'N/A').padEnd(23)\n const name = (user.name || 'Unnamed').padEnd(23)\n \n console.log(`${id.padEnd(35)} | ${email} | ${name} | ${orgName.padEnd(19)} | ${tenantName.padEnd(19)} | ${roles}`)\n }\n },\n}\n\nconst setPassword: ModuleCli = {\n command: 'set-password',\n async run(rest) {\n const args: Record<string, string> = {}\n for (let i = 0; i < rest.length; i += 2) {\n const k = rest[i]?.replace(/^--/, '')\n const v = rest[i + 1]\n if (k) args[k] = v\n }\n \n const email = args.email\n const password = args.password\n \n if (!email || !password) {\n console.error('Usage: mercato auth set-password --email <email> --password <newPassword>')\n return\n }\n if (!ensurePasswordPolicy(password)) return\n \n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n const emailHash = computeEmailHash(email)\n const user = await em.findOne(User, { $or: [{ email }, { emailHash }] })\n \n if (!user) {\n console.error(`User with email \"${email}\" not found`)\n return\n }\n \n user.passwordHash = await hash(password, 10)\n await em.persistAndFlush(user)\n \n console.log(`\u2705 Password updated successfully for user: ${email}`)\n },\n}\n\n// Export the full CLI list\nexport default [addUser, seedRoles, rotateEncryptionKey, addOrganization, setupApp, listOrganizations, listTenants, listUsers, setPassword]\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -13,6 +13,7 @@ import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
13
13
  import { parseBooleanToken } from "@open-mercato/shared/lib/boolean";
14
14
  const DEFAULT_ROLE_NAMES = ["employee", "admin", "superadmin"];
15
15
  const DEMO_SUPERADMIN_EMAIL = "superadmin@acme.com";
16
+ const DEFAULT_DERIVED_EMAIL_DOMAIN = DEMO_SUPERADMIN_EMAIL.split("@")[1] ?? "acme.com";
16
17
  async function ensureRolesInContext(em, roleNames, tenantId) {
17
18
  for (const name of roleNames) {
18
19
  const existing = await em.findOne(Role, { name, tenantId });
@@ -110,11 +111,10 @@ async function setupInitialTenant(em, options) {
110
111
  { email: primaryUser.email, roles: primaryRoles, name: resolvePrimaryName(primaryUser) }
111
112
  ];
112
113
  if (includeDerivedUsers) {
113
- const [, domain] = String(primaryUser.email).split("@");
114
114
  const adminOverride = readEnvValue(DERIVED_EMAIL_ENV.admin);
115
115
  const employeeOverride = readEnvValue(DERIVED_EMAIL_ENV.employee);
116
- const adminEmail = adminOverride ?? (domain ? `admin@${domain}` : "");
117
- const employeeEmail = employeeOverride ?? (domain ? `employee@${domain}` : "");
116
+ const adminEmail = adminOverride ?? `admin@${DEFAULT_DERIVED_EMAIL_DOMAIN}`;
117
+ const employeeEmail = employeeOverride ?? `employee@${DEFAULT_DERIVED_EMAIL_DOMAIN}`;
118
118
  const adminPassword = readEnvValue("OM_INIT_ADMIN_PASSWORD") || "secret";
119
119
  const employeePassword = readEnvValue("OM_INIT_EMPLOYEE_PASSWORD") || "secret";
120
120
  const adminPasswordHash = adminPassword ? await resolvePasswordHash({ email: adminEmail, password: adminPassword }) : null;
@@ -311,6 +311,11 @@ async function ensureDefaultRoleAcls(em, tenantId, modules, options = {}) {
311
311
  if (roleFeatures.admin) adminFeatures.push(...roleFeatures.admin);
312
312
  if (roleFeatures.employee) employeeFeatures.push(...roleFeatures.employee);
313
313
  }
314
+ console.log("\u2705 Seeded default role features", {
315
+ superadmin: superadminFeatures,
316
+ admin: adminFeatures,
317
+ employee: employeeFeatures
318
+ });
314
319
  if (includeSuperadminRole && superadminRole) {
315
320
  await ensureRoleAclFor(em, superadminRole, tenantId, superadminFeatures, { isSuperAdmin: true });
316
321
  }
@@ -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 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 { DEFAULT_ENCRYPTION_MAPS } from '@open-mercato/core/modules/entities/lib/encryptionDefaults'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { 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'\n\nexport type EnsureRolesOptions = {\n roleNames?: string[]\n tenantId?: string | null\n}\n\nasync function ensureRolesInContext(\n em: EntityManager,\n roleNames: string[],\n tenantId: string | null,\n) {\n for (const name of roleNames) {\n const existing = await em.findOne(Role, { name, tenantId })\n if (existing) continue\n if (tenantId !== null) {\n const globalRole = await em.findOne(Role, { name, tenantId: null })\n if (globalRole) {\n globalRole.tenantId = tenantId\n em.persist(globalRole)\n continue\n }\n }\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) ?? null\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 let role = await em.findOne(Role, { name, tenantId: normalizedTenant })\n if (!role && normalizedTenant !== null) {\n role = await em.findOne(Role, { name, tenantId: null })\n }\n return role\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\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\n const mainEmail = primaryUser.email\n const existingUser = await em.findOne(User, { email: mainEmail })\n if (existingUser && failIfUserExists) {\n throw new Error('USER_EXISTS')\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\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 [, domain] = String(primaryUser.email).split('@')\n const adminOverride = readEnvValue(DERIVED_EMAIL_ENV.admin)\n const employeeOverride = readEnvValue(DERIVED_EMAIL_ENV.employee)\n const adminEmail = adminOverride ?? (domain ? `admin@${domain}` : '')\n const employeeEmail = employeeOverride ?? (domain ? `employee@${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 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 DEFAULT_ENCRYPTION_MAPS) {\n const existing = await tem.findOne(EncryptionMap, { entityId: spec.entityId, tenantId: tenant.id, organizationId: organization.id, deletedAt: null })\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 tem.findOne(User, { email: base.email })\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 tem.findOne(UserRole, { user, role })\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 const resolvedModules = options.modules ?? tryGetModules()\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\nasync 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 superadminFeatures: string[] = []\n const adminFeatures: string[] = []\n const employeeFeatures: 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\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\nasync function ensureRoleAclFor(\n em: EntityManager,\n role: Role,\n tenantId: string,\n features: string[],\n options: { isSuperAdmin?: boolean } = {},\n) {\n const existing = await em.findOne(RoleAcl, { role, tenantId })\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.persistAndFlush(acl)\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.persistAndFlush(existing)\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 em.findOne(User, { email: DEMO_SUPERADMIN_EMAIL })\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.persistAndFlush(user)\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 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 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 { DEFAULT_ENCRYPTION_MAPS } from '@open-mercato/core/modules/entities/lib/encryptionDefaults'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { 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 | null\n}\n\nasync function ensureRolesInContext(\n em: EntityManager,\n roleNames: string[],\n tenantId: string | null,\n) {\n for (const name of roleNames) {\n const existing = await em.findOne(Role, { name, tenantId })\n if (existing) continue\n if (tenantId !== null) {\n const globalRole = await em.findOne(Role, { name, tenantId: null })\n if (globalRole) {\n globalRole.tenantId = tenantId\n em.persist(globalRole)\n continue\n }\n }\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) ?? null\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 let role = await em.findOne(Role, { name, tenantId: normalizedTenant })\n if (!role && normalizedTenant !== null) {\n role = await em.findOne(Role, { name, tenantId: null })\n }\n return role\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\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\n const mainEmail = primaryUser.email\n const existingUser = await em.findOne(User, { email: mainEmail })\n if (existingUser && failIfUserExists) {\n throw new Error('USER_EXISTS')\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\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 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 DEFAULT_ENCRYPTION_MAPS) {\n const existing = await tem.findOne(EncryptionMap, { entityId: spec.entityId, tenantId: tenant.id, organizationId: organization.id, deletedAt: null })\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 tem.findOne(User, { email: base.email })\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 tem.findOne(UserRole, { user, role })\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 const resolvedModules = options.modules ?? tryGetModules()\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\nasync 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 superadminFeatures: string[] = []\n const adminFeatures: string[] = []\n const employeeFeatures: 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\n console.log('\u2705 Seeded default role features', {\n superadmin: superadminFeatures,\n admin: adminFeatures,\n employee: employeeFeatures,\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\nasync function ensureRoleAclFor(\n em: EntityManager,\n role: Role,\n tenantId: string,\n features: string[],\n options: { isSuperAdmin?: boolean } = {},\n) {\n const existing = await em.findOne(RoleAcl, { role, tenantId })\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.persistAndFlush(acl)\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.persistAndFlush(existing)\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 em.findOne(User, { email: DEMO_SUPERADMIN_EMAIL })\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.persistAndFlush(user)\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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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,7 @@
1
1
  import * as semver from "semver";
2
+ import { getModules } from "@open-mercato/shared/lib/modules/registry";
3
+ import { Role, RoleAcl } from "@open-mercato/core/modules/auth/data/entities";
4
+ import { normalizeTenantId } from "@open-mercato/core/modules/auth/lib/tenantAccess";
2
5
  function compareVersions(a, b) {
3
6
  const cleanA = semver.valid(semver.coerce(a));
4
7
  const cleanB = semver.valid(semver.coerce(b));
@@ -10,7 +13,187 @@ function compareVersions(a, b) {
10
13
  }
11
14
  return semver.compare(cleanA, cleanB);
12
15
  }
13
- const upgradeActions = [];
16
+ function collectRoleFeatures(modules, moduleIds) {
17
+ const result = {
18
+ superadmin: [],
19
+ admin: [],
20
+ employee: []
21
+ };
22
+ const targetIds = new Set(moduleIds);
23
+ for (const mod of modules) {
24
+ if (!targetIds.has(mod.id)) continue;
25
+ const roleFeatures = mod.setup?.defaultRoleFeatures;
26
+ if (roleFeatures?.superadmin) result.superadmin.push(...roleFeatures.superadmin);
27
+ if (roleFeatures?.admin) result.admin.push(...roleFeatures.admin);
28
+ if (roleFeatures?.employee) result.employee.push(...roleFeatures.employee);
29
+ }
30
+ result.superadmin = Array.from(new Set(result.superadmin));
31
+ result.admin = Array.from(new Set(result.admin));
32
+ result.employee = Array.from(new Set(result.employee));
33
+ return result;
34
+ }
35
+ async function findRoleByName(em, name, tenantId) {
36
+ const normalizedTenant = normalizeTenantId(tenantId ?? null) ?? null;
37
+ let role = await em.findOne(Role, { name, tenantId: normalizedTenant });
38
+ if (!role && normalizedTenant !== null) {
39
+ role = await em.findOne(Role, { name, tenantId: null });
40
+ }
41
+ return role;
42
+ }
43
+ async function ensureRoleAclFor(em, role, tenantId, features, options = {}) {
44
+ const existing = await em.findOne(RoleAcl, { role, tenantId });
45
+ if (!existing) {
46
+ const acl = em.create(RoleAcl, {
47
+ role,
48
+ tenantId,
49
+ featuresJson: features,
50
+ isSuperAdmin: !!options.isSuperAdmin,
51
+ createdAt: /* @__PURE__ */ new Date()
52
+ });
53
+ await em.persistAndFlush(acl);
54
+ return;
55
+ }
56
+ const currentFeatures = Array.isArray(existing.featuresJson) ? existing.featuresJson : [];
57
+ const merged = Array.from(/* @__PURE__ */ new Set([...currentFeatures, ...features]));
58
+ const changed = merged.length !== currentFeatures.length || merged.some((value, index) => value !== currentFeatures[index]);
59
+ if (changed) existing.featuresJson = merged;
60
+ if (options.isSuperAdmin && !existing.isSuperAdmin) {
61
+ existing.isSuperAdmin = true;
62
+ }
63
+ if (changed || options.isSuperAdmin) {
64
+ await em.persistAndFlush(existing);
65
+ }
66
+ }
67
+ async function ensureRoleAclsForModules(em, tenantId, moduleIds) {
68
+ const modules = getModules();
69
+ const roleFeatures = collectRoleFeatures(modules, moduleIds);
70
+ if (!roleFeatures.superadmin.length && !roleFeatures.admin.length && !roleFeatures.employee.length) {
71
+ return;
72
+ }
73
+ const normalizedTenant = normalizeTenantId(tenantId) ?? null;
74
+ const [superadminRole, adminRole, employeeRole] = await Promise.all([
75
+ findRoleByName(em, "superadmin", normalizedTenant),
76
+ findRoleByName(em, "admin", normalizedTenant),
77
+ findRoleByName(em, "employee", normalizedTenant)
78
+ ]);
79
+ if (superadminRole && roleFeatures.superadmin.length) {
80
+ await ensureRoleAclFor(em, superadminRole, tenantId, roleFeatures.superadmin, { isSuperAdmin: true });
81
+ }
82
+ if (adminRole && roleFeatures.admin.length) {
83
+ await ensureRoleAclFor(em, adminRole, tenantId, roleFeatures.admin);
84
+ }
85
+ if (employeeRole && roleFeatures.employee.length) {
86
+ await ensureRoleAclFor(em, employeeRole, tenantId, roleFeatures.employee);
87
+ }
88
+ }
89
+ async function safeImport(label, loader) {
90
+ try {
91
+ return await loader();
92
+ } catch (error) {
93
+ console.warn(`[upgrade-actions] Skipping ${label} because module import failed.`, error);
94
+ return null;
95
+ }
96
+ }
97
+ const upgradeActions = [
98
+ {
99
+ id: "configs.upgrades.catalog.examples",
100
+ version: "0.3.4",
101
+ messageKey: "upgrades.v034.message",
102
+ ctaKey: "upgrades.v034.cta",
103
+ successKey: "upgrades.v034.success",
104
+ loadingKey: "upgrades.v034.loading",
105
+ async run(ctx) {
106
+ const catalogSeeds = await safeImport(
107
+ "catalog examples",
108
+ () => import("@open-mercato/core/modules/catalog/lib/seeds")
109
+ );
110
+ if (!catalogSeeds?.installExampleCatalogData) return;
111
+ await catalogSeeds.installExampleCatalogData(
112
+ ctx.container,
113
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId },
114
+ ctx.em
115
+ );
116
+ }
117
+ },
118
+ {
119
+ id: "configs.upgrades.sales.examples",
120
+ version: "0.3.6",
121
+ messageKey: "upgrades.v036.message",
122
+ ctaKey: "upgrades.v036.cta",
123
+ successKey: "upgrades.v036.success",
124
+ loadingKey: "upgrades.v036.loading",
125
+ async run(ctx) {
126
+ const salesSeeds = await safeImport(
127
+ "sales examples",
128
+ () => import("@open-mercato/core/modules/sales/seed/examples")
129
+ );
130
+ if (!salesSeeds?.seedSalesExamples) return;
131
+ await salesSeeds.seedSalesExamples(ctx.em, ctx.container, {
132
+ tenantId: ctx.tenantId,
133
+ organizationId: ctx.organizationId
134
+ });
135
+ }
136
+ },
137
+ {
138
+ id: "configs.upgrades.examples.currencies_workflows",
139
+ version: "0.3.13",
140
+ messageKey: "upgrades.v0313.message",
141
+ ctaKey: "upgrades.v0313.cta",
142
+ successKey: "upgrades.v0313.success",
143
+ loadingKey: "upgrades.v0313.loading",
144
+ async run(ctx) {
145
+ const currenciesSeeds = await safeImport(
146
+ "currencies examples",
147
+ () => import("@open-mercato/core/modules/currencies/lib/seeds")
148
+ );
149
+ const workflowsSeeds = await safeImport(
150
+ "workflows examples",
151
+ () => import("@open-mercato/core/modules/workflows/lib/seeds")
152
+ );
153
+ const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId };
154
+ if (currenciesSeeds?.seedExampleCurrencies) {
155
+ await currenciesSeeds.seedExampleCurrencies(ctx.em, scope);
156
+ }
157
+ if (workflowsSeeds?.seedExampleWorkflows) {
158
+ await workflowsSeeds.seedExampleWorkflows(ctx.em, scope);
159
+ }
160
+ await ensureRoleAclsForModules(ctx.em, ctx.tenantId, ["currencies", "workflows"]);
161
+ }
162
+ },
163
+ {
164
+ id: "configs.upgrades.examples.planner_staff_resources",
165
+ version: "0.4.1",
166
+ messageKey: "upgrades.v041.message",
167
+ ctaKey: "upgrades.v041.cta",
168
+ successKey: "upgrades.v041.success",
169
+ loadingKey: "upgrades.v041.loading",
170
+ async run(ctx) {
171
+ const plannerSeeds = await safeImport(
172
+ "planner examples",
173
+ () => import("@open-mercato/core/modules/planner/lib/seeds")
174
+ );
175
+ const staffSeeds = await safeImport(
176
+ "staff examples",
177
+ () => import("@open-mercato/core/modules/staff/lib/seeds")
178
+ );
179
+ const resourcesSeeds = await safeImport(
180
+ "resources examples",
181
+ () => import("@open-mercato/core/modules/resources/lib/seeds")
182
+ );
183
+ const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId };
184
+ if (plannerSeeds?.seedPlannerAvailabilityRuleSetDefaults) {
185
+ await plannerSeeds.seedPlannerAvailabilityRuleSetDefaults(ctx.em, scope);
186
+ }
187
+ if (staffSeeds?.seedStaffTeamExamples) {
188
+ await staffSeeds.seedStaffTeamExamples(ctx.em, scope);
189
+ }
190
+ if (resourcesSeeds?.seedResourcesResourceExamples) {
191
+ await resourcesSeeds.seedResourcesResourceExamples(ctx.em, scope);
192
+ }
193
+ await ensureRoleAclsForModules(ctx.em, ctx.tenantId, ["planner", "staff", "resources"]);
194
+ }
195
+ }
196
+ ];
14
197
  function actionsUpToVersion(version) {
15
198
  return upgradeActions.filter((action) => compareVersions(action.version, version) <= 0).sort((a, b) => compareVersions(a.version, b.version) || a.id.localeCompare(b.id));
16
199
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/configs/lib/upgrade-actions.ts"],
4
- "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport * as semver from 'semver'\n\nexport type UpgradeActionContext = {\n tenantId: string\n organizationId: string\n container: AwilixContainer\n em: EntityManager\n}\n\nexport type UpgradeActionDefinition = {\n id: string\n version: string\n messageKey: string\n ctaKey: string\n successKey: string\n loadingKey?: string\n run: (ctx: UpgradeActionContext) => Promise<void>\n}\n\n/**\n * Compare two semantic version strings.\n * Uses the semver library for robust version comparison.\n * Returns negative if a < b, positive if a > b, 0 if equal.\n * Throws an error if either version string is invalid.\n */\nexport function compareVersions(a: string, b: string): number {\n const cleanA = semver.valid(semver.coerce(a))\n const cleanB = semver.valid(semver.coerce(b))\n if (!cleanA) {\n throw new Error(`Invalid version string: \"${a}\". Expected a valid semver format (e.g., \"1.2.3\").`)\n }\n if (!cleanB) {\n throw new Error(`Invalid version string: \"${b}\". Expected a valid semver format (e.g., \"1.2.3\").`)\n }\n return semver.compare(cleanA, cleanB)\n}\n\nexport const upgradeActions: UpgradeActionDefinition[] = []\n\nexport function actionsUpToVersion(version: string): UpgradeActionDefinition[] {\n return upgradeActions\n .filter((action) => compareVersions(action.version, version) <= 0)\n .sort((a, b) => compareVersions(a.version, b.version) || a.id.localeCompare(b.id))\n}\n\nexport function findUpgradeAction(actionId: string, maxVersion: string): UpgradeActionDefinition | undefined {\n const matches = actionsUpToVersion(maxVersion).filter((action) => action.id === actionId)\n if (!matches.length) return undefined\n return matches[matches.length - 1]\n}\n"],
5
- "mappings": "AAEA,YAAY,YAAY;AAyBjB,SAAS,gBAAgB,GAAW,GAAmB;AAC5D,QAAM,SAAS,OAAO,MAAM,OAAO,OAAO,CAAC,CAAC;AAC5C,QAAM,SAAS,OAAO,MAAM,OAAO,OAAO,CAAC,CAAC;AAC5C,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,4BAA4B,CAAC,oDAAoD;AAAA,EACnG;AACA,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,4BAA4B,CAAC,oDAAoD;AAAA,EACnG;AACA,SAAO,OAAO,QAAQ,QAAQ,MAAM;AACtC;AAEO,MAAM,iBAA4C,CAAC;AAEnD,SAAS,mBAAmB,SAA4C;AAC7E,SAAO,eACJ,OAAO,CAAC,WAAW,gBAAgB,OAAO,SAAS,OAAO,KAAK,CAAC,EAChE,KAAK,CAAC,GAAG,MAAM,gBAAgB,EAAE,SAAS,EAAE,OAAO,KAAK,EAAE,GAAG,cAAc,EAAE,EAAE,CAAC;AACrF;AAEO,SAAS,kBAAkB,UAAkB,YAAyD;AAC3G,QAAM,UAAU,mBAAmB,UAAU,EAAE,OAAO,CAAC,WAAW,OAAO,OAAO,QAAQ;AACxF,MAAI,CAAC,QAAQ,OAAQ,QAAO;AAC5B,SAAO,QAAQ,QAAQ,SAAS,CAAC;AACnC;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport * as semver from 'semver'\nimport { getModules } from '@open-mercato/shared/lib/modules/registry'\nimport type { Module } from '@open-mercato/shared/modules/registry'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { Role, RoleAcl } from '@open-mercato/core/modules/auth/data/entities'\nimport { normalizeTenantId } from '@open-mercato/core/modules/auth/lib/tenantAccess'\n\nexport type UpgradeActionContext = {\n tenantId: string\n organizationId: string\n container: AppContainer\n em: EntityManager\n}\n\nexport type UpgradeActionDefinition = {\n id: string\n version: string\n messageKey: string\n ctaKey: string\n successKey: string\n loadingKey?: string\n run: (ctx: UpgradeActionContext) => Promise<void>\n}\n\n/**\n * Compare two semantic version strings.\n * Uses the semver library for robust version comparison.\n * Returns negative if a < b, positive if a > b, 0 if equal.\n * Throws an error if either version string is invalid.\n */\nexport function compareVersions(a: string, b: string): number {\n const cleanA = semver.valid(semver.coerce(a))\n const cleanB = semver.valid(semver.coerce(b))\n if (!cleanA) {\n throw new Error(`Invalid version string: \"${a}\". Expected a valid semver format (e.g., \"1.2.3\").`)\n }\n if (!cleanB) {\n throw new Error(`Invalid version string: \"${b}\". Expected a valid semver format (e.g., \"1.2.3\").`)\n }\n return semver.compare(cleanA, cleanB)\n}\n\ntype RoleName = 'superadmin' | 'admin' | 'employee'\n\nfunction collectRoleFeatures(modules: Module[], moduleIds: string[]): Record<RoleName, string[]> {\n const result: Record<RoleName, string[]> = {\n superadmin: [],\n admin: [],\n employee: [],\n }\n const targetIds = new Set(moduleIds)\n for (const mod of modules) {\n if (!targetIds.has(mod.id)) continue\n const roleFeatures = mod.setup?.defaultRoleFeatures\n if (roleFeatures?.superadmin) result.superadmin.push(...roleFeatures.superadmin)\n if (roleFeatures?.admin) result.admin.push(...roleFeatures.admin)\n if (roleFeatures?.employee) result.employee.push(...roleFeatures.employee)\n }\n result.superadmin = Array.from(new Set(result.superadmin))\n result.admin = Array.from(new Set(result.admin))\n result.employee = Array.from(new Set(result.employee))\n return result\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 let role = await em.findOne(Role, { name, tenantId: normalizedTenant })\n if (!role && normalizedTenant !== null) {\n role = await em.findOne(Role, { name, tenantId: null })\n }\n return role\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 em.findOne(RoleAcl, { role, tenantId })\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.persistAndFlush(acl)\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.persistAndFlush(existing)\n }\n}\n\nasync function ensureRoleAclsForModules(\n em: EntityManager,\n tenantId: string,\n moduleIds: string[],\n) {\n const modules = getModules()\n const roleFeatures = collectRoleFeatures(modules, moduleIds)\n if (!roleFeatures.superadmin.length && !roleFeatures.admin.length && !roleFeatures.employee.length) {\n return\n }\n const normalizedTenant = normalizeTenantId(tenantId) ?? null\n const [superadminRole, adminRole, employeeRole] = await Promise.all([\n findRoleByName(em, 'superadmin', normalizedTenant),\n findRoleByName(em, 'admin', normalizedTenant),\n findRoleByName(em, 'employee', normalizedTenant),\n ])\n if (superadminRole && roleFeatures.superadmin.length) {\n await ensureRoleAclFor(em, superadminRole, tenantId, roleFeatures.superadmin, { isSuperAdmin: true })\n }\n if (adminRole && roleFeatures.admin.length) {\n await ensureRoleAclFor(em, adminRole, tenantId, roleFeatures.admin)\n }\n if (employeeRole && roleFeatures.employee.length) {\n await ensureRoleAclFor(em, employeeRole, tenantId, roleFeatures.employee)\n }\n}\n\nasync function safeImport<T>(label: string, loader: () => Promise<T>): Promise<T | null> {\n try {\n return await loader()\n } catch (error) {\n console.warn(`[upgrade-actions] Skipping ${label} because module import failed.`, error)\n return null\n }\n}\n\nexport const upgradeActions: UpgradeActionDefinition[] = [\n {\n id: 'configs.upgrades.catalog.examples',\n version: '0.3.4',\n messageKey: 'upgrades.v034.message',\n ctaKey: 'upgrades.v034.cta',\n successKey: 'upgrades.v034.success',\n loadingKey: 'upgrades.v034.loading',\n async run(ctx) {\n const catalogSeeds = await safeImport('catalog examples', () =>\n import('@open-mercato/core/modules/catalog/lib/seeds')\n )\n if (!catalogSeeds?.installExampleCatalogData) return\n await catalogSeeds.installExampleCatalogData(\n ctx.container,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n ctx.em,\n )\n },\n },\n {\n id: 'configs.upgrades.sales.examples',\n version: '0.3.6',\n messageKey: 'upgrades.v036.message',\n ctaKey: 'upgrades.v036.cta',\n successKey: 'upgrades.v036.success',\n loadingKey: 'upgrades.v036.loading',\n async run(ctx) {\n const salesSeeds = await safeImport('sales examples', () =>\n import('@open-mercato/core/modules/sales/seed/examples')\n )\n if (!salesSeeds?.seedSalesExamples) return\n await salesSeeds.seedSalesExamples(ctx.em, ctx.container, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n },\n },\n {\n id: 'configs.upgrades.examples.currencies_workflows',\n version: '0.3.13',\n messageKey: 'upgrades.v0313.message',\n ctaKey: 'upgrades.v0313.cta',\n successKey: 'upgrades.v0313.success',\n loadingKey: 'upgrades.v0313.loading',\n async run(ctx) {\n const currenciesSeeds = await safeImport('currencies examples', () =>\n import('@open-mercato/core/modules/currencies/lib/seeds')\n )\n const workflowsSeeds = await safeImport('workflows examples', () =>\n import('@open-mercato/core/modules/workflows/lib/seeds')\n )\n const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n if (currenciesSeeds?.seedExampleCurrencies) {\n await currenciesSeeds.seedExampleCurrencies(ctx.em, scope)\n }\n if (workflowsSeeds?.seedExampleWorkflows) {\n await workflowsSeeds.seedExampleWorkflows(ctx.em, scope)\n }\n await ensureRoleAclsForModules(ctx.em, ctx.tenantId, ['currencies', 'workflows'])\n },\n },\n {\n id: 'configs.upgrades.examples.planner_staff_resources',\n version: '0.4.1',\n messageKey: 'upgrades.v041.message',\n ctaKey: 'upgrades.v041.cta',\n successKey: 'upgrades.v041.success',\n loadingKey: 'upgrades.v041.loading',\n async run(ctx) {\n const plannerSeeds = await safeImport('planner examples', () =>\n import('@open-mercato/core/modules/planner/lib/seeds')\n )\n const staffSeeds = await safeImport('staff examples', () =>\n import('@open-mercato/core/modules/staff/lib/seeds')\n )\n const resourcesSeeds = await safeImport('resources examples', () =>\n import('@open-mercato/core/modules/resources/lib/seeds')\n )\n const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n if (plannerSeeds?.seedPlannerAvailabilityRuleSetDefaults) {\n await plannerSeeds.seedPlannerAvailabilityRuleSetDefaults(ctx.em, scope)\n }\n if (staffSeeds?.seedStaffTeamExamples) {\n await staffSeeds.seedStaffTeamExamples(ctx.em, scope)\n }\n if (resourcesSeeds?.seedResourcesResourceExamples) {\n await resourcesSeeds.seedResourcesResourceExamples(ctx.em, scope)\n }\n await ensureRoleAclsForModules(ctx.em, ctx.tenantId, ['planner', 'staff', 'resources'])\n },\n },\n]\n\nexport function actionsUpToVersion(version: string): UpgradeActionDefinition[] {\n return upgradeActions\n .filter((action) => compareVersions(action.version, version) <= 0)\n .sort((a, b) => compareVersions(a.version, b.version) || a.id.localeCompare(b.id))\n}\n\nexport function findUpgradeAction(actionId: string, maxVersion: string): UpgradeActionDefinition | undefined {\n const matches = actionsUpToVersion(maxVersion).filter((action) => action.id === actionId)\n if (!matches.length) return undefined\n return matches[matches.length - 1]\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.4.2-canary-e2aeb1a7bf",
3
+ "version": "0.4.2-canary-601795d9cd",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -207,7 +207,7 @@
207
207
  }
208
208
  },
209
209
  "dependencies": {
210
- "@open-mercato/shared": "0.4.2-canary-e2aeb1a7bf",
210
+ "@open-mercato/shared": "0.4.2-canary-601795d9cd",
211
211
  "@xyflow/react": "^12.6.0",
212
212
  "date-fns": "^4.1.0",
213
213
  "date-fns-tz": "^3.2.0"
@@ -18,6 +18,7 @@ import type { KmsService, TenantDek } from '@open-mercato/shared/lib/encryption/
18
18
  import crypto from 'node:crypto'
19
19
  import { formatPasswordRequirements, getPasswordPolicy, validatePassword } from '@open-mercato/shared/lib/auth/passwordPolicy'
20
20
  import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
21
+ import { getCliModules } from '@open-mercato/shared/modules/registry'
21
22
 
22
23
  const addUser: ModuleCli = {
23
24
  command: 'add-user',
@@ -444,6 +445,7 @@ const setupApp: ModuleCli = {
444
445
  roleNames,
445
446
  primaryUser: { email, password, confirm: true },
446
447
  includeDerivedUsers: true,
448
+ modules: getCliModules(),
447
449
  })
448
450
 
449
451
  if (result.reusedExistingUser) {
@@ -16,6 +16,7 @@ import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
16
16
 
17
17
  const DEFAULT_ROLE_NAMES = ['employee', 'admin', 'superadmin'] as const
18
18
  const DEMO_SUPERADMIN_EMAIL = 'superadmin@acme.com'
19
+ const DEFAULT_DERIVED_EMAIL_DOMAIN = DEMO_SUPERADMIN_EMAIL.split('@')[1] ?? 'acme.com'
19
20
 
20
21
  export type EnsureRolesOptions = {
21
22
  roleNames?: string[]
@@ -183,11 +184,10 @@ export async function setupInitialTenant(
183
184
  { email: primaryUser.email, roles: primaryRoles, name: resolvePrimaryName(primaryUser) },
184
185
  ]
185
186
  if (includeDerivedUsers) {
186
- const [, domain] = String(primaryUser.email).split('@')
187
187
  const adminOverride = readEnvValue(DERIVED_EMAIL_ENV.admin)
188
188
  const employeeOverride = readEnvValue(DERIVED_EMAIL_ENV.employee)
189
- const adminEmail = adminOverride ?? (domain ? `admin@${domain}` : '')
190
- const employeeEmail = employeeOverride ?? (domain ? `employee@${domain}` : '')
189
+ const adminEmail = adminOverride ?? `admin@${DEFAULT_DERIVED_EMAIL_DOMAIN}`
190
+ const employeeEmail = employeeOverride ?? `employee@${DEFAULT_DERIVED_EMAIL_DOMAIN}`
191
191
  const adminPassword = readEnvValue('OM_INIT_ADMIN_PASSWORD') || 'secret'
192
192
  const employeePassword = readEnvValue('OM_INIT_EMPLOYEE_PASSWORD') || 'secret'
193
193
  const adminPasswordHash = adminPassword ? await resolvePasswordHash({ email: adminEmail, password: adminPassword }) : null
@@ -423,6 +423,12 @@ async function ensureDefaultRoleAcls(
423
423
  if (roleFeatures.employee) employeeFeatures.push(...roleFeatures.employee)
424
424
  }
425
425
 
426
+ console.log('✅ Seeded default role features', {
427
+ superadmin: superadminFeatures,
428
+ admin: adminFeatures,
429
+ employee: employeeFeatures,
430
+ })
431
+
426
432
  if (includeSuperadminRole && superadminRole) {
427
433
  await ensureRoleAclFor(em, superadminRole, tenantId, superadminFeatures, { isSuperAdmin: true })
428
434
  }
@@ -1,11 +1,15 @@
1
- import type { AwilixContainer } from 'awilix'
2
1
  import type { EntityManager } from '@mikro-orm/postgresql'
3
2
  import * as semver from 'semver'
3
+ import { getModules } from '@open-mercato/shared/lib/modules/registry'
4
+ import type { Module } from '@open-mercato/shared/modules/registry'
5
+ import type { AppContainer } from '@open-mercato/shared/lib/di/container'
6
+ import { Role, RoleAcl } from '@open-mercato/core/modules/auth/data/entities'
7
+ import { normalizeTenantId } from '@open-mercato/core/modules/auth/lib/tenantAccess'
4
8
 
5
9
  export type UpgradeActionContext = {
6
10
  tenantId: string
7
11
  organizationId: string
8
- container: AwilixContainer
12
+ container: AppContainer
9
13
  em: EntityManager
10
14
  }
11
15
 
@@ -37,7 +41,203 @@ export function compareVersions(a: string, b: string): number {
37
41
  return semver.compare(cleanA, cleanB)
38
42
  }
39
43
 
40
- export const upgradeActions: UpgradeActionDefinition[] = []
44
+ type RoleName = 'superadmin' | 'admin' | 'employee'
45
+
46
+ function collectRoleFeatures(modules: Module[], moduleIds: string[]): Record<RoleName, string[]> {
47
+ const result: Record<RoleName, string[]> = {
48
+ superadmin: [],
49
+ admin: [],
50
+ employee: [],
51
+ }
52
+ const targetIds = new Set(moduleIds)
53
+ for (const mod of modules) {
54
+ if (!targetIds.has(mod.id)) continue
55
+ const roleFeatures = mod.setup?.defaultRoleFeatures
56
+ if (roleFeatures?.superadmin) result.superadmin.push(...roleFeatures.superadmin)
57
+ if (roleFeatures?.admin) result.admin.push(...roleFeatures.admin)
58
+ if (roleFeatures?.employee) result.employee.push(...roleFeatures.employee)
59
+ }
60
+ result.superadmin = Array.from(new Set(result.superadmin))
61
+ result.admin = Array.from(new Set(result.admin))
62
+ result.employee = Array.from(new Set(result.employee))
63
+ return result
64
+ }
65
+
66
+ async function findRoleByName(
67
+ em: EntityManager,
68
+ name: string,
69
+ tenantId: string | null,
70
+ ): Promise<Role | null> {
71
+ const normalizedTenant = normalizeTenantId(tenantId ?? null) ?? null
72
+ let role = await em.findOne(Role, { name, tenantId: normalizedTenant })
73
+ if (!role && normalizedTenant !== null) {
74
+ role = await em.findOne(Role, { name, tenantId: null })
75
+ }
76
+ return role
77
+ }
78
+
79
+ async function ensureRoleAclFor(
80
+ em: EntityManager,
81
+ role: Role,
82
+ tenantId: string,
83
+ features: string[],
84
+ options: { isSuperAdmin?: boolean } = {},
85
+ ) {
86
+ const existing = await em.findOne(RoleAcl, { role, tenantId })
87
+ if (!existing) {
88
+ const acl = em.create(RoleAcl, {
89
+ role,
90
+ tenantId,
91
+ featuresJson: features,
92
+ isSuperAdmin: !!options.isSuperAdmin,
93
+ createdAt: new Date(),
94
+ })
95
+ await em.persistAndFlush(acl)
96
+ return
97
+ }
98
+ const currentFeatures = Array.isArray(existing.featuresJson) ? existing.featuresJson : []
99
+ const merged = Array.from(new Set([...currentFeatures, ...features]))
100
+ const changed =
101
+ merged.length !== currentFeatures.length ||
102
+ merged.some((value, index) => value !== currentFeatures[index])
103
+ if (changed) existing.featuresJson = merged
104
+ if (options.isSuperAdmin && !existing.isSuperAdmin) {
105
+ existing.isSuperAdmin = true
106
+ }
107
+ if (changed || options.isSuperAdmin) {
108
+ await em.persistAndFlush(existing)
109
+ }
110
+ }
111
+
112
+ async function ensureRoleAclsForModules(
113
+ em: EntityManager,
114
+ tenantId: string,
115
+ moduleIds: string[],
116
+ ) {
117
+ const modules = getModules()
118
+ const roleFeatures = collectRoleFeatures(modules, moduleIds)
119
+ if (!roleFeatures.superadmin.length && !roleFeatures.admin.length && !roleFeatures.employee.length) {
120
+ return
121
+ }
122
+ const normalizedTenant = normalizeTenantId(tenantId) ?? null
123
+ const [superadminRole, adminRole, employeeRole] = await Promise.all([
124
+ findRoleByName(em, 'superadmin', normalizedTenant),
125
+ findRoleByName(em, 'admin', normalizedTenant),
126
+ findRoleByName(em, 'employee', normalizedTenant),
127
+ ])
128
+ if (superadminRole && roleFeatures.superadmin.length) {
129
+ await ensureRoleAclFor(em, superadminRole, tenantId, roleFeatures.superadmin, { isSuperAdmin: true })
130
+ }
131
+ if (adminRole && roleFeatures.admin.length) {
132
+ await ensureRoleAclFor(em, adminRole, tenantId, roleFeatures.admin)
133
+ }
134
+ if (employeeRole && roleFeatures.employee.length) {
135
+ await ensureRoleAclFor(em, employeeRole, tenantId, roleFeatures.employee)
136
+ }
137
+ }
138
+
139
+ async function safeImport<T>(label: string, loader: () => Promise<T>): Promise<T | null> {
140
+ try {
141
+ return await loader()
142
+ } catch (error) {
143
+ console.warn(`[upgrade-actions] Skipping ${label} because module import failed.`, error)
144
+ return null
145
+ }
146
+ }
147
+
148
+ export const upgradeActions: UpgradeActionDefinition[] = [
149
+ {
150
+ id: 'configs.upgrades.catalog.examples',
151
+ version: '0.3.4',
152
+ messageKey: 'upgrades.v034.message',
153
+ ctaKey: 'upgrades.v034.cta',
154
+ successKey: 'upgrades.v034.success',
155
+ loadingKey: 'upgrades.v034.loading',
156
+ async run(ctx) {
157
+ const catalogSeeds = await safeImport('catalog examples', () =>
158
+ import('@open-mercato/core/modules/catalog/lib/seeds')
159
+ )
160
+ if (!catalogSeeds?.installExampleCatalogData) return
161
+ await catalogSeeds.installExampleCatalogData(
162
+ ctx.container,
163
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId },
164
+ ctx.em,
165
+ )
166
+ },
167
+ },
168
+ {
169
+ id: 'configs.upgrades.sales.examples',
170
+ version: '0.3.6',
171
+ messageKey: 'upgrades.v036.message',
172
+ ctaKey: 'upgrades.v036.cta',
173
+ successKey: 'upgrades.v036.success',
174
+ loadingKey: 'upgrades.v036.loading',
175
+ async run(ctx) {
176
+ const salesSeeds = await safeImport('sales examples', () =>
177
+ import('@open-mercato/core/modules/sales/seed/examples')
178
+ )
179
+ if (!salesSeeds?.seedSalesExamples) return
180
+ await salesSeeds.seedSalesExamples(ctx.em, ctx.container, {
181
+ tenantId: ctx.tenantId,
182
+ organizationId: ctx.organizationId,
183
+ })
184
+ },
185
+ },
186
+ {
187
+ id: 'configs.upgrades.examples.currencies_workflows',
188
+ version: '0.3.13',
189
+ messageKey: 'upgrades.v0313.message',
190
+ ctaKey: 'upgrades.v0313.cta',
191
+ successKey: 'upgrades.v0313.success',
192
+ loadingKey: 'upgrades.v0313.loading',
193
+ async run(ctx) {
194
+ const currenciesSeeds = await safeImport('currencies examples', () =>
195
+ import('@open-mercato/core/modules/currencies/lib/seeds')
196
+ )
197
+ const workflowsSeeds = await safeImport('workflows examples', () =>
198
+ import('@open-mercato/core/modules/workflows/lib/seeds')
199
+ )
200
+ const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
201
+ if (currenciesSeeds?.seedExampleCurrencies) {
202
+ await currenciesSeeds.seedExampleCurrencies(ctx.em, scope)
203
+ }
204
+ if (workflowsSeeds?.seedExampleWorkflows) {
205
+ await workflowsSeeds.seedExampleWorkflows(ctx.em, scope)
206
+ }
207
+ await ensureRoleAclsForModules(ctx.em, ctx.tenantId, ['currencies', 'workflows'])
208
+ },
209
+ },
210
+ {
211
+ id: 'configs.upgrades.examples.planner_staff_resources',
212
+ version: '0.4.1',
213
+ messageKey: 'upgrades.v041.message',
214
+ ctaKey: 'upgrades.v041.cta',
215
+ successKey: 'upgrades.v041.success',
216
+ loadingKey: 'upgrades.v041.loading',
217
+ async run(ctx) {
218
+ const plannerSeeds = await safeImport('planner examples', () =>
219
+ import('@open-mercato/core/modules/planner/lib/seeds')
220
+ )
221
+ const staffSeeds = await safeImport('staff examples', () =>
222
+ import('@open-mercato/core/modules/staff/lib/seeds')
223
+ )
224
+ const resourcesSeeds = await safeImport('resources examples', () =>
225
+ import('@open-mercato/core/modules/resources/lib/seeds')
226
+ )
227
+ const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
228
+ if (plannerSeeds?.seedPlannerAvailabilityRuleSetDefaults) {
229
+ await plannerSeeds.seedPlannerAvailabilityRuleSetDefaults(ctx.em, scope)
230
+ }
231
+ if (staffSeeds?.seedStaffTeamExamples) {
232
+ await staffSeeds.seedStaffTeamExamples(ctx.em, scope)
233
+ }
234
+ if (resourcesSeeds?.seedResourcesResourceExamples) {
235
+ await resourcesSeeds.seedResourcesResourceExamples(ctx.em, scope)
236
+ }
237
+ await ensureRoleAclsForModules(ctx.em, ctx.tenantId, ['planner', 'staff', 'resources'])
238
+ },
239
+ },
240
+ ]
41
241
 
42
242
  export function actionsUpToVersion(version: string): UpgradeActionDefinition[] {
43
243
  return upgradeActions
@@ -799,6 +799,8 @@
799
799
  "staff.teams.tabs.members": "Teammitglieder",
800
800
  "staff.notifications.leaveRequest.pending.title": "Urlaubsantrag ausstehend",
801
801
  "staff.notifications.leaveRequest.pending.body": "{memberName} hat Urlaub vom {startDate} bis {endDate} beantragt",
802
+ "staff.notifications.leaveRequest.approved.title": "Urlaubsantrag genehmigt",
803
+ "staff.notifications.leaveRequest.approved.body": "Dein Urlaubsantrag vom {startDate} bis {endDate} wurde genehmigt",
802
804
  "staff.notifications.leaveRequest.actions.approve": "Genehmigen",
803
805
  "staff.notifications.leaveRequest.actions.reject": "Ablehnen",
804
806
  "staff.leaveRequests.page.title": "Urlaubsantr\u00e4ge",
@@ -799,6 +799,8 @@
799
799
  "staff.teams.tabs.members": "Miembros del equipo",
800
800
  "staff.notifications.leaveRequest.pending.title": "Solicitud de ausencia pendiente",
801
801
  "staff.notifications.leaveRequest.pending.body": "{memberName} ha solicitado ausencia del {startDate} al {endDate}",
802
+ "staff.notifications.leaveRequest.approved.title": "Solicitud de ausencia aprobada",
803
+ "staff.notifications.leaveRequest.approved.body": "Tu solicitud de ausencia del {startDate} al {endDate} ha sido aprobada",
802
804
  "staff.notifications.leaveRequest.actions.approve": "Aprobar",
803
805
  "staff.notifications.leaveRequest.actions.reject": "Rechazar",
804
806
  "staff.leaveRequests.page.title": "Solicitudes de ausencia",
@@ -799,6 +799,8 @@
799
799
  "staff.teams.tabs.members": "Cz\u0142onkowie zespo\u0142u",
800
800
  "staff.notifications.leaveRequest.pending.title": "Wniosek urlopowy oczekuje",
801
801
  "staff.notifications.leaveRequest.pending.body": "{memberName} zło\u017cył wniosek o urlop od {startDate} do {endDate}",
802
+ "staff.notifications.leaveRequest.approved.title": "Wniosek urlopowy zatwierdzony",
803
+ "staff.notifications.leaveRequest.approved.body": "Tw\u00f3j wniosek urlopowy od {startDate} do {endDate} zosta\u0142 zatwierdzony",
802
804
  "staff.notifications.leaveRequest.actions.approve": "Zatwierd\u017a",
803
805
  "staff.notifications.leaveRequest.actions.reject": "Odrzu\u0107",
804
806
  "staff.leaveRequests.page.title": "Wnioski urlopowe",
@@ -746,7 +746,8 @@ describe('Transition Handler (Unit Tests)', () => {
746
746
  fromStepId: 'step-1',
747
747
  toStepId: 'step-2',
748
748
  }),
749
- })
749
+ }),
750
+ expect.objectContaining({ eventBus: undefined })
750
751
  )
751
752
  })
752
753
 
@@ -790,7 +791,8 @@ describe('Transition Handler (Unit Tests)', () => {
790
791
  expect.objectContaining({
791
792
  entityType: 'workflow:simple-approval:transition',
792
793
  eventType: 'post_transition',
793
- })
794
+ }),
795
+ expect.objectContaining({ eventBus: undefined })
794
796
  )
795
797
  })
796
798
 
@@ -838,7 +840,8 @@ describe('Transition Handler (Unit Tests)', () => {
838
840
  triggerData,
839
841
  }),
840
842
  executedBy: 'user-123',
841
- })
843
+ }),
844
+ expect.objectContaining({ eventBus: undefined })
842
845
  )
843
846
  })
844
847
  })