@open-mercato/core 0.10.1-develop.7752.1.fd7c06569b → 0.10.1-develop.7756.1.8f5dec1b96

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/helpers/integration/dbFixtures.js +1 -1
  3. package/dist/helpers/integration/dbFixtures.js.map +1 -1
  4. package/dist/modules/auth/api/roles/acl/route.js +2 -2
  5. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  6. package/dist/modules/auth/api/roles/route.js +1 -1
  7. package/dist/modules/auth/api/roles/route.js.map +2 -2
  8. package/dist/modules/auth/api/users/acl/route.js +10 -2
  9. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  10. package/dist/modules/auth/commands/acl.js +2 -2
  11. package/dist/modules/auth/commands/acl.js.map +2 -2
  12. package/dist/modules/auth/commands/roles.js +1 -1
  13. package/dist/modules/auth/commands/roles.js.map +2 -2
  14. package/dist/modules/auth/commands/users.js +7 -3
  15. package/dist/modules/auth/commands/users.js.map +2 -2
  16. package/dist/modules/auth/data/entities.js.map +2 -2
  17. package/dist/modules/auth/lib/executionPrincipal.js +2 -2
  18. package/dist/modules/auth/lib/executionPrincipal.js.map +2 -2
  19. package/dist/modules/auth/lib/grantChecks.js +38 -11
  20. package/dist/modules/auth/lib/grantChecks.js.map +2 -2
  21. package/dist/modules/auth/lib/setup-app.js +2 -2
  22. package/dist/modules/auth/lib/setup-app.js.map +2 -2
  23. package/dist/modules/auth/migrations/Migration20260828120000_auth.js +45 -0
  24. package/dist/modules/auth/migrations/Migration20260828120000_auth.js.map +7 -0
  25. package/dist/modules/auth/services/rbacService.js +138 -59
  26. package/dist/modules/auth/services/rbacService.js.map +3 -3
  27. package/dist/modules/entities/cli.js +12 -1
  28. package/dist/modules/entities/cli.js.map +2 -2
  29. package/dist/modules/entities/lib/helpers.js +13 -0
  30. package/dist/modules/entities/lib/helpers.js.map +2 -2
  31. package/dist/modules/entities/lib/rotate-custom-entity-documents.js +157 -0
  32. package/dist/modules/entities/lib/rotate-custom-entity-documents.js.map +7 -0
  33. package/dist/modules/notifications/lib/notificationRecipients.js +2 -2
  34. package/dist/modules/notifications/lib/notificationRecipients.js.map +2 -2
  35. package/dist/modules/query_index/lib/engine.js +179 -11
  36. package/dist/modules/query_index/lib/engine.js.map +3 -3
  37. package/package.json +7 -7
  38. package/src/helpers/integration/dbFixtures.ts +1 -1
  39. package/src/modules/auth/api/roles/acl/route.ts +2 -2
  40. package/src/modules/auth/api/roles/route.ts +1 -1
  41. package/src/modules/auth/api/users/acl/route.ts +10 -2
  42. package/src/modules/auth/commands/acl.ts +2 -2
  43. package/src/modules/auth/commands/roles.ts +1 -1
  44. package/src/modules/auth/commands/users.ts +7 -3
  45. package/src/modules/auth/data/entities.ts +12 -0
  46. package/src/modules/auth/lib/executionPrincipal.ts +2 -2
  47. package/src/modules/auth/lib/grantChecks.ts +115 -11
  48. package/src/modules/auth/lib/setup-app.ts +2 -2
  49. package/src/modules/auth/migrations/Migration20260828120000_auth.ts +70 -0
  50. package/src/modules/auth/services/rbacService.ts +161 -76
  51. package/src/modules/entities/cli.ts +12 -1
  52. package/src/modules/entities/lib/helpers.ts +17 -0
  53. package/src/modules/entities/lib/rotate-custom-entity-documents.ts +200 -0
  54. package/src/modules/notifications/lib/notificationRecipients.ts +6 -0
  55. package/src/modules/query_index/lib/engine.ts +187 -9
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/auth/data/entities.ts"],
4
- "sourcesContent": ["import { OptionalProps } from '@mikro-orm/core'\nimport { Entity, Index, ManyToOne, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'\n\n/**\n * The principal kind a `User` row represents. `human` (the default) is an\n * interactively-authenticating person; `agent` is a non-interactive AI principal\n * provisioned by the agent_orchestrator module (it owns no interactive credential\n * and is barred from the password/SSO login flow); `service` is reserved for\n * machine/system principals. Added additively (NOT NULL, default `'human'`) so\n * every existing row backfills to `human` and no existing reader breaks.\n */\nexport type UserKind = 'human' | 'agent' | 'service'\n\n@Entity({ tableName: 'users' })\n// Email uniqueness is per-tenant, enforced by a partial unique index\n// (`users_tenant_email_hash_uniq`) on `(tenant_id, email_hash)` over live rows\n// (`WHERE deleted_at IS NULL AND email_hash IS NOT NULL`), owned by raw SQL in\n// Migration20260610120000. It keys on the deterministic `email_hash`, not `email`, because\n// `email` is encrypted at rest with a per-row IV (see encryption.ts) \u2014 its ciphertext is\n// non-deterministic, so a unique index on it would not detect duplicates. A `@Unique`\n// decorator can't express a partial, tenant-scoped index, so the entity omits it \u2014 the\n// migration is the source of truth. A global unique constraint contradicts the multi-tenant\n// login flow and leaks cross-tenant account existence (#2934). Mirrors\n// `customer_users_tenant_email_hash_uniq`.\nexport class User {\n // `kind` carries a default ('human'); marking it optional keeps every existing\n // `em.create(User, \u2026)` caller (which never passed `kind`) compiling unchanged \u2014\n // the additive column is backfilled by the default at the DB + entity level.\n [OptionalProps]?: 'kind'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ type: 'text' })\n email!: string\n\n @Property({ name: 'email_hash', type: 'text', nullable: true })\n @Index({ name: 'users_email_hash_idx' })\n emailHash?: string | null\n\n @Property({ type: 'text', nullable: true })\n name?: string | null\n\n @Property({ name: 'password_hash', type: 'text', nullable: true })\n passwordHash?: string | null\n\n @Property({ name: 'is_confirmed', type: 'boolean', default: true })\n isConfirmed: boolean = true\n\n // Principal kind. Default 'human' keeps every existing row + external reader\n // unaffected; 'agent' marks a non-interactive AI principal (see UserKind).\n @Property({ name: 'kind', type: 'varchar', length: 20, default: 'human' })\n kind: UserKind = 'human'\n\n @Property({ name: 'last_login_at', type: Date, nullable: true })\n lastLoginAt?: Date\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date | null\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'roles' })\n@Unique({ properties: ['tenantId', 'name'] })\nexport class Role {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ type: 'text' })\n name!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'min_active_holders', type: 'integer', default: 0 })\n minActiveHolders: number = 0\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date | null\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'user_sidebar_preferences' })\n// Uniqueness is enforced by a partial unique index (`user_sidebar_preferences_active_unique_idx`)\n// scoped to live rows (`WHERE deleted_at IS NULL`) and owned by raw SQL in\n// Migration20260427143311. A `@Unique` decorator can't express a partial index,\n// so the entity intentionally omits it \u2014 the migration is the source of truth.\nexport class UserSidebarPreference {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User, { deleteRule: 'cascade' })\n user!: User\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ type: 'text' })\n locale!: string\n\n @Property({ name: 'settings_json', type: 'json', nullable: true })\n settingsJson?: unknown\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'role_sidebar_preferences' })\n// Uniqueness is enforced by a partial unique index (`role_sidebar_preferences_active_unique_idx`)\n// scoped to live rows (`WHERE deleted_at IS NULL`) and owned by raw SQL in\n// Migration20260427143311. A `@Unique` decorator can't express a partial index,\n// so the entity intentionally omits it \u2014 the migration is the source of truth.\nexport class RoleSidebarPreference {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => Role)\n role!: Role\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ type: 'text' })\n locale!: string\n\n @Property({ name: 'settings_json', type: 'json', nullable: true })\n settingsJson?: unknown\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'sidebar_variants' })\n// Uniqueness is enforced by a partial unique index (`sidebar_variants_active_name_unique_idx`)\n// scoped to live rows (`WHERE deleted_at IS NULL`) and owned by raw SQL in\n// Migration20260427143311. A `@Unique` decorator can't express a partial index,\n// so the entity intentionally omits it \u2014 the migration is the source of truth.\nexport class SidebarVariant {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User, { deleteRule: 'cascade' })\n user!: User\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ type: 'text' })\n locale!: string\n\n @Property({ type: 'text' })\n name!: string\n\n @Property({ name: 'settings_json', type: 'json', nullable: true })\n settingsJson?: unknown\n\n @Property({ name: 'is_active', type: 'boolean', default: false })\n isActive: boolean = false\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'user_roles' })\n@Index({ name: 'user_roles_user_id_idx', properties: ['user'] })\n@Index({ name: 'user_roles_role_id_idx', properties: ['role'] })\nexport class UserRole {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n @ManyToOne(() => Role)\n role!: Role\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'sessions' })\nexport class Session {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n @Property({ type: 'text', unique: true })\n token!: string\n\n @Property({ name: 'expires_at', type: Date })\n expiresAt!: Date\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'last_used_at', type: Date, nullable: true })\n lastUsedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'password_resets' })\nexport class PasswordReset {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n @Property({ type: 'text', unique: true })\n token!: string\n\n @Property({ name: 'expires_at', type: Date })\n expiresAt!: Date\n\n @Property({ name: 'used_at', type: Date, nullable: true })\n usedAt?: Date\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n// RBAC: Role-level ACL\n@Entity({ tableName: 'role_acls' })\nexport class RoleAcl {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => Role)\n role!: Role\n\n // Tenant scope is mandatory for ACL evaluation\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n // Feature list (string-based). Use JSON array to preserve order and allow wildcards like \"example.*\".\n @Property({ name: 'features_json', type: 'json', nullable: true })\n featuresJson?: string[] | null\n\n // If true, user with this role can do everything regardless of features\n @Property({ name: 'is_super_admin', type: 'boolean', default: false })\n isSuperAdmin: boolean = false\n\n // Visible organizations within the tenant. `null` or the `__all__` sentinel means every\n // organization. An EMPTY array is a deny-all scope for human principals \u2014 it projects to an\n // empty accessible set so scoped reads and deletes fail closed (#4033). API-key principals\n // read an empty list as \"inherit the key's own organization binding\" instead.\n @Property({ name: 'organizations_json', type: 'json', nullable: true })\n organizationsJson?: string[] | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n// RBAC: Per-user ACL override\n@Entity({ tableName: 'user_acls' })\nexport class UserAcl {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n // Tenant scope is mandatory for ACL evaluation\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n // Feature list (string-based). Use JSON array to preserve order and allow wildcards like \"example.*\".\n @Property({ name: 'features_json', type: 'json', nullable: true })\n featuresJson?: string[] | null\n\n // If true, this user can do everything regardless of features\n @Property({ name: 'is_super_admin', type: 'boolean', default: false })\n isSuperAdmin: boolean = false\n\n // Visible organizations within the tenant; null/empty means all organizations\n @Property({ name: 'organizations_json', type: 'json', nullable: true })\n organizationsJson?: string[] | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'user_consents' })\n@Unique({ properties: ['userId', 'tenantId', 'consentType'] })\nexport class UserConsent {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'user_id', type: 'uuid' })\n userId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'consent_type', type: 'text' })\n consentType!: string\n\n @Property({ name: 'is_granted', type: 'boolean', default: false })\n isGranted: boolean = false\n\n @Property({ name: 'granted_at', type: Date, nullable: true })\n grantedAt?: Date | null\n\n @Property({ name: 'withdrawn_at', type: Date, nullable: true })\n withdrawnAt?: Date | null\n\n @Property({ type: 'text', nullable: true })\n source?: string | null\n\n @Property({ name: 'ip_address', type: 'text', nullable: true })\n ipAddress?: string | null\n\n @Property({ name: 'integrity_hash', type: 'text', nullable: true })\n integrityHash?: string | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { OptionalProps } from '@mikro-orm/core'\nimport { Entity, Index, ManyToOne, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'\n\n/**\n * The principal kind a `User` row represents. `human` (the default) is an\n * interactively-authenticating person; `agent` is a non-interactive AI principal\n * provisioned by the agent_orchestrator module (it owns no interactive credential\n * and is barred from the password/SSO login flow); `service` is reserved for\n * machine/system principals. Added additively (NOT NULL, default `'human'`) so\n * every existing row backfills to `human` and no existing reader breaks.\n */\nexport type UserKind = 'human' | 'agent' | 'service'\n\n@Entity({ tableName: 'users' })\n// Email uniqueness is per-tenant, enforced by a partial unique index\n// (`users_tenant_email_hash_uniq`) on `(tenant_id, email_hash)` over live rows\n// (`WHERE deleted_at IS NULL AND email_hash IS NOT NULL`), owned by raw SQL in\n// Migration20260610120000. It keys on the deterministic `email_hash`, not `email`, because\n// `email` is encrypted at rest with a per-row IV (see encryption.ts) \u2014 its ciphertext is\n// non-deterministic, so a unique index on it would not detect duplicates. A `@Unique`\n// decorator can't express a partial, tenant-scoped index, so the entity omits it \u2014 the\n// migration is the source of truth. A global unique constraint contradicts the multi-tenant\n// login flow and leaks cross-tenant account existence (#2934). Mirrors\n// `customer_users_tenant_email_hash_uniq`.\nexport class User {\n // `kind` carries a default ('human'); marking it optional keeps every existing\n // `em.create(User, \u2026)` caller (which never passed `kind`) compiling unchanged \u2014\n // the additive column is backfilled by the default at the DB + entity level.\n [OptionalProps]?: 'kind'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ type: 'text' })\n email!: string\n\n @Property({ name: 'email_hash', type: 'text', nullable: true })\n @Index({ name: 'users_email_hash_idx' })\n emailHash?: string | null\n\n @Property({ type: 'text', nullable: true })\n name?: string | null\n\n @Property({ name: 'password_hash', type: 'text', nullable: true })\n passwordHash?: string | null\n\n @Property({ name: 'is_confirmed', type: 'boolean', default: true })\n isConfirmed: boolean = true\n\n // Principal kind. Default 'human' keeps every existing row + external reader\n // unaffected; 'agent' marks a non-interactive AI principal (see UserKind).\n @Property({ name: 'kind', type: 'varchar', length: 20, default: 'human' })\n kind: UserKind = 'human'\n\n @Property({ name: 'last_login_at', type: Date, nullable: true })\n lastLoginAt?: Date\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date | null\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'roles' })\n@Unique({ properties: ['tenantId', 'name'] })\nexport class Role {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ type: 'text' })\n name!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'min_active_holders', type: 'integer', default: 0 })\n minActiveHolders: number = 0\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date | null\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'user_sidebar_preferences' })\n// Uniqueness is enforced by a partial unique index (`user_sidebar_preferences_active_unique_idx`)\n// scoped to live rows (`WHERE deleted_at IS NULL`) and owned by raw SQL in\n// Migration20260427143311. A `@Unique` decorator can't express a partial index,\n// so the entity intentionally omits it \u2014 the migration is the source of truth.\nexport class UserSidebarPreference {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User, { deleteRule: 'cascade' })\n user!: User\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ type: 'text' })\n locale!: string\n\n @Property({ name: 'settings_json', type: 'json', nullable: true })\n settingsJson?: unknown\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'role_sidebar_preferences' })\n// Uniqueness is enforced by a partial unique index (`role_sidebar_preferences_active_unique_idx`)\n// scoped to live rows (`WHERE deleted_at IS NULL`) and owned by raw SQL in\n// Migration20260427143311. A `@Unique` decorator can't express a partial index,\n// so the entity intentionally omits it \u2014 the migration is the source of truth.\nexport class RoleSidebarPreference {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => Role)\n role!: Role\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ type: 'text' })\n locale!: string\n\n @Property({ name: 'settings_json', type: 'json', nullable: true })\n settingsJson?: unknown\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'sidebar_variants' })\n// Uniqueness is enforced by a partial unique index (`sidebar_variants_active_name_unique_idx`)\n// scoped to live rows (`WHERE deleted_at IS NULL`) and owned by raw SQL in\n// Migration20260427143311. A `@Unique` decorator can't express a partial index,\n// so the entity intentionally omits it \u2014 the migration is the source of truth.\nexport class SidebarVariant {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User, { deleteRule: 'cascade' })\n user!: User\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ type: 'text' })\n locale!: string\n\n @Property({ type: 'text' })\n name!: string\n\n @Property({ name: 'settings_json', type: 'json', nullable: true })\n settingsJson?: unknown\n\n @Property({ name: 'is_active', type: 'boolean', default: false })\n isActive: boolean = false\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'user_roles' })\n@Index({ name: 'user_roles_user_id_idx', properties: ['user'] })\n@Index({ name: 'user_roles_role_id_idx', properties: ['role'] })\nexport class UserRole {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n @ManyToOne(() => Role)\n role!: Role\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'sessions' })\nexport class Session {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n @Property({ type: 'text', unique: true })\n token!: string\n\n @Property({ name: 'expires_at', type: Date })\n expiresAt!: Date\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'last_used_at', type: Date, nullable: true })\n lastUsedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'password_resets' })\nexport class PasswordReset {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n @Property({ type: 'text', unique: true })\n token!: string\n\n @Property({ name: 'expires_at', type: Date })\n expiresAt!: Date\n\n @Property({ name: 'used_at', type: Date, nullable: true })\n usedAt?: Date\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n// RBAC: Role-level ACL\n@Entity({ tableName: 'role_acls' })\n// Uniqueness is enforced by a partial unique index (`role_acls_active_unique_idx`)\n// on `(role_id, tenant_id)` scoped to live rows (`WHERE deleted_at IS NULL`) and\n// owned by raw SQL in Migration20260828120000_auth. A `@Unique` decorator can't\n// express a partial index, so the entity intentionally omits it \u2014 the migration\n// is the source of truth.\nexport class RoleAcl {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => Role)\n role!: Role\n\n // Tenant scope is mandatory for ACL evaluation\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n // Feature list (string-based). Use JSON array to preserve order and allow wildcards like \"example.*\".\n @Property({ name: 'features_json', type: 'json', nullable: true })\n featuresJson?: string[] | null\n\n // If true, user with this role can do everything regardless of features\n @Property({ name: 'is_super_admin', type: 'boolean', default: false })\n isSuperAdmin: boolean = false\n\n // Visible organizations within the tenant. `null` or the `__all__` sentinel means every\n // organization. An EMPTY array is a deny-all scope for human principals \u2014 it projects to an\n // empty accessible set so scoped reads and deletes fail closed (#4033). API-key principals\n // read an empty list as \"inherit the key's own organization binding\" instead.\n @Property({ name: 'organizations_json', type: 'json', nullable: true })\n organizationsJson?: string[] | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n// RBAC: Per-user ACL override\n@Entity({ tableName: 'user_acls' })\n// Uniqueness is enforced by a partial unique index (`user_acls_active_unique_idx`)\n// on `(user_id, tenant_id)` scoped to live rows (`WHERE deleted_at IS NULL`) and\n// owned by raw SQL in Migration20260828120000_auth. A `@Unique` decorator can't\n// express a partial index, so the entity intentionally omits it \u2014 the migration\n// is the source of truth. This row REPLACES the user's role ACLs rather than\n// merging with them (see RbacService.loadAcl), so a second live row for the same\n// scope would silently decide the whole feature set.\nexport class UserAcl {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => User)\n user!: User\n\n // Tenant scope is mandatory for ACL evaluation\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n // Feature list (string-based). Use JSON array to preserve order and allow wildcards like \"example.*\".\n @Property({ name: 'features_json', type: 'json', nullable: true })\n featuresJson?: string[] | null\n\n // If true, this user can do everything regardless of features\n @Property({ name: 'is_super_admin', type: 'boolean', default: false })\n isSuperAdmin: boolean = false\n\n // Visible organizations within the tenant; null/empty means all organizations\n @Property({ name: 'organizations_json', type: 'json', nullable: true })\n organizationsJson?: string[] | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'user_consents' })\n@Unique({ properties: ['userId', 'tenantId', 'consentType'] })\nexport class UserConsent {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'user_id', type: 'uuid' })\n userId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'consent_type', type: 'text' })\n consentType!: string\n\n @Property({ name: 'is_granted', type: 'boolean', default: false })\n isGranted: boolean = false\n\n @Property({ name: 'granted_at', type: Date, nullable: true })\n grantedAt?: Date | null\n\n @Property({ name: 'withdrawn_at', type: Date, nullable: true })\n withdrawnAt?: Date | null\n\n @Property({ type: 'text', nullable: true })\n source?: string | null\n\n @Property({ name: 'ip_address', type: 'text', nullable: true })\n ipAddress?: string | null\n\n @Property({ name: 'integrity_hash', type: 'text', nullable: true })\n integrityHash?: string | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -83,7 +83,7 @@ async function provisionExecutionPrincipal(container, scope, input) {
83
83
  const existingAcl = await findOneWithDecryption(
84
84
  trx,
85
85
  RoleAcl,
86
- { role, tenantId },
86
+ { role, tenantId, deletedAt: null },
87
87
  {},
88
88
  { tenantId, organizationId: null }
89
89
  );
@@ -113,7 +113,7 @@ async function provisionExecutionPrincipal(container, scope, input) {
113
113
  const existingLink = await findOneWithDecryption(
114
114
  trx,
115
115
  UserRole,
116
- { user, role },
116
+ { user, role, deletedAt: null },
117
117
  {},
118
118
  { tenantId, organizationId: null }
119
119
  );
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/auth/lib/executionPrincipal.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n lockAuthorizationState,\n} from '@open-mercato/core/modules/auth/lib/authorizationStateLocks'\nimport { computeEmailHash } from './emailHash'\nimport { Role, RoleAcl, User, UserRole, type UserKind } from '../data/entities'\n\n/**\n * A non-interactive execution principal: a login-less `User` plus a scoped\n * `Role` whose ACL carries EXACTLY the features that principal may use.\n *\n * This lives in `auth` because `User` / `Role` / `RoleAcl` / `UserRole` are this\n * module's entities. Any module that needs a machine identity to write under \u2014\n * an agentic task, a workflow definition declaring a grant, a future scheduled\n * job \u2014 provisions one here rather than reimplementing the four find-or-create\n * steps and the ACL replacement.\n *\n * The `enterprise` agent_orchestrator module owns an older copy of this logic\n * (`lib/identity/agentPrincipalService.ts`), which core MUST NOT import (an\n * optional peer). The naming knobs below exist so that copy can be migrated onto\n * this helper WITHOUT changing any provisioned row's identity: calling with\n * `{ namePrefix: 'agent', kind: 'agent', mergeFeatures: true }` reproduces its\n * email and role name byte-for-byte.\n */\nexport type ExecutionPrincipalScope = { tenantId: string; organizationId: string }\n\nexport type ResolvedExecutionPrincipal = {\n userId: string\n roleId: string\n}\n\nexport type ProvisionExecutionPrincipalInput = {\n /**\n * Namespaced, stable identity key \u2014 e.g. `workflow:<definitionId>`. It is the\n * idempotency key: the deterministic email and role name are both derived from\n * it, so re-provisioning resolves the same rows instead of creating new ones.\n */\n principalKey: string\n displayName?: string\n /** The principal's complete feature set. Replaces the role ACL unless `mergeFeatures`. */\n features: string[]\n /** Defaults to `service` \u2014 a machine principal that is not an AI agent. */\n kind?: UserKind\n /** Local-part / role-name prefix. Defaults to `principal`. */\n namePrefix?: string\n /**\n * Merge into the existing ACL instead of replacing it. Default `false`:\n * merging cannot NARROW a previously over-granted principal, which is the\n * whole point of re-scoping on every save.\n */\n mergeFeatures?: boolean\n}\n\n/**\n * Non-routable internal domain. An execution principal never receives mail and\n * never logs in interactively, so this address is an identity key, not a mailbox.\n */\nconst EXECUTION_PRINCIPAL_EMAIL_DOMAIN = 'agent.internal'\nconst DEFAULT_NAME_PREFIX = 'principal'\n\nfunction slugifyPrincipalKey(principalKey: string): string {\n return principalKey.toLowerCase().replace(/[^a-z0-9._-]+/g, '-')\n}\n\nexport function executionPrincipalEmail(\n principalKey: string,\n organizationId: string,\n namePrefix: string = DEFAULT_NAME_PREFIX,\n): string {\n return `${namePrefix}+${slugifyPrincipalKey(principalKey)}+${organizationId}@${EXECUTION_PRINCIPAL_EMAIL_DOMAIN}`\n}\n\nexport function executionPrincipalRoleName(\n principalKey: string,\n namePrefix: string = DEFAULT_NAME_PREFIX,\n): string {\n return `${namePrefix}:${principalKey}`\n}\n\nexport function normalizeFeatureList(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n const seen = new Set<string>()\n for (const entry of value) {\n if (typeof entry !== 'string') continue\n const trimmed = entry.trim()\n if (trimmed) seen.add(trimmed)\n }\n return Array.from(seen).sort((a, b) => a.localeCompare(b))\n}\n\n/**\n * Provisions \u2014 idempotently \u2014 the execution principal for `principalKey` and\n * pins its role ACL to exactly `features`.\n *\n * Non-interactive by construction: `passwordHash = null` + `isConfirmed = false`\n * leaves the password/SSO flow nothing to authenticate against. `isSuperAdmin`\n * is always false and the ACL's organization visibility is pinned to the\n * provisioning organization, so the principal can never out-scope its owner.\n */\nexport async function provisionExecutionPrincipal(\n container: AwilixContainer,\n scope: ExecutionPrincipalScope,\n input: ProvisionExecutionPrincipalInput,\n): Promise<ResolvedExecutionPrincipal> {\n const { tenantId, organizationId } = scope\n const principalKey = input.principalKey.trim()\n if (!principalKey) throw new Error('[internal] execution principal requires a principalKey')\n if (!tenantId || !organizationId) {\n throw new Error('[internal] execution principal requires a tenant and organization scope')\n }\n\n const namePrefix = input.namePrefix ?? DEFAULT_NAME_PREFIX\n const kind: UserKind = input.kind ?? 'service'\n const features = normalizeFeatureList(input.features)\n const roleName = executionPrincipalRoleName(principalKey, namePrefix)\n const principalEmail = executionPrincipalEmail(principalKey, organizationId, namePrefix)\n\n const em = (container.resolve('em') as EntityManager).fork()\n\n return em.transactional(async (trx) => {\n let role = await findOneWithDecryption(\n trx,\n Role,\n { name: roleName, tenantId },\n {},\n { tenantId, organizationId: null },\n )\n if (!role) {\n role = trx.create(Role, { name: roleName, tenantId, minActiveHolders: 0, createdAt: new Date() })\n trx.persist(role)\n await trx.flush()\n }\n\n const emailHash = computeEmailHash(principalEmail)\n let user = await findOneWithDecryption(\n trx,\n User,\n { emailHash, tenantId, deletedAt: null },\n {},\n { tenantId, organizationId },\n )\n if (!user) {\n user = trx.create(User, {\n email: principalEmail,\n emailHash,\n passwordHash: null,\n isConfirmed: false,\n kind,\n name: input.displayName ?? principalKey,\n tenantId,\n organizationId,\n createdAt: new Date(),\n })\n trx.persist(user)\n await trx.flush()\n } else if (user.kind === 'human') {\n // Defensive: a row resolved by the deterministic principal email must never\n // be a human account. Never silently repurpose one.\n throw new Error('[internal] resolved a human User for an execution principal')\n }\n\n await lockAuthorizationState(trx, {\n userIds: [String(user.id)],\n roleIds: [String(role.id)],\n })\n\n const existingAcl = await findOneWithDecryption(\n trx,\n RoleAcl,\n { role, tenantId },\n {},\n { tenantId, organizationId: null },\n )\n if (!existingAcl) {\n trx.persist(\n trx.create(RoleAcl, {\n role,\n tenantId,\n featuresJson: features,\n isSuperAdmin: false,\n organizationsJson: [organizationId],\n createdAt: new Date(),\n }),\n )\n await trx.flush()\n } else {\n const current = normalizeFeatureList(existingAcl.featuresJson)\n const next = input.mergeFeatures ? normalizeFeatureList([...current, ...features]) : features\n if (\n existingAcl.isSuperAdmin ||\n current.length !== next.length ||\n current.some((feature, index) => feature !== next[index])\n ) {\n existingAcl.featuresJson = next\n existingAcl.isSuperAdmin = false\n existingAcl.organizationsJson = [organizationId]\n trx.persist(existingAcl)\n await trx.flush()\n }\n }\n\n const existingLink = await findOneWithDecryption(\n trx,\n UserRole,\n { user, role },\n {},\n { tenantId, organizationId: null },\n )\n if (!existingLink) {\n trx.persist(trx.create(UserRole, { user, role, createdAt: new Date() }))\n await trx.flush()\n }\n\n return { userId: user.id, roleId: role.id }\n })\n}\n\n/**\n * Resolves the `User` id of an already-provisioned execution principal without\n * writing anything. Returns null when it has not been provisioned (or was\n * soft-deleted) \u2014 callers MUST fail closed rather than fall back to a broader\n * identity.\n */\nexport async function resolveExecutionPrincipalUserId(\n em: EntityManager,\n principalKey: string,\n scope: ExecutionPrincipalScope,\n namePrefix: string = DEFAULT_NAME_PREFIX,\n): Promise<string | null> {\n const { tenantId, organizationId } = scope\n if (!principalKey || !tenantId || !organizationId) return null\n const emailHash = computeEmailHash(executionPrincipalEmail(principalKey, organizationId, namePrefix))\n const user = await findOneWithDecryption(\n em,\n User,\n { emailHash, tenantId, deletedAt: null },\n {},\n { tenantId, organizationId },\n )\n return user?.id ?? null\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n lockAuthorizationState,\n} from '@open-mercato/core/modules/auth/lib/authorizationStateLocks'\nimport { computeEmailHash } from './emailHash'\nimport { Role, RoleAcl, User, UserRole, type UserKind } from '../data/entities'\n\n/**\n * A non-interactive execution principal: a login-less `User` plus a scoped\n * `Role` whose ACL carries EXACTLY the features that principal may use.\n *\n * This lives in `auth` because `User` / `Role` / `RoleAcl` / `UserRole` are this\n * module's entities. Any module that needs a machine identity to write under \u2014\n * an agentic task, a workflow definition declaring a grant, a future scheduled\n * job \u2014 provisions one here rather than reimplementing the four find-or-create\n * steps and the ACL replacement.\n *\n * The `enterprise` agent_orchestrator module owns an older copy of this logic\n * (`lib/identity/agentPrincipalService.ts`), which core MUST NOT import (an\n * optional peer). The naming knobs below exist so that copy can be migrated onto\n * this helper WITHOUT changing any provisioned row's identity: calling with\n * `{ namePrefix: 'agent', kind: 'agent', mergeFeatures: true }` reproduces its\n * email and role name byte-for-byte.\n */\nexport type ExecutionPrincipalScope = { tenantId: string; organizationId: string }\n\nexport type ResolvedExecutionPrincipal = {\n userId: string\n roleId: string\n}\n\nexport type ProvisionExecutionPrincipalInput = {\n /**\n * Namespaced, stable identity key \u2014 e.g. `workflow:<definitionId>`. It is the\n * idempotency key: the deterministic email and role name are both derived from\n * it, so re-provisioning resolves the same rows instead of creating new ones.\n */\n principalKey: string\n displayName?: string\n /** The principal's complete feature set. Replaces the role ACL unless `mergeFeatures`. */\n features: string[]\n /** Defaults to `service` \u2014 a machine principal that is not an AI agent. */\n kind?: UserKind\n /** Local-part / role-name prefix. Defaults to `principal`. */\n namePrefix?: string\n /**\n * Merge into the existing ACL instead of replacing it. Default `false`:\n * merging cannot NARROW a previously over-granted principal, which is the\n * whole point of re-scoping on every save.\n */\n mergeFeatures?: boolean\n}\n\n/**\n * Non-routable internal domain. An execution principal never receives mail and\n * never logs in interactively, so this address is an identity key, not a mailbox.\n */\nconst EXECUTION_PRINCIPAL_EMAIL_DOMAIN = 'agent.internal'\nconst DEFAULT_NAME_PREFIX = 'principal'\n\nfunction slugifyPrincipalKey(principalKey: string): string {\n return principalKey.toLowerCase().replace(/[^a-z0-9._-]+/g, '-')\n}\n\nexport function executionPrincipalEmail(\n principalKey: string,\n organizationId: string,\n namePrefix: string = DEFAULT_NAME_PREFIX,\n): string {\n return `${namePrefix}+${slugifyPrincipalKey(principalKey)}+${organizationId}@${EXECUTION_PRINCIPAL_EMAIL_DOMAIN}`\n}\n\nexport function executionPrincipalRoleName(\n principalKey: string,\n namePrefix: string = DEFAULT_NAME_PREFIX,\n): string {\n return `${namePrefix}:${principalKey}`\n}\n\nexport function normalizeFeatureList(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n const seen = new Set<string>()\n for (const entry of value) {\n if (typeof entry !== 'string') continue\n const trimmed = entry.trim()\n if (trimmed) seen.add(trimmed)\n }\n return Array.from(seen).sort((a, b) => a.localeCompare(b))\n}\n\n/**\n * Provisions \u2014 idempotently \u2014 the execution principal for `principalKey` and\n * pins its role ACL to exactly `features`.\n *\n * Non-interactive by construction: `passwordHash = null` + `isConfirmed = false`\n * leaves the password/SSO flow nothing to authenticate against. `isSuperAdmin`\n * is always false and the ACL's organization visibility is pinned to the\n * provisioning organization, so the principal can never out-scope its owner.\n */\nexport async function provisionExecutionPrincipal(\n container: AwilixContainer,\n scope: ExecutionPrincipalScope,\n input: ProvisionExecutionPrincipalInput,\n): Promise<ResolvedExecutionPrincipal> {\n const { tenantId, organizationId } = scope\n const principalKey = input.principalKey.trim()\n if (!principalKey) throw new Error('[internal] execution principal requires a principalKey')\n if (!tenantId || !organizationId) {\n throw new Error('[internal] execution principal requires a tenant and organization scope')\n }\n\n const namePrefix = input.namePrefix ?? DEFAULT_NAME_PREFIX\n const kind: UserKind = input.kind ?? 'service'\n const features = normalizeFeatureList(input.features)\n const roleName = executionPrincipalRoleName(principalKey, namePrefix)\n const principalEmail = executionPrincipalEmail(principalKey, organizationId, namePrefix)\n\n const em = (container.resolve('em') as EntityManager).fork()\n\n return em.transactional(async (trx) => {\n let role = await findOneWithDecryption(\n trx,\n Role,\n { name: roleName, tenantId },\n {},\n { tenantId, organizationId: null },\n )\n if (!role) {\n role = trx.create(Role, { name: roleName, tenantId, minActiveHolders: 0, createdAt: new Date() })\n trx.persist(role)\n await trx.flush()\n }\n\n const emailHash = computeEmailHash(principalEmail)\n let user = await findOneWithDecryption(\n trx,\n User,\n { emailHash, tenantId, deletedAt: null },\n {},\n { tenantId, organizationId },\n )\n if (!user) {\n user = trx.create(User, {\n email: principalEmail,\n emailHash,\n passwordHash: null,\n isConfirmed: false,\n kind,\n name: input.displayName ?? principalKey,\n tenantId,\n organizationId,\n createdAt: new Date(),\n })\n trx.persist(user)\n await trx.flush()\n } else if (user.kind === 'human') {\n // Defensive: a row resolved by the deterministic principal email must never\n // be a human account. Never silently repurpose one.\n throw new Error('[internal] resolved a human User for an execution principal')\n }\n\n await lockAuthorizationState(trx, {\n userIds: [String(user.id)],\n roleIds: [String(role.id)],\n })\n\n const existingAcl = await findOneWithDecryption(\n trx,\n RoleAcl,\n { role, tenantId, deletedAt: null },\n {},\n { tenantId, organizationId: null },\n )\n if (!existingAcl) {\n trx.persist(\n trx.create(RoleAcl, {\n role,\n tenantId,\n featuresJson: features,\n isSuperAdmin: false,\n organizationsJson: [organizationId],\n createdAt: new Date(),\n }),\n )\n await trx.flush()\n } else {\n const current = normalizeFeatureList(existingAcl.featuresJson)\n const next = input.mergeFeatures ? normalizeFeatureList([...current, ...features]) : features\n if (\n existingAcl.isSuperAdmin ||\n current.length !== next.length ||\n current.some((feature, index) => feature !== next[index])\n ) {\n existingAcl.featuresJson = next\n existingAcl.isSuperAdmin = false\n existingAcl.organizationsJson = [organizationId]\n trx.persist(existingAcl)\n await trx.flush()\n }\n }\n\n const existingLink = await findOneWithDecryption(\n trx,\n UserRole,\n { user, role, deletedAt: null },\n {},\n { tenantId, organizationId: null },\n )\n if (!existingLink) {\n trx.persist(trx.create(UserRole, { user, role, createdAt: new Date() }))\n await trx.flush()\n }\n\n return { userId: user.id, roleId: role.id }\n })\n}\n\n/**\n * Resolves the `User` id of an already-provisioned execution principal without\n * writing anything. Returns null when it has not been provisioned (or was\n * soft-deleted) \u2014 callers MUST fail closed rather than fall back to a broader\n * identity.\n */\nexport async function resolveExecutionPrincipalUserId(\n em: EntityManager,\n principalKey: string,\n scope: ExecutionPrincipalScope,\n namePrefix: string = DEFAULT_NAME_PREFIX,\n): Promise<string | null> {\n const { tenantId, organizationId } = scope\n if (!principalKey || !tenantId || !organizationId) return null\n const emailHash = computeEmailHash(executionPrincipalEmail(principalKey, organizationId, namePrefix))\n const user = await findOneWithDecryption(\n em,\n User,\n { emailHash, tenantId, deletedAt: null },\n {},\n { tenantId, organizationId },\n )\n return user?.id ?? null\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -103,7 +103,7 @@ async function assertActorCanGrantRoles(input) {
103
103
  const acl = await findOneWithDecryption(
104
104
  input.em,
105
105
  RoleAcl,
106
- { role, tenantId },
106
+ { role, tenantId, deletedAt: null },
107
107
  {},
108
108
  { tenantId, organizationId: null }
109
109
  );
@@ -193,15 +193,31 @@ async function resolveActorIsSuperAdmin(input) {
193
193
  return acl.isSuperAdmin;
194
194
  }
195
195
  async function isUserEffectivelySuperAdmin(em, userId) {
196
+ const target = await findOneWithDecryption(
197
+ em,
198
+ User,
199
+ { id: userId },
200
+ {},
201
+ { tenantId: null, organizationId: null }
202
+ );
203
+ const tenantId = normalizeNullableString(target?.tenantId);
204
+ if (!tenantId) return false;
196
205
  const directGrant = await em.findOne(
197
206
  UserAcl,
198
- { user: userId, isSuperAdmin: true }
207
+ { user: userId, tenantId, isSuperAdmin: true, deletedAt: null }
199
208
  );
200
209
  if (directGrant && directGrant.isSuperAdmin === true) return true;
201
210
  const links = await findWithDecryption(
202
211
  em,
203
212
  UserRole,
204
- { user: userId },
213
+ // Roles of the user's own tenant, the same restriction `isGlobalSuperAdmin`
214
+ // applies. The encryption scope stays `{ null, null }`: this is about which
215
+ // rows count, not about which key decrypts them.
216
+ {
217
+ user: userId,
218
+ deletedAt: null,
219
+ role: { tenantId, deletedAt: null }
220
+ },
205
221
  { populate: ["role"] },
206
222
  { tenantId: null, organizationId: null }
207
223
  );
@@ -214,20 +230,29 @@ async function isUserEffectivelySuperAdmin(em, userId) {
214
230
  if (!roleIds.length) return false;
215
231
  const roleGrant = await em.findOne(
216
232
  RoleAcl,
217
- { role: { $in: roleIds }, isSuperAdmin: true }
233
+ { role: { $in: roleIds }, tenantId, isSuperAdmin: true, deletedAt: null }
218
234
  );
219
235
  return !!roleGrant && roleGrant.isSuperAdmin === true;
220
236
  }
221
237
  async function isRoleEffectivelySuperAdmin(em, roleId) {
238
+ const role = await findOneWithDecryption(
239
+ em,
240
+ Role,
241
+ { id: roleId },
242
+ {},
243
+ { tenantId: null, organizationId: null }
244
+ );
245
+ const tenantId = normalizeNullableString(role?.tenantId);
246
+ if (!tenantId) return false;
222
247
  const grant = await em.findOne(
223
248
  RoleAcl,
224
- { role: roleId, isSuperAdmin: true }
249
+ { role: roleId, tenantId, isSuperAdmin: true, deletedAt: null }
225
250
  );
226
251
  return !!grant && grant.isSuperAdmin === true;
227
252
  }
228
253
  async function listSuperAdminUserIds(em, tenantId) {
229
254
  const ids = /* @__PURE__ */ new Set();
230
- const userAclFilter = { isSuperAdmin: true };
255
+ const userAclFilter = { isSuperAdmin: true, deletedAt: null };
231
256
  if (tenantId) userAclFilter.tenantId = tenantId;
232
257
  const userAcls = await em.find(UserAcl, userAclFilter);
233
258
  for (const acl of userAcls) {
@@ -235,10 +260,9 @@ async function listSuperAdminUserIds(em, tenantId) {
235
260
  const userId = userRef && typeof userRef === "object" ? userRef.id : userRef;
236
261
  if (userId) ids.add(String(userId));
237
262
  }
238
- const roleAcls = await em.find(
239
- RoleAcl,
240
- { isSuperAdmin: true }
241
- );
263
+ const roleAclFilter = { isSuperAdmin: true, deletedAt: null };
264
+ if (tenantId) roleAclFilter.tenantId = tenantId;
265
+ const roleAcls = await em.find(RoleAcl, roleAclFilter);
242
266
  const roleIds = roleAcls.map((acl) => {
243
267
  const roleRef = acl.role;
244
268
  if (!roleRef) return null;
@@ -249,7 +273,10 @@ async function listSuperAdminUserIds(em, tenantId) {
249
273
  const links = await findWithDecryption(
250
274
  em,
251
275
  UserRole,
252
- { role: { $in: roleIds } },
276
+ {
277
+ deletedAt: null,
278
+ role: { id: { $in: roleIds }, deletedAt: null }
279
+ },
253
280
  {},
254
281
  { tenantId: null, organizationId: null }
255
282
  );
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/auth/lib/grantChecks.ts"],
4
- "sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { badRequest, CrudHttpError, forbidden } from '@open-mercato/shared/lib/crud/errors'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { Role, RoleAcl, User, UserAcl, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { OrganizationScope } from '@open-mercato/core/modules/directory/utils/organizationScope'\n\ntype ActorAcl = {\n isSuperAdmin: boolean\n features: string[]\n organizations: string[] | null\n}\n\ntype GrantCheckContext = {\n em: EntityManager\n rbacService: RbacService\n actorUserId: string | null | undefined\n tenantId: string | null | undefined\n organizationId?: string | null | undefined\n requireBoundRbac?: boolean\n}\n\ntype RoleGrantCheckInput = GrantCheckContext & {\n roles: Role[]\n}\n\ntype RoleTokenGrantCheckInput = GrantCheckContext & {\n roleTokens: unknown\n}\n\ntype UserDestinationRolesInput = {\n em: EntityManager\n targetUserId?: string | null\n destinationTenantId: string | null | undefined\n roleTokens: unknown\n}\n\ntype UserDestinationScopeCheckInput = GrantCheckContext & {\n actorIsSuperAdmin?: boolean\n allowedOrganizationIds?: string[] | null\n destinationTenantId: string | null | undefined\n destinationOrganizationId: string | null | undefined\n roles: Role[]\n}\n\ntype FeatureGrantCheckInput = GrantCheckContext & {\n features: unknown\n isSuperAdmin?: boolean\n organizations?: string[] | null\n}\n\ntype SuperAdminUserTargetInput = GrantCheckContext & {\n targetUserId: string\n actorIsSuperAdmin?: boolean\n}\n\ntype UserTargetAccessInput = SuperAdminUserTargetInput & {\n organizationScope: Pick<OrganizationScope, 'allowedIds'>\n}\n\ntype SuperAdminRoleTargetInput = GrantCheckContext & {\n targetRoleId: string\n actorIsSuperAdmin?: boolean\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nexport async function assertActorCanGrantRoleTokens(input: RoleTokenGrantCheckInput): Promise<Role[]> {\n const tokens = normalizeStringList(input.roleTokens)\n if (!tokens.length) return []\n\n const tenantId = normalizeNullableString(input.tenantId)\n const roles = await resolveRolesForGrant(input.em, tokens, tenantId)\n await assertActorCanGrantRoles({ ...input, tenantId, roles })\n return roles\n}\n\nexport async function resolveUserDestinationRoles(input: UserDestinationRolesInput): Promise<Role[]> {\n const destinationTenantId = normalizeNullableString(input.destinationTenantId)\n if (Array.isArray(input.roleTokens)) {\n return resolveRolesForGrant(input.em, normalizeStringList(input.roleTokens), destinationTenantId)\n }\n if (!input.targetUserId) return []\n\n const links = await findWithDecryption(\n input.em,\n UserRole,\n { user: input.targetUserId as unknown as User } as FilterQuery<UserRole>,\n { populate: ['role'] },\n { tenantId: null, organizationId: null },\n )\n const roles: Role[] = []\n for (const link of links) {\n const linkedRole = (link as { role?: Role | string | null }).role\n if (linkedRole && typeof linkedRole === 'object') {\n roles.push(linkedRole)\n continue\n }\n if (typeof linkedRole === 'string') {\n const resolvedRole = await resolveRoleForGrant(input.em, linkedRole, null)\n if (resolvedRole) {\n roles.push(resolvedRole)\n continue\n }\n }\n throw badRequest(await translateAuthError(\n 'auth.users.errors.invalidRoleAssignment',\n 'User has an invalid role assignment',\n ))\n }\n return roles\n}\n\nexport async function assertActorCanAssignUserDestination(\n input: UserDestinationScopeCheckInput,\n): Promise<void> {\n const destinationTenantId = normalizeNullableString(input.destinationTenantId)\n const destinationOrganizationId = normalizeNullableString(input.destinationOrganizationId)\n if (!destinationTenantId || !destinationOrganizationId) {\n return throwUserDestinationOrganizationNotFound(400)\n }\n\n for (const role of input.roles) {\n if (normalizeNullableString(role.tenantId) !== destinationTenantId) {\n throw forbidden(await translateAuthError(\n 'auth.users.errors.roleOutsideDestinationTenant',\n 'Cannot retain or assign a role outside the destination tenant.',\n ))\n }\n }\n\n if (await resolveActorIsSuperAdmin(input)) return\n\n const actorTenantId = normalizeNullableString(input.tenantId)\n if (!actorTenantId || actorTenantId !== destinationTenantId) {\n return throwUserDestinationOrganizationNotFound(404)\n }\n\n const actorAcl = await loadActorAcl(input)\n const allowedOrganizationIds = input.allowedOrganizationIds === undefined\n ? actorAcl.organizations\n : input.allowedOrganizationIds\n if (\n allowedOrganizationIds !== null\n && !allowedOrganizationIds.includes('__all__')\n && !allowedOrganizationIds.includes(destinationOrganizationId)\n ) {\n throw forbidden(await translateAuthError(\n 'auth.users.errors.destinationOrganizationOutsideScope',\n 'Cannot assign user to a destination organization outside actor scope.',\n ))\n }\n\n await assertActorCanGrantRoles({\n ...input,\n tenantId: destinationTenantId,\n roles: input.roles,\n })\n}\n\nexport async function throwUserDestinationOrganizationNotFound(status: 400 | 404): Promise<never> {\n throw new CrudHttpError(status, {\n error: await translateAuthError(\n 'auth.users.errors.organizationNotFound',\n 'Organization not found',\n ),\n })\n}\n\nexport async function assertActorCanGrantRoles(input: RoleGrantCheckInput): Promise<void> {\n if (!input.roles.length) return\n\n const tenantId = normalizeNullableString(input.tenantId)\n const actorAcl = await loadActorAcl({ ...input, tenantId })\n if (actorAcl.isSuperAdmin) return\n\n if (!tenantId) {\n throw forbidden('Tenant context is required to grant roles.')\n }\n\n for (const role of input.roles) {\n const roleTenantId = normalizeNullableString(role.tenantId)\n if (roleTenantId !== tenantId) {\n throw forbidden('Cannot grant a role outside the target tenant.')\n }\n\n const acl = await findOneWithDecryption(\n input.em,\n RoleAcl,\n { role, tenantId } as FilterQuery<RoleAcl>,\n {},\n { tenantId, organizationId: null },\n )\n if (!acl) continue\n\n assertActorCanGrantAclSnapshot(actorAcl, {\n isSuperAdmin: !!acl.isSuperAdmin,\n features: normalizeStringList(acl.featuresJson),\n organizations: normalizeOrganizationList(acl.organizationsJson),\n })\n }\n}\n\nexport async function assertActorCanGrantAcl(input: FeatureGrantCheckInput): Promise<void> {\n const actorAcl = await loadActorAcl(input)\n if (actorAcl.isSuperAdmin) return\n\n const tenantId = normalizeNullableString(input.tenantId)\n if (!tenantId) {\n throw forbidden('Tenant context is required to grant ACL features.')\n }\n\n assertActorCanGrantAclSnapshot(actorAcl, {\n isSuperAdmin: !!input.isSuperAdmin,\n features: normalizeStringList(input.features),\n organizations: input.organizations === undefined ? undefined : normalizeOrganizationList(input.organizations),\n })\n}\n\nexport function normalizeGrantFeatureList(features: unknown): string[] {\n return normalizeStringList(features)\n}\n\nexport async function assertActorCanModifySuperAdminUserTarget(input: SuperAdminUserTargetInput): Promise<void> {\n const actorIsSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (actorIsSuperAdmin) return\n const targetIsSuperAdmin = await isUserEffectivelySuperAdmin(input.em, input.targetUserId)\n if (targetIsSuperAdmin) {\n throw forbidden('Only super administrators can modify super administrator accounts.')\n }\n}\n\nexport async function assertActorCanModifySuperAdminRoleTarget(input: SuperAdminRoleTargetInput): Promise<void> {\n const actorIsSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (actorIsSuperAdmin) return\n const targetIsSuperAdmin = await isRoleEffectivelySuperAdmin(input.em, input.targetRoleId)\n if (targetIsSuperAdmin) {\n throw forbidden('Only super administrators can modify super administrator roles.')\n }\n}\n\n/**\n * `input.tenantId` is the ACTOR's scope, not the target's: the check below is a\n * comparison between the two, so passing a tenant derived from the target turns\n * it into a tautology. Callers that resolve a record scope separately MUST still\n * hand this guard `auth.tenantId`.\n */\nexport async function assertActorCanAccessUserTarget(input: UserTargetAccessInput): Promise<void> {\n const isSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (isSuperAdmin) return\n\n const target = await findOneWithDecryption(\n input.em,\n User,\n { id: input.targetUserId } as FilterQuery<User>,\n {},\n { tenantId: null, organizationId: null },\n )\n // Not found (incl. soft-deleted, which MikroORM's soft-delete filter hides):\n // delegate to the caller. Every wired call site is itself tenant-scoped \u2014 the\n // ACL/consents reads filter by auth.tenantId and the user commands re-load by\n // id within tenant \u2014 so a missing target yields a safe empty/404 there. The\n // guard's job is to block a foreign *existing* target, below.\n if (!target) return\n\n const actorTenantId = normalizeNullableString(input.tenantId)\n const targetTenantId = normalizeNullableString((target as { tenantId?: string | null }).tenantId)\n if (!targetTenantId || targetTenantId !== actorTenantId) {\n throw new CrudHttpError(404, { error: 'User not found' })\n }\n\n if (input.organizationScope.allowedIds !== null) {\n const targetOrganizationId = normalizeNullableString((target as { organizationId?: string | null }).organizationId)\n if (!targetOrganizationId || !input.organizationScope.allowedIds.includes(targetOrganizationId)) {\n throw forbidden('Not authorized to access this user.')\n }\n }\n}\n\nexport async function assertActorCanAccessRoleTarget(input: SuperAdminRoleTargetInput): Promise<void> {\n const isSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (isSuperAdmin) return\n\n const target = await findOneWithDecryption(\n input.em,\n Role,\n { id: input.targetRoleId } as FilterQuery<Role>,\n {},\n { tenantId: null, organizationId: null },\n )\n // Not found (incl. soft-deleted): delegate (see assertActorCanAccessUserTarget).\n if (!target) return\n\n const actorTenantId = normalizeNullableString(input.tenantId)\n const targetTenantId = normalizeNullableString((target as { tenantId?: string | null }).tenantId)\n if (!targetTenantId || targetTenantId !== actorTenantId) {\n throw new CrudHttpError(404, { error: 'Role not found' })\n }\n}\n\nasync function resolveActorIsSuperAdmin(input: GrantCheckContext & { actorIsSuperAdmin?: boolean }): Promise<boolean> {\n if (typeof input.actorIsSuperAdmin === 'boolean') return input.actorIsSuperAdmin\n const acl = await loadActorAcl(input)\n return acl.isSuperAdmin\n}\n\nexport async function isUserEffectivelySuperAdmin(em: EntityManager, userId: string): Promise<boolean> {\n const directGrant = await em.findOne(\n UserAcl,\n { user: userId as unknown, isSuperAdmin: true } as FilterQuery<UserAcl>,\n )\n if (directGrant && (directGrant as { isSuperAdmin?: boolean }).isSuperAdmin === true) return true\n const links = await findWithDecryption(\n em,\n UserRole,\n { user: userId as unknown } as FilterQuery<UserRole>,\n { populate: ['role'] },\n { tenantId: null, organizationId: null },\n )\n const roleIds = (Array.isArray(links) ? links : [])\n .map((link) => {\n const role = (link as { role?: { id?: unknown } | string | null }).role\n if (!role) return null\n if (typeof role === 'string') return role\n return role.id ? String(role.id) : null\n })\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n if (!roleIds.length) return false\n const roleGrant = await em.findOne(\n RoleAcl,\n { role: { $in: roleIds } as unknown, isSuperAdmin: true } as FilterQuery<RoleAcl>,\n )\n return !!roleGrant && (roleGrant as { isSuperAdmin?: boolean }).isSuperAdmin === true\n}\n\nexport async function isRoleEffectivelySuperAdmin(em: EntityManager, roleId: string): Promise<boolean> {\n const grant = await em.findOne(\n RoleAcl,\n { role: roleId as unknown, isSuperAdmin: true } as FilterQuery<RoleAcl>,\n )\n return !!grant && (grant as { isSuperAdmin?: boolean }).isSuperAdmin === true\n}\n\nexport async function listSuperAdminUserIds(em: EntityManager, tenantId: string | null): Promise<Set<string>> {\n const ids = new Set<string>()\n const userAclFilter: Record<string, unknown> = { isSuperAdmin: true }\n if (tenantId) userAclFilter.tenantId = tenantId\n const userAcls = await em.find(UserAcl, userAclFilter as FilterQuery<UserAcl>)\n for (const acl of userAcls) {\n const userRef = (acl as { user?: { id?: unknown } | string | null }).user\n const userId = userRef && typeof userRef === 'object'\n ? userRef.id\n : userRef\n if (userId) ids.add(String(userId))\n }\n const roleAcls = await em.find(\n RoleAcl,\n { isSuperAdmin: true } as FilterQuery<RoleAcl>,\n )\n const roleIds = roleAcls\n .map((acl) => {\n const roleRef = (acl as { role?: { id?: unknown } | string | null }).role\n if (!roleRef) return null\n if (typeof roleRef === 'string') return roleRef\n return roleRef.id ? String(roleRef.id) : null\n })\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n if (roleIds.length) {\n const links = await findWithDecryption(\n em,\n UserRole,\n { role: { $in: roleIds } as unknown } as FilterQuery<UserRole>,\n {},\n { tenantId: null, organizationId: null },\n )\n for (const link of Array.isArray(links) ? links : []) {\n const userRef = (link as { user?: { id?: unknown } | string | null }).user\n const userId = userRef && typeof userRef === 'object'\n ? userRef.id\n : userRef\n if (userId) ids.add(String(userId))\n }\n }\n return ids\n}\n\nasync function loadActorAcl(input: GrantCheckContext): Promise<ActorAcl> {\n const actorUserId = normalizeNullableString(input.actorUserId)\n if (!actorUserId) throw forbidden('Not authorized to grant ACL privileges.')\n\n const scope = {\n tenantId: normalizeNullableString(input.tenantId),\n organizationId: normalizeNullableString(input.organizationId),\n }\n const loadBound = input.rbacService.loadAclWithEntityManager\n if (input.requireBoundRbac && typeof loadBound !== 'function') {\n throw new Error('[internal] Transaction-bound RBAC resolution is unavailable')\n }\n const acl = typeof loadBound === 'function'\n ? await loadBound.call(input.rbacService, input.em, actorUserId, scope)\n : await input.rbacService.loadAcl(actorUserId, scope)\n\n return {\n isSuperAdmin: !!acl?.isSuperAdmin,\n features: normalizeStringList(acl?.features),\n organizations: normalizeOrganizationList(acl?.organizations),\n }\n}\n\nasync function resolveRolesForGrant(\n em: EntityManager,\n roleTokens: string[],\n tenantId: string | null,\n): Promise<Role[]> {\n const roles: Role[] = []\n const missingRoles: string[] = []\n\n for (const token of roleTokens) {\n const role = await resolveRoleForGrant(em, token, tenantId)\n if (!role) {\n missingRoles.push(token)\n } else {\n roles.push(role)\n }\n }\n\n if (missingRoles.length) {\n const labels = missingRoles.map((role) => `\"${role}\"`).join(', ')\n throw new CrudHttpError(400, { error: `Role(s) not found: ${labels}` })\n }\n\n return roles\n}\n\nasync function resolveRoleForGrant(\n em: EntityManager,\n token: string,\n tenantId: string | null,\n): Promise<Role | null> {\n const where: Record<string, unknown> = UUID_RE.test(token)\n ? { id: token, deletedAt: null }\n : { name: token, deletedAt: null }\n if (tenantId) where.tenantId = tenantId\n return findOneWithDecryption(\n em,\n Role,\n where as FilterQuery<Role>,\n {},\n { tenantId, organizationId: null },\n )\n}\n\nfunction assertActorCanGrantAclSnapshot(\n actorAcl: ActorAcl,\n requested: {\n isSuperAdmin: boolean\n features: string[]\n organizations?: string[] | null\n },\n): void {\n if (requested.isSuperAdmin) {\n throw forbidden('Only super administrators can grant super admin access.')\n }\n\n const actorGrantableFeatures = actorAcl.features.filter((grant) => grant !== '*')\n for (const feature of requested.features) {\n if (feature === '*') {\n throw forbidden('Only super administrators can grant global wildcard access.')\n }\n if (isWildcardFeature(feature)) {\n if (!hasFeature(actorGrantableFeatures, feature)) {\n throw forbidden(`Cannot grant feature wildcard ${feature}.`)\n }\n continue\n }\n if (!hasFeature(actorGrantableFeatures, feature)) {\n throw forbidden(`Cannot grant feature ${feature}.`)\n }\n }\n\n if (requested.organizations !== undefined) {\n assertActorCanGrantOrganizations(actorAcl.organizations, requested.organizations)\n }\n}\n\nfunction assertActorCanGrantOrganizations(\n actorOrganizations: string[] | null,\n requestedOrganizations: string[] | null,\n): void {\n if (actorOrganizations === null || actorOrganizations.includes('__all__')) return\n\n if (requestedOrganizations === null || requestedOrganizations.includes('__all__')) {\n throw forbidden('Cannot grant unrestricted organization access.')\n }\n\n for (const organizationId of requestedOrganizations) {\n if (!actorOrganizations.includes(organizationId)) {\n throw forbidden('Cannot grant organization access outside actor scope.')\n }\n }\n}\n\nfunction normalizeStringList(values: unknown): string[] {\n if (!Array.isArray(values)) return []\n const dedup = new Set<string>()\n for (const value of values) {\n if (typeof value !== 'string') continue\n const trimmed = value.trim()\n if (!trimmed) continue\n dedup.add(trimmed)\n }\n return Array.from(dedup)\n}\n\nfunction normalizeOrganizationList(values: unknown): string[] | null {\n if (values === null || values === undefined) return null\n return normalizeStringList(values)\n}\n\nfunction normalizeNullableString(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n}\n\nfunction isWildcardFeature(feature: string): boolean {\n return feature.endsWith('.*')\n}\n\nasync function translateAuthError(key: string, fallback: string): Promise<string> {\n const { translate } = await resolveTranslations()\n return translate(key, fallback)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { badRequest, CrudHttpError, forbidden } from '@open-mercato/shared/lib/crud/errors'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { Role, RoleAcl, User, UserAcl, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { OrganizationScope } from '@open-mercato/core/modules/directory/utils/organizationScope'\n\ntype ActorAcl = {\n isSuperAdmin: boolean\n features: string[]\n organizations: string[] | null\n}\n\ntype GrantCheckContext = {\n em: EntityManager\n rbacService: RbacService\n actorUserId: string | null | undefined\n tenantId: string | null | undefined\n organizationId?: string | null | undefined\n requireBoundRbac?: boolean\n}\n\ntype RoleGrantCheckInput = GrantCheckContext & {\n roles: Role[]\n}\n\ntype RoleTokenGrantCheckInput = GrantCheckContext & {\n roleTokens: unknown\n}\n\ntype UserDestinationRolesInput = {\n em: EntityManager\n targetUserId?: string | null\n destinationTenantId: string | null | undefined\n roleTokens: unknown\n}\n\ntype UserDestinationScopeCheckInput = GrantCheckContext & {\n actorIsSuperAdmin?: boolean\n allowedOrganizationIds?: string[] | null\n destinationTenantId: string | null | undefined\n destinationOrganizationId: string | null | undefined\n roles: Role[]\n}\n\ntype FeatureGrantCheckInput = GrantCheckContext & {\n features: unknown\n isSuperAdmin?: boolean\n organizations?: string[] | null\n}\n\ntype SuperAdminUserTargetInput = GrantCheckContext & {\n targetUserId: string\n actorIsSuperAdmin?: boolean\n}\n\ntype UserTargetAccessInput = SuperAdminUserTargetInput & {\n organizationScope: Pick<OrganizationScope, 'allowedIds'>\n}\n\ntype SuperAdminRoleTargetInput = GrantCheckContext & {\n targetRoleId: string\n actorIsSuperAdmin?: boolean\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nexport async function assertActorCanGrantRoleTokens(input: RoleTokenGrantCheckInput): Promise<Role[]> {\n const tokens = normalizeStringList(input.roleTokens)\n if (!tokens.length) return []\n\n const tenantId = normalizeNullableString(input.tenantId)\n const roles = await resolveRolesForGrant(input.em, tokens, tenantId)\n await assertActorCanGrantRoles({ ...input, tenantId, roles })\n return roles\n}\n\nexport async function resolveUserDestinationRoles(input: UserDestinationRolesInput): Promise<Role[]> {\n const destinationTenantId = normalizeNullableString(input.destinationTenantId)\n if (Array.isArray(input.roleTokens)) {\n return resolveRolesForGrant(input.em, normalizeStringList(input.roleTokens), destinationTenantId)\n }\n if (!input.targetUserId) return []\n\n const links = await findWithDecryption(\n input.em,\n UserRole,\n { user: input.targetUserId as unknown as User } as FilterQuery<UserRole>,\n { populate: ['role'] },\n { tenantId: null, organizationId: null },\n )\n const roles: Role[] = []\n for (const link of links) {\n const linkedRole = (link as { role?: Role | string | null }).role\n if (linkedRole && typeof linkedRole === 'object') {\n roles.push(linkedRole)\n continue\n }\n if (typeof linkedRole === 'string') {\n const resolvedRole = await resolveRoleForGrant(input.em, linkedRole, null)\n if (resolvedRole) {\n roles.push(resolvedRole)\n continue\n }\n }\n throw badRequest(await translateAuthError(\n 'auth.users.errors.invalidRoleAssignment',\n 'User has an invalid role assignment',\n ))\n }\n return roles\n}\n\nexport async function assertActorCanAssignUserDestination(\n input: UserDestinationScopeCheckInput,\n): Promise<void> {\n const destinationTenantId = normalizeNullableString(input.destinationTenantId)\n const destinationOrganizationId = normalizeNullableString(input.destinationOrganizationId)\n if (!destinationTenantId || !destinationOrganizationId) {\n return throwUserDestinationOrganizationNotFound(400)\n }\n\n for (const role of input.roles) {\n if (normalizeNullableString(role.tenantId) !== destinationTenantId) {\n throw forbidden(await translateAuthError(\n 'auth.users.errors.roleOutsideDestinationTenant',\n 'Cannot retain or assign a role outside the destination tenant.',\n ))\n }\n }\n\n if (await resolveActorIsSuperAdmin(input)) return\n\n const actorTenantId = normalizeNullableString(input.tenantId)\n if (!actorTenantId || actorTenantId !== destinationTenantId) {\n return throwUserDestinationOrganizationNotFound(404)\n }\n\n const actorAcl = await loadActorAcl(input)\n const allowedOrganizationIds = input.allowedOrganizationIds === undefined\n ? actorAcl.organizations\n : input.allowedOrganizationIds\n if (\n allowedOrganizationIds !== null\n && !allowedOrganizationIds.includes('__all__')\n && !allowedOrganizationIds.includes(destinationOrganizationId)\n ) {\n throw forbidden(await translateAuthError(\n 'auth.users.errors.destinationOrganizationOutsideScope',\n 'Cannot assign user to a destination organization outside actor scope.',\n ))\n }\n\n await assertActorCanGrantRoles({\n ...input,\n tenantId: destinationTenantId,\n roles: input.roles,\n })\n}\n\nexport async function throwUserDestinationOrganizationNotFound(status: 400 | 404): Promise<never> {\n throw new CrudHttpError(status, {\n error: await translateAuthError(\n 'auth.users.errors.organizationNotFound',\n 'Organization not found',\n ),\n })\n}\n\nexport async function assertActorCanGrantRoles(input: RoleGrantCheckInput): Promise<void> {\n if (!input.roles.length) return\n\n const tenantId = normalizeNullableString(input.tenantId)\n const actorAcl = await loadActorAcl({ ...input, tenantId })\n if (actorAcl.isSuperAdmin) return\n\n if (!tenantId) {\n throw forbidden('Tenant context is required to grant roles.')\n }\n\n for (const role of input.roles) {\n const roleTenantId = normalizeNullableString(role.tenantId)\n if (roleTenantId !== tenantId) {\n throw forbidden('Cannot grant a role outside the target tenant.')\n }\n\n const acl = await findOneWithDecryption(\n input.em,\n RoleAcl,\n { role, tenantId, deletedAt: null } as FilterQuery<RoleAcl>,\n {},\n { tenantId, organizationId: null },\n )\n if (!acl) continue\n\n assertActorCanGrantAclSnapshot(actorAcl, {\n isSuperAdmin: !!acl.isSuperAdmin,\n features: normalizeStringList(acl.featuresJson),\n organizations: normalizeOrganizationList(acl.organizationsJson),\n })\n }\n}\n\nexport async function assertActorCanGrantAcl(input: FeatureGrantCheckInput): Promise<void> {\n const actorAcl = await loadActorAcl(input)\n if (actorAcl.isSuperAdmin) return\n\n const tenantId = normalizeNullableString(input.tenantId)\n if (!tenantId) {\n throw forbidden('Tenant context is required to grant ACL features.')\n }\n\n assertActorCanGrantAclSnapshot(actorAcl, {\n isSuperAdmin: !!input.isSuperAdmin,\n features: normalizeStringList(input.features),\n organizations: input.organizations === undefined ? undefined : normalizeOrganizationList(input.organizations),\n })\n}\n\nexport function normalizeGrantFeatureList(features: unknown): string[] {\n return normalizeStringList(features)\n}\n\nexport async function assertActorCanModifySuperAdminUserTarget(input: SuperAdminUserTargetInput): Promise<void> {\n const actorIsSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (actorIsSuperAdmin) return\n const targetIsSuperAdmin = await isUserEffectivelySuperAdmin(input.em, input.targetUserId)\n if (targetIsSuperAdmin) {\n throw forbidden('Only super administrators can modify super administrator accounts.')\n }\n}\n\nexport async function assertActorCanModifySuperAdminRoleTarget(input: SuperAdminRoleTargetInput): Promise<void> {\n const actorIsSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (actorIsSuperAdmin) return\n const targetIsSuperAdmin = await isRoleEffectivelySuperAdmin(input.em, input.targetRoleId)\n if (targetIsSuperAdmin) {\n throw forbidden('Only super administrators can modify super administrator roles.')\n }\n}\n\n/**\n * `input.tenantId` is the ACTOR's scope, not the target's: the check below is a\n * comparison between the two, so passing a tenant derived from the target turns\n * it into a tautology. Callers that resolve a record scope separately MUST still\n * hand this guard `auth.tenantId`.\n */\nexport async function assertActorCanAccessUserTarget(input: UserTargetAccessInput): Promise<void> {\n const isSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (isSuperAdmin) return\n\n const target = await findOneWithDecryption(\n input.em,\n User,\n { id: input.targetUserId } as FilterQuery<User>,\n {},\n { tenantId: null, organizationId: null },\n )\n // Not found (incl. soft-deleted, which MikroORM's soft-delete filter hides):\n // delegate to the caller. Every wired call site is itself tenant-scoped \u2014 the\n // ACL/consents reads filter by auth.tenantId and the user commands re-load by\n // id within tenant \u2014 so a missing target yields a safe empty/404 there. The\n // guard's job is to block a foreign *existing* target, below.\n if (!target) return\n\n const actorTenantId = normalizeNullableString(input.tenantId)\n const targetTenantId = normalizeNullableString((target as { tenantId?: string | null }).tenantId)\n if (!targetTenantId || targetTenantId !== actorTenantId) {\n throw new CrudHttpError(404, { error: 'User not found' })\n }\n\n if (input.organizationScope.allowedIds !== null) {\n const targetOrganizationId = normalizeNullableString((target as { organizationId?: string | null }).organizationId)\n if (!targetOrganizationId || !input.organizationScope.allowedIds.includes(targetOrganizationId)) {\n throw forbidden('Not authorized to access this user.')\n }\n }\n}\n\nexport async function assertActorCanAccessRoleTarget(input: SuperAdminRoleTargetInput): Promise<void> {\n const isSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (isSuperAdmin) return\n\n const target = await findOneWithDecryption(\n input.em,\n Role,\n { id: input.targetRoleId } as FilterQuery<Role>,\n {},\n { tenantId: null, organizationId: null },\n )\n // Not found (incl. soft-deleted): delegate (see assertActorCanAccessUserTarget).\n if (!target) return\n\n const actorTenantId = normalizeNullableString(input.tenantId)\n const targetTenantId = normalizeNullableString((target as { tenantId?: string | null }).tenantId)\n if (!targetTenantId || targetTenantId !== actorTenantId) {\n throw new CrudHttpError(404, { error: 'Role not found' })\n }\n}\n\nasync function resolveActorIsSuperAdmin(input: GrantCheckContext & { actorIsSuperAdmin?: boolean }): Promise<boolean> {\n if (typeof input.actorIsSuperAdmin === 'boolean') return input.actorIsSuperAdmin\n const acl = await loadActorAcl(input)\n return acl.isSuperAdmin\n}\n\n/**\n * Answers \"is this user effectively a super-admin\" about a TARGET \u2014 the input to\n * `assertActorCanModifySuperAdminUserTarget`, not to any authorization decision\n * about the caller.\n *\n * It is bound to the target's OWN tenant (`users.tenant_id`) and to live grant\n * rows, because that is the reading the two authorities already share:\n * `RbacService.isGlobalSuperAdmin` and `lib/sessionIntegrity.ts`\n * (`userAclGrantsSuperAdmin` / `roleAclGrantsSuperAdmin`). A protection\n * predicate that answered differently would protect people who hold nothing \u2014\n * a grant stamped a foreign tenant, or one that was revoked \u2014 while every\n * authorization path treats them as ordinary users. That over-protection does\n * not expire: nothing short of a super-admin could edit such an account again,\n * and revoking someone's super-admin would leave them permanently unmanageable\n * by their own tenant's admins.\n *\n * Two asymmetries with `isGlobalSuperAdmin` are deliberate:\n *\n * - Organization-restricted role grants are NOT excluded. That exclusion only\n * keeps them out of the GLOBAL answer; `loadAcl`'s scoped projection still\n * returns `isSuperAdmin: true` for such a role inside the organizations it\n * names, so its holder is a privileged target. A protection predicate may be\n * a superset of the authorization answer; it must never be a subset.\n * - The `User` load carries no `deletedAt` filter, matching the load in\n * `assertActorCanAccessUserTarget` below. A soft-deleted super-admin stays\n * protected; it is the ACL rows that must be live.\n *\n * \"No user row\" and \"no tenant of their own\" both answer false, as\n * `isGlobalSuperAdmin` does. Neither is exploitable, because every call site\n * pairs this guard with `assertActorCanAccessUserTarget`: that one delegates a\n * missing target to the caller's own tenant-scoped load, and 404s a target\n * whose `tenantId` is absent, so no mutation reaches either case.\n */\nexport async function isUserEffectivelySuperAdmin(em: EntityManager, userId: string): Promise<boolean> {\n const target = await findOneWithDecryption(\n em,\n User,\n { id: userId } as FilterQuery<User>,\n {},\n { tenantId: null, organizationId: null },\n )\n const tenantId = normalizeNullableString((target as { tenantId?: string | null } | null)?.tenantId)\n if (!tenantId) return false\n const directGrant = await em.findOne(\n UserAcl,\n { user: userId as unknown, tenantId, isSuperAdmin: true, deletedAt: null } as FilterQuery<UserAcl>,\n )\n if (directGrant && (directGrant as { isSuperAdmin?: boolean }).isSuperAdmin === true) return true\n const links = await findWithDecryption(\n em,\n UserRole,\n // Roles of the user's own tenant, the same restriction `isGlobalSuperAdmin`\n // applies. The encryption scope stays `{ null, null }`: this is about which\n // rows count, not about which key decrypts them.\n {\n user: userId as unknown,\n deletedAt: null,\n role: { tenantId, deletedAt: null },\n } as FilterQuery<UserRole>,\n { populate: ['role'] },\n { tenantId: null, organizationId: null },\n )\n const roleIds = (Array.isArray(links) ? links : [])\n .map((link) => {\n const role = (link as { role?: { id?: unknown } | string | null }).role\n if (!role) return null\n if (typeof role === 'string') return role\n return role.id ? String(role.id) : null\n })\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n if (!roleIds.length) return false\n const roleGrant = await em.findOne(\n RoleAcl,\n { role: { $in: roleIds } as unknown, tenantId, isSuperAdmin: true, deletedAt: null } as FilterQuery<RoleAcl>,\n )\n return !!roleGrant && (roleGrant as { isSuperAdmin?: boolean }).isSuperAdmin === true\n}\n\n/**\n * The role twin of `isUserEffectivelySuperAdmin`, and it carries the same\n * reading for the same reasons: the grant must be live, and it must be stamped\n * with the role's OWN tenant. A `role_acls` row stamped elsewhere confers\n * nothing (`RbacService.loadAcl` reads role ACLs `{ tenantId, deletedAt: null }`),\n * so protecting a role on the strength of one would refuse an edit that every\n * authorization path considers ordinary.\n *\n * A role that does not exist answers false, which its paired guard\n * `assertActorCanAccessRoleTarget` then delegates to the caller in the same way\n * the user side does.\n */\nexport async function isRoleEffectivelySuperAdmin(em: EntityManager, roleId: string): Promise<boolean> {\n const role = await findOneWithDecryption(\n em,\n Role,\n { id: roleId } as FilterQuery<Role>,\n {},\n { tenantId: null, organizationId: null },\n )\n const tenantId = normalizeNullableString((role as { tenantId?: string | null } | null)?.tenantId)\n if (!tenantId) return false\n const grant = await em.findOne(\n RoleAcl,\n { role: roleId as unknown, tenantId, isSuperAdmin: true, deletedAt: null } as FilterQuery<RoleAcl>,\n )\n return !!grant && (grant as { isSuperAdmin?: boolean }).isSuperAdmin === true\n}\n\n/**\n * The super-admin user ids a non-super-admin must not be shown while looking at\n * `tenantId` \u2014 an exclusion list, not an authorization answer.\n *\n * `tenantId` names the tenant whose users are being listed. Because a\n * super-admin grant is bound to the holder's own tenant (see\n * `isUserEffectivelySuperAdmin` above and `RbacService.isGlobalSuperAdmin`),\n * that is also the tenant a grant must be stamped with in order to count here,\n * so the filter is applied to BOTH ACL tables. It used to reach only\n * `user_acls`, which made the two halves disagree about what a tenant-scoped\n * answer meant.\n *\n * The narrowing on `role_acls` is only safe TOGETHER with that tenant binding:\n * while a foreign-stamped row still conferred super-admin, dropping it from this\n * list would have unhidden a real one.\n *\n * The caller owes the other half of the scoping. This returns ids; it is the\n * caller's own subject query that must already be restricted to `tenantId`.\n * Both wired call sites are \u2014 `auth/api/users/route.ts` filters the user query\n * by the same tenant, and documents' principals route scopes to `ctx.tenantId`\n * \u2014 so a role member living in another tenant is merely excluded from a list\n * they were never in. That is not licence to pass a tenant unrelated to the\n * subject set.\n *\n * Deliberately NOT narrowed further: organization-restricted role grants count\n * (their holders are super-admins inside those organizations), and the\n * `user_roles` expansion stays tenant-less. Over-inclusion there is bounded by\n * the caller's subject scoping, and for an exclusion list hiding one user too\n * many is the safe direction.\n */\nexport async function listSuperAdminUserIds(em: EntityManager, tenantId: string | null): Promise<Set<string>> {\n const ids = new Set<string>()\n // A revoked override must not keep answering \u2014 the same rule the ACL reads in\n // `RbacService` follow.\n const userAclFilter: Record<string, unknown> = { isSuperAdmin: true, deletedAt: null }\n if (tenantId) userAclFilter.tenantId = tenantId\n const userAcls = await em.find(UserAcl, userAclFilter as FilterQuery<UserAcl>)\n for (const acl of userAcls) {\n const userRef = (acl as { user?: { id?: unknown } | string | null }).user\n const userId = userRef && typeof userRef === 'object'\n ? userRef.id\n : userRef\n if (userId) ids.add(String(userId))\n }\n const roleAclFilter: Record<string, unknown> = { isSuperAdmin: true, deletedAt: null }\n if (tenantId) roleAclFilter.tenantId = tenantId\n const roleAcls = await em.find(RoleAcl, roleAclFilter as FilterQuery<RoleAcl>)\n const roleIds = roleAcls\n .map((acl) => {\n const roleRef = (acl as { role?: { id?: unknown } | string | null }).role\n if (!roleRef) return null\n if (typeof roleRef === 'string') return roleRef\n return roleRef.id ? String(roleRef.id) : null\n })\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n if (roleIds.length) {\n const links = await findWithDecryption(\n em,\n UserRole,\n {\n deletedAt: null,\n role: { id: { $in: roleIds }, deletedAt: null },\n } as FilterQuery<UserRole>,\n {},\n { tenantId: null, organizationId: null },\n )\n for (const link of Array.isArray(links) ? links : []) {\n const userRef = (link as { user?: { id?: unknown } | string | null }).user\n const userId = userRef && typeof userRef === 'object'\n ? userRef.id\n : userRef\n if (userId) ids.add(String(userId))\n }\n }\n return ids\n}\n\nasync function loadActorAcl(input: GrantCheckContext): Promise<ActorAcl> {\n const actorUserId = normalizeNullableString(input.actorUserId)\n if (!actorUserId) throw forbidden('Not authorized to grant ACL privileges.')\n\n const scope = {\n tenantId: normalizeNullableString(input.tenantId),\n organizationId: normalizeNullableString(input.organizationId),\n }\n const loadBound = input.rbacService.loadAclWithEntityManager\n if (input.requireBoundRbac && typeof loadBound !== 'function') {\n throw new Error('[internal] Transaction-bound RBAC resolution is unavailable')\n }\n const acl = typeof loadBound === 'function'\n ? await loadBound.call(input.rbacService, input.em, actorUserId, scope)\n : await input.rbacService.loadAcl(actorUserId, scope)\n\n return {\n isSuperAdmin: !!acl?.isSuperAdmin,\n features: normalizeStringList(acl?.features),\n organizations: normalizeOrganizationList(acl?.organizations),\n }\n}\n\nasync function resolveRolesForGrant(\n em: EntityManager,\n roleTokens: string[],\n tenantId: string | null,\n): Promise<Role[]> {\n const roles: Role[] = []\n const missingRoles: string[] = []\n\n for (const token of roleTokens) {\n const role = await resolveRoleForGrant(em, token, tenantId)\n if (!role) {\n missingRoles.push(token)\n } else {\n roles.push(role)\n }\n }\n\n if (missingRoles.length) {\n const labels = missingRoles.map((role) => `\"${role}\"`).join(', ')\n throw new CrudHttpError(400, { error: `Role(s) not found: ${labels}` })\n }\n\n return roles\n}\n\nasync function resolveRoleForGrant(\n em: EntityManager,\n token: string,\n tenantId: string | null,\n): Promise<Role | null> {\n const where: Record<string, unknown> = UUID_RE.test(token)\n ? { id: token, deletedAt: null }\n : { name: token, deletedAt: null }\n if (tenantId) where.tenantId = tenantId\n return findOneWithDecryption(\n em,\n Role,\n where as FilterQuery<Role>,\n {},\n { tenantId, organizationId: null },\n )\n}\n\nfunction assertActorCanGrantAclSnapshot(\n actorAcl: ActorAcl,\n requested: {\n isSuperAdmin: boolean\n features: string[]\n organizations?: string[] | null\n },\n): void {\n if (requested.isSuperAdmin) {\n throw forbidden('Only super administrators can grant super admin access.')\n }\n\n const actorGrantableFeatures = actorAcl.features.filter((grant) => grant !== '*')\n for (const feature of requested.features) {\n if (feature === '*') {\n throw forbidden('Only super administrators can grant global wildcard access.')\n }\n if (isWildcardFeature(feature)) {\n if (!hasFeature(actorGrantableFeatures, feature)) {\n throw forbidden(`Cannot grant feature wildcard ${feature}.`)\n }\n continue\n }\n if (!hasFeature(actorGrantableFeatures, feature)) {\n throw forbidden(`Cannot grant feature ${feature}.`)\n }\n }\n\n if (requested.organizations !== undefined) {\n assertActorCanGrantOrganizations(actorAcl.organizations, requested.organizations)\n }\n}\n\nfunction assertActorCanGrantOrganizations(\n actorOrganizations: string[] | null,\n requestedOrganizations: string[] | null,\n): void {\n if (actorOrganizations === null || actorOrganizations.includes('__all__')) return\n\n if (requestedOrganizations === null || requestedOrganizations.includes('__all__')) {\n throw forbidden('Cannot grant unrestricted organization access.')\n }\n\n for (const organizationId of requestedOrganizations) {\n if (!actorOrganizations.includes(organizationId)) {\n throw forbidden('Cannot grant organization access outside actor scope.')\n }\n }\n}\n\nfunction normalizeStringList(values: unknown): string[] {\n if (!Array.isArray(values)) return []\n const dedup = new Set<string>()\n for (const value of values) {\n if (typeof value !== 'string') continue\n const trimmed = value.trim()\n if (!trimmed) continue\n dedup.add(trimmed)\n }\n return Array.from(dedup)\n}\n\nfunction normalizeOrganizationList(values: unknown): string[] | null {\n if (values === null || values === undefined) return null\n return normalizeStringList(values)\n}\n\nfunction normalizeNullableString(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n}\n\nfunction isWildcardFeature(feature: string): boolean {\n return feature.endsWith('.*')\n}\n\nasync function translateAuthError(key: string, fallback: string): Promise<string> {\n const { translate } = await resolveTranslations()\n return translate(key, fallback)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -342,7 +342,7 @@ async function setupInitialTenant(em, options) {
342
342
  roleIds: assignedRoles.map((role) => String(role.id))
343
343
  });
344
344
  for (const role of assignedRoles) {
345
- const existingLink = await findOneWithDecryption(tem, UserRole, { user, role }, {}, { tenantId: tenantId ?? null, organizationId: null });
345
+ const existingLink = await findOneWithDecryption(tem, UserRole, { user, role, deletedAt: null }, {}, { tenantId: tenantId ?? null, organizationId: null });
346
346
  if (!existingLink) tem.persist(tem.create(UserRole, { user, role, createdAt: /* @__PURE__ */ new Date() }));
347
347
  }
348
348
  await tem.flush();
@@ -486,7 +486,7 @@ async function ensureRoleAclFor(em, role, tenantId, features, options = {}) {
486
486
  const uniqueFeatures = Array.from(new Set(features));
487
487
  await withAtomicFlush(em, [async () => {
488
488
  await lockRoleAclWriterAuthorizationState(em, [String(role.id)]);
489
- const existing = await findOneWithDecryption(em, RoleAcl, { role, tenantId }, {}, { tenantId, organizationId: null });
489
+ const existing = await findOneWithDecryption(em, RoleAcl, { role, tenantId, deletedAt: null }, {}, { tenantId, organizationId: null });
490
490
  if (!existing) {
491
491
  const acl = em.create(RoleAcl, {
492
492
  role,