@open-mercato/core 0.6.8-develop.6931.1.4a1e1c786b → 0.6.8-develop.6941.1.3c26284542
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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/helpers/integration/dbFixtures.js.map +2 -2
- package/dist/modules/auth/lib/setup-app.js +3 -2
- package/dist/modules/auth/lib/setup-app.js.map +2 -2
- package/dist/modules/catalog/widgets/injection-table.js +5 -4
- package/dist/modules/catalog/widgets/injection-table.js.map +2 -2
- package/dist/modules/currencies/di.js +2 -0
- package/dist/modules/currencies/di.js.map +2 -2
- package/dist/modules/currencies/services/providers/registry.js +31 -0
- package/dist/modules/currencies/services/providers/registry.js.map +7 -0
- package/dist/modules/directory/api/organization-switcher/route.js +7 -4
- package/dist/modules/directory/api/organization-switcher/route.js.map +2 -2
- package/dist/modules/entities/api/encryption.js +22 -11
- package/dist/modules/entities/api/encryption.js.map +2 -2
- package/dist/modules/notifications/api/openapi.js +5 -0
- package/dist/modules/notifications/api/openapi.js.map +2 -2
- package/dist/modules/notifications/api/route.js +27 -4
- package/dist/modules/notifications/api/route.js.map +2 -2
- package/dist/modules/notifications/api/settings/route.js +2 -1
- package/dist/modules/notifications/api/settings/route.js.map +2 -2
- package/dist/modules/notifications/api/unread-count/route.js +4 -2
- package/dist/modules/notifications/api/unread-count/route.js.map +2 -2
- package/dist/modules/notifications/lib/routeHelpers.js +25 -0
- package/dist/modules/notifications/lib/routeHelpers.js.map +2 -2
- package/dist/modules/payment_gateways/lib/descriptor-service.js +12 -1
- package/dist/modules/payment_gateways/lib/descriptor-service.js.map +2 -2
- package/dist/modules/progress/data/validators.js +2 -1
- package/dist/modules/progress/data/validators.js.map +2 -2
- package/dist/modules/progress/lib/progressServiceImpl.js +4 -0
- package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
- package/dist/modules/sales/commands/payments.js +6 -4
- package/dist/modules/sales/commands/payments.js.map +2 -2
- package/dist/modules/sales/widgets/injection-table.js +7 -4
- package/dist/modules/sales/widgets/injection-table.js.map +2 -2
- package/package.json +7 -7
- package/src/helpers/integration/dbFixtures.ts +20 -2
- package/src/modules/auth/lib/setup-app.ts +8 -2
- package/src/modules/catalog/widgets/injection-table.ts +5 -4
- package/src/modules/currencies/di.ts +2 -0
- package/src/modules/currencies/services/providers/registry.ts +35 -0
- package/src/modules/customers/i18n/pl.json +34 -34
- package/src/modules/directory/api/organization-switcher/route.ts +16 -4
- package/src/modules/entities/api/encryption.ts +22 -12
- package/src/modules/notifications/api/openapi.ts +5 -0
- package/src/modules/notifications/api/route.ts +31 -2
- package/src/modules/notifications/api/settings/route.ts +8 -1
- package/src/modules/notifications/api/unread-count/route.ts +4 -2
- package/src/modules/notifications/lib/routeHelpers.ts +60 -0
- package/src/modules/payment_gateways/lib/descriptor-service.ts +12 -0
- package/src/modules/progress/data/validators.ts +1 -0
- package/src/modules/progress/lib/progressServiceImpl.ts +4 -0
- package/src/modules/sales/commands/payments.ts +8 -4
- package/src/modules/sales/widgets/injection-table.ts +7 -4
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/helpers/integration/dbFixtures.ts"],
|
|
4
|
-
"sourcesContent": ["import { readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { Client } from 'pg';\n\n/**\n * Database fixtures for the org-scope fail-open hardening tests.\n *\n * These helpers talk to Postgres directly via `pg` rather than bootstrapping the\n * app DI container, because:\n * - the directory create command denies non-super-admin actors (the only\n * loginable accounts here), so orgs cannot be created over the API; and\n * - granting `customers.*` through the ACL API requires a super-admin actor.\n * Raw SQL keeps the test self-contained and avoids depending on built package\n * `dist/` output for an in-process MikroORM bootstrap.\n */\n\nfunction resolveAppRoot(): string {\n const fromEnv = process.env.OM_TEST_APP_ROOT?.trim();\n return fromEnv ? path.resolve(fromEnv) : path.resolve(process.cwd(), 'apps/mercato');\n}\n\nfunction readEnvValue(key: string): string | undefined {\n if (process.env[key]) return process.env[key];\n const candidatePaths = [\n path.resolve(resolveAppRoot(), '.env'),\n path.resolve(process.cwd(), 'apps/mercato/.env'),\n path.resolve(process.cwd(), '.env'),\n ];\n for (const envPath of candidatePaths) {\n try {\n const content = readFileSync(envPath, 'utf-8');\n const match = content.match(new RegExp(`^${key}=(.+)$`, 'm'));\n if (match?.[1]) return match[1].trim();\n } catch {\n continue;\n }\n }\n return undefined;\n}\n\nfunction resolveDatabaseUrl(): string {\n const url = readEnvValue('DATABASE_URL');\n if (!url) throw new Error('[internal] DATABASE_URL is not configured for integration DB fixtures');\n return url;\n}\n\nexport async function withClient<T>(run: (client:
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,OAAO,UAAU;AACjB,SAAS,cAAc;AAcvB,SAAS,iBAAyB;AAChC,QAAM,UAAU,QAAQ,IAAI,kBAAkB,KAAK;AACnD,SAAO,UAAU,KAAK,QAAQ,OAAO,IAAI,KAAK,QAAQ,QAAQ,IAAI,GAAG,cAAc;AACrF;AAEA,SAAS,aAAa,KAAiC;AACrD,MAAI,QAAQ,IAAI,GAAG,EAAG,QAAO,QAAQ,IAAI,GAAG;AAC5C,QAAM,iBAAiB;AAAA,IACrB,KAAK,QAAQ,eAAe,GAAG,MAAM;AAAA,IACrC,KAAK,QAAQ,QAAQ,IAAI,GAAG,mBAAmB;AAAA,IAC/C,KAAK,QAAQ,QAAQ,IAAI,GAAG,MAAM;AAAA,EACpC;AACA,aAAW,WAAW,gBAAgB;AACpC,QAAI;AACF,YAAM,UAAU,aAAa,SAAS,OAAO;AAC7C,YAAM,QAAQ,QAAQ,MAAM,IAAI,OAAO,IAAI,GAAG,UAAU,GAAG,CAAC;AAC5D,UAAI,QAAQ,CAAC,EAAG,QAAO,MAAM,CAAC,EAAE,KAAK;AAAA,IACvC,QAAQ;AACN;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,qBAA6B;AACpC,QAAM,MAAM,aAAa,cAAc;AACvC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,uEAAuE;AACjG,SAAO;AACT;
|
|
4
|
+
"sourcesContent": ["import { readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { Client } from 'pg';\n\n/**\n * Database fixtures for the org-scope fail-open hardening tests.\n *\n * These helpers talk to Postgres directly via `pg` rather than bootstrapping the\n * app DI container, because:\n * - the directory create command denies non-super-admin actors (the only\n * loginable accounts here), so orgs cannot be created over the API; and\n * - granting `customers.*` through the ACL API requires a super-admin actor.\n * Raw SQL keeps the test self-contained and avoids depending on built package\n * `dist/` output for an in-process MikroORM bootstrap.\n */\n\nfunction resolveAppRoot(): string {\n const fromEnv = process.env.OM_TEST_APP_ROOT?.trim();\n return fromEnv ? path.resolve(fromEnv) : path.resolve(process.cwd(), 'apps/mercato');\n}\n\nfunction readEnvValue(key: string): string | undefined {\n if (process.env[key]) return process.env[key];\n const candidatePaths = [\n path.resolve(resolveAppRoot(), '.env'),\n path.resolve(process.cwd(), 'apps/mercato/.env'),\n path.resolve(process.cwd(), '.env'),\n ];\n for (const envPath of candidatePaths) {\n try {\n const content = readFileSync(envPath, 'utf-8');\n const match = content.match(new RegExp(`^${key}=(.+)$`, 'm'));\n if (match?.[1]) return match[1].trim();\n } catch {\n continue;\n }\n }\n return undefined;\n}\n\nfunction resolveDatabaseUrl(): string {\n const url = readEnvValue('DATABASE_URL');\n if (!url) throw new Error('[internal] DATABASE_URL is not configured for integration DB fixtures');\n return url;\n}\n\n/**\n * The slice of `pg`'s client these fixtures use, declared structurally.\n *\n * `pg` publishes `Client` as a class merged with a namespace, and the app project's module\n * resolution picks the namespace half in type position (`TS2709`), so naming the class here made\n * every caller outside `packages/core` fail to typecheck. A structural type is resolvable from\n * every project and keeps `query<T>()` generic, which `InstanceType<typeof Client>` did not.\n */\nexport type IntegrationDbClient = {\n query<T = Record<string, unknown>>(\n text: string,\n values?: readonly unknown[],\n ): Promise<{ rows: T[]; rowCount: number | null }>;\n};\n\nexport async function withClient<T>(run: (client: IntegrationDbClient) => Promise<T>): Promise<T> {\n const client = new Client({ connectionString: resolveDatabaseUrl() }) as unknown as IntegrationDbClient & {\n connect: () => Promise<void>;\n end: () => Promise<void>;\n };\n await client.connect();\n try {\n return await run(client);\n } finally {\n await client.end();\n }\n}\n\n/**\n * Nulls a user's home organization (`organization_id`) directly in the database.\n *\n * Required to construct the \"floating restricted user\" precondition: the JWT\n * `auth.orgId` is minted from `users.organization_id` at login, so this MUST run\n * BEFORE the user logs in.\n */\nexport async function clearUserHomeOrganization(userId: string): Promise<void> {\n await withClient(async (client) => {\n await client.query('update users set organization_id = null where id = $1', [userId]);\n });\n}\n\n/**\n * Upserts a per-user ACL row, setting the effective feature list and the\n * organization-visibility list.\n *\n * `organizations` maps to `OrganizationScope.allowedIds` (write path) and\n * `filterIds` (read path):\n * - `[orgA]` => restricted to orgA (write fail-open precondition, #2239)\n * - `[]` => restricted to zero orgs (read fail-open precondition, #2245)\n * - `null` => unrestricted\n */\nexport async function setUserAclInDb(input: {\n userId: string;\n tenantId: string;\n features: string[];\n organizations: string[] | null;\n}): Promise<void> {\n await withClient(async (client) => {\n const existing = await client.query<{ id: string }>(\n 'select id from user_acls where user_id = $1 and tenant_id = $2 limit 1',\n [input.userId, input.tenantId],\n );\n const featuresJson = JSON.stringify(input.features);\n const organizationsJson = input.organizations === null ? null : JSON.stringify(input.organizations);\n if (existing.rows.length > 0) {\n await client.query(\n 'update user_acls set features_json = $2::jsonb, organizations_json = $3::jsonb, is_super_admin = false, updated_at = now() where id = $1',\n [existing.rows[0].id, featuresJson, organizationsJson],\n );\n return;\n }\n await client.query(\n `insert into user_acls (id, user_id, tenant_id, features_json, organizations_json, is_super_admin, created_at)\n values (gen_random_uuid(), $1, $2, $3::jsonb, $4::jsonb, false, now())`,\n [input.userId, input.tenantId, featuresJson, organizationsJson],\n );\n });\n}\n\n/** Removes any per-user ACL rows for the user (best-effort test cleanup). */\nexport async function deleteUserAclInDb(userId: string): Promise<void> {\n if (!userId) return;\n await withClient(async (client) => {\n await client.query('delete from user_acls where user_id = $1', [userId]);\n });\n}\n\n/**\n * Creates an organization directly in the database within the given tenant.\n *\n * The directory create command routes through `enforceTenantSelection`, which\n * denies the (non-super-admin) accounts loginable on this instance. Landing the\n * org in a known tenant lets the floating user (created under it) share the\n * tenant \u2014 required because cross-tenant access returns 404 before the\n * org-scope guard ever runs.\n */\nexport async function createOrganizationInDb(input: { name: string; tenantId: string }): Promise<string> {\n return withClient(async (client) => {\n const result = await client.query<{ id: string }>(\n `insert into organizations\n (id, tenant_id, name, is_active, ancestor_ids, child_ids, descendant_ids, depth, created_at, updated_at)\n values (gen_random_uuid(), $1, $2, true, '[]'::jsonb, '[]'::jsonb, '[]'::jsonb, 0, now(), now())\n returning id`,\n [input.tenantId, input.name],\n );\n return result.rows[0].id;\n });\n}\n\n/** Hard-deletes an organization row (best-effort test cleanup). */\nexport async function deleteOrganizationInDb(organizationId: string | null): Promise<void> {\n if (!organizationId) return;\n await withClient(async (client) => {\n await client.query('delete from organizations where id = $1', [organizationId]);\n });\n}\n\n/**\n * Hard-deletes integration credential rows for an organization (best-effort test\n * cleanup). Integration credentials are stored per (integration_id,\n * organization_id, tenant_id, user_id) with no FK to organizations, so a\n * throwaway org's credential rows must be removed explicitly when the org row is\n * torn down.\n */\nexport async function deleteIntegrationCredentialsInDb(organizationId: string | null): Promise<void> {\n if (!organizationId) return;\n await withClient(async (client) => {\n await client.query('delete from integration_credentials where organization_id = $1', [organizationId]);\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -451,12 +451,13 @@ async function ensureCustomRoleAcls(em, tenantId, modules) {
|
|
|
451
451
|
}
|
|
452
452
|
}
|
|
453
453
|
async function ensureRoleAclFor(em, role, tenantId, features, options = {}) {
|
|
454
|
+
const uniqueFeatures = Array.from(new Set(features));
|
|
454
455
|
const existing = await findOneWithDecryption(em, RoleAcl, { role, tenantId }, {}, { tenantId, organizationId: null });
|
|
455
456
|
if (!existing) {
|
|
456
457
|
const acl = em.create(RoleAcl, {
|
|
457
458
|
role,
|
|
458
459
|
tenantId,
|
|
459
|
-
featuresJson:
|
|
460
|
+
featuresJson: uniqueFeatures,
|
|
460
461
|
isSuperAdmin: !!options.isSuperAdmin,
|
|
461
462
|
createdAt: /* @__PURE__ */ new Date()
|
|
462
463
|
});
|
|
@@ -464,7 +465,7 @@ async function ensureRoleAclFor(em, role, tenantId, features, options = {}) {
|
|
|
464
465
|
return;
|
|
465
466
|
}
|
|
466
467
|
const currentFeatures = Array.isArray(existing.featuresJson) ? existing.featuresJson : [];
|
|
467
|
-
const merged = Array.from(/* @__PURE__ */ new Set([...currentFeatures, ...
|
|
468
|
+
const merged = Array.from(/* @__PURE__ */ new Set([...currentFeatures, ...uniqueFeatures]));
|
|
468
469
|
const changed = merged.length !== currentFeatures.length || merged.some((value, index) => value !== currentFeatures[index]);
|
|
469
470
|
if (changed) existing.featuresJson = merged;
|
|
470
471
|
if (options.isSuperAdmin && !existing.isSuperAdmin) {
|
|
@@ -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 { randomBytes } from 'node:crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { Role, RoleAcl, User, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport { Tenant, Organization } from '@open-mercato/core/modules/directory/data/entities'\nimport { rebuildHierarchyForTenant } from '@open-mercato/core/modules/directory/lib/hierarchy'\nimport { normalizeTenantId } from './tenantAccess'\nimport { computeEmailHash, emailHashLookupValues } from '@open-mercato/core/modules/auth/lib/emailHash'\nimport { getDefaultEncryptionMaps, type Module } from '@open-mercato/shared/modules/registry'\nimport { isEncryptionDebugEnabled, isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'\nimport { EncryptionMap } from '@open-mercato/core/modules/entities/data/entities'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('auth').child({ component: 'setup' })\n\nconst DEFAULT_ROLE_NAMES = ['employee', 'admin', 'superadmin'] as const\nconst DEMO_SUPERADMIN_EMAIL = 'superadmin@acme.com'\nconst DEFAULT_DERIVED_EMAIL_DOMAIN = DEMO_SUPERADMIN_EMAIL.split('@')[1] ?? 'acme.com'\n\nexport type EnsureRolesOptions = {\n roleNames?: string[]\n tenantId?: string\n}\n\nasync function ensureRolesInContext(\n em: EntityManager,\n roleNames: string[],\n tenantId: string,\n) {\n for (const name of roleNames) {\n const existing = await findOneWithDecryption(em, Role, { name, tenantId }, {}, { tenantId, organizationId: null })\n if (existing) continue\n em.persist(em.create(Role, { name, tenantId, minActiveHolders: name === 'admin' ? 1 : 0, createdAt: new Date() }))\n }\n}\n\nexport async function ensureRoles(em: EntityManager, options: EnsureRolesOptions = {}) {\n const roleNames = options.roleNames ?? [...DEFAULT_ROLE_NAMES]\n const tenantId = normalizeTenantId(options.tenantId ?? null)\n if (!tenantId) {\n throw new Error('ensureRoles requires a tenantId \u2014 global roles are not supported')\n }\n await em.transactional(async (tem) => {\n await ensureRolesInContext(tem, roleNames, tenantId)\n await tem.flush()\n })\n}\n\nasync function findRoleByName(\n em: EntityManager,\n name: string,\n tenantId: string | null,\n): Promise<Role | null> {\n const normalizedTenant = normalizeTenantId(tenantId ?? null) ?? null\n return findOneWithDecryption(em, Role, { name, tenantId: normalizedTenant }, {}, { tenantId: normalizedTenant, organizationId: null })\n}\n\nasync function findRoleByNameOrFail(\n em: EntityManager,\n name: string,\n tenantId: string | null,\n): Promise<Role> {\n const role = await findRoleByName(em, name, tenantId)\n if (!role) throw new Error(`ROLE_NOT_FOUND:${name}`)\n return role\n}\n\ntype PrimaryUserInput = {\n email: string\n password?: string\n hashedPassword?: string | null\n firstName?: string | null\n lastName?: string | null\n displayName?: string | null\n confirm?: boolean\n}\n\nconst DERIVED_EMAIL_ENV = {\n admin: 'OM_INIT_ADMIN_EMAIL',\n employee: 'OM_INIT_EMPLOYEE_EMAIL',\n} as const\n\nexport type DemoUserRole = 'superadmin' | 'admin' | 'employee'\nexport type DemoUserEmail = { role: DemoUserRole; email: string }\n\n/**\n * Returns the canonical list of demo user emails the setup path may have\n * seeded. Honors OM_INIT_ADMIN_EMAIL / OM_INIT_EMPLOYEE_EMAIL exactly the\n * same way the derived-user seeding branch does, so the deactivation loop\n * never drifts from the seeding loop.\n *\n * Pure function \u2014 no side effects, safe to unit-test in isolation.\n *\n * @internal Exported for tests; not part of the public auth API.\n */\nexport function resolveDemoUserEmails(): DemoUserEmail[] {\n const adminEmail = readEnvValue(DERIVED_EMAIL_ENV.admin) ?? `admin@${DEFAULT_DERIVED_EMAIL_DOMAIN}`\n const employeeEmail = readEnvValue(DERIVED_EMAIL_ENV.employee) ?? `employee@${DEFAULT_DERIVED_EMAIL_DOMAIN}`\n return [\n { role: 'superadmin', email: DEMO_SUPERADMIN_EMAIL },\n { role: 'admin', email: adminEmail },\n { role: 'employee', email: employeeEmail },\n ]\n}\n\nexport type SetupInitialTenantOptions = {\n orgName: string\n primaryUser: PrimaryUserInput\n roleNames?: string[]\n includeDerivedUsers?: boolean\n failIfUserExists?: boolean\n primaryUserRoles?: string[]\n includeSuperadminRole?: boolean\n /** Optional list of enabled modules. When provided, module setup hooks are called. */\n modules?: Module[]\n /**\n * Optional global slug to persist on the new Organization. When set, a\n * pre-flight uniqueness check across all tenants throws OrgSlugExistsError\n * on collision so scriptable callers can fail loudly instead of silently\n * reusing or clobbering an existing organization.\n */\n orgSlug?: string\n /**\n * Opt-in flag that allows seeding the derived admin/employee accounts when\n * the OM_INIT_ADMIN_PASSWORD / OM_INIT_EMPLOYEE_PASSWORD env vars are unset.\n *\n * - In non-production (`NODE_ENV !== 'production'`): the demo accounts are\n * seeded with the well-known `'secret'` password so local dev workflows\n * (e.g. `mercato init`) stay predictable for developers.\n * - In production: the fallback uses a randomly generated 96-bit password\n * (printed once by the CLI) so an operator who deliberately opts in still\n * avoids the historical hardcoded credential. When this flag is false/unset\n * in production and the env vars are missing, `DerivedUserPasswordRequiredError`\n * is thrown instead of silently seeding any account.\n */\n allowDemoDerivedPasswords?: boolean\n}\n\nexport class OrgSlugExistsError extends Error {\n constructor(public readonly slug: string) {\n super(`ORG_SLUG_EXISTS: an organization with slug \"${slug}\" already exists`)\n this.name = 'OrgSlugExistsError'\n }\n}\n\nexport class DerivedUserPasswordRequiredError extends Error {\n constructor(public readonly missing: string[]) {\n super(\n `DERIVED_USER_PASSWORD_REQUIRED: missing ${missing.join(', ')} (set the env vars, pass allowDemoDerivedPasswords: true / --include-demo-users in non-production, or disable derived user seeding)`,\n )\n this.name = 'DerivedUserPasswordRequiredError'\n }\n}\n\nexport type SetupInitialTenantResult = {\n tenantId: string\n organizationId: string\n users: Array<{ user: User; roles: string[]; created: boolean; generatedPassword?: string | null }>\n reusedExistingUser: boolean\n}\n\nexport async function setupInitialTenant(\n em: EntityManager,\n options: SetupInitialTenantOptions,\n): Promise<SetupInitialTenantResult> {\n const {\n primaryUser,\n includeDerivedUsers = true,\n failIfUserExists = false,\n primaryUserRoles,\n includeSuperadminRole = true,\n } = options\n const primaryRolesInput = primaryUserRoles && primaryUserRoles.length ? primaryUserRoles : ['superadmin']\n const primaryRoles = includeSuperadminRole\n ? primaryRolesInput\n : primaryRolesInput.filter((role) => role !== 'superadmin')\n if (primaryRoles.length === 0) {\n throw new Error('PRIMARY_ROLES_REQUIRED')\n }\n const defaultRoleNames = options.roleNames ?? [...DEFAULT_ROLE_NAMES]\n const resolvedRoleNames = includeSuperadminRole\n ? defaultRoleNames\n : defaultRoleNames.filter((role) => role !== 'superadmin')\n const roleNames = Array.from(new Set([...resolvedRoleNames, ...primaryRoles]))\n const resolvedModules = options.modules ?? tryGetModules()\n const defaultEncryptionMaps = getDefaultEncryptionMaps(resolvedModules)\n\n const mainEmail = primaryUser.email\n const existingUserByHash = await findOneWithDecryption(\n em,\n User,\n { emailHash: { $in: emailHashLookupValues(mainEmail) }, deletedAt: null },\n {},\n { tenantId: null, organizationId: null },\n )\n const existingUser = existingUserByHash ?? await findOneWithDecryption(\n em,\n User,\n { email: mainEmail, deletedAt: null },\n {},\n { tenantId: null, organizationId: null },\n )\n if (existingUser && failIfUserExists) {\n throw new Error('USER_EXISTS')\n }\n\n if (options.orgSlug) {\n const slugConflict = await findOneWithDecryption(\n em,\n Organization,\n { slug: options.orgSlug },\n {},\n { tenantId: null, organizationId: null },\n )\n if (slugConflict) {\n throw new OrgSlugExistsError(options.orgSlug)\n }\n }\n\n let tenantId: string | undefined\n let organizationId: string | undefined\n let reusedExistingUser = false\n const userSnapshots: Array<{ user: User; roles: string[]; created: boolean; generatedPassword?: string | null }> = []\n\n await em.transactional(async (tem) => {\n if (!existingUser) return\n reusedExistingUser = true\n tenantId = existingUser.tenantId ? String(existingUser.tenantId) : undefined\n organizationId = existingUser.organizationId ? String(existingUser.organizationId) : undefined\n const roleTenantId = normalizeTenantId(existingUser.tenantId ?? null) ?? null\n if (!roleTenantId) {\n throw new Error('Cannot reuse a user without a tenantId \u2014 global roles are not supported')\n }\n\n await ensureRolesInContext(tem, roleNames, roleTenantId)\n await tem.flush()\n\n const requiredRoleSet = new Set([...roleNames, ...primaryRoles])\n const links = await findWithDecryption(\n tem,\n UserRole,\n { user: existingUser },\n { populate: ['role'] },\n { tenantId: roleTenantId, organizationId: null },\n )\n const currentRoles = new Set(links.map((link) => link.role.name))\n for (const roleName of requiredRoleSet) {\n if (!currentRoles.has(roleName)) {\n const role = await findRoleByNameOrFail(tem, roleName, roleTenantId)\n tem.persist(tem.create(UserRole, { user: existingUser, role, createdAt: new Date() }))\n }\n }\n await tem.flush()\n const roles = Array.from(new Set([...currentRoles, ...roleNames]))\n userSnapshots.push({ user: existingUser, roles, created: false })\n })\n\n if (!existingUser) {\n const baseUsers: Array<{\n email: string\n roles: string[]\n name?: string | null\n passwordHash?: string | null\n generatedPassword?: string | null\n }> = [\n { email: primaryUser.email, roles: primaryRoles, name: resolvePrimaryName(primaryUser) },\n ]\n if (includeDerivedUsers) {\n const adminOverride = readEnvValue(DERIVED_EMAIL_ENV.admin)\n const employeeOverride = readEnvValue(DERIVED_EMAIL_ENV.employee)\n const adminEmail = adminOverride ?? `admin@${DEFAULT_DERIVED_EMAIL_DOMAIN}`\n const employeeEmail = employeeOverride ?? `employee@${DEFAULT_DERIVED_EMAIL_DOMAIN}`\n const envAdminPwd = readEnvValue('OM_INIT_ADMIN_PASSWORD')\n const envEmployeePwd = readEnvValue('OM_INIT_EMPLOYEE_PASSWORD')\n const isProduction = process.env.NODE_ENV === 'production'\n const allowDemo = options.allowDemoDerivedPasswords === true\n if (isProduction && !allowDemo) {\n const missing: string[] = []\n if (!envAdminPwd) missing.push('OM_INIT_ADMIN_PASSWORD')\n if (!envEmployeePwd) missing.push('OM_INIT_EMPLOYEE_PASSWORD')\n if (missing.length) {\n throw new DerivedUserPasswordRequiredError(missing)\n }\n }\n // In non-production, fall back to the well-known DEMO_DERIVED_PASSWORD so\n // local dev workflows stay predictable (admin@/employee@ login with the\n // documented demo password). In production we either have env-supplied\n // values, or \u2014 for opt-in --include-demo-users flows \u2014 we generate a\n // random one-time password and surface it via `generatedPassword` so the\n // operator can capture it. Production callers without env vars and\n // without the opt-in already threw above.\n const fallbackAdminPwd = isProduction ? generateDerivedPassword() : DEMO_DERIVED_PASSWORD\n const fallbackEmployeePwd = isProduction ? generateDerivedPassword() : DEMO_DERIVED_PASSWORD\n const adminPasswordPlain = envAdminPwd ?? fallbackAdminPwd\n const employeePasswordPlain = envEmployeePwd ?? fallbackEmployeePwd\n const adminPasswordHash = await hash(adminPasswordPlain, 10)\n const employeePasswordHash = await hash(employeePasswordPlain, 10)\n addUniqueBaseUser(baseUsers, {\n email: adminEmail,\n roles: ['admin'],\n passwordHash: adminPasswordHash,\n generatedPassword: envAdminPwd ? null : adminPasswordPlain,\n })\n addUniqueBaseUser(baseUsers, {\n email: employeeEmail,\n roles: ['employee'],\n passwordHash: employeePasswordHash,\n generatedPassword: envEmployeePwd ? null : employeePasswordPlain,\n })\n }\n const passwordHash = await resolvePasswordHash(primaryUser)\n\n await em.transactional(async (tem) => {\n const tenant = tem.create(Tenant, {\n name: options.orgName,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n tem.persist(tenant)\n await tem.flush()\n\n const organization = tem.create(Organization, {\n name: options.orgName,\n slug: options.orgSlug ?? null,\n tenant,\n isActive: true,\n depth: 0,\n ancestorIds: [],\n childIds: [],\n descendantIds: [],\n logoPreserveAspectRatio: false,\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 logger.info('Provisioning tenant DEK', { tenantId: String(tenant.id) })\n }\n await kms.createTenantDek(String(tenant.id))\n if (isEncryptionDebugEnabled()) {\n logger.info('Created tenant DEK during setup', { tenantId: String(tenant.id) })\n }\n } else {\n if (isEncryptionDebugEnabled()) {\n logger.warn('KMS not healthy, skipping tenant DEK creation', { tenantId: String(tenant.id) })\n }\n }\n } catch (err) {\n if (isEncryptionDebugEnabled()) {\n logger.warn('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 // System-scoped maps are resolved from module code, never from a tenant row.\n // Persisting one here would make the tenant-scoped encryption CLIs believe they\n // own that entity and re-wrap its `system:<entityId>` ciphertext under the tenant\n // DEK, which runtime decryption can no longer read.\n for (const spec of defaultEncryptionMaps) {\n if (spec.keyScope === 'system') continue\n const existing = await findOneWithDecryption(tem, EncryptionMap, { entityId: spec.entityId, tenantId: tenant.id, organizationId: organization.id, deletedAt: null }, {}, { tenantId: String(tenant.id), organizationId: String(organization.id) })\n if (!existing) {\n tem.persist(tem.create(EncryptionMap, {\n entityId: spec.entityId,\n tenantId: tenant.id,\n organizationId: organization.id,\n fieldsJson: spec.fields,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n }))\n } else {\n existing.fieldsJson = spec.fields\n existing.isActive = true\n }\n }\n await tem.flush()\n }\n })\n\n await em.transactional(async (tem) => {\n if (!tenantId || !organizationId) return\n const roleTenantId = tenantId\n const encryptionService = isTenantDataEncryptionEnabled()\n ? new TenantDataEncryptionService(tem as any, { kms: createKmsService() })\n : null\n if (encryptionService) {\n await encryptionService.invalidateMap('auth:user', String(tenantId), String(organizationId))\n await encryptionService.invalidateMap('auth:user', String(tenantId), null)\n }\n\n for (const base of baseUsers) {\n const resolvedPasswordHash = base.passwordHash ?? passwordHash\n let user = await findOneWithDecryption(tem, User, { email: base.email }, {}, { tenantId: tenantId ?? null, organizationId: organizationId ?? null })\n const confirm = primaryUser.confirm ?? true\n const encryptedPayload = encryptionService\n ? await encryptionService.encryptEntityPayload('auth:user', { email: base.email }, tenantId, organizationId)\n : { email: base.email, emailHash: computeEmailHash(base.email) }\n if (user) {\n user.passwordHash = resolvedPasswordHash\n user.organizationId = organizationId\n user.tenantId = tenantId\n if (isTenantDataEncryptionEnabled()) {\n user.email = encryptedPayload.email as any\n user.emailHash = (encryptedPayload as any).emailHash ?? computeEmailHash(base.email)\n }\n if (base.name) user.name = base.name\n if (confirm) user.isConfirmed = true\n tem.persist(user)\n userSnapshots.push({ user, roles: base.roles, created: false, generatedPassword: base.generatedPassword ?? null })\n } else {\n user = tem.create(User, {\n email: (encryptedPayload as any).email ?? base.email,\n emailHash: isTenantDataEncryptionEnabled() ? (encryptedPayload as any).emailHash ?? computeEmailHash(base.email) : undefined,\n passwordHash: resolvedPasswordHash,\n organizationId,\n tenantId,\n name: base.name ?? undefined,\n isConfirmed: confirm,\n createdAt: new Date(),\n })\n tem.persist(user)\n userSnapshots.push({ user, roles: base.roles, created: true, generatedPassword: base.generatedPassword ?? null })\n }\n await tem.flush()\n for (const roleName of base.roles) {\n const role = await findRoleByNameOrFail(tem, roleName, roleTenantId)\n const existingLink = await findOneWithDecryption(tem, UserRole, { user, role }, {}, { tenantId: tenantId ?? null, organizationId: null })\n if (!existingLink) tem.persist(tem.create(UserRole, { user, role, createdAt: new Date() }))\n }\n await tem.flush()\n }\n })\n }\n\n if (!tenantId || !organizationId) {\n throw new Error('SETUP_FAILED')\n }\n\n if (!reusedExistingUser) {\n await rebuildHierarchyForTenant(em, tenantId)\n }\n\n await ensureDefaultRoleAcls(em, tenantId, resolvedModules, { includeSuperadminRole })\n await deactivateDemoUsersIfSelfOnboardingEnabled(em)\n\n // Call module onTenantCreated hooks\n for (const mod of resolvedModules) {\n if (mod.setup?.onTenantCreated) {\n await mod.setup.onTenantCreated({ em, tenantId, organizationId })\n }\n }\n\n return {\n tenantId,\n organizationId,\n users: userSnapshots,\n reusedExistingUser,\n }\n}\n\nfunction resolvePrimaryName(input: PrimaryUserInput): string | null {\n if (input.displayName && input.displayName.trim()) return input.displayName.trim()\n const parts = [input.firstName, input.lastName].map((value) => value?.trim()).filter(Boolean)\n if (parts.length) return parts.join(' ')\n return null\n}\n\nfunction readEnvValue(key: string): string | undefined {\n const value = process.env[key]\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\nfunction addUniqueBaseUser(\n baseUsers: Array<{ email: string; roles: string[]; name?: string | null; passwordHash?: string | null; generatedPassword?: string | null }>,\n entry: { email: string; roles: string[]; name?: string | null; passwordHash?: string | null; generatedPassword?: string | null },\n) {\n if (!entry.email) return\n const normalized = entry.email.toLowerCase()\n if (baseUsers.some((user) => user.email.toLowerCase() === normalized)) return\n baseUsers.push(entry)\n}\n\n/**\n * Well-known demo password seeded for derived admin@/employee@ accounts in\n * non-production (`NODE_ENV !== 'production'`) when no env override is set.\n * Keeps the documented `yarn dev` / `mercato init` DX predictable. Never used\n * in production: the production branch either consumes env-supplied values or\n * falls back to `generateDerivedPassword()` so credentials remain non-guessable.\n */\nconst DEMO_DERIVED_PASSWORD = 'secret'\n\n/**\n * Generate a 16-character base64url password (96 bits of entropy) for a derived\n * demo user in production when no env override is provided AND the caller\n * explicitly opted in via `allowDemoDerivedPasswords`. Surfaced via the\n * `users[].generatedPassword` snapshot so CLI callers can print it to the\n * operator \u2014 there is no other recovery path for these credentials.\n */\nfunction generateDerivedPassword(): string {\n return randomBytes(12).toString('base64url')\n}\n\nfunction isDemoModeEnabled(): boolean {\n const parsed = parseBooleanToken(process.env.DEMO_MODE ?? '')\n return parsed === false ? false : true\n}\n\nfunction shouldKeepDemoSuperadminDuringInit(): boolean {\n if (process.env.OM_INIT_FLOW !== 'true') return false\n if (!readEnvValue('OM_INIT_SUPERADMIN_EMAIL')) return false\n return isDemoModeEnabled()\n}\n\nasync function resolvePasswordHash(input: PrimaryUserInput): Promise<string | null> {\n if (typeof input.hashedPassword === 'string') return input.hashedPassword\n if (input.password) return hash(input.password, 10)\n return null\n}\n\nexport async function ensureDefaultRoleAcls(\n em: EntityManager,\n tenantId: string,\n modules: Module[],\n options: { includeSuperadminRole?: boolean } = {},\n) {\n const includeSuperadminRole = options.includeSuperadminRole ?? true\n const roleTenantId = normalizeTenantId(tenantId) ?? null\n const superadminRole = includeSuperadminRole ? await findRoleByName(em, 'superadmin', roleTenantId) : null\n const adminRole = await findRoleByName(em, 'admin', roleTenantId)\n const employeeRole = await findRoleByName(em, 'employee', roleTenantId)\n\n // Merge features from all enabled modules' setup configs\n const builtInRoles = ['superadmin', 'admin', 'employee'] as const\n const superadminFeatures: string[] = []\n const adminFeatures: string[] = []\n const employeeFeatures: string[] = []\n const customRoleFeatures = new Map<string, string[]>()\n\n for (const mod of modules) {\n const roleFeatures = mod.setup?.defaultRoleFeatures\n if (!roleFeatures) continue\n if (roleFeatures.superadmin) superadminFeatures.push(...roleFeatures.superadmin)\n if (roleFeatures.admin) adminFeatures.push(...roleFeatures.admin)\n if (roleFeatures.employee) employeeFeatures.push(...roleFeatures.employee)\n\n // Collect features for custom roles (any key not in builtInRoles)\n for (const [roleName, features] of Object.entries(roleFeatures)) {\n if ((builtInRoles as readonly string[]).includes(roleName)) continue\n if (!Array.isArray(features)) continue\n const existing = customRoleFeatures.get(roleName) ?? []\n existing.push(...features)\n customRoleFeatures.set(roleName, existing)\n }\n }\n\n logger.info('Seeded default role features', {\n superadmin: superadminFeatures,\n admin: adminFeatures,\n employee: employeeFeatures,\n ...(customRoleFeatures.size > 0\n ? Object.fromEntries(customRoleFeatures)\n : {}),\n })\n\n if (includeSuperadminRole && superadminRole) {\n await ensureRoleAclFor(em, superadminRole, tenantId, superadminFeatures, { isSuperAdmin: true })\n }\n if (adminRole) {\n await ensureRoleAclFor(em, adminRole, tenantId, adminFeatures)\n }\n if (employeeRole) {\n await ensureRoleAclFor(em, employeeRole, tenantId, employeeFeatures)\n }\n\n // Seed ACLs for custom roles defined by app modules.\n // NOTE: Custom roles may not exist yet if they are created in seedDefaults\n // (which runs after this function). In that case, use ensureCustomRoleAcls()\n // after seedDefaults to pick them up.\n for (const [roleName, features] of customRoleFeatures) {\n const role = await findRoleByName(em, roleName, roleTenantId)\n if (role) {\n await ensureRoleAclFor(em, role, tenantId, features)\n }\n }\n}\n\n/**\n * Seed ACLs for custom roles defined in module defaultRoleFeatures.\n * Call this AFTER seedDefaults to pick up roles created by app modules.\n * Safe to call multiple times \u2014 ensureRoleAclFor merges features idempotently.\n */\nexport async function ensureCustomRoleAcls(\n em: EntityManager,\n tenantId: string,\n modules?: Module[],\n): Promise<void> {\n const resolvedModules = modules ?? tryGetModules()\n const roleTenantId = normalizeTenantId(tenantId) ?? null\n const builtInRoles = ['superadmin', 'admin', 'employee']\n const customRoleFeatures = new Map<string, string[]>()\n\n for (const mod of resolvedModules) {\n const roleFeatures = mod.setup?.defaultRoleFeatures\n if (!roleFeatures) continue\n for (const [roleName, features] of Object.entries(roleFeatures)) {\n if (builtInRoles.includes(roleName)) continue\n if (!Array.isArray(features)) continue\n const existing = customRoleFeatures.get(roleName) ?? []\n existing.push(...features)\n customRoleFeatures.set(roleName, existing)\n }\n }\n\n if (customRoleFeatures.size === 0) return\n\n let seeded = 0\n for (const [roleName, features] of customRoleFeatures) {\n const role = await findRoleByName(em, roleName, roleTenantId)\n if (role) {\n await ensureRoleAclFor(em, role, tenantId, features)\n seeded++\n }\n }\n if (seeded > 0) {\n logger.info('Seeded custom role ACLs', { seeded })\n }\n}\n\nasync function ensureRoleAclFor(\n em: EntityManager,\n role: Role,\n tenantId: string,\n features: string[],\n options: { isSuperAdmin?: boolean } = {},\n) {\n const existing = await findOneWithDecryption(em, RoleAcl, { role, tenantId }, {}, { tenantId, organizationId: null })\n if (!existing) {\n const acl = em.create(RoleAcl, {\n role,\n tenantId,\n featuresJson: features,\n isSuperAdmin: !!options.isSuperAdmin,\n createdAt: new Date(),\n })\n await em.persist(acl).flush()\n return\n }\n const currentFeatures = Array.isArray(existing.featuresJson) ? existing.featuresJson : []\n const merged = Array.from(new Set([...currentFeatures, ...features]))\n const changed =\n merged.length !== currentFeatures.length ||\n merged.some((value, index) => value !== currentFeatures[index])\n if (changed) existing.featuresJson = merged\n if (options.isSuperAdmin && !existing.isSuperAdmin) {\n existing.isSuperAdmin = true\n }\n if (changed || options.isSuperAdmin) {\n await em.persist(existing).flush()\n }\n}\n\n/**\n * Neutralizes every demo account the setup path may have seeded\n * (superadmin, admin, employee \u2014 honoring OM_INIT_*_EMAIL overrides)\n * when SELF_SERVICE_ONBOARDING_ENABLED is on and the operator did not\n * opt in to keeping demo credentials via shouldKeepDemoSuperadminDuringInit.\n *\n * Each user is processed in its own try/catch so a single failure (e.g.\n * decryption error on a legacy row) does not skip the remaining accounts.\n *\n * @internal Exported for tests; not part of the public auth API.\n */\nexport async function deactivateDemoUsersIfSelfOnboardingEnabled(em: EntityManager) {\n if (process.env.SELF_SERVICE_ONBOARDING_ENABLED !== 'true') return\n if (shouldKeepDemoSuperadminDuringInit()) return\n for (const { role, email } of resolveDemoUserEmails()) {\n try {\n const user = await findOneWithDecryption(\n em,\n User,\n { email },\n {},\n { tenantId: null, organizationId: null },\n )\n if (!user) continue\n let dirty = false\n if (user.passwordHash) {\n user.passwordHash = null\n dirty = true\n }\n if (user.isConfirmed !== false) {\n user.isConfirmed = false\n dirty = true\n }\n if (dirty) {\n await em.persist(user).flush()\n }\n } catch (error) {\n logger.error('Failed to deactivate demo user', { role, email, err: error })\n }\n }\n}\n\n/**\n * @deprecated Renamed to {@link deactivateDemoUsersIfSelfOnboardingEnabled}\n * because the helper now neutralizes admin/employee demo accounts in addition\n * to superadmin (security tracker finding #5). Kept as an internal alias to\n * avoid breaking any out-of-tree caller that imported the old name; will be\n * removed in a future major.\n */\nconst deactivateDemoSuperAdminIfSelfOnboardingEnabled = deactivateDemoUsersIfSelfOnboardingEnabled\n\n/** Try to get modules from runtime registry; returns empty array if not yet registered. */\nfunction tryGetModules(): Module[] {\n try {\n const { getModules } = require('@open-mercato/shared/lib/modules/registry')\n return getModules()\n } catch {\n return []\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,YAAY;AACrB,SAAS,mBAAmB;AAE5B,SAAS,MAAM,SAAS,MAAM,gBAAgB;AAC9C,SAAS,QAAQ,oBAAoB;AACrC,SAAS,iCAAiC;AAC1C,SAAS,yBAAyB;AAClC,SAAS,kBAAkB,6BAA6B;AACxD,SAAS,gCAA6C;AACtD,SAAS,0BAA0B,qCAAqC;AACxE,SAAS,qBAAqB;AAC9B,SAAS,wBAAwB;AACjC,SAAS,mCAAmC;AAC5C,SAAS,uBAAuB,0BAA0B;AAC1D,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,MAAM,EAAE,MAAM,EAAE,WAAW,QAAQ,CAAC;AAEhE,MAAM,qBAAqB,CAAC,YAAY,SAAS,YAAY;AAC7D,MAAM,wBAAwB;AAC9B,MAAM,+BAA+B,sBAAsB,MAAM,GAAG,EAAE,CAAC,KAAK;AAO5E,eAAe,qBACb,IACA,WACA,UACA;AACA,aAAW,QAAQ,WAAW;AAC5B,UAAM,WAAW,MAAM,sBAAsB,IAAI,MAAM,EAAE,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,UAAU,gBAAgB,KAAK,CAAC;AACjH,QAAI,SAAU;AACd,OAAG,QAAQ,GAAG,OAAO,MAAM,EAAE,MAAM,UAAU,kBAAkB,SAAS,UAAU,IAAI,GAAG,WAAW,oBAAI,KAAK,EAAE,CAAC,CAAC;AAAA,EACnH;AACF;AAEA,eAAsB,YAAY,IAAmB,UAA8B,CAAC,GAAG;AACrF,QAAM,YAAY,QAAQ,aAAa,CAAC,GAAG,kBAAkB;AAC7D,QAAM,WAAW,kBAAkB,QAAQ,YAAY,IAAI;AAC3D,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,uEAAkE;AAAA,EACpF;AACA,QAAM,GAAG,cAAc,OAAO,QAAQ;AACpC,UAAM,qBAAqB,KAAK,WAAW,QAAQ;AACnD,UAAM,IAAI,MAAM;AAAA,EAClB,CAAC;AACH;AAEA,eAAe,eACb,IACA,MACA,UACsB;AACtB,QAAM,mBAAmB,kBAAkB,YAAY,IAAI,KAAK;AAChE,SAAO,sBAAsB,IAAI,MAAM,EAAE,MAAM,UAAU,iBAAiB,GAAG,CAAC,GAAG,EAAE,UAAU,kBAAkB,gBAAgB,KAAK,CAAC;AACvI;AAEA,eAAe,qBACb,IACA,MACA,UACe;AACf,QAAM,OAAO,MAAM,eAAe,IAAI,MAAM,QAAQ;AACpD,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,kBAAkB,IAAI,EAAE;AACnD,SAAO;AACT;AAYA,MAAM,oBAAoB;AAAA,EACxB,OAAO;AAAA,EACP,UAAU;AACZ;AAeO,SAAS,wBAAyC;AACvD,QAAM,aAAa,aAAa,kBAAkB,KAAK,KAAK,SAAS,4BAA4B;AACjG,QAAM,gBAAgB,aAAa,kBAAkB,QAAQ,KAAK,YAAY,4BAA4B;AAC1G,SAAO;AAAA,IACL,EAAE,MAAM,cAAc,OAAO,sBAAsB;AAAA,IACnD,EAAE,MAAM,SAAS,OAAO,WAAW;AAAA,IACnC,EAAE,MAAM,YAAY,OAAO,cAAc;AAAA,EAC3C;AACF;AAmCO,MAAM,2BAA2B,MAAM;AAAA,EAC5C,YAA4B,MAAc;AACxC,UAAM,+CAA+C,IAAI,kBAAkB;AADjD;AAE1B,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,yCAAyC,MAAM;AAAA,EAC1D,YAA4B,SAAmB;AAC7C;AAAA,MACE,2CAA2C,QAAQ,KAAK,IAAI,CAAC;AAAA,IAC/D;AAH0B;AAI1B,SAAK,OAAO;AAAA,EACd;AACF;AASA,eAAsB,mBACpB,IACA,SACmC;AACnC,QAAM;AAAA,IACJ;AAAA,IACA,sBAAsB;AAAA,IACtB,mBAAmB;AAAA,IACnB;AAAA,IACA,wBAAwB;AAAA,EAC1B,IAAI;AACJ,QAAM,oBAAoB,oBAAoB,iBAAiB,SAAS,mBAAmB,CAAC,YAAY;AACxG,QAAM,eAAe,wBACjB,oBACA,kBAAkB,OAAO,CAAC,SAAS,SAAS,YAAY;AAC5D,MAAI,aAAa,WAAW,GAAG;AAC7B,UAAM,IAAI,MAAM,wBAAwB;AAAA,EAC1C;AACA,QAAM,mBAAmB,QAAQ,aAAa,CAAC,GAAG,kBAAkB;AACpE,QAAM,oBAAoB,wBACtB,mBACA,iBAAiB,OAAO,CAAC,SAAS,SAAS,YAAY;AAC3D,QAAM,YAAY,MAAM,KAAK,oBAAI,IAAI,CAAC,GAAG,mBAAmB,GAAG,YAAY,CAAC,CAAC;AAC7E,QAAM,kBAAkB,QAAQ,WAAW,cAAc;AACzD,QAAM,wBAAwB,yBAAyB,eAAe;AAEtE,QAAM,YAAY,YAAY;AAC9B,QAAM,qBAAqB,MAAM;AAAA,IAC/B;AAAA,IACA;AAAA,IACA,EAAE,WAAW,EAAE,KAAK,sBAAsB,SAAS,EAAE,GAAG,WAAW,KAAK;AAAA,IACxE,CAAC;AAAA,IACD,EAAE,UAAU,MAAM,gBAAgB,KAAK;AAAA,EACzC;AACA,QAAM,eAAe,sBAAsB,MAAM;AAAA,IAC/C;AAAA,IACA;AAAA,IACA,EAAE,OAAO,WAAW,WAAW,KAAK;AAAA,IACpC,CAAC;AAAA,IACD,EAAE,UAAU,MAAM,gBAAgB,KAAK;AAAA,EACzC;AACA,MAAI,gBAAgB,kBAAkB;AACpC,UAAM,IAAI,MAAM,aAAa;AAAA,EAC/B;AAEA,MAAI,QAAQ,SAAS;AACnB,UAAM,eAAe,MAAM;AAAA,MACzB;AAAA,MACA;AAAA,MACA,EAAE,MAAM,QAAQ,QAAQ;AAAA,MACxB,CAAC;AAAA,MACD,EAAE,UAAU,MAAM,gBAAgB,KAAK;AAAA,IACzC;AACA,QAAI,cAAc;AAChB,YAAM,IAAI,mBAAmB,QAAQ,OAAO;AAAA,IAC9C;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACJ,MAAI,qBAAqB;AACzB,QAAM,gBAA6G,CAAC;AAEpH,QAAM,GAAG,cAAc,OAAO,QAAQ;AACpC,QAAI,CAAC,aAAc;AACnB,yBAAqB;AACrB,eAAW,aAAa,WAAW,OAAO,aAAa,QAAQ,IAAI;AACnE,qBAAiB,aAAa,iBAAiB,OAAO,aAAa,cAAc,IAAI;AACrF,UAAM,eAAe,kBAAkB,aAAa,YAAY,IAAI,KAAK;AACzE,QAAI,CAAC,cAAc;AACjB,YAAM,IAAI,MAAM,8EAAyE;AAAA,IAC3F;AAEA,UAAM,qBAAqB,KAAK,WAAW,YAAY;AACvD,UAAM,IAAI,MAAM;AAEhB,UAAM,kBAAkB,oBAAI,IAAI,CAAC,GAAG,WAAW,GAAG,YAAY,CAAC;AAC/D,UAAM,QAAQ,MAAM;AAAA,MAClB;AAAA,MACA;AAAA,MACA,EAAE,MAAM,aAAa;AAAA,MACrB,EAAE,UAAU,CAAC,MAAM,EAAE;AAAA,MACrB,EAAE,UAAU,cAAc,gBAAgB,KAAK;AAAA,IACjD;AACA,UAAM,eAAe,IAAI,IAAI,MAAM,IAAI,CAAC,SAAS,KAAK,KAAK,IAAI,CAAC;AAChE,eAAW,YAAY,iBAAiB;AACtC,UAAI,CAAC,aAAa,IAAI,QAAQ,GAAG;AAC/B,cAAM,OAAO,MAAM,qBAAqB,KAAK,UAAU,YAAY;AACnE,YAAI,QAAQ,IAAI,OAAO,UAAU,EAAE,MAAM,cAAc,MAAM,WAAW,oBAAI,KAAK,EAAE,CAAC,CAAC;AAAA,MACvF;AAAA,IACF;AACA,UAAM,IAAI,MAAM;AAChB,UAAM,QAAQ,MAAM,KAAK,oBAAI,IAAI,CAAC,GAAG,cAAc,GAAG,SAAS,CAAC,CAAC;AACjE,kBAAc,KAAK,EAAE,MAAM,cAAc,OAAO,SAAS,MAAM,CAAC;AAAA,EAClE,CAAC;AAED,MAAI,CAAC,cAAc;AACjB,UAAM,YAMD;AAAA,MACH,EAAE,OAAO,YAAY,OAAO,OAAO,cAAc,MAAM,mBAAmB,WAAW,EAAE;AAAA,IACzF;AACA,QAAI,qBAAqB;AACvB,YAAM,gBAAgB,aAAa,kBAAkB,KAAK;AAC1D,YAAM,mBAAmB,aAAa,kBAAkB,QAAQ;AAChE,YAAM,aAAa,iBAAiB,SAAS,4BAA4B;AACzE,YAAM,gBAAgB,oBAAoB,YAAY,4BAA4B;AAClF,YAAM,cAAc,aAAa,wBAAwB;AACzD,YAAM,iBAAiB,aAAa,2BAA2B;AAC/D,YAAM,eAAe,QAAQ,IAAI,aAAa;AAC9C,YAAM,YAAY,QAAQ,8BAA8B;AACxD,UAAI,gBAAgB,CAAC,WAAW;AAC9B,cAAM,UAAoB,CAAC;AAC3B,YAAI,CAAC,YAAa,SAAQ,KAAK,wBAAwB;AACvD,YAAI,CAAC,eAAgB,SAAQ,KAAK,2BAA2B;AAC7D,YAAI,QAAQ,QAAQ;AAClB,gBAAM,IAAI,iCAAiC,OAAO;AAAA,QACpD;AAAA,MACF;AAQA,YAAM,mBAAmB,eAAe,wBAAwB,IAAI;AACpE,YAAM,sBAAsB,eAAe,wBAAwB,IAAI;AACvE,YAAM,qBAAqB,eAAe;AAC1C,YAAM,wBAAwB,kBAAkB;AAChD,YAAM,oBAAoB,MAAM,KAAK,oBAAoB,EAAE;AAC3D,YAAM,uBAAuB,MAAM,KAAK,uBAAuB,EAAE;AACjE,wBAAkB,WAAW;AAAA,QAC3B,OAAO;AAAA,QACP,OAAO,CAAC,OAAO;AAAA,QACf,cAAc;AAAA,QACd,mBAAmB,cAAc,OAAO;AAAA,MAC1C,CAAC;AACD,wBAAkB,WAAW;AAAA,QAC3B,OAAO;AAAA,QACP,OAAO,CAAC,UAAU;AAAA,QAClB,cAAc;AAAA,QACd,mBAAmB,iBAAiB,OAAO;AAAA,MAC7C,CAAC;AAAA,IACH;AACA,UAAM,eAAe,MAAM,oBAAoB,WAAW;AAE1D,UAAM,GAAG,cAAc,OAAO,QAAQ;AACpC,YAAM,SAAS,IAAI,OAAO,QAAQ;AAAA,QAChC,MAAM,QAAQ;AAAA,QACd,UAAU;AAAA,QACV,WAAW,oBAAI,KAAK;AAAA,QACpB,WAAW,oBAAI,KAAK;AAAA,MACtB,CAAC;AACD,UAAI,QAAQ,MAAM;AAClB,YAAM,IAAI,MAAM;AAEhB,YAAM,eAAe,IAAI,OAAO,cAAc;AAAA,QAC5C,MAAM,QAAQ;AAAA,QACd,MAAM,QAAQ,WAAW;AAAA,QACzB;AAAA,QACA,UAAU;AAAA,QACV,OAAO;AAAA,QACP,aAAa,CAAC;AAAA,QACd,UAAU,CAAC;AAAA,QACX,eAAe,CAAC;AAAA,QAChB,yBAAyB;AAAA,QACzB,WAAW,oBAAI,KAAK;AAAA,QACpB,WAAW,oBAAI,KAAK;AAAA,MACtB,CAAC;AACD,UAAI,QAAQ,YAAY;AACxB,YAAM,IAAI,MAAM;AAEhB,iBAAW,OAAO,OAAO,EAAE;AAC3B,uBAAiB,OAAO,aAAa,EAAE;AACvC,YAAM,eAAe;AAErB,UAAI,8BAA8B,GAAG;AACnC,YAAI;AACF,gBAAM,MAAM,iBAAiB;AAC7B,cAAI,IAAI,UAAU,GAAG;AACnB,gBAAI,yBAAyB,GAAG;AAC9B,qBAAO,KAAK,2BAA2B,EAAE,UAAU,OAAO,OAAO,EAAE,EAAE,CAAC;AAAA,YACxE;AACA,kBAAM,IAAI,gBAAgB,OAAO,OAAO,EAAE,CAAC;AAC3C,gBAAI,yBAAyB,GAAG;AAC9B,qBAAO,KAAK,mCAAmC,EAAE,UAAU,OAAO,OAAO,EAAE,EAAE,CAAC;AAAA,YAChF;AAAA,UACF,OAAO;AACL,gBAAI,yBAAyB,GAAG;AAC9B,qBAAO,KAAK,iDAAiD,EAAE,UAAU,OAAO,OAAO,EAAE,EAAE,CAAC;AAAA,YAC9F;AAAA,UACF;AAAA,QACF,SAAS,KAAK;AACZ,cAAI,yBAAyB,GAAG;AAC9B,mBAAO,KAAK,+BAA+B,EAAE,IAAI,CAAC;AAAA,UACpD;AAAA,QACF;AAAA,MACF;AAEA,YAAM,qBAAqB,KAAK,WAAW,YAAY;AACvD,YAAM,IAAI,MAAM;AAEhB,UAAI,8BAA8B,GAAG;AAKnC,mBAAW,QAAQ,uBAAuB;AACxC,cAAI,KAAK,aAAa,SAAU;AAChC,gBAAM,WAAW,MAAM,sBAAsB,KAAK,eAAe,EAAE,UAAU,KAAK,UAAU,UAAU,OAAO,IAAI,gBAAgB,aAAa,IAAI,WAAW,KAAK,GAAG,CAAC,GAAG,EAAE,UAAU,OAAO,OAAO,EAAE,GAAG,gBAAgB,OAAO,aAAa,EAAE,EAAE,CAAC;AACjP,cAAI,CAAC,UAAU;AACb,gBAAI,QAAQ,IAAI,OAAO,eAAe;AAAA,cACpC,UAAU,KAAK;AAAA,cACf,UAAU,OAAO;AAAA,cACjB,gBAAgB,aAAa;AAAA,cAC7B,YAAY,KAAK;AAAA,cACjB,UAAU;AAAA,cACV,WAAW,oBAAI,KAAK;AAAA,cACpB,WAAW,oBAAI,KAAK;AAAA,YACtB,CAAC,CAAC;AAAA,UACJ,OAAO;AACL,qBAAS,aAAa,KAAK;AAC3B,qBAAS,WAAW;AAAA,UACtB;AAAA,QACF;AACA,cAAM,IAAI,MAAM;AAAA,MAClB;AAAA,IACF,CAAC;AAED,UAAM,GAAG,cAAc,OAAO,QAAQ;AACpC,UAAI,CAAC,YAAY,CAAC,eAAgB;AAClC,YAAM,eAAe;AACrB,YAAM,oBAAoB,8BAA8B,IACpD,IAAI,4BAA4B,KAAY,EAAE,KAAK,iBAAiB,EAAE,CAAC,IACvE;AACJ,UAAI,mBAAmB;AACrB,cAAM,kBAAkB,cAAc,aAAa,OAAO,QAAQ,GAAG,OAAO,cAAc,CAAC;AAC3F,cAAM,kBAAkB,cAAc,aAAa,OAAO,QAAQ,GAAG,IAAI;AAAA,MAC3E;AAEA,iBAAW,QAAQ,WAAW;AAC5B,cAAM,uBAAuB,KAAK,gBAAgB;AAClD,YAAI,OAAO,MAAM,sBAAsB,KAAK,MAAM,EAAE,OAAO,KAAK,MAAM,GAAG,CAAC,GAAG,EAAE,UAAU,YAAY,MAAM,gBAAgB,kBAAkB,KAAK,CAAC;AACnJ,cAAM,UAAU,YAAY,WAAW;AACvC,cAAM,mBAAmB,oBACrB,MAAM,kBAAkB,qBAAqB,aAAa,EAAE,OAAO,KAAK,MAAM,GAAG,UAAU,cAAc,IACzG,EAAE,OAAO,KAAK,OAAO,WAAW,iBAAiB,KAAK,KAAK,EAAE;AACjE,YAAI,MAAM;AACR,eAAK,eAAe;AACpB,eAAK,iBAAiB;AACtB,eAAK,WAAW;AAChB,cAAI,8BAA8B,GAAG;AACnC,iBAAK,QAAQ,iBAAiB;AAC9B,iBAAK,YAAa,iBAAyB,aAAa,iBAAiB,KAAK,KAAK;AAAA,UACrF;AACA,cAAI,KAAK,KAAM,MAAK,OAAO,KAAK;AAChC,cAAI,QAAS,MAAK,cAAc;AAChC,cAAI,QAAQ,IAAI;AAChB,wBAAc,KAAK,EAAE,MAAM,OAAO,KAAK,OAAO,SAAS,OAAO,mBAAmB,KAAK,qBAAqB,KAAK,CAAC;AAAA,QACnH,OAAO;AACL,iBAAO,IAAI,OAAO,MAAM;AAAA,YACtB,OAAQ,iBAAyB,SAAS,KAAK;AAAA,YAC/C,WAAW,8BAA8B,IAAK,iBAAyB,aAAa,iBAAiB,KAAK,KAAK,IAAI;AAAA,YACnH,cAAc;AAAA,YACd;AAAA,YACA;AAAA,YACA,MAAM,KAAK,QAAQ;AAAA,YACnB,aAAa;AAAA,YACb,WAAW,oBAAI,KAAK;AAAA,UACtB,CAAC;AACD,cAAI,QAAQ,IAAI;AAChB,wBAAc,KAAK,EAAE,MAAM,OAAO,KAAK,OAAO,SAAS,MAAM,mBAAmB,KAAK,qBAAqB,KAAK,CAAC;AAAA,QAClH;AACA,cAAM,IAAI,MAAM;AAChB,mBAAW,YAAY,KAAK,OAAO;AACjC,gBAAM,OAAO,MAAM,qBAAqB,KAAK,UAAU,YAAY;AACnE,gBAAM,eAAe,MAAM,sBAAsB,KAAK,UAAU,EAAE,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,UAAU,YAAY,MAAM,gBAAgB,KAAK,CAAC;AACxI,cAAI,CAAC,aAAc,KAAI,QAAQ,IAAI,OAAO,UAAU,EAAE,MAAM,MAAM,WAAW,oBAAI,KAAK,EAAE,CAAC,CAAC;AAAA,QAC5F;AACA,cAAM,IAAI,MAAM;AAAA,MAClB;AAAA,IACF,CAAC;AAAA,EACH;AAEA,MAAI,CAAC,YAAY,CAAC,gBAAgB;AAChC,UAAM,IAAI,MAAM,cAAc;AAAA,EAChC;AAEA,MAAI,CAAC,oBAAoB;AACvB,UAAM,0BAA0B,IAAI,QAAQ;AAAA,EAC9C;AAEA,QAAM,sBAAsB,IAAI,UAAU,iBAAiB,EAAE,sBAAsB,CAAC;AACpF,QAAM,2CAA2C,EAAE;AAGnD,aAAW,OAAO,iBAAiB;AACjC,QAAI,IAAI,OAAO,iBAAiB;AAC9B,YAAM,IAAI,MAAM,gBAAgB,EAAE,IAAI,UAAU,eAAe,CAAC;AAAA,IAClE;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP;AAAA,EACF;AACF;AAEA,SAAS,mBAAmB,OAAwC;AAClE,MAAI,MAAM,eAAe,MAAM,YAAY,KAAK,EAAG,QAAO,MAAM,YAAY,KAAK;AACjF,QAAM,QAAQ,CAAC,MAAM,WAAW,MAAM,QAAQ,EAAE,IAAI,CAAC,UAAU,OAAO,KAAK,CAAC,EAAE,OAAO,OAAO;AAC5F,MAAI,MAAM,OAAQ,QAAO,MAAM,KAAK,GAAG;AACvC,SAAO;AACT;AAEA,SAAS,aAAa,KAAiC;AACrD,QAAM,QAAQ,QAAQ,IAAI,GAAG;AAC7B,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,SAAO,QAAQ,SAAS,IAAI,UAAU;AACxC;AAEA,SAAS,kBACP,WACA,OACA;AACA,MAAI,CAAC,MAAM,MAAO;AAClB,QAAM,aAAa,MAAM,MAAM,YAAY;AAC3C,MAAI,UAAU,KAAK,CAAC,SAAS,KAAK,MAAM,YAAY,MAAM,UAAU,EAAG;AACvE,YAAU,KAAK,KAAK;AACtB;AASA,MAAM,wBAAwB;AAS9B,SAAS,0BAAkC;AACzC,SAAO,YAAY,EAAE,EAAE,SAAS,WAAW;AAC7C;AAEA,SAAS,oBAA6B;AACpC,QAAM,SAAS,kBAAkB,QAAQ,IAAI,aAAa,EAAE;AAC5D,SAAO,WAAW,QAAQ,QAAQ;AACpC;AAEA,SAAS,qCAA8C;AACrD,MAAI,QAAQ,IAAI,iBAAiB,OAAQ,QAAO;AAChD,MAAI,CAAC,aAAa,0BAA0B,EAAG,QAAO;AACtD,SAAO,kBAAkB;AAC3B;AAEA,eAAe,oBAAoB,OAAiD;AAClF,MAAI,OAAO,MAAM,mBAAmB,SAAU,QAAO,MAAM;AAC3D,MAAI,MAAM,SAAU,QAAO,KAAK,MAAM,UAAU,EAAE;AAClD,SAAO;AACT;AAEA,eAAsB,sBACpB,IACA,UACA,SACA,UAA+C,CAAC,GAChD;AACA,QAAM,wBAAwB,QAAQ,yBAAyB;AAC/D,QAAM,eAAe,kBAAkB,QAAQ,KAAK;AACpD,QAAM,iBAAiB,wBAAwB,MAAM,eAAe,IAAI,cAAc,YAAY,IAAI;AACtG,QAAM,YAAY,MAAM,eAAe,IAAI,SAAS,YAAY;AAChE,QAAM,eAAe,MAAM,eAAe,IAAI,YAAY,YAAY;AAGtE,QAAM,eAAe,CAAC,cAAc,SAAS,UAAU;AACvD,QAAM,qBAA+B,CAAC;AACtC,QAAM,gBAA0B,CAAC;AACjC,QAAM,mBAA6B,CAAC;AACpC,QAAM,qBAAqB,oBAAI,IAAsB;AAErD,aAAW,OAAO,SAAS;AACzB,UAAM,eAAe,IAAI,OAAO;AAChC,QAAI,CAAC,aAAc;AACnB,QAAI,aAAa,WAAY,oBAAmB,KAAK,GAAG,aAAa,UAAU;AAC/E,QAAI,aAAa,MAAO,eAAc,KAAK,GAAG,aAAa,KAAK;AAChE,QAAI,aAAa,SAAU,kBAAiB,KAAK,GAAG,aAAa,QAAQ;AAGzE,eAAW,CAAC,UAAU,QAAQ,KAAK,OAAO,QAAQ,YAAY,GAAG;AAC/D,UAAK,aAAmC,SAAS,QAAQ,EAAG;AAC5D,UAAI,CAAC,MAAM,QAAQ,QAAQ,EAAG;AAC9B,YAAM,WAAW,mBAAmB,IAAI,QAAQ,KAAK,CAAC;AACtD,eAAS,KAAK,GAAG,QAAQ;AACzB,yBAAmB,IAAI,UAAU,QAAQ;AAAA,IAC3C;AAAA,EACF;AAEA,SAAO,KAAK,gCAAgC;AAAA,IAC1C,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,GAAI,mBAAmB,OAAO,IAC1B,OAAO,YAAY,kBAAkB,IACrC,CAAC;AAAA,EACP,CAAC;AAED,MAAI,yBAAyB,gBAAgB;AAC3C,UAAM,iBAAiB,IAAI,gBAAgB,UAAU,oBAAoB,EAAE,cAAc,KAAK,CAAC;AAAA,EACjG;AACA,MAAI,WAAW;AACb,UAAM,iBAAiB,IAAI,WAAW,UAAU,aAAa;AAAA,EAC/D;AACA,MAAI,cAAc;AAChB,UAAM,iBAAiB,IAAI,cAAc,UAAU,gBAAgB;AAAA,EACrE;AAMA,aAAW,CAAC,UAAU,QAAQ,KAAK,oBAAoB;AACrD,UAAM,OAAO,MAAM,eAAe,IAAI,UAAU,YAAY;AAC5D,QAAI,MAAM;AACR,YAAM,iBAAiB,IAAI,MAAM,UAAU,QAAQ;AAAA,IACrD;AAAA,EACF;AACF;AAOA,eAAsB,qBACpB,IACA,UACA,SACe;AACf,QAAM,kBAAkB,WAAW,cAAc;AACjD,QAAM,eAAe,kBAAkB,QAAQ,KAAK;AACpD,QAAM,eAAe,CAAC,cAAc,SAAS,UAAU;AACvD,QAAM,qBAAqB,oBAAI,IAAsB;AAErD,aAAW,OAAO,iBAAiB;AACjC,UAAM,eAAe,IAAI,OAAO;AAChC,QAAI,CAAC,aAAc;AACnB,eAAW,CAAC,UAAU,QAAQ,KAAK,OAAO,QAAQ,YAAY,GAAG;AAC/D,UAAI,aAAa,SAAS,QAAQ,EAAG;AACrC,UAAI,CAAC,MAAM,QAAQ,QAAQ,EAAG;AAC9B,YAAM,WAAW,mBAAmB,IAAI,QAAQ,KAAK,CAAC;AACtD,eAAS,KAAK,GAAG,QAAQ;AACzB,yBAAmB,IAAI,UAAU,QAAQ;AAAA,IAC3C;AAAA,EACF;AAEA,MAAI,mBAAmB,SAAS,EAAG;AAEnC,MAAI,SAAS;AACb,aAAW,CAAC,UAAU,QAAQ,KAAK,oBAAoB;AACrD,UAAM,OAAO,MAAM,eAAe,IAAI,UAAU,YAAY;AAC5D,QAAI,MAAM;AACR,YAAM,iBAAiB,IAAI,MAAM,UAAU,QAAQ;AACnD;AAAA,IACF;AAAA,EACF;AACA,MAAI,SAAS,GAAG;AACd,WAAO,KAAK,2BAA2B,EAAE,OAAO,CAAC;AAAA,EACnD;AACF;AAEA,eAAe,iBACb,IACA,MACA,UACA,UACA,UAAsC,CAAC,GACvC;AACA,QAAM,WAAW,MAAM,sBAAsB,IAAI,SAAS,EAAE,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,UAAU,gBAAgB,KAAK,CAAC;AACpH,MAAI,CAAC,UAAU;AACb,UAAM,MAAM,GAAG,OAAO,SAAS;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,cAAc;AAAA,MACd,cAAc,CAAC,CAAC,QAAQ;AAAA,MACxB,WAAW,oBAAI,KAAK;AAAA,IACtB,CAAC;AACD,UAAM,GAAG,QAAQ,GAAG,EAAE,MAAM;AAC5B;AAAA,EACF;AACA,QAAM,kBAAkB,MAAM,QAAQ,SAAS,YAAY,IAAI,SAAS,eAAe,CAAC;AACxF,QAAM,SAAS,MAAM,KAAK,oBAAI,IAAI,CAAC,GAAG,iBAAiB,GAAG,QAAQ,CAAC,CAAC;AACpE,QAAM,UACJ,OAAO,WAAW,gBAAgB,UAClC,OAAO,KAAK,CAAC,OAAO,UAAU,UAAU,gBAAgB,KAAK,CAAC;AAChE,MAAI,QAAS,UAAS,eAAe;AACrC,MAAI,QAAQ,gBAAgB,CAAC,SAAS,cAAc;AAClD,aAAS,eAAe;AAAA,EAC1B;AACA,MAAI,WAAW,QAAQ,cAAc;AACnC,UAAM,GAAG,QAAQ,QAAQ,EAAE,MAAM;AAAA,EACnC;AACF;AAaA,eAAsB,2CAA2C,IAAmB;AAClF,MAAI,QAAQ,IAAI,oCAAoC,OAAQ;AAC5D,MAAI,mCAAmC,EAAG;AAC1C,aAAW,EAAE,MAAM,MAAM,KAAK,sBAAsB,GAAG;AACrD,QAAI;AACF,YAAM,OAAO,MAAM;AAAA,QACjB;AAAA,QACA;AAAA,QACA,EAAE,MAAM;AAAA,QACR,CAAC;AAAA,QACD,EAAE,UAAU,MAAM,gBAAgB,KAAK;AAAA,MACzC;AACA,UAAI,CAAC,KAAM;AACX,UAAI,QAAQ;AACZ,UAAI,KAAK,cAAc;AACrB,aAAK,eAAe;AACpB,gBAAQ;AAAA,MACV;AACA,UAAI,KAAK,gBAAgB,OAAO;AAC9B,aAAK,cAAc;AACnB,gBAAQ;AAAA,MACV;AACA,UAAI,OAAO;AACT,cAAM,GAAG,QAAQ,IAAI,EAAE,MAAM;AAAA,MAC/B;AAAA,IACF,SAAS,OAAO;AACd,aAAO,MAAM,kCAAkC,EAAE,MAAM,OAAO,KAAK,MAAM,CAAC;AAAA,IAC5E;AAAA,EACF;AACF;AASA,MAAM,kDAAkD;AAGxD,SAAS,gBAA0B;AACjC,MAAI;AACF,UAAM,EAAE,WAAW,IAAI,QAAQ,2CAA2C;AAC1E,WAAO,WAAW;AAAA,EACpB,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;",
|
|
4
|
+
"sourcesContent": ["import { hash } from 'bcryptjs'\nimport { randomBytes } from 'node:crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { Role, RoleAcl, User, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport { Tenant, Organization } from '@open-mercato/core/modules/directory/data/entities'\nimport { rebuildHierarchyForTenant } from '@open-mercato/core/modules/directory/lib/hierarchy'\nimport { normalizeTenantId } from './tenantAccess'\nimport { computeEmailHash, emailHashLookupValues } from '@open-mercato/core/modules/auth/lib/emailHash'\nimport { getDefaultEncryptionMaps, type Module } from '@open-mercato/shared/modules/registry'\nimport { isEncryptionDebugEnabled, isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'\nimport { EncryptionMap } from '@open-mercato/core/modules/entities/data/entities'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('auth').child({ component: 'setup' })\n\nconst DEFAULT_ROLE_NAMES = ['employee', 'admin', 'superadmin'] as const\nconst DEMO_SUPERADMIN_EMAIL = 'superadmin@acme.com'\nconst DEFAULT_DERIVED_EMAIL_DOMAIN = DEMO_SUPERADMIN_EMAIL.split('@')[1] ?? 'acme.com'\n\nexport type EnsureRolesOptions = {\n roleNames?: string[]\n tenantId?: string\n}\n\nasync function ensureRolesInContext(\n em: EntityManager,\n roleNames: string[],\n tenantId: string,\n) {\n for (const name of roleNames) {\n const existing = await findOneWithDecryption(em, Role, { name, tenantId }, {}, { tenantId, organizationId: null })\n if (existing) continue\n em.persist(em.create(Role, { name, tenantId, minActiveHolders: name === 'admin' ? 1 : 0, createdAt: new Date() }))\n }\n}\n\nexport async function ensureRoles(em: EntityManager, options: EnsureRolesOptions = {}) {\n const roleNames = options.roleNames ?? [...DEFAULT_ROLE_NAMES]\n const tenantId = normalizeTenantId(options.tenantId ?? null)\n if (!tenantId) {\n throw new Error('ensureRoles requires a tenantId \u2014 global roles are not supported')\n }\n await em.transactional(async (tem) => {\n await ensureRolesInContext(tem, roleNames, tenantId)\n await tem.flush()\n })\n}\n\nasync function findRoleByName(\n em: EntityManager,\n name: string,\n tenantId: string | null,\n): Promise<Role | null> {\n const normalizedTenant = normalizeTenantId(tenantId ?? null) ?? null\n return findOneWithDecryption(em, Role, { name, tenantId: normalizedTenant }, {}, { tenantId: normalizedTenant, organizationId: null })\n}\n\nasync function findRoleByNameOrFail(\n em: EntityManager,\n name: string,\n tenantId: string | null,\n): Promise<Role> {\n const role = await findRoleByName(em, name, tenantId)\n if (!role) throw new Error(`ROLE_NOT_FOUND:${name}`)\n return role\n}\n\ntype PrimaryUserInput = {\n email: string\n password?: string\n hashedPassword?: string | null\n firstName?: string | null\n lastName?: string | null\n displayName?: string | null\n confirm?: boolean\n}\n\nconst DERIVED_EMAIL_ENV = {\n admin: 'OM_INIT_ADMIN_EMAIL',\n employee: 'OM_INIT_EMPLOYEE_EMAIL',\n} as const\n\nexport type DemoUserRole = 'superadmin' | 'admin' | 'employee'\nexport type DemoUserEmail = { role: DemoUserRole; email: string }\n\n/**\n * Returns the canonical list of demo user emails the setup path may have\n * seeded. Honors OM_INIT_ADMIN_EMAIL / OM_INIT_EMPLOYEE_EMAIL exactly the\n * same way the derived-user seeding branch does, so the deactivation loop\n * never drifts from the seeding loop.\n *\n * Pure function \u2014 no side effects, safe to unit-test in isolation.\n *\n * @internal Exported for tests; not part of the public auth API.\n */\nexport function resolveDemoUserEmails(): DemoUserEmail[] {\n const adminEmail = readEnvValue(DERIVED_EMAIL_ENV.admin) ?? `admin@${DEFAULT_DERIVED_EMAIL_DOMAIN}`\n const employeeEmail = readEnvValue(DERIVED_EMAIL_ENV.employee) ?? `employee@${DEFAULT_DERIVED_EMAIL_DOMAIN}`\n return [\n { role: 'superadmin', email: DEMO_SUPERADMIN_EMAIL },\n { role: 'admin', email: adminEmail },\n { role: 'employee', email: employeeEmail },\n ]\n}\n\nexport type SetupInitialTenantOptions = {\n orgName: string\n primaryUser: PrimaryUserInput\n roleNames?: string[]\n includeDerivedUsers?: boolean\n failIfUserExists?: boolean\n primaryUserRoles?: string[]\n includeSuperadminRole?: boolean\n /** Optional list of enabled modules. When provided, module setup hooks are called. */\n modules?: Module[]\n /**\n * Optional global slug to persist on the new Organization. When set, a\n * pre-flight uniqueness check across all tenants throws OrgSlugExistsError\n * on collision so scriptable callers can fail loudly instead of silently\n * reusing or clobbering an existing organization.\n */\n orgSlug?: string\n /**\n * Opt-in flag that allows seeding the derived admin/employee accounts when\n * the OM_INIT_ADMIN_PASSWORD / OM_INIT_EMPLOYEE_PASSWORD env vars are unset.\n *\n * - In non-production (`NODE_ENV !== 'production'`): the demo accounts are\n * seeded with the well-known `'secret'` password so local dev workflows\n * (e.g. `mercato init`) stay predictable for developers.\n * - In production: the fallback uses a randomly generated 96-bit password\n * (printed once by the CLI) so an operator who deliberately opts in still\n * avoids the historical hardcoded credential. When this flag is false/unset\n * in production and the env vars are missing, `DerivedUserPasswordRequiredError`\n * is thrown instead of silently seeding any account.\n */\n allowDemoDerivedPasswords?: boolean\n}\n\nexport class OrgSlugExistsError extends Error {\n constructor(public readonly slug: string) {\n super(`ORG_SLUG_EXISTS: an organization with slug \"${slug}\" already exists`)\n this.name = 'OrgSlugExistsError'\n }\n}\n\nexport class DerivedUserPasswordRequiredError extends Error {\n constructor(public readonly missing: string[]) {\n super(\n `DERIVED_USER_PASSWORD_REQUIRED: missing ${missing.join(', ')} (set the env vars, pass allowDemoDerivedPasswords: true / --include-demo-users in non-production, or disable derived user seeding)`,\n )\n this.name = 'DerivedUserPasswordRequiredError'\n }\n}\n\nexport type SetupInitialTenantResult = {\n tenantId: string\n organizationId: string\n users: Array<{ user: User; roles: string[]; created: boolean; generatedPassword?: string | null }>\n reusedExistingUser: boolean\n}\n\nexport async function setupInitialTenant(\n em: EntityManager,\n options: SetupInitialTenantOptions,\n): Promise<SetupInitialTenantResult> {\n const {\n primaryUser,\n includeDerivedUsers = true,\n failIfUserExists = false,\n primaryUserRoles,\n includeSuperadminRole = true,\n } = options\n const primaryRolesInput = primaryUserRoles && primaryUserRoles.length ? primaryUserRoles : ['superadmin']\n const primaryRoles = includeSuperadminRole\n ? primaryRolesInput\n : primaryRolesInput.filter((role) => role !== 'superadmin')\n if (primaryRoles.length === 0) {\n throw new Error('PRIMARY_ROLES_REQUIRED')\n }\n const defaultRoleNames = options.roleNames ?? [...DEFAULT_ROLE_NAMES]\n const resolvedRoleNames = includeSuperadminRole\n ? defaultRoleNames\n : defaultRoleNames.filter((role) => role !== 'superadmin')\n const roleNames = Array.from(new Set([...resolvedRoleNames, ...primaryRoles]))\n const resolvedModules = options.modules ?? tryGetModules()\n const defaultEncryptionMaps = getDefaultEncryptionMaps(resolvedModules)\n\n const mainEmail = primaryUser.email\n const existingUserByHash = await findOneWithDecryption(\n em,\n User,\n { emailHash: { $in: emailHashLookupValues(mainEmail) }, deletedAt: null },\n {},\n { tenantId: null, organizationId: null },\n )\n const existingUser = existingUserByHash ?? await findOneWithDecryption(\n em,\n User,\n { email: mainEmail, deletedAt: null },\n {},\n { tenantId: null, organizationId: null },\n )\n if (existingUser && failIfUserExists) {\n throw new Error('USER_EXISTS')\n }\n\n if (options.orgSlug) {\n const slugConflict = await findOneWithDecryption(\n em,\n Organization,\n { slug: options.orgSlug },\n {},\n { tenantId: null, organizationId: null },\n )\n if (slugConflict) {\n throw new OrgSlugExistsError(options.orgSlug)\n }\n }\n\n let tenantId: string | undefined\n let organizationId: string | undefined\n let reusedExistingUser = false\n const userSnapshots: Array<{ user: User; roles: string[]; created: boolean; generatedPassword?: string | null }> = []\n\n await em.transactional(async (tem) => {\n if (!existingUser) return\n reusedExistingUser = true\n tenantId = existingUser.tenantId ? String(existingUser.tenantId) : undefined\n organizationId = existingUser.organizationId ? String(existingUser.organizationId) : undefined\n const roleTenantId = normalizeTenantId(existingUser.tenantId ?? null) ?? null\n if (!roleTenantId) {\n throw new Error('Cannot reuse a user without a tenantId \u2014 global roles are not supported')\n }\n\n await ensureRolesInContext(tem, roleNames, roleTenantId)\n await tem.flush()\n\n const requiredRoleSet = new Set([...roleNames, ...primaryRoles])\n const links = await findWithDecryption(\n tem,\n UserRole,\n { user: existingUser },\n { populate: ['role'] },\n { tenantId: roleTenantId, organizationId: null },\n )\n const currentRoles = new Set(links.map((link) => link.role.name))\n for (const roleName of requiredRoleSet) {\n if (!currentRoles.has(roleName)) {\n const role = await findRoleByNameOrFail(tem, roleName, roleTenantId)\n tem.persist(tem.create(UserRole, { user: existingUser, role, createdAt: new Date() }))\n }\n }\n await tem.flush()\n const roles = Array.from(new Set([...currentRoles, ...roleNames]))\n userSnapshots.push({ user: existingUser, roles, created: false })\n })\n\n if (!existingUser) {\n const baseUsers: Array<{\n email: string\n roles: string[]\n name?: string | null\n passwordHash?: string | null\n generatedPassword?: string | null\n }> = [\n { email: primaryUser.email, roles: primaryRoles, name: resolvePrimaryName(primaryUser) },\n ]\n if (includeDerivedUsers) {\n const adminOverride = readEnvValue(DERIVED_EMAIL_ENV.admin)\n const employeeOverride = readEnvValue(DERIVED_EMAIL_ENV.employee)\n const adminEmail = adminOverride ?? `admin@${DEFAULT_DERIVED_EMAIL_DOMAIN}`\n const employeeEmail = employeeOverride ?? `employee@${DEFAULT_DERIVED_EMAIL_DOMAIN}`\n const envAdminPwd = readEnvValue('OM_INIT_ADMIN_PASSWORD')\n const envEmployeePwd = readEnvValue('OM_INIT_EMPLOYEE_PASSWORD')\n const isProduction = process.env.NODE_ENV === 'production'\n const allowDemo = options.allowDemoDerivedPasswords === true\n if (isProduction && !allowDemo) {\n const missing: string[] = []\n if (!envAdminPwd) missing.push('OM_INIT_ADMIN_PASSWORD')\n if (!envEmployeePwd) missing.push('OM_INIT_EMPLOYEE_PASSWORD')\n if (missing.length) {\n throw new DerivedUserPasswordRequiredError(missing)\n }\n }\n // In non-production, fall back to the well-known DEMO_DERIVED_PASSWORD so\n // local dev workflows stay predictable (admin@/employee@ login with the\n // documented demo password). In production we either have env-supplied\n // values, or \u2014 for opt-in --include-demo-users flows \u2014 we generate a\n // random one-time password and surface it via `generatedPassword` so the\n // operator can capture it. Production callers without env vars and\n // without the opt-in already threw above.\n const fallbackAdminPwd = isProduction ? generateDerivedPassword() : DEMO_DERIVED_PASSWORD\n const fallbackEmployeePwd = isProduction ? generateDerivedPassword() : DEMO_DERIVED_PASSWORD\n const adminPasswordPlain = envAdminPwd ?? fallbackAdminPwd\n const employeePasswordPlain = envEmployeePwd ?? fallbackEmployeePwd\n const adminPasswordHash = await hash(adminPasswordPlain, 10)\n const employeePasswordHash = await hash(employeePasswordPlain, 10)\n addUniqueBaseUser(baseUsers, {\n email: adminEmail,\n roles: ['admin'],\n passwordHash: adminPasswordHash,\n generatedPassword: envAdminPwd ? null : adminPasswordPlain,\n })\n addUniqueBaseUser(baseUsers, {\n email: employeeEmail,\n roles: ['employee'],\n passwordHash: employeePasswordHash,\n generatedPassword: envEmployeePwd ? null : employeePasswordPlain,\n })\n }\n const passwordHash = await resolvePasswordHash(primaryUser)\n\n await em.transactional(async (tem) => {\n const tenant = tem.create(Tenant, {\n name: options.orgName,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n tem.persist(tenant)\n await tem.flush()\n\n const organization = tem.create(Organization, {\n name: options.orgName,\n slug: options.orgSlug ?? null,\n tenant,\n isActive: true,\n depth: 0,\n ancestorIds: [],\n childIds: [],\n descendantIds: [],\n logoPreserveAspectRatio: false,\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 logger.info('Provisioning tenant DEK', { tenantId: String(tenant.id) })\n }\n await kms.createTenantDek(String(tenant.id))\n if (isEncryptionDebugEnabled()) {\n logger.info('Created tenant DEK during setup', { tenantId: String(tenant.id) })\n }\n } else {\n if (isEncryptionDebugEnabled()) {\n logger.warn('KMS not healthy, skipping tenant DEK creation', { tenantId: String(tenant.id) })\n }\n }\n } catch (err) {\n if (isEncryptionDebugEnabled()) {\n logger.warn('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 // System-scoped maps are resolved from module code, never from a tenant row.\n // Persisting one here would make the tenant-scoped encryption CLIs believe they\n // own that entity and re-wrap its `system:<entityId>` ciphertext under the tenant\n // DEK, which runtime decryption can no longer read.\n for (const spec of defaultEncryptionMaps) {\n if (spec.keyScope === 'system') continue\n const existing = await findOneWithDecryption(tem, EncryptionMap, { entityId: spec.entityId, tenantId: tenant.id, organizationId: organization.id, deletedAt: null }, {}, { tenantId: String(tenant.id), organizationId: String(organization.id) })\n if (!existing) {\n tem.persist(tem.create(EncryptionMap, {\n entityId: spec.entityId,\n tenantId: tenant.id,\n organizationId: organization.id,\n fieldsJson: spec.fields,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n }))\n } else {\n existing.fieldsJson = spec.fields\n existing.isActive = true\n }\n }\n await tem.flush()\n }\n })\n\n await em.transactional(async (tem) => {\n if (!tenantId || !organizationId) return\n const roleTenantId = tenantId\n const encryptionService = isTenantDataEncryptionEnabled()\n ? new TenantDataEncryptionService(tem as any, { kms: createKmsService() })\n : null\n if (encryptionService) {\n await encryptionService.invalidateMap('auth:user', String(tenantId), String(organizationId))\n await encryptionService.invalidateMap('auth:user', String(tenantId), null)\n }\n\n for (const base of baseUsers) {\n const resolvedPasswordHash = base.passwordHash ?? passwordHash\n let user = await findOneWithDecryption(tem, User, { email: base.email }, {}, { tenantId: tenantId ?? null, organizationId: organizationId ?? null })\n const confirm = primaryUser.confirm ?? true\n const encryptedPayload = encryptionService\n ? await encryptionService.encryptEntityPayload('auth:user', { email: base.email }, tenantId, organizationId)\n : { email: base.email, emailHash: computeEmailHash(base.email) }\n if (user) {\n user.passwordHash = resolvedPasswordHash\n user.organizationId = organizationId\n user.tenantId = tenantId\n if (isTenantDataEncryptionEnabled()) {\n user.email = encryptedPayload.email as any\n user.emailHash = (encryptedPayload as any).emailHash ?? computeEmailHash(base.email)\n }\n if (base.name) user.name = base.name\n if (confirm) user.isConfirmed = true\n tem.persist(user)\n userSnapshots.push({ user, roles: base.roles, created: false, generatedPassword: base.generatedPassword ?? null })\n } else {\n user = tem.create(User, {\n email: (encryptedPayload as any).email ?? base.email,\n emailHash: isTenantDataEncryptionEnabled() ? (encryptedPayload as any).emailHash ?? computeEmailHash(base.email) : undefined,\n passwordHash: resolvedPasswordHash,\n organizationId,\n tenantId,\n name: base.name ?? undefined,\n isConfirmed: confirm,\n createdAt: new Date(),\n })\n tem.persist(user)\n userSnapshots.push({ user, roles: base.roles, created: true, generatedPassword: base.generatedPassword ?? null })\n }\n await tem.flush()\n for (const roleName of base.roles) {\n const role = await findRoleByNameOrFail(tem, roleName, roleTenantId)\n const existingLink = await findOneWithDecryption(tem, UserRole, { user, role }, {}, { tenantId: tenantId ?? null, organizationId: null })\n if (!existingLink) tem.persist(tem.create(UserRole, { user, role, createdAt: new Date() }))\n }\n await tem.flush()\n }\n })\n }\n\n if (!tenantId || !organizationId) {\n throw new Error('SETUP_FAILED')\n }\n\n if (!reusedExistingUser) {\n await rebuildHierarchyForTenant(em, tenantId)\n }\n\n await ensureDefaultRoleAcls(em, tenantId, resolvedModules, { includeSuperadminRole })\n await deactivateDemoUsersIfSelfOnboardingEnabled(em)\n\n // Call module onTenantCreated hooks\n for (const mod of resolvedModules) {\n if (mod.setup?.onTenantCreated) {\n await mod.setup.onTenantCreated({ em, tenantId, organizationId })\n }\n }\n\n return {\n tenantId,\n organizationId,\n users: userSnapshots,\n reusedExistingUser,\n }\n}\n\nfunction resolvePrimaryName(input: PrimaryUserInput): string | null {\n if (input.displayName && input.displayName.trim()) return input.displayName.trim()\n const parts = [input.firstName, input.lastName].map((value) => value?.trim()).filter(Boolean)\n if (parts.length) return parts.join(' ')\n return null\n}\n\nfunction readEnvValue(key: string): string | undefined {\n const value = process.env[key]\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\nfunction addUniqueBaseUser(\n baseUsers: Array<{ email: string; roles: string[]; name?: string | null; passwordHash?: string | null; generatedPassword?: string | null }>,\n entry: { email: string; roles: string[]; name?: string | null; passwordHash?: string | null; generatedPassword?: string | null },\n) {\n if (!entry.email) return\n const normalized = entry.email.toLowerCase()\n if (baseUsers.some((user) => user.email.toLowerCase() === normalized)) return\n baseUsers.push(entry)\n}\n\n/**\n * Well-known demo password seeded for derived admin@/employee@ accounts in\n * non-production (`NODE_ENV !== 'production'`) when no env override is set.\n * Keeps the documented `yarn dev` / `mercato init` DX predictable. Never used\n * in production: the production branch either consumes env-supplied values or\n * falls back to `generateDerivedPassword()` so credentials remain non-guessable.\n */\nconst DEMO_DERIVED_PASSWORD = 'secret'\n\n/**\n * Generate a 16-character base64url password (96 bits of entropy) for a derived\n * demo user in production when no env override is provided AND the caller\n * explicitly opted in via `allowDemoDerivedPasswords`. Surfaced via the\n * `users[].generatedPassword` snapshot so CLI callers can print it to the\n * operator \u2014 there is no other recovery path for these credentials.\n */\nfunction generateDerivedPassword(): string {\n return randomBytes(12).toString('base64url')\n}\n\nfunction isDemoModeEnabled(): boolean {\n const parsed = parseBooleanToken(process.env.DEMO_MODE ?? '')\n return parsed === false ? false : true\n}\n\nfunction shouldKeepDemoSuperadminDuringInit(): boolean {\n if (process.env.OM_INIT_FLOW !== 'true') return false\n if (!readEnvValue('OM_INIT_SUPERADMIN_EMAIL')) return false\n return isDemoModeEnabled()\n}\n\nasync function resolvePasswordHash(input: PrimaryUserInput): Promise<string | null> {\n if (typeof input.hashedPassword === 'string') return input.hashedPassword\n if (input.password) return hash(input.password, 10)\n return null\n}\n\nexport async function ensureDefaultRoleAcls(\n em: EntityManager,\n tenantId: string,\n modules: Module[],\n options: { includeSuperadminRole?: boolean } = {},\n) {\n const includeSuperadminRole = options.includeSuperadminRole ?? true\n const roleTenantId = normalizeTenantId(tenantId) ?? null\n const superadminRole = includeSuperadminRole ? await findRoleByName(em, 'superadmin', roleTenantId) : null\n const adminRole = await findRoleByName(em, 'admin', roleTenantId)\n const employeeRole = await findRoleByName(em, 'employee', roleTenantId)\n\n // Merge features from all enabled modules' setup configs\n const builtInRoles = ['superadmin', 'admin', 'employee'] as const\n const superadminFeatures: string[] = []\n const adminFeatures: string[] = []\n const employeeFeatures: string[] = []\n const customRoleFeatures = new Map<string, string[]>()\n\n for (const mod of modules) {\n const roleFeatures = mod.setup?.defaultRoleFeatures\n if (!roleFeatures) continue\n if (roleFeatures.superadmin) superadminFeatures.push(...roleFeatures.superadmin)\n if (roleFeatures.admin) adminFeatures.push(...roleFeatures.admin)\n if (roleFeatures.employee) employeeFeatures.push(...roleFeatures.employee)\n\n // Collect features for custom roles (any key not in builtInRoles)\n for (const [roleName, features] of Object.entries(roleFeatures)) {\n if ((builtInRoles as readonly string[]).includes(roleName)) continue\n if (!Array.isArray(features)) continue\n const existing = customRoleFeatures.get(roleName) ?? []\n existing.push(...features)\n customRoleFeatures.set(roleName, existing)\n }\n }\n\n logger.info('Seeded default role features', {\n superadmin: superadminFeatures,\n admin: adminFeatures,\n employee: employeeFeatures,\n ...(customRoleFeatures.size > 0\n ? Object.fromEntries(customRoleFeatures)\n : {}),\n })\n\n if (includeSuperadminRole && superadminRole) {\n await ensureRoleAclFor(em, superadminRole, tenantId, superadminFeatures, { isSuperAdmin: true })\n }\n if (adminRole) {\n await ensureRoleAclFor(em, adminRole, tenantId, adminFeatures)\n }\n if (employeeRole) {\n await ensureRoleAclFor(em, employeeRole, tenantId, employeeFeatures)\n }\n\n // Seed ACLs for custom roles defined by app modules.\n // NOTE: Custom roles may not exist yet if they are created in seedDefaults\n // (which runs after this function). In that case, use ensureCustomRoleAcls()\n // after seedDefaults to pick them up.\n for (const [roleName, features] of customRoleFeatures) {\n const role = await findRoleByName(em, roleName, roleTenantId)\n if (role) {\n await ensureRoleAclFor(em, role, tenantId, features)\n }\n }\n}\n\n/**\n * Seed ACLs for custom roles defined in module defaultRoleFeatures.\n * Call this AFTER seedDefaults to pick up roles created by app modules.\n * Safe to call multiple times \u2014 ensureRoleAclFor merges features idempotently.\n */\nexport async function ensureCustomRoleAcls(\n em: EntityManager,\n tenantId: string,\n modules?: Module[],\n): Promise<void> {\n const resolvedModules = modules ?? tryGetModules()\n const roleTenantId = normalizeTenantId(tenantId) ?? null\n const builtInRoles = ['superadmin', 'admin', 'employee']\n const customRoleFeatures = new Map<string, string[]>()\n\n for (const mod of resolvedModules) {\n const roleFeatures = mod.setup?.defaultRoleFeatures\n if (!roleFeatures) continue\n for (const [roleName, features] of Object.entries(roleFeatures)) {\n if (builtInRoles.includes(roleName)) continue\n if (!Array.isArray(features)) continue\n const existing = customRoleFeatures.get(roleName) ?? []\n existing.push(...features)\n customRoleFeatures.set(roleName, existing)\n }\n }\n\n if (customRoleFeatures.size === 0) return\n\n let seeded = 0\n for (const [roleName, features] of customRoleFeatures) {\n const role = await findRoleByName(em, roleName, roleTenantId)\n if (role) {\n await ensureRoleAclFor(em, role, tenantId, features)\n seeded++\n }\n }\n if (seeded > 0) {\n logger.info('Seeded custom role ACLs', { seeded })\n }\n}\n\nasync function ensureRoleAclFor(\n em: EntityManager,\n role: Role,\n tenantId: string,\n features: string[],\n options: { isSuperAdmin?: boolean } = {},\n) {\n // Deduplicated on the create path too, not only on merge. `ensureDefaultRoleAcls` concatenates\n // every enabled module's declared features, and two modules legitimately declare the same\n // grant (the example module asks for `payment_gateways.view`, and so does that module itself),\n // so a first init used to store the string twice while the second run silently collapsed it.\n // Same list, two shapes, depending only on how many times setup had run.\n const uniqueFeatures = Array.from(new Set(features))\n const existing = await findOneWithDecryption(em, RoleAcl, { role, tenantId }, {}, { tenantId, organizationId: null })\n if (!existing) {\n const acl = em.create(RoleAcl, {\n role,\n tenantId,\n featuresJson: uniqueFeatures,\n isSuperAdmin: !!options.isSuperAdmin,\n createdAt: new Date(),\n })\n await em.persist(acl).flush()\n return\n }\n const currentFeatures = Array.isArray(existing.featuresJson) ? existing.featuresJson : []\n const merged = Array.from(new Set([...currentFeatures, ...uniqueFeatures]))\n const changed =\n merged.length !== currentFeatures.length ||\n merged.some((value, index) => value !== currentFeatures[index])\n if (changed) existing.featuresJson = merged\n if (options.isSuperAdmin && !existing.isSuperAdmin) {\n existing.isSuperAdmin = true\n }\n if (changed || options.isSuperAdmin) {\n await em.persist(existing).flush()\n }\n}\n\n/**\n * Neutralizes every demo account the setup path may have seeded\n * (superadmin, admin, employee \u2014 honoring OM_INIT_*_EMAIL overrides)\n * when SELF_SERVICE_ONBOARDING_ENABLED is on and the operator did not\n * opt in to keeping demo credentials via shouldKeepDemoSuperadminDuringInit.\n *\n * Each user is processed in its own try/catch so a single failure (e.g.\n * decryption error on a legacy row) does not skip the remaining accounts.\n *\n * @internal Exported for tests; not part of the public auth API.\n */\nexport async function deactivateDemoUsersIfSelfOnboardingEnabled(em: EntityManager) {\n if (process.env.SELF_SERVICE_ONBOARDING_ENABLED !== 'true') return\n if (shouldKeepDemoSuperadminDuringInit()) return\n for (const { role, email } of resolveDemoUserEmails()) {\n try {\n const user = await findOneWithDecryption(\n em,\n User,\n { email },\n {},\n { tenantId: null, organizationId: null },\n )\n if (!user) continue\n let dirty = false\n if (user.passwordHash) {\n user.passwordHash = null\n dirty = true\n }\n if (user.isConfirmed !== false) {\n user.isConfirmed = false\n dirty = true\n }\n if (dirty) {\n await em.persist(user).flush()\n }\n } catch (error) {\n logger.error('Failed to deactivate demo user', { role, email, err: error })\n }\n }\n}\n\n/**\n * @deprecated Renamed to {@link deactivateDemoUsersIfSelfOnboardingEnabled}\n * because the helper now neutralizes admin/employee demo accounts in addition\n * to superadmin (security tracker finding #5). Kept as an internal alias to\n * avoid breaking any out-of-tree caller that imported the old name; will be\n * removed in a future major.\n */\nconst deactivateDemoSuperAdminIfSelfOnboardingEnabled = deactivateDemoUsersIfSelfOnboardingEnabled\n\n/** Try to get modules from runtime registry; returns empty array if not yet registered. */\nfunction tryGetModules(): Module[] {\n try {\n const { getModules } = require('@open-mercato/shared/lib/modules/registry')\n return getModules()\n } catch {\n return []\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -12,10 +12,11 @@ const injectionTable = {
|
|
|
12
12
|
],
|
|
13
13
|
// Fallback alias when forms derive spot id from entity id
|
|
14
14
|
"crud-form:catalog.catalog_product": "catalog.injection.product-seo",
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
// `ProductsDataTable` passes `perspective={{ tableId: 'catalog.products.list' }}`,
|
|
16
|
+
// which wins over `injectionSpotId` when `DataTable` derives `extensionTableId`,
|
|
17
|
+
// so the deep-extension surfaces only ever resolve the `catalog.products.list`
|
|
18
|
+
// spelling. The base-spot spelling binds solely for the header/footer/toolbar/
|
|
19
|
+
// search-trailing family below.
|
|
19
20
|
"data-table:catalog.products.list:bulk-actions": {
|
|
20
21
|
widgetId: "catalog.injection.product-bulk-delete",
|
|
21
22
|
priority: 40
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/catalog/widgets/injection-table.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'\n\n/**\n * Catalog module injection table\n * Maps injection spot IDs to widget IDs for automatic widget injection\n */\nexport const injectionTable: ModuleInjectionTable = {\n // Inject the SEO helper widget into the catalog product CRUD form\n 'crud-form:catalog.product': [\n {\n widgetId: 'catalog.injection.product-seo',\n kind: 'group',\n column: 2,\n groupLabel: 'catalog.widgets.productSeo.groupLabel',\n groupDescription: 'catalog.widgets.productSeo.groupDescription',\n priority: 50,\n },\n ],\n // Fallback alias when forms derive spot id from entity id\n 'crud-form:catalog.catalog_product': 'catalog.injection.product-seo',\n '
|
|
5
|
-
"mappings": "AAMO,MAAM,iBAAuC;AAAA;AAAA,EAElD,6BAA6B;AAAA,IAC3B;AAAA,MACE,UAAU;AAAA,MACV,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,kBAAkB;AAAA,MAClB,UAAU;AAAA,IACZ;AAAA,EACF;AAAA;AAAA,EAEA,qCAAqC;AAAA
|
|
4
|
+
"sourcesContent": ["import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'\n\n/**\n * Catalog module injection table\n * Maps injection spot IDs to widget IDs for automatic widget injection\n */\nexport const injectionTable: ModuleInjectionTable = {\n // Inject the SEO helper widget into the catalog product CRUD form\n 'crud-form:catalog.product': [\n {\n widgetId: 'catalog.injection.product-seo',\n kind: 'group',\n column: 2,\n groupLabel: 'catalog.widgets.productSeo.groupLabel',\n groupDescription: 'catalog.widgets.productSeo.groupDescription',\n priority: 50,\n },\n ],\n // Fallback alias when forms derive spot id from entity id\n 'crud-form:catalog.catalog_product': 'catalog.injection.product-seo',\n // `ProductsDataTable` passes `perspective={{ tableId: 'catalog.products.list' }}`,\n // which wins over `injectionSpotId` when `DataTable` derives `extensionTableId`,\n // so the deep-extension surfaces only ever resolve the `catalog.products.list`\n // spelling. The base-spot spelling binds solely for the header/footer/toolbar/\n // search-trailing family below.\n 'data-table:catalog.products.list:bulk-actions': {\n widgetId: 'catalog.injection.product-bulk-delete',\n priority: 40,\n },\n // Step 5.15 \u2014 Phase 3 WS-D.\n // Merchandising assistant trigger moved behind the injection system so the\n // products list page no longer imports `MerchandisingAssistantSheet`\n // directly. The DataTable's `injectionSpotId=\"data-table:catalog.products\"`\n // exposes the `:search-trailing` variant that this widget targets \u2014 the\n // round icon-only trigger lives next to the products list search input.\n 'data-table:catalog.products:search-trailing': [\n {\n widgetId: 'catalog.injection.merchandising-assistant-trigger',\n priority: 100,\n },\n ],\n}\n\nexport default injectionTable\n"],
|
|
5
|
+
"mappings": "AAMO,MAAM,iBAAuC;AAAA;AAAA,EAElD,6BAA6B;AAAA,IAC3B;AAAA,MACE,UAAU;AAAA,MACV,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,kBAAkB;AAAA,MAClB,UAAU;AAAA,IACZ;AAAA,EACF;AAAA;AAAA,EAEA,qCAAqC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMrC,iDAAiD;AAAA,IAC/C,UAAU;AAAA,IACV,UAAU;AAAA,EACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,+CAA+C;AAAA,IAC7C;AAAA,MACE,UAAU;AAAA,MACV,UAAU;AAAA,IACZ;AAAA,EACF;AACF;AAEA,IAAO,0BAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,6 +2,7 @@ import { RateFetchingService } from "./services/rateFetchingService.js";
|
|
|
2
2
|
import { ExchangeRateService } from "./services/exchangeRateService.js";
|
|
3
3
|
import { NBPProvider } from "./services/providers/nbp.js";
|
|
4
4
|
import { RaiffeisenPolandProvider } from "./services/providers/raiffeisen.js";
|
|
5
|
+
import { listCurrencyRateProviders } from "./services/providers/registry.js";
|
|
5
6
|
import { BaseCurrencyService } from "./services/baseCurrencyService.js";
|
|
6
7
|
function register(container) {
|
|
7
8
|
container.register({
|
|
@@ -11,6 +12,7 @@ function register(container) {
|
|
|
11
12
|
const service = new RateFetchingService(em);
|
|
12
13
|
service.registerProvider(new NBPProvider());
|
|
13
14
|
service.registerProvider(new RaiffeisenPolandProvider());
|
|
15
|
+
for (const provider of listCurrencyRateProviders()) service.registerProvider(provider);
|
|
14
16
|
return service;
|
|
15
17
|
}
|
|
16
18
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/currencies/di.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { RateFetchingService } from './services/rateFetchingService'\nimport { ExchangeRateService } from './services/exchangeRateService'\nimport { NBPProvider } from './services/providers/nbp'\nimport { RaiffeisenPolandProvider } from './services/providers/raiffeisen'\nimport { BaseCurrencyService } from './services/baseCurrencyService'\n\nexport function register(container: AppContainer) {\n container.register({\n rateFetchingService: {\n resolve: (c) => {\n const em = c.resolve<EntityManager>('em')\n const service = new RateFetchingService(em)\n \n // Register default providers\n service.registerProvider(new NBPProvider())\n service.registerProvider(new RaiffeisenPolandProvider())\n \n return service\n },\n },\n exchangeRateService: {\n resolve: (c) => {\n const em = c.resolve<EntityManager>('em')\n const rateFetchingService = c.resolve<RateFetchingService>('rateFetchingService')\n return new ExchangeRateService(em, rateFetchingService)\n },\n },\n baseCurrencyService: {\n resolve: (c) => new BaseCurrencyService(c.resolve<EntityManager>('em')),\n },\n })\n}\n"],
|
|
5
|
-
"mappings": "AAEA,SAAS,2BAA2B;AACpC,SAAS,2BAA2B;AACpC,SAAS,mBAAmB;AAC5B,SAAS,gCAAgC;AACzC,SAAS,2BAA2B;AAE7B,SAAS,SAAS,WAAyB;AAChD,YAAU,SAAS;AAAA,IACjB,qBAAqB;AAAA,MACnB,SAAS,CAAC,MAAM;AACd,cAAM,KAAK,EAAE,QAAuB,IAAI;AACxC,cAAM,UAAU,IAAI,oBAAoB,EAAE;AAG1C,gBAAQ,iBAAiB,IAAI,YAAY,CAAC;AAC1C,gBAAQ,iBAAiB,IAAI,yBAAyB,CAAC;
|
|
4
|
+
"sourcesContent": ["import type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { RateFetchingService } from './services/rateFetchingService'\nimport { ExchangeRateService } from './services/exchangeRateService'\nimport { NBPProvider } from './services/providers/nbp'\nimport { RaiffeisenPolandProvider } from './services/providers/raiffeisen'\nimport { listCurrencyRateProviders } from './services/providers/registry'\nimport { BaseCurrencyService } from './services/baseCurrencyService'\n\nexport function register(container: AppContainer) {\n container.register({\n rateFetchingService: {\n resolve: (c) => {\n const em = c.resolve<EntityManager>('em')\n const service = new RateFetchingService(em)\n \n // Register default providers\n service.registerProvider(new NBPProvider())\n service.registerProvider(new RaiffeisenPolandProvider())\n for (const provider of listCurrencyRateProviders()) service.registerProvider(provider)\n \n return service\n },\n },\n exchangeRateService: {\n resolve: (c) => {\n const em = c.resolve<EntityManager>('em')\n const rateFetchingService = c.resolve<RateFetchingService>('rateFetchingService')\n return new ExchangeRateService(em, rateFetchingService)\n },\n },\n baseCurrencyService: {\n resolve: (c) => new BaseCurrencyService(c.resolve<EntityManager>('em')),\n },\n })\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAS,2BAA2B;AACpC,SAAS,2BAA2B;AACpC,SAAS,mBAAmB;AAC5B,SAAS,gCAAgC;AACzC,SAAS,iCAAiC;AAC1C,SAAS,2BAA2B;AAE7B,SAAS,SAAS,WAAyB;AAChD,YAAU,SAAS;AAAA,IACjB,qBAAqB;AAAA,MACnB,SAAS,CAAC,MAAM;AACd,cAAM,KAAK,EAAE,QAAuB,IAAI;AACxC,cAAM,UAAU,IAAI,oBAAoB,EAAE;AAG1C,gBAAQ,iBAAiB,IAAI,YAAY,CAAC;AAC1C,gBAAQ,iBAAiB,IAAI,yBAAyB,CAAC;AACvD,mBAAW,YAAY,0BAA0B,EAAG,SAAQ,iBAAiB,QAAQ;AAErF,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,qBAAqB;AAAA,MACnB,SAAS,CAAC,MAAM;AACd,cAAM,KAAK,EAAE,QAAuB,IAAI;AACxC,cAAM,sBAAsB,EAAE,QAA6B,qBAAqB;AAChF,eAAO,IAAI,oBAAoB,IAAI,mBAAmB;AAAA,MACxD;AAAA,IACF;AAAA,IACA,qBAAqB;AAAA,MACnB,SAAS,CAAC,MAAM,IAAI,oBAAoB,EAAE,QAAuB,IAAI,CAAC;AAAA,IACxE;AAAA,EACF,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const CURRENCY_RATE_PROVIDER_REGISTRY_KEY = /* @__PURE__ */ Symbol.for("@open-mercato/currencies/rate-provider-registry");
|
|
2
|
+
function getProviderRegistry() {
|
|
3
|
+
const globalScope = globalThis;
|
|
4
|
+
if (!globalScope[CURRENCY_RATE_PROVIDER_REGISTRY_KEY]) {
|
|
5
|
+
globalScope[CURRENCY_RATE_PROVIDER_REGISTRY_KEY] = /* @__PURE__ */ new Map();
|
|
6
|
+
}
|
|
7
|
+
return globalScope[CURRENCY_RATE_PROVIDER_REGISTRY_KEY];
|
|
8
|
+
}
|
|
9
|
+
function registerCurrencyRateProvider(provider) {
|
|
10
|
+
const providerRegistry = getProviderRegistry();
|
|
11
|
+
providerRegistry.set(provider.source, provider);
|
|
12
|
+
return () => {
|
|
13
|
+
if (providerRegistry.get(provider.source) === provider) providerRegistry.delete(provider.source);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function getCurrencyRateProvider(source) {
|
|
17
|
+
return getProviderRegistry().get(source);
|
|
18
|
+
}
|
|
19
|
+
function listCurrencyRateProviders() {
|
|
20
|
+
return Array.from(getProviderRegistry().values());
|
|
21
|
+
}
|
|
22
|
+
function clearCurrencyRateProviders() {
|
|
23
|
+
getProviderRegistry().clear();
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
clearCurrencyRateProviders,
|
|
27
|
+
getCurrencyRateProvider,
|
|
28
|
+
listCurrencyRateProviders,
|
|
29
|
+
registerCurrencyRateProvider
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/currencies/services/providers/registry.ts"],
|
|
4
|
+
"sourcesContent": ["import type { RateProvider } from './base'\n\nconst CURRENCY_RATE_PROVIDER_REGISTRY_KEY = Symbol.for('@open-mercato/currencies/rate-provider-registry')\n\ntype GlobalWithCurrencyRateProviderRegistry = typeof globalThis & {\n [CURRENCY_RATE_PROVIDER_REGISTRY_KEY]?: Map<string, RateProvider>\n}\n\nfunction getProviderRegistry(): Map<string, RateProvider> {\n const globalScope = globalThis as GlobalWithCurrencyRateProviderRegistry\n if (!globalScope[CURRENCY_RATE_PROVIDER_REGISTRY_KEY]) {\n globalScope[CURRENCY_RATE_PROVIDER_REGISTRY_KEY] = new Map<string, RateProvider>()\n }\n return globalScope[CURRENCY_RATE_PROVIDER_REGISTRY_KEY]\n}\n\nexport function registerCurrencyRateProvider(provider: RateProvider): () => void {\n const providerRegistry = getProviderRegistry()\n providerRegistry.set(provider.source, provider)\n return () => {\n if (providerRegistry.get(provider.source) === provider) providerRegistry.delete(provider.source)\n }\n}\n\nexport function getCurrencyRateProvider(source: string): RateProvider | undefined {\n return getProviderRegistry().get(source)\n}\n\nexport function listCurrencyRateProviders(): RateProvider[] {\n return Array.from(getProviderRegistry().values())\n}\n\nexport function clearCurrencyRateProviders(): void {\n getProviderRegistry().clear()\n}\n"],
|
|
5
|
+
"mappings": "AAEA,MAAM,sCAAsC,uBAAO,IAAI,iDAAiD;AAMxG,SAAS,sBAAiD;AACxD,QAAM,cAAc;AACpB,MAAI,CAAC,YAAY,mCAAmC,GAAG;AACrD,gBAAY,mCAAmC,IAAI,oBAAI,IAA0B;AAAA,EACnF;AACA,SAAO,YAAY,mCAAmC;AACxD;AAEO,SAAS,6BAA6B,UAAoC;AAC/E,QAAM,mBAAmB,oBAAoB;AAC7C,mBAAiB,IAAI,SAAS,QAAQ,QAAQ;AAC9C,SAAO,MAAM;AACX,QAAI,iBAAiB,IAAI,SAAS,MAAM,MAAM,SAAU,kBAAiB,OAAO,SAAS,MAAM;AAAA,EACjG;AACF;AAEO,SAAS,wBAAwB,QAA0C;AAChF,SAAO,oBAAoB,EAAE,IAAI,MAAM;AACzC;AAEO,SAAS,4BAA4C;AAC1D,SAAO,MAAM,KAAK,oBAAoB,EAAE,OAAO,CAAC;AAClD;AAEO,SAAS,6BAAmC;AACjD,sBAAoB,EAAE,MAAM;AAC9B;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -101,7 +101,10 @@ async function GET(req) {
|
|
|
101
101
|
const rbac = container.resolve("rbacService");
|
|
102
102
|
const rawTenantParam = url.searchParams.get("tenantId");
|
|
103
103
|
const cookieTenant = getSelectedTenantFromRequest(req);
|
|
104
|
-
const
|
|
104
|
+
const normalizeTenantId = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
105
|
+
const authTenantId = normalizeTenantId(auth.tenantId);
|
|
106
|
+
const actorTenantField = auth.actorTenantId;
|
|
107
|
+
const actorHomeTenantId = actorTenantField === void 0 ? authTenantId : normalizeTenantId(actorTenantField);
|
|
105
108
|
const actorIsSuperAdmin = auth.isSuperAdmin === true;
|
|
106
109
|
let requestedTenantId = rawTenantParam ?? (cookieTenant ?? void 0);
|
|
107
110
|
if (requestedTenantId === "") requestedTenantId = void 0;
|
|
@@ -114,12 +117,12 @@ async function GET(req) {
|
|
|
114
117
|
name: typeof tenant.name === "string" && tenant.name.length > 0 ? tenant.name : String(tenant.id),
|
|
115
118
|
isActive: tenant.isActive !== false
|
|
116
119
|
}));
|
|
117
|
-
if (!tenantId) tenantId =
|
|
120
|
+
if (!tenantId) tenantId = actorHomeTenantId ?? (tenantRecords[0]?.id ?? null);
|
|
118
121
|
if (tenantId && tenantRecords.length && !tenantRecords.some((record) => record.id === tenantId)) {
|
|
119
122
|
tenantId = tenantRecords[0]?.id ?? tenantId;
|
|
120
123
|
}
|
|
121
124
|
} else {
|
|
122
|
-
tenantId =
|
|
125
|
+
tenantId = authTenantId;
|
|
123
126
|
}
|
|
124
127
|
if (!tenantId) {
|
|
125
128
|
return NextResponse.json({
|
|
@@ -169,7 +172,7 @@ async function GET(req) {
|
|
|
169
172
|
logger.warn("Failed to read organization switcher cache", { err });
|
|
170
173
|
}
|
|
171
174
|
}
|
|
172
|
-
const scopedOrgId =
|
|
175
|
+
const scopedOrgId = authTenantId && authTenantId === tenantId ? auth.orgId ?? null : null;
|
|
173
176
|
const acl = await rbac.loadAcl(auth.sub, { tenantId, organizationId: scopedOrgId });
|
|
174
177
|
const aclIsSuperAdmin = acl?.isSuperAdmin === true;
|
|
175
178
|
const effectiveIsSuperAdmin = actorIsSuperAdmin || aclIsSuperAdmin;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/directory/api/organization-switcher/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { logCrudAccess } from '@open-mercato/shared/lib/crud/factory'\nimport { Organization } from '@open-mercato/core/modules/directory/data/entities'\nimport { computeHierarchyForOrganizations, type ComputedHierarchy } from '@open-mercato/core/modules/directory/lib/hierarchy'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport {\n buildOrgScopeTenantCacheTag,\n getSelectedOrganizationFromRequest,\n getSelectedTenantFromRequest,\n resolveOrganizationScope,\n} from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { runWithCacheTenant, type CacheStrategy } from '@open-mercato/cache'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n directoryTag,\n directoryErrorSchema,\n directoryIdSchema,\n organizationSwitcherResponseSchema,\n} from '../openapi'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('directory').child({ component: 'organization-switcher' })\nconst ORGANIZATION_SWITCHER_CACHE_TTL_MS = 60_000\nconst organizationSwitcherCachePayloadSchema = organizationSwitcherResponseSchema.or(\n organizationSwitcherResponseSchema.pick({\n items: true,\n selectedId: true,\n canManage: true,\n }),\n)\n\nfunction buildSelectionCacheKeyPart(rawSelected: string | null): string | null {\n if (isAllOrganizationsSelection(rawSelected)) return 'mode:all'\n if (rawSelected === null) return 'mode:none'\n const parsedOrganizationId = directoryIdSchema.safeParse(rawSelected)\n return parsedOrganizationId.success ? `org:${parsedOrganizationId.data.toLowerCase()}` : null\n}\n\ntype OrganizationMenuNode = {\n id: string\n name: string\n depth: number\n selectable: boolean\n children: OrganizationMenuNode[]\n}\n\nfunction buildOrganizationMenu(\n hierarchy: ComputedHierarchy,\n accessible: string[] | null,\n): { nodes: OrganizationMenuNode[]; selectableIds: Set<string> } {\n const roots: OrganizationMenuNode[] = []\n const selectableIds = new Set<string>()\n if (!hierarchy.ordered.length) return { nodes: roots, selectableIds }\n\n const includeSet = new Set<string>()\n if (accessible === null) {\n for (const node of hierarchy.ordered) {\n includeSet.add(node.id)\n selectableIds.add(node.id)\n }\n } else {\n const accessibleSet = new Set(accessible)\n for (const id of accessibleSet) {\n const node = hierarchy.map.get(id)\n if (!node) continue\n includeSet.add(id)\n selectableIds.add(id)\n for (const desc of node.descendantIds) {\n includeSet.add(desc)\n selectableIds.add(desc)\n }\n for (const anc of node.ancestorIds) includeSet.add(anc)\n }\n }\n\n const menuNodes = new Map<string, OrganizationMenuNode>()\n for (const node of hierarchy.ordered) {\n if (!includeSet.has(node.id)) continue\n const menuNode: OrganizationMenuNode = {\n id: node.id,\n name: node.name,\n depth: node.depth,\n selectable: accessible === null ? true : selectableIds.has(node.id),\n children: [],\n }\n menuNodes.set(node.id, menuNode)\n }\n\n const ensureChild = (parent: OrganizationMenuNode, child: OrganizationMenuNode) => {\n if (!parent.children.some((existing) => existing.id === child.id)) parent.children.push(child)\n }\n\n for (const node of hierarchy.ordered) {\n if (!includeSet.has(node.id)) continue\n const menuNode = menuNodes.get(node.id)!\n const parentId = node.parentId\n if (parentId && menuNodes.has(parentId)) {\n ensureChild(menuNodes.get(parentId)!, menuNode)\n } else if (!roots.some((existing) => existing.id === menuNode.id)) {\n roots.push(menuNode)\n }\n }\n\n return { nodes: roots, selectableIds }\n}\n\nexport const metadata = {\n GET: { requireAuth: true },\n}\n\nexport async function GET(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.sub) {\n return NextResponse.json({ items: [], selectedId: null, canManage: false, tenantId: null, tenants: [], isSuperAdmin: false }, { status: auth ? 200 : 401 })\n }\n\n const url = new URL(req.url)\n\n try {\n const container = await createRequestContainer()\n const em = container.resolve<EntityManager>('em')\n const rbac = container.resolve<RbacService>('rbacService')\n\n const rawTenantParam = url.searchParams.get('tenantId')\n const cookieTenant = getSelectedTenantFromRequest(req)\n const actorTenantId = typeof auth.tenantId === 'string' && auth.tenantId.trim().length > 0 ? auth.tenantId.trim() : null\n const actorIsSuperAdmin = auth.isSuperAdmin === true\n\n let requestedTenantId = rawTenantParam ?? (cookieTenant ?? undefined)\n if (requestedTenantId === '') requestedTenantId = undefined\n let tenantId = typeof requestedTenantId === 'string' && requestedTenantId.trim().length > 0 ? requestedTenantId.trim() : null\n\n let tenantRecords: { id: string; name: string; isActive: boolean }[] = []\n if (actorIsSuperAdmin) {\n const tenants = await em.find(Tenant, { deletedAt: null }, { orderBy: { name: 'ASC' } })\n tenantRecords = tenants.map((tenant: Tenant) => ({\n id: String(tenant.id),\n name: typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : String(tenant.id),\n isActive: tenant.isActive !== false,\n }))\n if (!tenantId) tenantId = actorTenantId ?? (tenantRecords[0]?.id ?? null)\n if (tenantId && tenantRecords.length && !tenantRecords.some((record) => record.id === tenantId)) {\n tenantId = tenantRecords[0]?.id ?? tenantId\n }\n } else {\n tenantId = actorTenantId\n }\n\n if (!tenantId) {\n return NextResponse.json({\n items: [],\n selectedId: null,\n canManage: false,\n tenantId: null,\n tenants: tenantRecords,\n isSuperAdmin: actorIsSuperAdmin,\n })\n }\n\n const rawSelected = getSelectedOrganizationFromRequest(req)\n const requestedAll = isAllOrganizationsSelection(rawSelected)\n const selectedKeyPart = buildSelectionCacheKeyPart(rawSelected)\n const cacheKey = selectedKeyPart\n ? `org-switcher:v1:${auth.sub}:${tenantId}:${selectedKeyPart}`\n : null\n let cache: CacheStrategy | null = null\n if (!actorIsSuperAdmin && cacheKey) {\n try {\n cache = container.resolve<CacheStrategy>('cache')\n } catch {\n cache = null\n }\n }\n\n const logAccess = async (payload: { items: unknown[]; selectedId: string | null }) => {\n await logCrudAccess({\n container,\n auth,\n request: req,\n items: payload.items,\n idField: 'id',\n resourceKind: 'directory.organization_switcher',\n organizationId: payload.selectedId,\n tenantId,\n query: Object.fromEntries(url.searchParams.entries()),\n })\n }\n\n if (cache && cacheKey) {\n const activeCache = cache\n try {\n const cached = await runWithCacheTenant(tenantId, () => activeCache.get(cacheKey))\n const parsedCached = organizationSwitcherCachePayloadSchema.safeParse(cached)\n if (parsedCached.success) {\n await logAccess(parsedCached.data)\n return NextResponse.json(parsedCached.data)\n }\n } catch (err) {\n logger.warn('Failed to read organization switcher cache', { err })\n }\n }\n\n const scopedOrgId = actorTenantId && actorTenantId === tenantId ? auth.orgId ?? null : null\n const acl = await rbac.loadAcl(auth.sub, { tenantId, organizationId: scopedOrgId })\n const aclIsSuperAdmin = acl?.isSuperAdmin === true\n const effectiveIsSuperAdmin = actorIsSuperAdmin || aclIsSuperAdmin\n const hasManageFeature =\n aclIsSuperAdmin ||\n await rbac.userHasAllFeatures(auth.sub, ['directory.organizations.manage'], {\n tenantId,\n organizationId: scopedOrgId,\n }) ||\n actorIsSuperAdmin\n\n const orgFilter: FilterQuery<Organization> = {\n tenant: tenantId,\n deletedAt: null,\n }\n const orgEntities: Organization[] = await em.find(\n Organization,\n orgFilter,\n { orderBy: { name: 'ASC' } },\n )\n const hierarchy = computeHierarchyForOrganizations(orgEntities, tenantId)\n let hasSelectionCookie = rawSelected !== null\n const scope = await resolveOrganizationScope({\n em,\n rbac,\n auth: {\n ...auth,\n tenantId,\n orgId: scopedOrgId,\n },\n selectedId: rawSelected,\n tenantId,\n })\n const accessible = scope.allowedIds\n const menuData = buildOrganizationMenu(hierarchy, accessible)\n\n let selectedId = requestedAll ? null : (scope.selectedId ?? null)\n if (selectedId && !menuData.selectableIds.has(selectedId)) {\n selectedId = null\n if (!requestedAll) {\n hasSelectionCookie = false\n }\n }\n if (!selectedId && !hasSelectionCookie && auth.orgId) {\n if (accessible === null || menuData.selectableIds.has(auth.orgId)) {\n selectedId = auth.orgId\n }\n }\n\n const showMenu = menuData.nodes.length > 0 || hasManageFeature || effectiveIsSuperAdmin\n const response = showMenu\n ? {\n items: menuData.nodes,\n selectedId,\n canManage: !!hasManageFeature,\n canViewAllOrganizations: accessible === null,\n tenantId,\n tenants: tenantRecords,\n isSuperAdmin: effectiveIsSuperAdmin,\n }\n : {\n items: [],\n selectedId: null,\n canManage: false,\n }\n\n if (cache && cacheKey) {\n const activeCache = cache\n try {\n await runWithCacheTenant(tenantId, () =>\n activeCache.set(cacheKey, response, {\n ttl: ORGANIZATION_SWITCHER_CACHE_TTL_MS,\n tags: [\n buildOrgScopeTenantCacheTag(tenantId),\n `rbac:user:${auth.sub}`,\n ],\n }),\n )\n } catch (err) {\n logger.warn('Failed to write organization switcher cache', { err })\n }\n }\n\n await logAccess(response)\n\n return NextResponse.json(response)\n } catch (err) {\n logger.error('Failed to build organization switcher payload', { err })\n return NextResponse.json({ items: [], selectedId: null, canManage: false, tenantId: null, tenants: [], isSuperAdmin: false }, { status: 500 })\n }\n}\n\nconst organizationSwitcherGetDoc: OpenApiMethodDoc = {\n summary: 'Load organization switcher menu',\n description: 'Returns the hierarchical menu of organizations the current user may switch to within the active tenant.',\n tags: [directoryTag],\n responses: [\n { status: 200, description: 'Organization switcher payload.', schema: organizationSwitcherResponseSchema },\n ],\n errors: [\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: directoryTag,\n summary: 'Organization switcher menu',\n methods: {\n GET: organizationSwitcherGetDoc,\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAsC;AAC/C,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAC7B,SAAS,wCAAgE;AACzE,SAAS,mCAAmC;AAC5C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,0BAA8C;AAEvD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,cAAc;AAIvB,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,WAAW,EAAE,MAAM,EAAE,WAAW,wBAAwB,CAAC;AACrF,MAAM,qCAAqC;AAC3C,MAAM,yCAAyC,mCAAmC;AAAA,EAChF,mCAAmC,KAAK;AAAA,IACtC,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,WAAW;AAAA,EACb,CAAC;AACH;AAEA,SAAS,2BAA2B,aAA2C;AAC7E,MAAI,4BAA4B,WAAW,EAAG,QAAO;AACrD,MAAI,gBAAgB,KAAM,QAAO;AACjC,QAAM,uBAAuB,kBAAkB,UAAU,WAAW;AACpE,SAAO,qBAAqB,UAAU,OAAO,qBAAqB,KAAK,YAAY,CAAC,KAAK;AAC3F;AAUA,SAAS,sBACP,WACA,YAC+D;AAC/D,QAAM,QAAgC,CAAC;AACvC,QAAM,gBAAgB,oBAAI,IAAY;AACtC,MAAI,CAAC,UAAU,QAAQ,OAAQ,QAAO,EAAE,OAAO,OAAO,cAAc;AAEpE,QAAM,aAAa,oBAAI,IAAY;AACnC,MAAI,eAAe,MAAM;AACvB,eAAW,QAAQ,UAAU,SAAS;AACpC,iBAAW,IAAI,KAAK,EAAE;AACtB,oBAAc,IAAI,KAAK,EAAE;AAAA,IAC3B;AAAA,EACF,OAAO;AACL,UAAM,gBAAgB,IAAI,IAAI,UAAU;AACxC,eAAW,MAAM,eAAe;AAC9B,YAAM,OAAO,UAAU,IAAI,IAAI,EAAE;AACjC,UAAI,CAAC,KAAM;AACX,iBAAW,IAAI,EAAE;AACjB,oBAAc,IAAI,EAAE;AACpB,iBAAW,QAAQ,KAAK,eAAe;AACrC,mBAAW,IAAI,IAAI;AACnB,sBAAc,IAAI,IAAI;AAAA,MACxB;AACA,iBAAW,OAAO,KAAK,YAAa,YAAW,IAAI,GAAG;AAAA,IACxD;AAAA,EACF;AAEA,QAAM,YAAY,oBAAI,IAAkC;AACxD,aAAW,QAAQ,UAAU,SAAS;AACpC,QAAI,CAAC,WAAW,IAAI,KAAK,EAAE,EAAG;AAC9B,UAAM,WAAiC;AAAA,MACrC,IAAI,KAAK;AAAA,MACT,MAAM,KAAK;AAAA,MACX,OAAO,KAAK;AAAA,MACZ,YAAY,eAAe,OAAO,OAAO,cAAc,IAAI,KAAK,EAAE;AAAA,MAClE,UAAU,CAAC;AAAA,IACb;AACA,cAAU,IAAI,KAAK,IAAI,QAAQ;AAAA,EACjC;AAEA,QAAM,cAAc,CAAC,QAA8B,UAAgC;AACjF,QAAI,CAAC,OAAO,SAAS,KAAK,CAAC,aAAa,SAAS,OAAO,MAAM,EAAE,EAAG,QAAO,SAAS,KAAK,KAAK;AAAA,EAC/F;AAEA,aAAW,QAAQ,UAAU,SAAS;AACpC,QAAI,CAAC,WAAW,IAAI,KAAK,EAAE,EAAG;AAC9B,UAAM,WAAW,UAAU,IAAI,KAAK,EAAE;AACtC,UAAM,WAAW,KAAK;AACtB,QAAI,YAAY,UAAU,IAAI,QAAQ,GAAG;AACvC,kBAAY,UAAU,IAAI,QAAQ,GAAI,QAAQ;AAAA,IAChD,WAAW,CAAC,MAAM,KAAK,CAAC,aAAa,SAAS,OAAO,SAAS,EAAE,GAAG;AACjE,YAAM,KAAK,QAAQ;AAAA,IACrB;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,OAAO,cAAc;AACvC;AAEO,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,KAAK;AAC3B;AAEA,eAAsB,IAAI,KAAkB;AAC1C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,QAAQ,CAAC,KAAK,KAAK;AACtB,WAAO,aAAa,KAAK,EAAE,OAAO,CAAC,GAAG,YAAY,MAAM,WAAW,OAAO,UAAU,MAAM,SAAS,CAAC,GAAG,cAAc,MAAM,GAAG,EAAE,QAAQ,OAAO,MAAM,IAAI,CAAC;AAAA,EAC5J;AAEA,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAE3B,MAAI;AACF,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,KAAK,UAAU,QAAuB,IAAI;AAChD,UAAM,OAAO,UAAU,QAAqB,aAAa;AAEzD,UAAM,iBAAiB,IAAI,aAAa,IAAI,UAAU;AACtD,UAAM,eAAe,6BAA6B,GAAG;AACrD,UAAM,
|
|
4
|
+
"sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { logCrudAccess } from '@open-mercato/shared/lib/crud/factory'\nimport { Organization } from '@open-mercato/core/modules/directory/data/entities'\nimport { computeHierarchyForOrganizations, type ComputedHierarchy } from '@open-mercato/core/modules/directory/lib/hierarchy'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport {\n buildOrgScopeTenantCacheTag,\n getSelectedOrganizationFromRequest,\n getSelectedTenantFromRequest,\n resolveOrganizationScope,\n} from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { runWithCacheTenant, type CacheStrategy } from '@open-mercato/cache'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n directoryTag,\n directoryErrorSchema,\n directoryIdSchema,\n organizationSwitcherResponseSchema,\n} from '../openapi'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('directory').child({ component: 'organization-switcher' })\nconst ORGANIZATION_SWITCHER_CACHE_TTL_MS = 60_000\nconst organizationSwitcherCachePayloadSchema = organizationSwitcherResponseSchema.or(\n organizationSwitcherResponseSchema.pick({\n items: true,\n selectedId: true,\n canManage: true,\n }),\n)\n\nfunction buildSelectionCacheKeyPart(rawSelected: string | null): string | null {\n if (isAllOrganizationsSelection(rawSelected)) return 'mode:all'\n if (rawSelected === null) return 'mode:none'\n const parsedOrganizationId = directoryIdSchema.safeParse(rawSelected)\n return parsedOrganizationId.success ? `org:${parsedOrganizationId.data.toLowerCase()}` : null\n}\n\ntype OrganizationMenuNode = {\n id: string\n name: string\n depth: number\n selectable: boolean\n children: OrganizationMenuNode[]\n}\n\nfunction buildOrganizationMenu(\n hierarchy: ComputedHierarchy,\n accessible: string[] | null,\n): { nodes: OrganizationMenuNode[]; selectableIds: Set<string> } {\n const roots: OrganizationMenuNode[] = []\n const selectableIds = new Set<string>()\n if (!hierarchy.ordered.length) return { nodes: roots, selectableIds }\n\n const includeSet = new Set<string>()\n if (accessible === null) {\n for (const node of hierarchy.ordered) {\n includeSet.add(node.id)\n selectableIds.add(node.id)\n }\n } else {\n const accessibleSet = new Set(accessible)\n for (const id of accessibleSet) {\n const node = hierarchy.map.get(id)\n if (!node) continue\n includeSet.add(id)\n selectableIds.add(id)\n for (const desc of node.descendantIds) {\n includeSet.add(desc)\n selectableIds.add(desc)\n }\n for (const anc of node.ancestorIds) includeSet.add(anc)\n }\n }\n\n const menuNodes = new Map<string, OrganizationMenuNode>()\n for (const node of hierarchy.ordered) {\n if (!includeSet.has(node.id)) continue\n const menuNode: OrganizationMenuNode = {\n id: node.id,\n name: node.name,\n depth: node.depth,\n selectable: accessible === null ? true : selectableIds.has(node.id),\n children: [],\n }\n menuNodes.set(node.id, menuNode)\n }\n\n const ensureChild = (parent: OrganizationMenuNode, child: OrganizationMenuNode) => {\n if (!parent.children.some((existing) => existing.id === child.id)) parent.children.push(child)\n }\n\n for (const node of hierarchy.ordered) {\n if (!includeSet.has(node.id)) continue\n const menuNode = menuNodes.get(node.id)!\n const parentId = node.parentId\n if (parentId && menuNodes.has(parentId)) {\n ensureChild(menuNodes.get(parentId)!, menuNode)\n } else if (!roots.some((existing) => existing.id === menuNode.id)) {\n roots.push(menuNode)\n }\n }\n\n return { nodes: roots, selectableIds }\n}\n\nexport const metadata = {\n GET: { requireAuth: true },\n}\n\nexport async function GET(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.sub) {\n return NextResponse.json({ items: [], selectedId: null, canManage: false, tenantId: null, tenants: [], isSuperAdmin: false }, { status: auth ? 200 : 401 })\n }\n\n const url = new URL(req.url)\n\n try {\n const container = await createRequestContainer()\n const em = container.resolve<EntityManager>('em')\n const rbac = container.resolve<RbacService>('rbacService')\n\n const rawTenantParam = url.searchParams.get('tenantId')\n const cookieTenant = getSelectedTenantFromRequest(req)\n const normalizeTenantId = (value: unknown): string | null =>\n typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n\n const authTenantId = normalizeTenantId(auth.tenantId)\n // `auth.tenantId` is the tenant the session is currently scoped to, which a super-admin's\n // cookie override replaces. The tenant they actually belong to survives under `actorTenantId`,\n // so the \"no selection\" fallback below has to read that field \u2014 mirroring `organizationScope`.\n // Reading `auth.tenantId` instead landed a scoped-away super-admin on `tenantRecords[0]`, the\n // alphabetically-first tenant in the instance, rather than their own.\n const actorTenantField = (auth as { actorTenantId?: string | null }).actorTenantId\n const actorHomeTenantId = actorTenantField === undefined\n ? authTenantId\n : normalizeTenantId(actorTenantField)\n const actorIsSuperAdmin = auth.isSuperAdmin === true\n\n let requestedTenantId = rawTenantParam ?? (cookieTenant ?? undefined)\n if (requestedTenantId === '') requestedTenantId = undefined\n let tenantId = typeof requestedTenantId === 'string' && requestedTenantId.trim().length > 0 ? requestedTenantId.trim() : null\n\n let tenantRecords: { id: string; name: string; isActive: boolean }[] = []\n if (actorIsSuperAdmin) {\n const tenants = await em.find(Tenant, { deletedAt: null }, { orderBy: { name: 'ASC' } })\n tenantRecords = tenants.map((tenant: Tenant) => ({\n id: String(tenant.id),\n name: typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : String(tenant.id),\n isActive: tenant.isActive !== false,\n }))\n if (!tenantId) tenantId = actorHomeTenantId ?? (tenantRecords[0]?.id ?? null)\n if (tenantId && tenantRecords.length && !tenantRecords.some((record) => record.id === tenantId)) {\n tenantId = tenantRecords[0]?.id ?? tenantId\n }\n } else {\n tenantId = authTenantId\n }\n\n if (!tenantId) {\n return NextResponse.json({\n items: [],\n selectedId: null,\n canManage: false,\n tenantId: null,\n tenants: tenantRecords,\n isSuperAdmin: actorIsSuperAdmin,\n })\n }\n\n const rawSelected = getSelectedOrganizationFromRequest(req)\n const requestedAll = isAllOrganizationsSelection(rawSelected)\n const selectedKeyPart = buildSelectionCacheKeyPart(rawSelected)\n const cacheKey = selectedKeyPart\n ? `org-switcher:v1:${auth.sub}:${tenantId}:${selectedKeyPart}`\n : null\n let cache: CacheStrategy | null = null\n if (!actorIsSuperAdmin && cacheKey) {\n try {\n cache = container.resolve<CacheStrategy>('cache')\n } catch {\n cache = null\n }\n }\n\n const logAccess = async (payload: { items: unknown[]; selectedId: string | null }) => {\n await logCrudAccess({\n container,\n auth,\n request: req,\n items: payload.items,\n idField: 'id',\n resourceKind: 'directory.organization_switcher',\n organizationId: payload.selectedId,\n tenantId,\n query: Object.fromEntries(url.searchParams.entries()),\n })\n }\n\n if (cache && cacheKey) {\n const activeCache = cache\n try {\n const cached = await runWithCacheTenant(tenantId, () => activeCache.get(cacheKey))\n const parsedCached = organizationSwitcherCachePayloadSchema.safeParse(cached)\n if (parsedCached.success) {\n await logAccess(parsedCached.data)\n return NextResponse.json(parsedCached.data)\n }\n } catch (err) {\n logger.warn('Failed to read organization switcher cache', { err })\n }\n }\n\n const scopedOrgId = authTenantId && authTenantId === tenantId ? auth.orgId ?? null : null\n const acl = await rbac.loadAcl(auth.sub, { tenantId, organizationId: scopedOrgId })\n const aclIsSuperAdmin = acl?.isSuperAdmin === true\n const effectiveIsSuperAdmin = actorIsSuperAdmin || aclIsSuperAdmin\n const hasManageFeature =\n aclIsSuperAdmin ||\n await rbac.userHasAllFeatures(auth.sub, ['directory.organizations.manage'], {\n tenantId,\n organizationId: scopedOrgId,\n }) ||\n actorIsSuperAdmin\n\n const orgFilter: FilterQuery<Organization> = {\n tenant: tenantId,\n deletedAt: null,\n }\n const orgEntities: Organization[] = await em.find(\n Organization,\n orgFilter,\n { orderBy: { name: 'ASC' } },\n )\n const hierarchy = computeHierarchyForOrganizations(orgEntities, tenantId)\n let hasSelectionCookie = rawSelected !== null\n const scope = await resolveOrganizationScope({\n em,\n rbac,\n auth: {\n ...auth,\n tenantId,\n orgId: scopedOrgId,\n },\n selectedId: rawSelected,\n tenantId,\n })\n const accessible = scope.allowedIds\n const menuData = buildOrganizationMenu(hierarchy, accessible)\n\n let selectedId = requestedAll ? null : (scope.selectedId ?? null)\n if (selectedId && !menuData.selectableIds.has(selectedId)) {\n selectedId = null\n if (!requestedAll) {\n hasSelectionCookie = false\n }\n }\n if (!selectedId && !hasSelectionCookie && auth.orgId) {\n if (accessible === null || menuData.selectableIds.has(auth.orgId)) {\n selectedId = auth.orgId\n }\n }\n\n const showMenu = menuData.nodes.length > 0 || hasManageFeature || effectiveIsSuperAdmin\n const response = showMenu\n ? {\n items: menuData.nodes,\n selectedId,\n canManage: !!hasManageFeature,\n canViewAllOrganizations: accessible === null,\n tenantId,\n tenants: tenantRecords,\n isSuperAdmin: effectiveIsSuperAdmin,\n }\n : {\n items: [],\n selectedId: null,\n canManage: false,\n }\n\n if (cache && cacheKey) {\n const activeCache = cache\n try {\n await runWithCacheTenant(tenantId, () =>\n activeCache.set(cacheKey, response, {\n ttl: ORGANIZATION_SWITCHER_CACHE_TTL_MS,\n tags: [\n buildOrgScopeTenantCacheTag(tenantId),\n `rbac:user:${auth.sub}`,\n ],\n }),\n )\n } catch (err) {\n logger.warn('Failed to write organization switcher cache', { err })\n }\n }\n\n await logAccess(response)\n\n return NextResponse.json(response)\n } catch (err) {\n logger.error('Failed to build organization switcher payload', { err })\n return NextResponse.json({ items: [], selectedId: null, canManage: false, tenantId: null, tenants: [], isSuperAdmin: false }, { status: 500 })\n }\n}\n\nconst organizationSwitcherGetDoc: OpenApiMethodDoc = {\n summary: 'Load organization switcher menu',\n description: 'Returns the hierarchical menu of organizations the current user may switch to within the active tenant.',\n tags: [directoryTag],\n responses: [\n { status: 200, description: 'Organization switcher payload.', schema: organizationSwitcherResponseSchema },\n ],\n errors: [\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: directoryTag,\n summary: 'Organization switcher menu',\n methods: {\n GET: organizationSwitcherGetDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|