@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/commands/acl.ts"],
4
- "sourcesContent": ["// =============================================================================\n// Auth ACL Commands \u2014 Undo Policy\n// =============================================================================\n//\n// Neither command in this file defines `undo` or `redo`, so the command bus\n// never mints an undo token for them: `CommandBus.isUndoable()` requires a\n// handler-provided `undo`. The explicit `isUndoable: false` below is executable\n// documentation of that intent, not the mechanism \u2014 the guarantee comes from\n// the absence of the handlers, and the unit tests pin both.\n//\n// The policy is deliberate even though an ACL grant is trivially reversible.\n// The undo and redo endpoints are gated on `audit_logs.undo_self` /\n// `audit_logs.undo_tenant` (and their redo counterparts), not on\n// `auth.acl.manage`. `audit_logs.undo_self` is a default `employee` grant and\n// `audit_logs.undo_tenant` reaches every `admin` through `audit_logs.*`, so an\n// undoable ACL command would let a caller revert or replay someone else's\n// permission change without ever holding the feature that authorizes editing\n// permissions.\n//\n// Reversal stays available to the callers who are actually authorized for it:\n// re-submitting the ACL form, gated on `auth.acl.manage` and guarded by\n// `assertActorCanGrantAcl`. The audit log still captures the full before/after\n// via `buildLog`, so any such correction is itself fully traceable.\n// =============================================================================\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { snapshotsEqual } from '@open-mercato/shared/lib/commands/helpers'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { Role, RoleAcl, User, UserAcl } from '@open-mercato/core/modules/auth/data/entities'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport {\n lockRoleWriterAuthorizationState,\n lockUserAclWriterAuthorizationState,\n} from '@open-mercato/core/modules/auth/lib/authorizationStateLocks'\n\nconst logger = createLogger('auth').child({ component: 'acl-commands' })\n\ntype TaggableCache = { deleteByTags?: (tags: string[]) => Promise<void> | void }\n\n/** Structural view of the fields both `RoleAcl` and `UserAcl` expose. */\ntype AclRecord = {\n isSuperAdmin: boolean\n featuresJson?: string[] | null\n organizationsJson?: string[] | null\n}\n\n/**\n * Audit snapshot of a single ACL row.\n *\n * `features` and `organizations` are grant *sets*, but `features_json` /\n * `organizations_json` preserve the client's insertion order. The command bus\n * derives `changes` from these snapshots with a deep equality check that is\n * order-sensitive for arrays, so an unsorted snapshot would report a spurious\n * `features` change every time an admin re-saves an unmodified form. Sorting\n * here makes the derived diff reflect real grant changes only.\n *\n * `organizations: null` (visible in every organization) is deliberately\n * distinct from `[]` (visible in none).\n */\ntype AclSnapshot = {\n isSuperAdmin: boolean\n features: string[]\n organizations: string[] | null\n}\n\nconst EMPTY_ACL_SNAPSHOT: AclSnapshot = {\n isSuperAdmin: false,\n features: [],\n organizations: null,\n}\n\n/**\n * Identity of the role or user an entry is about.\n *\n * `action_logs` keeps only `resource_id`, so an entry degrades to a bare UUID\n * the moment the role or account is deleted \u2014 exactly when the trail matters\n * most. This rides in `context` rather than in the snapshots because the\n * command bus derives `changes` from the two snapshots with a deep comparison:\n * a target renamed between two ACL edits would otherwise be reported as a\n * permission change, and the no-op guard below would stop recognising an\n * unchanged ACL.\n */\ntype AclTarget =\n | { kind: 'user'; id: string; email: string | null; name: string | null }\n | { kind: 'role'; id: string; name: string | null }\n\n/**\n * What the caller asked for when the route trimmed the request instead of\n * refusing it.\n *\n * `sanitizeTenantFeatures` drops restricted grants silently, so such a request\n * leaves `before` and `after` identical and is indistinguishable from a no-op \u2014\n * which `buildLog` skips. Recording the original request both keeps the attempt\n * visible and exempts the entry from that guard.\n *\n * Features are the only axis carried, because they are the only one the route\n * can trim: a super-admin request is honoured or refused outright, and\n * organizations are never sanitized. Recording either would put a value next to\n * the snapshots that always matches them, inviting a reader to diff the two and\n * conclude a grant was refused.\n */\ntype SanitizedAclRequest = {\n features: string[]\n}\n\n/** How the change reads at a glance, without diffing two grant lists. */\ntype AclChangeEffect = 'granted' | 'changed' | 'revoked'\n\n/**\n * Codepoint ordering rather than `localeCompare`: these snapshots are persisted\n * audit records, so the ordering must not shift with the runtime's default\n * locale. Feature and organization ids are opaque ASCII identifiers that are\n * never shown in this order to a user.\n *\n * The default comparator would coerce and compare the same way today, but\n * omitting one is banned repo-wide (`explicit-sort-comparators.test.ts`,\n * #3620): the ordering must be stated rather than inherited, so it cannot\n * silently change meaning if a call site ever drifts to non-strings. This is\n * the canonical-key form that audit prescribes.\n */\nfunction compareIdentifiers(left: string, right: string): number {\n if (left === right) return 0\n return left < right ? -1 : 1\n}\n\nfunction toSortedList(values: unknown): string[] {\n if (!Array.isArray(values)) return []\n return [...values].filter((value): value is string => typeof value === 'string').sort(compareIdentifiers)\n}\n\nfunction captureAclSnapshot(acl: AclRecord | null | undefined): AclSnapshot {\n if (!acl) return { ...EMPTY_ACL_SNAPSHOT }\n return {\n isSuperAdmin: Boolean(acl.isSuperAdmin),\n features: toSortedList(acl.featuresJson),\n organizations: Array.isArray(acl.organizationsJson) ? toSortedList(acl.organizationsJson) : null,\n }\n}\n\nfunction holdsGrants(snapshot: AclSnapshot): boolean {\n return snapshot.isSuperAdmin || snapshot.features.length > 0\n}\n\nfunction resolveEffect(before: AclSnapshot, after: AclSnapshot): AclChangeEffect {\n if (!holdsGrants(before) && holdsGrants(after)) return 'granted'\n if (holdsGrants(before) && !holdsGrants(after)) return 'revoked'\n return 'changed'\n}\n\nfunction resolveEm(ctx: CommandRuntimeContext): EntityManager {\n return ctx.transactionalEm ?? ctx.container.resolve('em') as EntityManager\n}\n\n/**\n * The log row's organization must belong to the same tenant as the row itself.\n *\n * A super admin may edit a role in a tenant other than their own, and\n * `ActionLogService.buildListQuery` filters with strict equality\n * (`organization_id = ?`, applied only when the reader supplies one). Stamping\n * the actor's organization onto a row scoped to a different tenant produces a\n * (tenant B, organization from tenant A) pair that no reader can ever match, so\n * the cross-tenant permission change \u2014 exactly the record worth keeping \u2014\n * becomes invisible. `null` is the honest value there: it still reaches\n * tenant-scoped readers whose organization filter is unset.\n */\nfunction resolveOrganizationId(ctx: CommandRuntimeContext, tenantId: string): string | null {\n if ((ctx.auth?.tenantId ?? null) !== tenantId) return null\n return ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n}\n\n/**\n * A runtime with no cache registered (CLI, workers, tests) is a configuration,\n * not a failure, so resolution is guarded here. An adapter that *is* wired and\n * then throws is a failure, and it propagates to the single error-level guard\n * in `execute` \u2014 the same one that covers the `rbacService` half of this\n * invalidation. Reporting one half at `debug` and the other at `error` would\n * hide a nav cache still serving revoked grants.\n */\nasync function deleteCacheTags(ctx: CommandRuntimeContext, tags: string[]): Promise<void> {\n let cache: TaggableCache | undefined\n try {\n cache = ctx.container.resolve('cache') as TaggableCache | undefined\n } catch (err) {\n logger.debug('No cache registered for ACL tag invalidation', { err, tags })\n return\n }\n if (!cache?.deleteByTags) return\n await cache.deleteByTags(tags)\n}\n\ntype AclCommandValues = {\n isSuperAdmin: boolean\n features: string[]\n organizations: string[] | null\n}\n\ntype AclCommandInput = AclCommandValues & { tenantId: string }\n\nexport type RoleAclUpdateInput = AclCommandInput & {\n roleId: string\n}\n\nexport type UserAclUpdateInput = AclCommandInput & {\n userId: string\n /** Mirrors the route's `!hasCustomAcl`: drop the override row instead of upserting it. */\n clear: boolean\n /** Set by the route only when it wrote less than the caller asked for. */\n requested?: SanitizedAclRequest | null\n}\n\nexport type AclUpdateResult = {\n resourceId: string\n tenantId: string\n organizationId: string | null\n}\n\nexport const AUTH_ROLE_ACL_UPDATE_COMMAND_ID = 'auth.role-acl.update'\nexport const AUTH_USER_ACL_UPDATE_COMMAND_ID = 'auth.user-acl.update'\n\ntype AclCommandConfig<TInput extends AclCommandInput> = {\n id: string\n resourceKind: string\n labelKey: string\n labelFallback: string\n resourceId: (input: TInput) => string\n lockParent: (em: EntityManager, input: TInput) => Promise<void>\n loadAcl: (em: EntityManager, input: TInput) => Promise<AclRecord | null>\n persist: (params: { em: EntityManager; input: TInput; existing: AclRecord | null }) => Promise<void>\n invalidate: (params: { ctx: CommandRuntimeContext; input: TInput }) => Promise<void>\n /**\n * Post-state when the row is absent after the write. Only the user command's\n * clear path legitimately removes the row; anywhere else a missing row means\n * the re-read failed, which must be logged as \"unknown\" rather than guessed.\n */\n snapshotWhenAbsent: (input: TInput) => AclSnapshot | null\n /** Human-readable identity of the target; `null` when it cannot be resolved. */\n loadTarget: (em: EntityManager, input: TInput, ctx: CommandRuntimeContext) => Promise<AclTarget | null>\n /** Only the user route sanitizes, so only it can report a trimmed request. */\n sanitizedRequest?: (input: TInput) => SanitizedAclRequest | null\n}\n\n/**\n * Enrichment must never cost the entry.\n *\n * The bus persists the log after `execute` has already committed the permission\n * change, so a throw from the target lookup would leave the write recorded\n * nowhere \u2014 the same \"committed but unaudited\" hole the cache-invalidation\n * guard in `execute` closes. A missing label is a far smaller loss.\n */\nasync function loadTargetSafely<TInput extends AclCommandInput>(\n config: AclCommandConfig<TInput>,\n input: TInput,\n ctx: CommandRuntimeContext,\n): Promise<AclTarget | null> {\n try {\n return await config.loadTarget(resolveEm(ctx).fork(), input, ctx)\n } catch (err) {\n logger.warn('Could not resolve the ACL target for the audit entry', {\n err,\n commandId: config.id,\n resourceId: config.resourceId(input),\n tenantId: input.tenantId,\n })\n return null\n }\n}\n\nfunction buildAuditContext(params: {\n target: AclTarget | null\n effect: AclChangeEffect | null\n sanitizedRequest: SanitizedAclRequest | null\n}): Record<string, unknown> | null {\n const context: Record<string, unknown> = {}\n if (params.target) context.target = params.target\n if (params.effect) context.effect = params.effect\n if (params.sanitizedRequest) context.sanitizedRequest = params.sanitizedRequest\n return Object.keys(context).length ? context : null\n}\n\n/**\n * Both ACL commands differ only in entity, lookup key, cache invalidation and\n * labels \u2014 `prepare`, `captureAfter` and `buildLog` are otherwise identical.\n * Sharing them here keeps the two audit-entry shapes from drifting apart.\n */\nfunction createAclUpdateCommand<TInput extends AclCommandInput>(\n config: AclCommandConfig<TInput>,\n): CommandHandler<TInput, AclUpdateResult> {\n return {\n id: config.id,\n // See \"Auth ACL Commands \u2014 Undo Policy\" at top of file.\n isUndoable: false,\n async prepare(input, ctx) {\n const existing = await config.loadAcl(resolveEm(ctx).fork(), input)\n return { before: captureAclSnapshot(existing) }\n },\n async execute(input, ctx) {\n const em = resolveEm(ctx)\n await withAtomicFlush(em, [async () => {\n await config.lockParent(em, input)\n const existing = await config.loadAcl(em, input)\n await config.persist({ em, input, existing })\n }], { transaction: true, label: `${config.id}.locked` })\n // Cache invalidation runs only after the write commits, never inside the\n // atomic flush \u2014 and must never propagate. The command bus persists the\n // action log *after* `execute` returns, so a throw here would commit the\n // permission change and then suppress its audit entry: precisely the\n // \"committed but unaudited\" hole these commands exist to close, and it\n // would open exactly when infrastructure is already degraded.\n // `RbacService.deleteCacheByTags` awaits the cache adapter without a\n // guard of its own, so an outage does reach us here.\n //\n // The resulting staleness (a revoked grant served from cache until its\n // TTL) is not introduced by swallowing \u2014 it happens either way once the\n // adapter is down. Logging at error level keeps it alarmable instead of\n // trading a recorded change for an HTTP 500 that misreports a write which\n // did commit.\n try {\n await config.invalidate({ ctx, input })\n } catch (err) {\n logger.error('ACL cache invalidation failed after a committed permission change', {\n err,\n commandId: config.id,\n resourceId: config.resourceId(input),\n tenantId: input.tenantId,\n })\n }\n return {\n resourceId: config.resourceId(input),\n tenantId: input.tenantId,\n organizationId: resolveOrganizationId(ctx, input.tenantId),\n }\n },\n captureAfter: async (input, _result, ctx) => {\n // Read the committed row back rather than echoing the request, so a grant\n // the route sanitized away can never be logged as if it had been applied.\n const persisted = await config.loadAcl(resolveEm(ctx).fork(), input)\n if (persisted) return captureAclSnapshot(persisted)\n return config.snapshotWhenAbsent(input)\n },\n buildLog: async ({ input, result, ctx, snapshots }) => {\n const before = (snapshots.before as AclSnapshot | undefined) ?? null\n const after = (snapshots.after as AclSnapshot | undefined) ?? null\n // An unknown post-state (`after: null`, the failed re-read) counts as a\n // change: it is the one case where the entry is the only evidence left.\n // Both snapshots leave `captureAclSnapshot` with their grant lists sorted,\n // so the shared order-sensitive comparison is a set comparison here.\n const changed = !before || !after || !snapshotsEqual(before, after)\n const sanitizedRequest = config.sanitizedRequest?.(input) ?? null\n // The admin user-edit page PUTs the ACL on every save, even when only the\n // name or the roles changed, and most accounts carry no per-user override\n // at all \u2014 so recording an unchanged ACL faithfully buries the real\n // permission changes under identical empty entries. A request the route\n // trimmed rather than refused also arrives with `before == after`; that\n // one is an escalation attempt and must survive this guard.\n if (!changed && !sanitizedRequest) return { skipLog: true }\n const { translate } = await resolveTranslations()\n const target = await loadTargetSafely(config, input, ctx)\n return {\n actionLabel: translate(config.labelKey, config.labelFallback),\n resourceKind: config.resourceKind,\n resourceId: result.resourceId,\n tenantId: result.tenantId,\n organizationId: result.organizationId,\n snapshotBefore: before,\n snapshotAfter: after,\n context: buildAuditContext({\n target,\n // A sanitized-only entry is written *because* nothing changed, so\n // labelling it would have the trail assert a permission change that\n // never happened.\n effect: changed && before && after ? resolveEffect(before, after) : null,\n sanitizedRequest,\n }),\n }\n },\n }\n}\n\nconst updateRoleAclCommand = createAclUpdateCommand<RoleAclUpdateInput>({\n id: AUTH_ROLE_ACL_UPDATE_COMMAND_ID,\n resourceKind: 'auth.role_acl',\n labelKey: 'auth.audit.roleAcl.update',\n labelFallback: 'Change role permissions',\n resourceId: (input) => input.roleId,\n lockParent: async (em, input) => {\n await lockRoleWriterAuthorizationState(em, [input.roleId])\n },\n loadAcl: (em, input) =>\n em.findOne(RoleAcl, { role: input.roleId as unknown as Role, tenantId: input.tenantId }),\n persist: async ({ em, input, existing }) => {\n const acl =\n (existing as RoleAcl | null) ??\n em.create(RoleAcl, {\n role: em.getReference(Role, input.roleId),\n tenantId: input.tenantId,\n createdAt: new Date(),\n isSuperAdmin: false,\n })\n acl.organizationsJson = input.organizations\n acl.isSuperAdmin = input.isSuperAdmin\n acl.featuresJson = input.features\n em.persist(acl)\n },\n invalidate: async ({ ctx, input }) => {\n // Every user in the tenant inherits this role's grants, so the whole tenant\n // scope is invalidated.\n const rbacService = ctx.container.resolve('rbacService') as RbacService\n await rbacService.invalidateTenantCache(input.tenantId)\n // Sidebar nav caches depend on RBAC; invalidate tenant scope nav caches\n await deleteCacheTags(ctx, [`rbac:tenant:${input.tenantId}`])\n },\n snapshotWhenAbsent: () => null,\n // Looked up by id alone: the entry already names this role through\n // `resource_id`, and a tenant predicate would only blank the label when a\n // super admin edits a role from outside the tenant scope they are writing in.\n loadTarget: async (em, input) => {\n const role = await em.findOne(Role, { id: input.roleId } as FilterQuery<Role>)\n if (!role) return null\n return { kind: 'role', id: input.roleId, name: role.name ?? null }\n },\n})\n\nconst updateUserAclCommand = createAclUpdateCommand<UserAclUpdateInput>({\n id: AUTH_USER_ACL_UPDATE_COMMAND_ID,\n resourceKind: 'auth.user_acl',\n labelKey: 'auth.audit.userAcl.update',\n labelFallback: 'Change user permissions',\n resourceId: (input) => input.userId,\n lockParent: async (em, input) => {\n await lockUserAclWriterAuthorizationState(em, [input.userId])\n },\n loadAcl: (em, input) =>\n em.findOne(UserAcl, { user: input.userId as unknown as User, tenantId: input.tenantId }),\n persist: async ({ em, input, existing }) => {\n if (input.clear) {\n if (!existing) return\n const aclToRemove = existing as UserAcl\n em.remove(aclToRemove)\n return\n }\n const acl =\n (existing as UserAcl | null) ??\n em.create(UserAcl, {\n user: em.getReference(User, input.userId),\n tenantId: input.tenantId,\n createdAt: new Date(),\n isSuperAdmin: false,\n })\n acl.isSuperAdmin = input.isSuperAdmin\n acl.featuresJson = input.features\n acl.organizationsJson = input.organizations\n em.persist(acl)\n },\n invalidate: async ({ ctx, input }) => {\n const rbacService = ctx.container.resolve('rbacService') as RbacService\n await rbacService.invalidateUserCache(input.userId)\n await deleteCacheTags(ctx, [`rbac:user:${input.userId}`])\n },\n // Clearing removes the row, so an absent row is the real post-state here.\n snapshotWhenAbsent: (input) => (input.clear ? { ...EMPTY_ACL_SNAPSHOT } : null),\n // `userId` arrives in the request body and `auth:user` carries encrypted\n // personal data, so the lookup is tenant-scoped: an id belonging to another\n // tenant must not be decrypted under this scope and stamped into this\n // tenant's trail. A global account (`users.tenant_id` is nullable) still\n // resolves, mirroring how the role route widens its own lookup.\n loadTarget: async (em, input, ctx) => {\n const user = await findOneWithDecryption(\n em,\n User,\n { id: input.userId, $or: [{ tenantId: input.tenantId }, { tenantId: null }] } as FilterQuery<User>,\n {},\n { tenantId: input.tenantId, organizationId: ctx.auth?.orgId ?? null },\n )\n if (!user) return null\n return { kind: 'user', id: input.userId, email: user.email ?? null, name: user.name ?? null }\n },\n sanitizedRequest: (input) => input.requested ?? null,\n})\n\nregisterCommand(updateRoleAclCommand)\nregisterCommand(updateUserAclCommand)\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["// =============================================================================\n// Auth ACL Commands \u2014 Undo Policy\n// =============================================================================\n//\n// Neither command in this file defines `undo` or `redo`, so the command bus\n// never mints an undo token for them: `CommandBus.isUndoable()` requires a\n// handler-provided `undo`. The explicit `isUndoable: false` below is executable\n// documentation of that intent, not the mechanism \u2014 the guarantee comes from\n// the absence of the handlers, and the unit tests pin both.\n//\n// The policy is deliberate even though an ACL grant is trivially reversible.\n// The undo and redo endpoints are gated on `audit_logs.undo_self` /\n// `audit_logs.undo_tenant` (and their redo counterparts), not on\n// `auth.acl.manage`. `audit_logs.undo_self` is a default `employee` grant and\n// `audit_logs.undo_tenant` reaches every `admin` through `audit_logs.*`, so an\n// undoable ACL command would let a caller revert or replay someone else's\n// permission change without ever holding the feature that authorizes editing\n// permissions.\n//\n// Reversal stays available to the callers who are actually authorized for it:\n// re-submitting the ACL form, gated on `auth.acl.manage` and guarded by\n// `assertActorCanGrantAcl`. The audit log still captures the full before/after\n// via `buildLog`, so any such correction is itself fully traceable.\n// =============================================================================\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { snapshotsEqual } from '@open-mercato/shared/lib/commands/helpers'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { Role, RoleAcl, User, UserAcl } from '@open-mercato/core/modules/auth/data/entities'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport {\n lockRoleWriterAuthorizationState,\n lockUserAclWriterAuthorizationState,\n} from '@open-mercato/core/modules/auth/lib/authorizationStateLocks'\n\nconst logger = createLogger('auth').child({ component: 'acl-commands' })\n\ntype TaggableCache = { deleteByTags?: (tags: string[]) => Promise<void> | void }\n\n/** Structural view of the fields both `RoleAcl` and `UserAcl` expose. */\ntype AclRecord = {\n isSuperAdmin: boolean\n featuresJson?: string[] | null\n organizationsJson?: string[] | null\n}\n\n/**\n * Audit snapshot of a single ACL row.\n *\n * `features` and `organizations` are grant *sets*, but `features_json` /\n * `organizations_json` preserve the client's insertion order. The command bus\n * derives `changes` from these snapshots with a deep equality check that is\n * order-sensitive for arrays, so an unsorted snapshot would report a spurious\n * `features` change every time an admin re-saves an unmodified form. Sorting\n * here makes the derived diff reflect real grant changes only.\n *\n * `organizations: null` (visible in every organization) is deliberately\n * distinct from `[]` (visible in none).\n */\ntype AclSnapshot = {\n isSuperAdmin: boolean\n features: string[]\n organizations: string[] | null\n}\n\nconst EMPTY_ACL_SNAPSHOT: AclSnapshot = {\n isSuperAdmin: false,\n features: [],\n organizations: null,\n}\n\n/**\n * Identity of the role or user an entry is about.\n *\n * `action_logs` keeps only `resource_id`, so an entry degrades to a bare UUID\n * the moment the role or account is deleted \u2014 exactly when the trail matters\n * most. This rides in `context` rather than in the snapshots because the\n * command bus derives `changes` from the two snapshots with a deep comparison:\n * a target renamed between two ACL edits would otherwise be reported as a\n * permission change, and the no-op guard below would stop recognising an\n * unchanged ACL.\n */\ntype AclTarget =\n | { kind: 'user'; id: string; email: string | null; name: string | null }\n | { kind: 'role'; id: string; name: string | null }\n\n/**\n * What the caller asked for when the route trimmed the request instead of\n * refusing it.\n *\n * `sanitizeTenantFeatures` drops restricted grants silently, so such a request\n * leaves `before` and `after` identical and is indistinguishable from a no-op \u2014\n * which `buildLog` skips. Recording the original request both keeps the attempt\n * visible and exempts the entry from that guard.\n *\n * Features are the only axis carried, because they are the only one the route\n * can trim: a super-admin request is honoured or refused outright, and\n * organizations are never sanitized. Recording either would put a value next to\n * the snapshots that always matches them, inviting a reader to diff the two and\n * conclude a grant was refused.\n */\ntype SanitizedAclRequest = {\n features: string[]\n}\n\n/** How the change reads at a glance, without diffing two grant lists. */\ntype AclChangeEffect = 'granted' | 'changed' | 'revoked'\n\n/**\n * Codepoint ordering rather than `localeCompare`: these snapshots are persisted\n * audit records, so the ordering must not shift with the runtime's default\n * locale. Feature and organization ids are opaque ASCII identifiers that are\n * never shown in this order to a user.\n *\n * The default comparator would coerce and compare the same way today, but\n * omitting one is banned repo-wide (`explicit-sort-comparators.test.ts`,\n * #3620): the ordering must be stated rather than inherited, so it cannot\n * silently change meaning if a call site ever drifts to non-strings. This is\n * the canonical-key form that audit prescribes.\n */\nfunction compareIdentifiers(left: string, right: string): number {\n if (left === right) return 0\n return left < right ? -1 : 1\n}\n\nfunction toSortedList(values: unknown): string[] {\n if (!Array.isArray(values)) return []\n return [...values].filter((value): value is string => typeof value === 'string').sort(compareIdentifiers)\n}\n\nfunction captureAclSnapshot(acl: AclRecord | null | undefined): AclSnapshot {\n if (!acl) return { ...EMPTY_ACL_SNAPSHOT }\n return {\n isSuperAdmin: Boolean(acl.isSuperAdmin),\n features: toSortedList(acl.featuresJson),\n organizations: Array.isArray(acl.organizationsJson) ? toSortedList(acl.organizationsJson) : null,\n }\n}\n\nfunction holdsGrants(snapshot: AclSnapshot): boolean {\n return snapshot.isSuperAdmin || snapshot.features.length > 0\n}\n\nfunction resolveEffect(before: AclSnapshot, after: AclSnapshot): AclChangeEffect {\n if (!holdsGrants(before) && holdsGrants(after)) return 'granted'\n if (holdsGrants(before) && !holdsGrants(after)) return 'revoked'\n return 'changed'\n}\n\nfunction resolveEm(ctx: CommandRuntimeContext): EntityManager {\n return ctx.transactionalEm ?? ctx.container.resolve('em') as EntityManager\n}\n\n/**\n * The log row's organization must belong to the same tenant as the row itself.\n *\n * A super admin may edit a role in a tenant other than their own, and\n * `ActionLogService.buildListQuery` filters with strict equality\n * (`organization_id = ?`, applied only when the reader supplies one). Stamping\n * the actor's organization onto a row scoped to a different tenant produces a\n * (tenant B, organization from tenant A) pair that no reader can ever match, so\n * the cross-tenant permission change \u2014 exactly the record worth keeping \u2014\n * becomes invisible. `null` is the honest value there: it still reaches\n * tenant-scoped readers whose organization filter is unset.\n */\nfunction resolveOrganizationId(ctx: CommandRuntimeContext, tenantId: string): string | null {\n if ((ctx.auth?.tenantId ?? null) !== tenantId) return null\n return ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n}\n\n/**\n * A runtime with no cache registered (CLI, workers, tests) is a configuration,\n * not a failure, so resolution is guarded here. An adapter that *is* wired and\n * then throws is a failure, and it propagates to the single error-level guard\n * in `execute` \u2014 the same one that covers the `rbacService` half of this\n * invalidation. Reporting one half at `debug` and the other at `error` would\n * hide a nav cache still serving revoked grants.\n */\nasync function deleteCacheTags(ctx: CommandRuntimeContext, tags: string[]): Promise<void> {\n let cache: TaggableCache | undefined\n try {\n cache = ctx.container.resolve('cache') as TaggableCache | undefined\n } catch (err) {\n logger.debug('No cache registered for ACL tag invalidation', { err, tags })\n return\n }\n if (!cache?.deleteByTags) return\n await cache.deleteByTags(tags)\n}\n\ntype AclCommandValues = {\n isSuperAdmin: boolean\n features: string[]\n organizations: string[] | null\n}\n\ntype AclCommandInput = AclCommandValues & { tenantId: string }\n\nexport type RoleAclUpdateInput = AclCommandInput & {\n roleId: string\n}\n\nexport type UserAclUpdateInput = AclCommandInput & {\n userId: string\n /** Mirrors the route's `!hasCustomAcl`: drop the override row instead of upserting it. */\n clear: boolean\n /** Set by the route only when it wrote less than the caller asked for. */\n requested?: SanitizedAclRequest | null\n}\n\nexport type AclUpdateResult = {\n resourceId: string\n tenantId: string\n organizationId: string | null\n}\n\nexport const AUTH_ROLE_ACL_UPDATE_COMMAND_ID = 'auth.role-acl.update'\nexport const AUTH_USER_ACL_UPDATE_COMMAND_ID = 'auth.user-acl.update'\n\ntype AclCommandConfig<TInput extends AclCommandInput> = {\n id: string\n resourceKind: string\n labelKey: string\n labelFallback: string\n resourceId: (input: TInput) => string\n lockParent: (em: EntityManager, input: TInput) => Promise<void>\n loadAcl: (em: EntityManager, input: TInput) => Promise<AclRecord | null>\n persist: (params: { em: EntityManager; input: TInput; existing: AclRecord | null }) => Promise<void>\n invalidate: (params: { ctx: CommandRuntimeContext; input: TInput }) => Promise<void>\n /**\n * Post-state when the row is absent after the write. Only the user command's\n * clear path legitimately removes the row; anywhere else a missing row means\n * the re-read failed, which must be logged as \"unknown\" rather than guessed.\n */\n snapshotWhenAbsent: (input: TInput) => AclSnapshot | null\n /** Human-readable identity of the target; `null` when it cannot be resolved. */\n loadTarget: (em: EntityManager, input: TInput, ctx: CommandRuntimeContext) => Promise<AclTarget | null>\n /** Only the user route sanitizes, so only it can report a trimmed request. */\n sanitizedRequest?: (input: TInput) => SanitizedAclRequest | null\n}\n\n/**\n * Enrichment must never cost the entry.\n *\n * The bus persists the log after `execute` has already committed the permission\n * change, so a throw from the target lookup would leave the write recorded\n * nowhere \u2014 the same \"committed but unaudited\" hole the cache-invalidation\n * guard in `execute` closes. A missing label is a far smaller loss.\n */\nasync function loadTargetSafely<TInput extends AclCommandInput>(\n config: AclCommandConfig<TInput>,\n input: TInput,\n ctx: CommandRuntimeContext,\n): Promise<AclTarget | null> {\n try {\n return await config.loadTarget(resolveEm(ctx).fork(), input, ctx)\n } catch (err) {\n logger.warn('Could not resolve the ACL target for the audit entry', {\n err,\n commandId: config.id,\n resourceId: config.resourceId(input),\n tenantId: input.tenantId,\n })\n return null\n }\n}\n\nfunction buildAuditContext(params: {\n target: AclTarget | null\n effect: AclChangeEffect | null\n sanitizedRequest: SanitizedAclRequest | null\n}): Record<string, unknown> | null {\n const context: Record<string, unknown> = {}\n if (params.target) context.target = params.target\n if (params.effect) context.effect = params.effect\n if (params.sanitizedRequest) context.sanitizedRequest = params.sanitizedRequest\n return Object.keys(context).length ? context : null\n}\n\n/**\n * Both ACL commands differ only in entity, lookup key, cache invalidation and\n * labels \u2014 `prepare`, `captureAfter` and `buildLog` are otherwise identical.\n * Sharing them here keeps the two audit-entry shapes from drifting apart.\n */\nfunction createAclUpdateCommand<TInput extends AclCommandInput>(\n config: AclCommandConfig<TInput>,\n): CommandHandler<TInput, AclUpdateResult> {\n return {\n id: config.id,\n // See \"Auth ACL Commands \u2014 Undo Policy\" at top of file.\n isUndoable: false,\n async prepare(input, ctx) {\n const existing = await config.loadAcl(resolveEm(ctx).fork(), input)\n return { before: captureAclSnapshot(existing) }\n },\n async execute(input, ctx) {\n const em = resolveEm(ctx)\n await withAtomicFlush(em, [async () => {\n await config.lockParent(em, input)\n const existing = await config.loadAcl(em, input)\n await config.persist({ em, input, existing })\n }], { transaction: true, label: `${config.id}.locked` })\n // Cache invalidation runs only after the write commits, never inside the\n // atomic flush \u2014 and must never propagate. The command bus persists the\n // action log *after* `execute` returns, so a throw here would commit the\n // permission change and then suppress its audit entry: precisely the\n // \"committed but unaudited\" hole these commands exist to close, and it\n // would open exactly when infrastructure is already degraded.\n // `RbacService.deleteCacheByTags` awaits the cache adapter without a\n // guard of its own, so an outage does reach us here.\n //\n // The resulting staleness (a revoked grant served from cache until its\n // TTL) is not introduced by swallowing \u2014 it happens either way once the\n // adapter is down. Logging at error level keeps it alarmable instead of\n // trading a recorded change for an HTTP 500 that misreports a write which\n // did commit.\n try {\n await config.invalidate({ ctx, input })\n } catch (err) {\n logger.error('ACL cache invalidation failed after a committed permission change', {\n err,\n commandId: config.id,\n resourceId: config.resourceId(input),\n tenantId: input.tenantId,\n })\n }\n return {\n resourceId: config.resourceId(input),\n tenantId: input.tenantId,\n organizationId: resolveOrganizationId(ctx, input.tenantId),\n }\n },\n captureAfter: async (input, _result, ctx) => {\n // Read the committed row back rather than echoing the request, so a grant\n // the route sanitized away can never be logged as if it had been applied.\n const persisted = await config.loadAcl(resolveEm(ctx).fork(), input)\n if (persisted) return captureAclSnapshot(persisted)\n return config.snapshotWhenAbsent(input)\n },\n buildLog: async ({ input, result, ctx, snapshots }) => {\n const before = (snapshots.before as AclSnapshot | undefined) ?? null\n const after = (snapshots.after as AclSnapshot | undefined) ?? null\n // An unknown post-state (`after: null`, the failed re-read) counts as a\n // change: it is the one case where the entry is the only evidence left.\n // Both snapshots leave `captureAclSnapshot` with their grant lists sorted,\n // so the shared order-sensitive comparison is a set comparison here.\n const changed = !before || !after || !snapshotsEqual(before, after)\n const sanitizedRequest = config.sanitizedRequest?.(input) ?? null\n // The admin user-edit page PUTs the ACL on every save, even when only the\n // name or the roles changed, and most accounts carry no per-user override\n // at all \u2014 so recording an unchanged ACL faithfully buries the real\n // permission changes under identical empty entries. A request the route\n // trimmed rather than refused also arrives with `before == after`; that\n // one is an escalation attempt and must survive this guard.\n if (!changed && !sanitizedRequest) return { skipLog: true }\n const { translate } = await resolveTranslations()\n const target = await loadTargetSafely(config, input, ctx)\n return {\n actionLabel: translate(config.labelKey, config.labelFallback),\n resourceKind: config.resourceKind,\n resourceId: result.resourceId,\n tenantId: result.tenantId,\n organizationId: result.organizationId,\n snapshotBefore: before,\n snapshotAfter: after,\n context: buildAuditContext({\n target,\n // A sanitized-only entry is written *because* nothing changed, so\n // labelling it would have the trail assert a permission change that\n // never happened.\n effect: changed && before && after ? resolveEffect(before, after) : null,\n sanitizedRequest,\n }),\n }\n },\n }\n}\n\nconst updateRoleAclCommand = createAclUpdateCommand<RoleAclUpdateInput>({\n id: AUTH_ROLE_ACL_UPDATE_COMMAND_ID,\n resourceKind: 'auth.role_acl',\n labelKey: 'auth.audit.roleAcl.update',\n labelFallback: 'Change role permissions',\n resourceId: (input) => input.roleId,\n lockParent: async (em, input) => {\n await lockRoleWriterAuthorizationState(em, [input.roleId])\n },\n loadAcl: (em, input) =>\n em.findOne(RoleAcl, { role: input.roleId as unknown as Role, tenantId: input.tenantId, deletedAt: null }),\n persist: async ({ em, input, existing }) => {\n const acl =\n (existing as RoleAcl | null) ??\n em.create(RoleAcl, {\n role: em.getReference(Role, input.roleId),\n tenantId: input.tenantId,\n createdAt: new Date(),\n isSuperAdmin: false,\n })\n acl.organizationsJson = input.organizations\n acl.isSuperAdmin = input.isSuperAdmin\n acl.featuresJson = input.features\n em.persist(acl)\n },\n invalidate: async ({ ctx, input }) => {\n // Every user in the tenant inherits this role's grants, so the whole tenant\n // scope is invalidated.\n const rbacService = ctx.container.resolve('rbacService') as RbacService\n await rbacService.invalidateTenantCache(input.tenantId)\n // Sidebar nav caches depend on RBAC; invalidate tenant scope nav caches\n await deleteCacheTags(ctx, [`rbac:tenant:${input.tenantId}`])\n },\n snapshotWhenAbsent: () => null,\n // Looked up by id alone: the entry already names this role through\n // `resource_id`, and a tenant predicate would only blank the label when a\n // super admin edits a role from outside the tenant scope they are writing in.\n loadTarget: async (em, input) => {\n const role = await em.findOne(Role, { id: input.roleId } as FilterQuery<Role>)\n if (!role) return null\n return { kind: 'role', id: input.roleId, name: role.name ?? null }\n },\n})\n\nconst updateUserAclCommand = createAclUpdateCommand<UserAclUpdateInput>({\n id: AUTH_USER_ACL_UPDATE_COMMAND_ID,\n resourceKind: 'auth.user_acl',\n labelKey: 'auth.audit.userAcl.update',\n labelFallback: 'Change user permissions',\n resourceId: (input) => input.userId,\n lockParent: async (em, input) => {\n await lockUserAclWriterAuthorizationState(em, [input.userId])\n },\n loadAcl: (em, input) =>\n em.findOne(UserAcl, { user: input.userId as unknown as User, tenantId: input.tenantId, deletedAt: null }),\n persist: async ({ em, input, existing }) => {\n if (input.clear) {\n if (!existing) return\n const aclToRemove = existing as UserAcl\n em.remove(aclToRemove)\n return\n }\n const acl =\n (existing as UserAcl | null) ??\n em.create(UserAcl, {\n user: em.getReference(User, input.userId),\n tenantId: input.tenantId,\n createdAt: new Date(),\n isSuperAdmin: false,\n })\n acl.isSuperAdmin = input.isSuperAdmin\n acl.featuresJson = input.features\n acl.organizationsJson = input.organizations\n em.persist(acl)\n },\n invalidate: async ({ ctx, input }) => {\n const rbacService = ctx.container.resolve('rbacService') as RbacService\n await rbacService.invalidateUserCache(input.userId)\n await deleteCacheTags(ctx, [`rbac:user:${input.userId}`])\n },\n // Clearing removes the row, so an absent row is the real post-state here.\n snapshotWhenAbsent: (input) => (input.clear ? { ...EMPTY_ACL_SNAPSHOT } : null),\n // `userId` arrives in the request body and `auth:user` carries encrypted\n // personal data, so the lookup is tenant-scoped: an id belonging to another\n // tenant must not be decrypted under this scope and stamped into this\n // tenant's trail. A global account (`users.tenant_id` is nullable) still\n // resolves, mirroring how the role route widens its own lookup.\n loadTarget: async (em, input, ctx) => {\n const user = await findOneWithDecryption(\n em,\n User,\n { id: input.userId, $or: [{ tenantId: input.tenantId }, { tenantId: null }] } as FilterQuery<User>,\n {},\n { tenantId: input.tenantId, organizationId: ctx.auth?.orgId ?? null },\n )\n if (!user) return null\n return { kind: 'user', id: input.userId, email: user.email ?? null, name: user.name ?? null }\n },\n sanitizedRequest: (input) => input.requested ?? null,\n})\n\nregisterCommand(updateRoleAclCommand)\nregisterCommand(updateUserAclCommand)\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -828,7 +828,7 @@ function captureRoleSnapshots(role, acls = [], custom) {
828
828
  };
829
829
  }
830
830
  async function loadRoleAclSnapshots(em, roleId) {
831
- const entries = await findWithDecryption(em, RoleAcl, { role: roleId }, {}, { tenantId: null, organizationId: null });
831
+ const entries = await findWithDecryption(em, RoleAcl, { role: roleId, deletedAt: null }, {}, { tenantId: null, organizationId: null });
832
832
  return entries.map((entry) => ({
833
833
  id: entry.id ? String(entry.id) : null,
834
834
  tenantId: String(entry.tenantId),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/auth/commands/roles.ts"],
4
- "sourcesContent": ["import type { CommandHandler, CommandReplayAuthorizationArgs, CommandUndoLogEntry } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport {\n parseWithCustomFields,\n setCustomFieldsIfAny,\n emitCrudSideEffects,\n emitCrudUndoSideEffects,\n buildChanges,\n requireId,\n} from '@open-mercato/shared/lib/commands/helpers'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport { CrudHttpError, forbidden } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { z } from 'zod'\nimport { Role, RoleAcl, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n loadCustomFieldSnapshot,\n buildCustomFieldResetMap,\n diffCustomFieldChanges,\n} from '@open-mercato/shared/lib/commands/customFieldSnapshots'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { resolveIsSuperAdmin, normalizeTenantId } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport {\n assertActorCanAccessRoleTarget,\n assertActorCanGrantAcl,\n assertActorCanModifySuperAdminRoleTarget,\n} from '@open-mercato/core/modules/auth/lib/grantChecks'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport {\n assertReplaySnapshotMatches,\n extractStoredReplayInput,\n lockReplayAuthorizationState,\n rerunReplayTransactionGuardAfterLocks,\n requireCurrentReplayFeature,\n requireCurrentReplaySuperAdmin,\n requireTransactionalReplayEntityManager,\n} from '@open-mercato/core/modules/auth/lib/commandReplay'\nimport { lockRoleWriterAuthorizationState } from '@open-mercato/core/modules/auth/lib/authorizationStateLocks'\n\ntype SerializedRole = {\n name: string\n tenantId: string\n custom?: Record<string, unknown>\n}\n\ntype RoleAclSnapshot = {\n id: string | null\n tenantId: string\n features: string[] | null\n isSuperAdmin: boolean\n organizations: string[] | null\n}\n\ntype RoleUndoSnapshot = {\n id: string\n name: string\n tenantId: string\n acls: RoleAclSnapshot[]\n custom?: Record<string, unknown>\n}\n\ntype RoleSnapshots = {\n view: SerializedRole\n undo: RoleUndoSnapshot\n}\n\nconst ROLE_REDO_LOG = Symbol('auth.roles.redoLog')\n\ntype RoleReplayRuntimeContext = CommandRuntimeContext & {\n [ROLE_REDO_LOG]?: CommandUndoLogEntry\n}\n\nfunction withRoleRedoLog(ctx: CommandRuntimeContext, logEntry: CommandUndoLogEntry): RoleReplayRuntimeContext {\n return { ...ctx, [ROLE_REDO_LOG]: logEntry }\n}\n\nfunction resolveCommandEm(ctx: CommandRuntimeContext, forkWhenUnbound = false): EntityManager {\n if (ctx.transactionalEm) return ctx.transactionalEm\n const em = ctx.container.resolve('em') as EntityManager\n return forkWhenUnbound ? em.fork() : em\n}\n\nfunction resolveActorTenantScope(ctx: CommandRuntimeContext): string | null {\n if (ctx.systemActor === true) return null\n const auth = ctx.auth\n if (!auth) return null\n if ((auth as { isSuperAdmin?: boolean }).isSuperAdmin === true) return null\n return normalizeTenantId(auth.tenantId ?? null) ?? null\n}\n\nfunction assertRoleTenantInScope(actorTenantScope: string | null, targetTenantId: unknown): void {\n if (!actorTenantScope) return\n const targetTenant = normalizeTenantId(targetTenantId) ?? null\n if (!targetTenant || targetTenant !== actorTenantScope) {\n throw new CrudHttpError(404, { error: 'Role not found' })\n }\n}\n\nconst RESERVED_ROLE_NAMES = new Set(['superadmin', 'admin'])\n\nfunction isReservedRoleName(name: string | undefined | null): boolean {\n if (typeof name !== 'string') return false\n const normalized = name.trim().toLowerCase()\n return normalized.length > 0 && RESERVED_ROLE_NAMES.has(normalized)\n}\n\nfunction assertRoleNameAllowed(name: string | undefined | null) {\n if (isReservedRoleName(name)) {\n throw new CrudHttpError(400, { error: 'Role name is reserved' })\n }\n}\n\ntype ResolvedActorScope = { isSuperAdmin: boolean; actorTenantId: string | null }\n\nasync function resolveActorScope(ctx: { auth: { tenantId?: string | null; sub?: string | null; orgId?: string | null; isSuperAdmin?: boolean } | null; container: { resolve<T = unknown>(name: string): T } }): Promise<ResolvedActorScope> {\n const isSuperAdmin = await resolveIsSuperAdmin(ctx)\n const actorTenantId = normalizeTenantId(ctx.auth?.tenantId ?? null) ?? null\n return { isSuperAdmin, actorTenantId }\n}\n\nfunction buildScopedRoleFilter(roleId: string, scope: ResolvedActorScope): FilterQuery<Role> {\n const filter: FilterQuery<Role> = { id: roleId, deletedAt: null }\n if (!scope.isSuperAdmin) {\n ;(filter as Record<string, unknown>).tenantId = scope.actorTenantId\n }\n return filter\n}\n\nconst createSchema = z.object({\n name: z.string().min(2).max(100),\n tenantId: z.string().uuid().optional(),\n})\n\nconst updateSchema = z.object({\n id: z.string().uuid(),\n name: z.string().min(2).max(100).optional(),\n tenantId: z.string().uuid().optional(),\n})\n\nexport const roleCrudEvents: CrudEventsConfig = {\n module: 'auth',\n entity: 'role',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\nexport const roleCrudIndexer: CrudIndexerConfig = {\n entityType: E.auth.role,\n buildUpsertPayload: (ctx) => ({\n entityType: E.auth.role,\n recordId: ctx.identifiers.id,\n tenantId: ctx.identifiers.tenantId,\n }),\n buildDeletePayload: (ctx) => ({\n entityType: E.auth.role,\n recordId: ctx.identifiers.id,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\nconst createRoleCommand: CommandHandler<Record<string, unknown>, Role> = {\n id: 'auth.roles.create',\n atomicReplay: true,\n stabilizeReplay: stabilizeRoleReplay,\n authorizeReplay: (params) => authorizeRoleReplay('create', params),\n async execute(rawInput, ctx) {\n const rawBody = rawInput && typeof rawInput === 'object' ? rawInput as Record<string, unknown> : {}\n if ('tenantId' in rawBody && rawBody.tenantId === null) {\n throw new CrudHttpError(400, { error: 'tenantId cannot be null \u2014 global roles are not supported' })\n }\n const { parsed, custom } = parseWithCustomFields(createSchema, rawInput)\n assertRoleNameAllowed(parsed.name)\n const scope = await resolveActorScope(ctx)\n const requestedTenantId = parsed.tenantId ?? null\n if (!scope.isSuperAdmin && requestedTenantId && requestedTenantId !== scope.actorTenantId) {\n throw new CrudHttpError(403, { error: 'Not authorized to target this tenant.' })\n }\n const resolvedTenantId = scope.isSuperAdmin\n ? (requestedTenantId ?? scope.actorTenantId)\n : scope.actorTenantId\n if (!resolvedTenantId) {\n throw new CrudHttpError(400, { error: 'tenantId is required \u2014 global roles are not supported' })\n }\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n const role = await de.createOrmEntity({\n entity: Role,\n data: {\n name: parsed.name,\n tenantId: resolvedTenantId,\n },\n })\n\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: String(role.id),\n organizationId: null,\n tenantId: resolvedTenantId,\n values: custom,\n })\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: role,\n identifiers: {\n id: String(role.id),\n organizationId: null,\n tenantId: resolvedTenantId,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n\n return role\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveCommandEm(ctx, true)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n })\n return serializeRole(result, custom)\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = resolveCommandEm(ctx, true)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n })\n const snapshot = captureRoleSnapshots(result, [], custom)\n return {\n actionLabel: translate('auth.audit.roles.create', 'Create role'),\n resourceKind: 'auth.role',\n resourceId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n snapshotAfter: snapshot.view,\n payload: {\n undo: {\n after: snapshot.undo,\n },\n },\n }\n },\n undo: async ({ input, logEntry, ctx }) => {\n const undo = extractUndoPayload<RoleUndoPayload>(logEntry)?.after\n if (!undo) return\n const em = resolveCommandEm(ctx)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await withAtomicFlush(em, [async () => {\n await authorizeRoleReplayAtMutation('create', {\n operation: 'undo',\n input,\n ctx,\n logEntry,\n })\n await lockRoleWriterAuthorizationState(em, [undo.id])\n await em.nativeDelete(RoleAcl, { role: undo.id as unknown as Role })\n if (undo.custom && Object.keys(undo.custom).length) {\n const reset = buildCustomFieldResetMap(undefined, undo.custom)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: undo.id,\n organizationId: null,\n tenantId: undo.tenantId ?? null,\n values: reset,\n notify: false,\n })\n }\n }\n await de.deleteOrmEntity({\n entity: Role,\n where: { id: undo.id, deletedAt: null } as FilterQuery<Role>,\n soft: false,\n })\n }], { transaction: true, label: 'auth.roles.create.undo' })\n },\n redo: async ({ input, logEntry, ctx }) => {\n const after = resolveRedoSnapshot<RoleUndoSnapshot>(logEntry)\n if (!after) throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for role create' })\n const em = resolveCommandEm(ctx)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n let role: Role | null = null\n await withAtomicFlush(em, [async () => {\n await authorizeRoleReplayAtMutation('create', {\n operation: 'redo',\n input,\n ctx,\n logEntry,\n })\n role = await findOneWithDecryption(em, Role, { id: after.id }, {}, { tenantId: null, organizationId: null })\n if (role) {\n role.deletedAt = null\n role.name = after.name\n role.tenantId = after.tenantId\n await em.flush()\n } else {\n role = await de.createOrmEntity({\n entity: Role,\n data: {\n id: after.id,\n name: after.name,\n tenantId: after.tenantId,\n },\n })\n }\n await restoreRoleAcls(em, after.id, after.acls)\n if (after.custom && Object.keys(after.custom).length) {\n const reset = buildCustomFieldResetMap(after.custom, undefined)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: after.id,\n organizationId: null,\n tenantId: after.tenantId ?? null,\n values: reset,\n notify: false,\n })\n }\n }\n }], { transaction: true, label: 'auth.roles.create.redo' })\n if (!role) throw new CrudHttpError(400, { error: '[internal] redo failed to restore role row' })\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: role,\n identifiers: {\n id: after.id,\n organizationId: null,\n tenantId: after.tenantId ?? null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n return role\n },\n}\n\nconst updateRoleCommand: CommandHandler<Record<string, unknown>, Role> = {\n id: 'auth.roles.update',\n atomicReplay: true,\n stabilizeReplay: stabilizeRoleReplay,\n authorizeReplay: (params) => authorizeRoleReplay('update', params),\n async prepare(rawInput, ctx) {\n const { parsed } = parseWithCustomFields(updateSchema, rawInput)\n const em = resolveCommandEm(ctx)\n const scope = await resolveActorScope(ctx)\n const existing = await findOneWithDecryption(em, Role, buildScopedRoleFilter(parsed.id, scope), {}, { tenantId: scope.actorTenantId, organizationId: null })\n if (!existing) throw new CrudHttpError(404, { error: 'Role not found' })\n assertRoleTenantInScope(resolveActorTenantScope(ctx), existing.tenantId)\n const acls = await loadRoleAclSnapshots(em, parsed.id)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: parsed.id,\n tenantId: existing.tenantId ? String(existing.tenantId) : null,\n })\n return { before: captureRoleSnapshots(existing, acls, custom) }\n },\n async execute(rawInput, ctx) {\n const { parsed, custom } = parseWithCustomFields(updateSchema, rawInput)\n const em = resolveCommandEm(ctx)\n const scope = await resolveActorScope(ctx)\n const current = await findOneWithDecryption(em, Role, buildScopedRoleFilter(parsed.id, scope), {}, { tenantId: scope.actorTenantId, organizationId: null })\n if (!current) throw new CrudHttpError(404, { error: 'Role not found' })\n const actorTenantScope = resolveActorTenantScope(ctx)\n assertRoleTenantInScope(actorTenantScope, current.tenantId)\n if (parsed.name !== undefined) {\n const nextName = parsed.name\n if (nextName !== current.name) assertRoleNameAllowed(nextName)\n if (nextName !== current.name) {\n const assignments = await em.count(UserRole, { role: current, deletedAt: null })\n if (assignments > 0) {\n throw new CrudHttpError(400, { error: 'Role name cannot be changed while users are assigned' })\n }\n }\n }\n const wantsTenantChange = parsed.tenantId !== undefined && parsed.tenantId !== current.tenantId\n if (wantsTenantChange) {\n if (!scope.isSuperAdmin) {\n throw new CrudHttpError(403, { error: 'Not authorized to target this tenant.' })\n }\n const assignments = await em.count(UserRole, { role: current, deletedAt: null })\n if (assignments > 0) {\n throw new CrudHttpError(400, { error: 'Role cannot be moved to another tenant while users are assigned' })\n }\n }\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n const redoLogEntry = (ctx as RoleReplayRuntimeContext)[ROLE_REDO_LOG]\n let role: Role | null = null\n await withAtomicFlush(em, [async () => {\n if (redoLogEntry) {\n await authorizeRoleReplayAtMutation('update', {\n operation: 'redo',\n input: rawInput,\n ctx,\n logEntry: redoLogEntry,\n })\n }\n await lockRoleWriterAuthorizationState(em, [parsed.id])\n if (wantsTenantChange) {\n await em.nativeDelete(RoleAcl, { role: parsed.id as unknown as Role })\n }\n role = await de.updateOrmEntity({\n entity: Role,\n where: buildScopedRoleFilter(parsed.id, scope),\n apply: (entity) => {\n if (parsed.name !== undefined) entity.name = parsed.name\n if (parsed.tenantId !== undefined && scope.isSuperAdmin) entity.tenantId = parsed.tenantId\n },\n })\n if (!role) throw new CrudHttpError(404, { error: 'Role not found' })\n\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: String(role.id),\n organizationId: null,\n tenantId: role.tenantId ? String(role.tenantId) : null,\n values: custom,\n })\n }], {\n transaction: true,\n label: redoLogEntry ? 'auth.roles.update.redo' : 'auth.roles.update',\n })\n const updatedRole = role as Role | null\n if (!updatedRole) throw new CrudHttpError(404, { error: 'Role not found' })\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: updatedRole,\n identifiers: {\n id: String(updatedRole.id),\n organizationId: null,\n tenantId: updatedRole.tenantId ? String(updatedRole.tenantId) : null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n\n return updatedRole\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveCommandEm(ctx, true)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n })\n return serializeRole(result, custom)\n },\n buildLog: async ({ result, snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const beforeSnapshots = snapshots.before as RoleSnapshots | undefined\n const before = beforeSnapshots?.view\n const beforeUndo = beforeSnapshots?.undo ?? null\n const em = resolveCommandEm(ctx, true)\n const afterAcls = await loadRoleAclSnapshots(em, String(result.id))\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n })\n const afterSnapshots = captureRoleSnapshots(result, afterAcls, custom)\n const after = afterSnapshots.view\n const changes = buildChanges(before ?? null, after as Record<string, unknown>, ['name', 'tenantId'])\n const customDiff = diffCustomFieldChanges(before?.custom, custom)\n for (const [key, diff] of Object.entries(customDiff)) {\n changes[`cf_${key}`] = diff\n }\n return {\n actionLabel: translate('auth.audit.roles.update', 'Update role'),\n resourceKind: 'auth.role',\n resourceId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n changes,\n snapshotBefore: before ?? null,\n snapshotAfter: after,\n payload: {\n undo: {\n before: beforeUndo,\n after: afterSnapshots.undo,\n },\n },\n }\n },\n undo: async ({ input, logEntry, ctx }) => {\n const undo = extractUndoPayload<RoleUndoPayload>(logEntry)\n const before = undo?.before\n const after = undo?.after\n if (!before) return\n const storedReplayInput = extractStoredReplayInput(logEntry.commandPayload, logEntry)\n const replayInput = Object.keys(storedReplayInput).length\n ? storedReplayInput\n : deriveRoleUpdateReplayInput(before, after ?? null)\n const em = resolveCommandEm(ctx)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n let updated: Role | null = null\n await withAtomicFlush(em, [async () => {\n await authorizeRoleReplayAtMutation('update', {\n operation: 'undo',\n input,\n ctx,\n logEntry,\n })\n updated = await de.updateOrmEntity({\n entity: Role,\n where: { id: before.id, deletedAt: null } as FilterQuery<Role>,\n apply: (entity) => {\n if ('name' in replayInput) entity.name = before.name\n if ('tenantId' in replayInput) entity.tenantId = before.tenantId\n },\n })\n const restoresAcls = 'tenantId' in replayInput && after?.tenantId !== before.tenantId\n if (updated && restoresAcls) {\n await restoreRoleAcls(em, before.id, before.acls)\n }\n const reset = buildCustomFieldResetMap(before.custom, after?.custom)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: before.id,\n organizationId: null,\n tenantId: before.tenantId,\n values: reset,\n notify: false,\n })\n }\n }], { transaction: true, label: 'auth.roles.update.undo' })\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: updated,\n identifiers: {\n id: before.id,\n organizationId: null,\n tenantId: before.tenantId ?? null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n },\n redo: ({ input, ctx, logEntry }) => updateRoleCommand.execute(input, withRoleRedoLog(ctx, logEntry)),\n}\n\nconst deleteRoleCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, Role> = {\n id: 'auth.roles.delete',\n atomicReplay: true,\n stabilizeReplay: stabilizeRoleReplay,\n authorizeReplay: (params) => authorizeRoleReplay('delete', params),\n async prepare(input, ctx) {\n const id = requireId(input, 'Role id required')\n const em = resolveCommandEm(ctx)\n const scope = await resolveActorScope(ctx)\n const existing = await findOneWithDecryption(em, Role, buildScopedRoleFilter(id, scope), {}, { tenantId: scope.actorTenantId, organizationId: null })\n if (!existing) return {}\n const actorTenantScope = resolveActorTenantScope(ctx)\n if (actorTenantScope) {\n const targetTenant = normalizeTenantId(existing.tenantId) ?? null\n if (!targetTenant || targetTenant !== actorTenantScope) return {}\n }\n const acls = await loadRoleAclSnapshots(em, id)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: id,\n tenantId: existing.tenantId ? String(existing.tenantId) : null,\n })\n return { before: captureRoleSnapshots(existing, acls, custom) }\n },\n async execute(input, ctx) {\n const id = requireId(input, 'Role id required')\n const em = resolveCommandEm(ctx)\n const scope = await resolveActorScope(ctx)\n const role = await findOneWithDecryption(em, Role, buildScopedRoleFilter(id, scope), {}, { tenantId: scope.actorTenantId, organizationId: null })\n if (!role) throw new CrudHttpError(404, { error: 'Role not found' })\n assertRoleTenantInScope(resolveActorTenantScope(ctx), role.tenantId)\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n const redoLogEntry = (ctx as RoleReplayRuntimeContext)[ROLE_REDO_LOG]\n let deleted: Role | null = null\n await withAtomicFlush(em, [async () => {\n if (redoLogEntry) {\n await authorizeRoleReplayAtMutation('delete', {\n operation: 'redo',\n input,\n ctx,\n logEntry: redoLogEntry,\n })\n }\n await lockRoleWriterAuthorizationState(em, [id])\n const activeAssignments = await em.count(UserRole, { role, deletedAt: null })\n if (activeAssignments > 0) throw new CrudHttpError(400, { error: 'Role has assigned users' })\n await em.nativeDelete(RoleAcl, { role: id })\n deleted = await de.deleteOrmEntity({\n entity: Role,\n where: buildScopedRoleFilter(id, scope),\n soft: false,\n })\n if (!deleted) throw new CrudHttpError(404, { error: 'Role not found' })\n }], {\n transaction: true,\n label: redoLogEntry ? 'auth.roles.delete.redo' : 'auth.roles.delete',\n })\n const deletedRole = deleted as Role | null\n if (!deletedRole) throw new CrudHttpError(404, { error: 'Role not found' })\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: deletedRole,\n identifiers: {\n id,\n organizationId: null,\n tenantId: deletedRole.tenantId ? String(deletedRole.tenantId) : null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n\n return deletedRole\n },\n buildLog: async ({ snapshots, input }) => {\n const { translate } = await resolveTranslations()\n const beforeSnapshots = snapshots.before as RoleSnapshots | undefined\n const before = beforeSnapshots?.view\n const beforeUndo = beforeSnapshots?.undo ?? null\n const id = requireId(input, 'Role id required')\n return {\n actionLabel: translate('auth.audit.roles.delete', 'Delete role'),\n resourceKind: 'auth.role',\n resourceId: id,\n tenantId: before?.tenantId ?? null,\n snapshotBefore: before ?? null,\n payload: {\n undo: {\n before: beforeUndo,\n },\n },\n }\n },\n undo: async ({ input, logEntry, ctx }) => {\n const before = extractUndoPayload<RoleUndoPayload>(logEntry)?.before\n if (!before) return\n const em = resolveCommandEm(ctx)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n let role: Role | null = null\n await withAtomicFlush(em, [async () => {\n await authorizeRoleReplayAtMutation('delete', {\n operation: 'undo',\n input,\n ctx,\n logEntry,\n })\n role = await findOneWithDecryption(em, Role, { id: before.id }, {}, { tenantId: null, organizationId: null })\n if (role) {\n role.deletedAt = null\n role.name = before.name\n role.tenantId = before.tenantId\n await em.flush()\n } else {\n role = await de.createOrmEntity({\n entity: Role,\n data: {\n id: before.id,\n name: before.name,\n tenantId: before.tenantId,\n },\n })\n }\n await restoreRoleAcls(em, before.id, before.acls)\n const reset = buildCustomFieldResetMap(before.custom, undefined)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: before.id,\n organizationId: null,\n tenantId: before.tenantId ?? null,\n values: reset,\n notify: false,\n })\n }\n }], { transaction: true, label: 'auth.roles.delete.undo' })\n if (!role) throw new CrudHttpError(400, { error: '[internal] undo failed to restore role row' })\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: role,\n identifiers: {\n id: before.id,\n organizationId: null,\n tenantId: before.tenantId ?? null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n },\n redo: ({ input, ctx, logEntry }) => deleteRoleCommand.execute(input, withRoleRedoLog(ctx, logEntry)),\n}\n\ntype RoleReplayCommandKind = 'create' | 'update' | 'delete'\n\nfunction normalizeRoleAclSnapshots(acls: RoleAclSnapshot[]): RoleAclSnapshot[] {\n return acls\n .map((acl) => ({\n ...acl,\n features: acl.features ? [...acl.features].sort((left, right) => left.localeCompare(right)) : null,\n organizations: acl.organizations ? [...acl.organizations].sort((left, right) => left.localeCompare(right)) : null,\n }))\n .sort((left, right) => {\n const tenantOrder = left.tenantId.localeCompare(right.tenantId)\n if (tenantOrder !== 0) return tenantOrder\n return (left.id ?? '').localeCompare(right.id ?? '')\n })\n}\n\nfunction normalizeRoleReplaySnapshot(snapshot: RoleUndoSnapshot): RoleUndoSnapshot {\n return {\n ...snapshot,\n acls: normalizeRoleAclSnapshots(snapshot.acls),\n }\n}\n\nfunction toRoleUpdateReplayState(snapshot: RoleUndoSnapshot): SerializedRole {\n return {\n name: snapshot.name,\n tenantId: snapshot.tenantId,\n ...(snapshot.custom && Object.keys(snapshot.custom).length ? { custom: snapshot.custom } : {}),\n }\n}\n\nfunction deriveRoleUpdateReplayInput(\n before: RoleUndoSnapshot | null,\n after: RoleUndoSnapshot | null,\n): Record<string, unknown> {\n if (!before) return {}\n if (!after) return { id: before.id, name: before.name, tenantId: before.tenantId }\n const input: Record<string, unknown> = { id: after.id }\n if (before.name !== after.name) input.name = after.name\n if (before.tenantId !== after.tenantId) input.tenantId = after.tenantId\n return input\n}\n\nasync function authorizeRoleReplay(\n commandKind: RoleReplayCommandKind,\n params: CommandReplayAuthorizationArgs<unknown>,\n): Promise<void> {\n const { operation, ctx, logEntry } = params\n const em = requireTransactionalReplayEntityManager(ctx)\n await requireCurrentReplayFeature(ctx, 'auth.roles.manage')\n\n const undoPayload = extractUndoPayload<RoleUndoPayload>(logEntry)\n const before = undoPayload?.before ?? null\n const after = undoPayload?.after ?? null\n const roleId = logEntry.resourceId ?? before?.id ?? after?.id ?? null\n if (!roleId) {\n await assertReplaySnapshotMatches(null, { required: 'role replay snapshot' })\n return\n }\n\n const currentRole = await findOneWithDecryption(\n em,\n Role,\n { id: roleId, deletedAt: null },\n {},\n { tenantId: null, organizationId: null },\n )\n const rbacService = ctx.systemActor === true\n ? null\n : ctx.container.resolve('rbacService') as RbacService\n if (rbacService && currentRole) {\n await assertActorCanAccessRoleTarget({\n em,\n rbacService,\n requireBoundRbac: true,\n actorUserId: ctx.auth?.sub,\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.auth?.orgId ?? null,\n targetRoleId: roleId,\n })\n await assertActorCanModifySuperAdminRoleTarget({\n em,\n rbacService,\n requireBoundRbac: true,\n actorUserId: ctx.auth?.sub,\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.auth?.orgId ?? null,\n targetRoleId: roleId,\n })\n }\n\n let currentSnapshot: RoleUndoSnapshot | null = null\n if (currentRole) {\n const currentTenantId = currentRole.tenantId ? String(currentRole.tenantId) : null\n const [acls, custom] = await Promise.all([\n loadRoleAclSnapshots(em, roleId),\n loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: roleId,\n tenantId: currentTenantId,\n }),\n ])\n currentSnapshot = captureRoleSnapshots(currentRole, acls, custom).undo\n }\n\n const expectedCurrent = operation === 'undo'\n ? commandKind === 'create' ? after : commandKind === 'update' ? after : null\n : commandKind === 'create' ? null : before\n const storedReplayInput = extractStoredReplayInput(params.input, logEntry)\n const replayInput = Object.keys(storedReplayInput).length\n ? storedReplayInput\n : deriveRoleUpdateReplayInput(before, after)\n const movesTenant = commandKind === 'update'\n && 'tenantId' in replayInput\n && before?.tenantId !== after?.tenantId\n if (movesTenant) {\n await requireCurrentReplaySuperAdmin(ctx)\n }\n\n if (commandKind === 'update' && !movesTenant) {\n if (!currentSnapshot || !expectedCurrent) {\n await assertReplaySnapshotMatches(currentSnapshot, expectedCurrent)\n } else {\n await assertReplaySnapshotMatches(\n toRoleUpdateReplayState(currentSnapshot),\n toRoleUpdateReplayState(expectedCurrent),\n )\n }\n } else {\n await assertReplaySnapshotMatches(\n currentSnapshot ? normalizeRoleReplaySnapshot(currentSnapshot) : null,\n expectedCurrent ? normalizeRoleReplaySnapshot(expectedCurrent) : null,\n )\n }\n\n const desiredState = operation === 'undo'\n ? commandKind === 'create' ? null : before\n : commandKind === 'delete' ? null : after\n if (desiredState && !currentRole && rbacService) {\n const actorUserId = ctx.auth?.sub\n if (!actorUserId) throw forbidden()\n const actorAcl = await rbacService.loadAclWithEntityManager(em, actorUserId, {\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.auth?.orgId ?? null,\n })\n if (!actorAcl.isSuperAdmin) assertRoleTenantInScope(ctx.auth?.tenantId ?? null, desiredState.tenantId)\n }\n const restoresAcls = Boolean(\n desiredState\n && desiredState.acls.length > 0\n && (commandKind !== 'update' || movesTenant),\n )\n if (!restoresAcls || !desiredState || !rbacService) return\n\n await requireCurrentReplayFeature(ctx, 'auth.acl.manage')\n for (const acl of desiredState.acls) {\n await assertActorCanGrantAcl({\n em,\n rbacService,\n requireBoundRbac: true,\n actorUserId: ctx.auth?.sub,\n tenantId: acl.tenantId,\n organizationId: null,\n features: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n organizations: acl.organizations,\n })\n }\n}\n\nasync function authorizeRoleReplayAtMutation(\n commandKind: RoleReplayCommandKind,\n params: CommandReplayAuthorizationArgs<unknown>,\n): Promise<void> {\n const em = params.ctx.transactionalEm\n ?? params.ctx.container.resolve('em') as EntityManager\n const boundParams = params.ctx.transactionalEm\n ? params\n : { ...params, ctx: { ...params.ctx, transactionalEm: em } }\n await stabilizeRoleReplay(boundParams)\n await rerunReplayTransactionGuardAfterLocks(boundParams, em)\n await authorizeRoleReplay(commandKind, boundParams)\n}\n\nasync function stabilizeRoleReplay(\n params: CommandReplayAuthorizationArgs<unknown>,\n): Promise<void> {\n const undoPayload = extractUndoPayload<RoleUndoPayload>(params.logEntry)\n const before = undoPayload?.before ?? null\n const after = undoPayload?.after ?? null\n const targetRoleId = params.logEntry.resourceId ?? before?.id ?? after?.id ?? null\n const em = requireTransactionalReplayEntityManager(params.ctx)\n await lockReplayAuthorizationState(em, params.ctx, {\n targetRoleIds: targetRoleId ? [targetRoleId] : [],\n targetTenantIds: [\n ...(before?.tenantId ? [before.tenantId] : []),\n ...(after?.tenantId ? [after.tenantId] : []),\n ...(params.logEntry.tenantId ? [params.logEntry.tenantId] : []),\n ],\n })\n}\n\nregisterCommand(createRoleCommand)\nregisterCommand(updateRoleCommand)\nregisterCommand(deleteRoleCommand)\n\nfunction serializeRole(role: Role, custom?: Record<string, unknown> | null): SerializedRole {\n const payload: SerializedRole = {\n name: String(role.name ?? ''),\n tenantId: String(role.tenantId),\n }\n if (custom && Object.keys(custom).length) payload.custom = custom\n return payload\n}\n\nfunction captureRoleSnapshots(\n role: Role,\n acls: RoleAclSnapshot[] = [],\n custom?: Record<string, unknown> | null\n): RoleSnapshots {\n return {\n view: serializeRole(role, custom),\n undo: {\n id: String(role.id),\n name: String(role.name ?? ''),\n tenantId: String(role.tenantId),\n acls,\n ...(custom && Object.keys(custom).length ? { custom } : {}),\n },\n }\n}\n\nasync function loadRoleAclSnapshots(em: EntityManager, roleId: string): Promise<RoleAclSnapshot[]> {\n const entries = await findWithDecryption(em, RoleAcl, { role: roleId as unknown as Role }, {}, { tenantId: null, organizationId: null })\n return entries.map((entry) => ({\n id: entry.id ? String(entry.id) : null,\n tenantId: String(entry.tenantId),\n features: Array.isArray(entry.featuresJson) ? [...entry.featuresJson] : null,\n isSuperAdmin: Boolean(entry.isSuperAdmin),\n organizations: Array.isArray(entry.organizationsJson) ? [...entry.organizationsJson] : null,\n }))\n}\n\nasync function restoreRoleAcls(em: EntityManager, roleId: string, acls: RoleAclSnapshot[]) {\n await lockRoleWriterAuthorizationState(em, [roleId])\n await em.nativeDelete(RoleAcl, { role: roleId as unknown as Role })\n if (!acls.length) {\n await em.flush()\n return\n }\n const roleRef = em.getReference(Role, roleId)\n for (const acl of acls) {\n const entity = em.create(RoleAcl, {\n id: acl.id ?? undefined,\n role: roleRef,\n tenantId: acl.tenantId,\n featuresJson: acl.features ?? null,\n isSuperAdmin: acl.isSuperAdmin,\n organizationsJson: acl.organizations ?? null,\n createdAt: new Date(),\n })\n em.persist(entity)\n }\n await em.flush()\n}\n\ntype RoleUndoPayload = { before?: RoleUndoSnapshot | null; after?: RoleUndoSnapshot | null }\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { CommandHandler, CommandReplayAuthorizationArgs, CommandUndoLogEntry } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport {\n parseWithCustomFields,\n setCustomFieldsIfAny,\n emitCrudSideEffects,\n emitCrudUndoSideEffects,\n buildChanges,\n requireId,\n} from '@open-mercato/shared/lib/commands/helpers'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport { CrudHttpError, forbidden } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { z } from 'zod'\nimport { Role, RoleAcl, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n loadCustomFieldSnapshot,\n buildCustomFieldResetMap,\n diffCustomFieldChanges,\n} from '@open-mercato/shared/lib/commands/customFieldSnapshots'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { resolveIsSuperAdmin, normalizeTenantId } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport {\n assertActorCanAccessRoleTarget,\n assertActorCanGrantAcl,\n assertActorCanModifySuperAdminRoleTarget,\n} from '@open-mercato/core/modules/auth/lib/grantChecks'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport {\n assertReplaySnapshotMatches,\n extractStoredReplayInput,\n lockReplayAuthorizationState,\n rerunReplayTransactionGuardAfterLocks,\n requireCurrentReplayFeature,\n requireCurrentReplaySuperAdmin,\n requireTransactionalReplayEntityManager,\n} from '@open-mercato/core/modules/auth/lib/commandReplay'\nimport { lockRoleWriterAuthorizationState } from '@open-mercato/core/modules/auth/lib/authorizationStateLocks'\n\ntype SerializedRole = {\n name: string\n tenantId: string\n custom?: Record<string, unknown>\n}\n\ntype RoleAclSnapshot = {\n id: string | null\n tenantId: string\n features: string[] | null\n isSuperAdmin: boolean\n organizations: string[] | null\n}\n\ntype RoleUndoSnapshot = {\n id: string\n name: string\n tenantId: string\n acls: RoleAclSnapshot[]\n custom?: Record<string, unknown>\n}\n\ntype RoleSnapshots = {\n view: SerializedRole\n undo: RoleUndoSnapshot\n}\n\nconst ROLE_REDO_LOG = Symbol('auth.roles.redoLog')\n\ntype RoleReplayRuntimeContext = CommandRuntimeContext & {\n [ROLE_REDO_LOG]?: CommandUndoLogEntry\n}\n\nfunction withRoleRedoLog(ctx: CommandRuntimeContext, logEntry: CommandUndoLogEntry): RoleReplayRuntimeContext {\n return { ...ctx, [ROLE_REDO_LOG]: logEntry }\n}\n\nfunction resolveCommandEm(ctx: CommandRuntimeContext, forkWhenUnbound = false): EntityManager {\n if (ctx.transactionalEm) return ctx.transactionalEm\n const em = ctx.container.resolve('em') as EntityManager\n return forkWhenUnbound ? em.fork() : em\n}\n\nfunction resolveActorTenantScope(ctx: CommandRuntimeContext): string | null {\n if (ctx.systemActor === true) return null\n const auth = ctx.auth\n if (!auth) return null\n if ((auth as { isSuperAdmin?: boolean }).isSuperAdmin === true) return null\n return normalizeTenantId(auth.tenantId ?? null) ?? null\n}\n\nfunction assertRoleTenantInScope(actorTenantScope: string | null, targetTenantId: unknown): void {\n if (!actorTenantScope) return\n const targetTenant = normalizeTenantId(targetTenantId) ?? null\n if (!targetTenant || targetTenant !== actorTenantScope) {\n throw new CrudHttpError(404, { error: 'Role not found' })\n }\n}\n\nconst RESERVED_ROLE_NAMES = new Set(['superadmin', 'admin'])\n\nfunction isReservedRoleName(name: string | undefined | null): boolean {\n if (typeof name !== 'string') return false\n const normalized = name.trim().toLowerCase()\n return normalized.length > 0 && RESERVED_ROLE_NAMES.has(normalized)\n}\n\nfunction assertRoleNameAllowed(name: string | undefined | null) {\n if (isReservedRoleName(name)) {\n throw new CrudHttpError(400, { error: 'Role name is reserved' })\n }\n}\n\ntype ResolvedActorScope = { isSuperAdmin: boolean; actorTenantId: string | null }\n\nasync function resolveActorScope(ctx: { auth: { tenantId?: string | null; sub?: string | null; orgId?: string | null; isSuperAdmin?: boolean } | null; container: { resolve<T = unknown>(name: string): T } }): Promise<ResolvedActorScope> {\n const isSuperAdmin = await resolveIsSuperAdmin(ctx)\n const actorTenantId = normalizeTenantId(ctx.auth?.tenantId ?? null) ?? null\n return { isSuperAdmin, actorTenantId }\n}\n\nfunction buildScopedRoleFilter(roleId: string, scope: ResolvedActorScope): FilterQuery<Role> {\n const filter: FilterQuery<Role> = { id: roleId, deletedAt: null }\n if (!scope.isSuperAdmin) {\n ;(filter as Record<string, unknown>).tenantId = scope.actorTenantId\n }\n return filter\n}\n\nconst createSchema = z.object({\n name: z.string().min(2).max(100),\n tenantId: z.string().uuid().optional(),\n})\n\nconst updateSchema = z.object({\n id: z.string().uuid(),\n name: z.string().min(2).max(100).optional(),\n tenantId: z.string().uuid().optional(),\n})\n\nexport const roleCrudEvents: CrudEventsConfig = {\n module: 'auth',\n entity: 'role',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\nexport const roleCrudIndexer: CrudIndexerConfig = {\n entityType: E.auth.role,\n buildUpsertPayload: (ctx) => ({\n entityType: E.auth.role,\n recordId: ctx.identifiers.id,\n tenantId: ctx.identifiers.tenantId,\n }),\n buildDeletePayload: (ctx) => ({\n entityType: E.auth.role,\n recordId: ctx.identifiers.id,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\nconst createRoleCommand: CommandHandler<Record<string, unknown>, Role> = {\n id: 'auth.roles.create',\n atomicReplay: true,\n stabilizeReplay: stabilizeRoleReplay,\n authorizeReplay: (params) => authorizeRoleReplay('create', params),\n async execute(rawInput, ctx) {\n const rawBody = rawInput && typeof rawInput === 'object' ? rawInput as Record<string, unknown> : {}\n if ('tenantId' in rawBody && rawBody.tenantId === null) {\n throw new CrudHttpError(400, { error: 'tenantId cannot be null \u2014 global roles are not supported' })\n }\n const { parsed, custom } = parseWithCustomFields(createSchema, rawInput)\n assertRoleNameAllowed(parsed.name)\n const scope = await resolveActorScope(ctx)\n const requestedTenantId = parsed.tenantId ?? null\n if (!scope.isSuperAdmin && requestedTenantId && requestedTenantId !== scope.actorTenantId) {\n throw new CrudHttpError(403, { error: 'Not authorized to target this tenant.' })\n }\n const resolvedTenantId = scope.isSuperAdmin\n ? (requestedTenantId ?? scope.actorTenantId)\n : scope.actorTenantId\n if (!resolvedTenantId) {\n throw new CrudHttpError(400, { error: 'tenantId is required \u2014 global roles are not supported' })\n }\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n const role = await de.createOrmEntity({\n entity: Role,\n data: {\n name: parsed.name,\n tenantId: resolvedTenantId,\n },\n })\n\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: String(role.id),\n organizationId: null,\n tenantId: resolvedTenantId,\n values: custom,\n })\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: role,\n identifiers: {\n id: String(role.id),\n organizationId: null,\n tenantId: resolvedTenantId,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n\n return role\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveCommandEm(ctx, true)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n })\n return serializeRole(result, custom)\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = resolveCommandEm(ctx, true)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n })\n const snapshot = captureRoleSnapshots(result, [], custom)\n return {\n actionLabel: translate('auth.audit.roles.create', 'Create role'),\n resourceKind: 'auth.role',\n resourceId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n snapshotAfter: snapshot.view,\n payload: {\n undo: {\n after: snapshot.undo,\n },\n },\n }\n },\n undo: async ({ input, logEntry, ctx }) => {\n const undo = extractUndoPayload<RoleUndoPayload>(logEntry)?.after\n if (!undo) return\n const em = resolveCommandEm(ctx)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await withAtomicFlush(em, [async () => {\n await authorizeRoleReplayAtMutation('create', {\n operation: 'undo',\n input,\n ctx,\n logEntry,\n })\n await lockRoleWriterAuthorizationState(em, [undo.id])\n await em.nativeDelete(RoleAcl, { role: undo.id as unknown as Role })\n if (undo.custom && Object.keys(undo.custom).length) {\n const reset = buildCustomFieldResetMap(undefined, undo.custom)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: undo.id,\n organizationId: null,\n tenantId: undo.tenantId ?? null,\n values: reset,\n notify: false,\n })\n }\n }\n await de.deleteOrmEntity({\n entity: Role,\n where: { id: undo.id, deletedAt: null } as FilterQuery<Role>,\n soft: false,\n })\n }], { transaction: true, label: 'auth.roles.create.undo' })\n },\n redo: async ({ input, logEntry, ctx }) => {\n const after = resolveRedoSnapshot<RoleUndoSnapshot>(logEntry)\n if (!after) throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for role create' })\n const em = resolveCommandEm(ctx)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n let role: Role | null = null\n await withAtomicFlush(em, [async () => {\n await authorizeRoleReplayAtMutation('create', {\n operation: 'redo',\n input,\n ctx,\n logEntry,\n })\n role = await findOneWithDecryption(em, Role, { id: after.id }, {}, { tenantId: null, organizationId: null })\n if (role) {\n role.deletedAt = null\n role.name = after.name\n role.tenantId = after.tenantId\n await em.flush()\n } else {\n role = await de.createOrmEntity({\n entity: Role,\n data: {\n id: after.id,\n name: after.name,\n tenantId: after.tenantId,\n },\n })\n }\n await restoreRoleAcls(em, after.id, after.acls)\n if (after.custom && Object.keys(after.custom).length) {\n const reset = buildCustomFieldResetMap(after.custom, undefined)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: after.id,\n organizationId: null,\n tenantId: after.tenantId ?? null,\n values: reset,\n notify: false,\n })\n }\n }\n }], { transaction: true, label: 'auth.roles.create.redo' })\n if (!role) throw new CrudHttpError(400, { error: '[internal] redo failed to restore role row' })\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: role,\n identifiers: {\n id: after.id,\n organizationId: null,\n tenantId: after.tenantId ?? null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n return role\n },\n}\n\nconst updateRoleCommand: CommandHandler<Record<string, unknown>, Role> = {\n id: 'auth.roles.update',\n atomicReplay: true,\n stabilizeReplay: stabilizeRoleReplay,\n authorizeReplay: (params) => authorizeRoleReplay('update', params),\n async prepare(rawInput, ctx) {\n const { parsed } = parseWithCustomFields(updateSchema, rawInput)\n const em = resolveCommandEm(ctx)\n const scope = await resolveActorScope(ctx)\n const existing = await findOneWithDecryption(em, Role, buildScopedRoleFilter(parsed.id, scope), {}, { tenantId: scope.actorTenantId, organizationId: null })\n if (!existing) throw new CrudHttpError(404, { error: 'Role not found' })\n assertRoleTenantInScope(resolveActorTenantScope(ctx), existing.tenantId)\n const acls = await loadRoleAclSnapshots(em, parsed.id)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: parsed.id,\n tenantId: existing.tenantId ? String(existing.tenantId) : null,\n })\n return { before: captureRoleSnapshots(existing, acls, custom) }\n },\n async execute(rawInput, ctx) {\n const { parsed, custom } = parseWithCustomFields(updateSchema, rawInput)\n const em = resolveCommandEm(ctx)\n const scope = await resolveActorScope(ctx)\n const current = await findOneWithDecryption(em, Role, buildScopedRoleFilter(parsed.id, scope), {}, { tenantId: scope.actorTenantId, organizationId: null })\n if (!current) throw new CrudHttpError(404, { error: 'Role not found' })\n const actorTenantScope = resolveActorTenantScope(ctx)\n assertRoleTenantInScope(actorTenantScope, current.tenantId)\n if (parsed.name !== undefined) {\n const nextName = parsed.name\n if (nextName !== current.name) assertRoleNameAllowed(nextName)\n if (nextName !== current.name) {\n const assignments = await em.count(UserRole, { role: current, deletedAt: null })\n if (assignments > 0) {\n throw new CrudHttpError(400, { error: 'Role name cannot be changed while users are assigned' })\n }\n }\n }\n const wantsTenantChange = parsed.tenantId !== undefined && parsed.tenantId !== current.tenantId\n if (wantsTenantChange) {\n if (!scope.isSuperAdmin) {\n throw new CrudHttpError(403, { error: 'Not authorized to target this tenant.' })\n }\n const assignments = await em.count(UserRole, { role: current, deletedAt: null })\n if (assignments > 0) {\n throw new CrudHttpError(400, { error: 'Role cannot be moved to another tenant while users are assigned' })\n }\n }\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n const redoLogEntry = (ctx as RoleReplayRuntimeContext)[ROLE_REDO_LOG]\n let role: Role | null = null\n await withAtomicFlush(em, [async () => {\n if (redoLogEntry) {\n await authorizeRoleReplayAtMutation('update', {\n operation: 'redo',\n input: rawInput,\n ctx,\n logEntry: redoLogEntry,\n })\n }\n await lockRoleWriterAuthorizationState(em, [parsed.id])\n if (wantsTenantChange) {\n await em.nativeDelete(RoleAcl, { role: parsed.id as unknown as Role })\n }\n role = await de.updateOrmEntity({\n entity: Role,\n where: buildScopedRoleFilter(parsed.id, scope),\n apply: (entity) => {\n if (parsed.name !== undefined) entity.name = parsed.name\n if (parsed.tenantId !== undefined && scope.isSuperAdmin) entity.tenantId = parsed.tenantId\n },\n })\n if (!role) throw new CrudHttpError(404, { error: 'Role not found' })\n\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: String(role.id),\n organizationId: null,\n tenantId: role.tenantId ? String(role.tenantId) : null,\n values: custom,\n })\n }], {\n transaction: true,\n label: redoLogEntry ? 'auth.roles.update.redo' : 'auth.roles.update',\n })\n const updatedRole = role as Role | null\n if (!updatedRole) throw new CrudHttpError(404, { error: 'Role not found' })\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: updatedRole,\n identifiers: {\n id: String(updatedRole.id),\n organizationId: null,\n tenantId: updatedRole.tenantId ? String(updatedRole.tenantId) : null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n\n return updatedRole\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveCommandEm(ctx, true)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n })\n return serializeRole(result, custom)\n },\n buildLog: async ({ result, snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const beforeSnapshots = snapshots.before as RoleSnapshots | undefined\n const before = beforeSnapshots?.view\n const beforeUndo = beforeSnapshots?.undo ?? null\n const em = resolveCommandEm(ctx, true)\n const afterAcls = await loadRoleAclSnapshots(em, String(result.id))\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n })\n const afterSnapshots = captureRoleSnapshots(result, afterAcls, custom)\n const after = afterSnapshots.view\n const changes = buildChanges(before ?? null, after as Record<string, unknown>, ['name', 'tenantId'])\n const customDiff = diffCustomFieldChanges(before?.custom, custom)\n for (const [key, diff] of Object.entries(customDiff)) {\n changes[`cf_${key}`] = diff\n }\n return {\n actionLabel: translate('auth.audit.roles.update', 'Update role'),\n resourceKind: 'auth.role',\n resourceId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n changes,\n snapshotBefore: before ?? null,\n snapshotAfter: after,\n payload: {\n undo: {\n before: beforeUndo,\n after: afterSnapshots.undo,\n },\n },\n }\n },\n undo: async ({ input, logEntry, ctx }) => {\n const undo = extractUndoPayload<RoleUndoPayload>(logEntry)\n const before = undo?.before\n const after = undo?.after\n if (!before) return\n const storedReplayInput = extractStoredReplayInput(logEntry.commandPayload, logEntry)\n const replayInput = Object.keys(storedReplayInput).length\n ? storedReplayInput\n : deriveRoleUpdateReplayInput(before, after ?? null)\n const em = resolveCommandEm(ctx)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n let updated: Role | null = null\n await withAtomicFlush(em, [async () => {\n await authorizeRoleReplayAtMutation('update', {\n operation: 'undo',\n input,\n ctx,\n logEntry,\n })\n updated = await de.updateOrmEntity({\n entity: Role,\n where: { id: before.id, deletedAt: null } as FilterQuery<Role>,\n apply: (entity) => {\n if ('name' in replayInput) entity.name = before.name\n if ('tenantId' in replayInput) entity.tenantId = before.tenantId\n },\n })\n const restoresAcls = 'tenantId' in replayInput && after?.tenantId !== before.tenantId\n if (updated && restoresAcls) {\n await restoreRoleAcls(em, before.id, before.acls)\n }\n const reset = buildCustomFieldResetMap(before.custom, after?.custom)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: before.id,\n organizationId: null,\n tenantId: before.tenantId,\n values: reset,\n notify: false,\n })\n }\n }], { transaction: true, label: 'auth.roles.update.undo' })\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: updated,\n identifiers: {\n id: before.id,\n organizationId: null,\n tenantId: before.tenantId ?? null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n },\n redo: ({ input, ctx, logEntry }) => updateRoleCommand.execute(input, withRoleRedoLog(ctx, logEntry)),\n}\n\nconst deleteRoleCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, Role> = {\n id: 'auth.roles.delete',\n atomicReplay: true,\n stabilizeReplay: stabilizeRoleReplay,\n authorizeReplay: (params) => authorizeRoleReplay('delete', params),\n async prepare(input, ctx) {\n const id = requireId(input, 'Role id required')\n const em = resolveCommandEm(ctx)\n const scope = await resolveActorScope(ctx)\n const existing = await findOneWithDecryption(em, Role, buildScopedRoleFilter(id, scope), {}, { tenantId: scope.actorTenantId, organizationId: null })\n if (!existing) return {}\n const actorTenantScope = resolveActorTenantScope(ctx)\n if (actorTenantScope) {\n const targetTenant = normalizeTenantId(existing.tenantId) ?? null\n if (!targetTenant || targetTenant !== actorTenantScope) return {}\n }\n const acls = await loadRoleAclSnapshots(em, id)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: id,\n tenantId: existing.tenantId ? String(existing.tenantId) : null,\n })\n return { before: captureRoleSnapshots(existing, acls, custom) }\n },\n async execute(input, ctx) {\n const id = requireId(input, 'Role id required')\n const em = resolveCommandEm(ctx)\n const scope = await resolveActorScope(ctx)\n const role = await findOneWithDecryption(em, Role, buildScopedRoleFilter(id, scope), {}, { tenantId: scope.actorTenantId, organizationId: null })\n if (!role) throw new CrudHttpError(404, { error: 'Role not found' })\n assertRoleTenantInScope(resolveActorTenantScope(ctx), role.tenantId)\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n const redoLogEntry = (ctx as RoleReplayRuntimeContext)[ROLE_REDO_LOG]\n let deleted: Role | null = null\n await withAtomicFlush(em, [async () => {\n if (redoLogEntry) {\n await authorizeRoleReplayAtMutation('delete', {\n operation: 'redo',\n input,\n ctx,\n logEntry: redoLogEntry,\n })\n }\n await lockRoleWriterAuthorizationState(em, [id])\n const activeAssignments = await em.count(UserRole, { role, deletedAt: null })\n if (activeAssignments > 0) throw new CrudHttpError(400, { error: 'Role has assigned users' })\n await em.nativeDelete(RoleAcl, { role: id })\n deleted = await de.deleteOrmEntity({\n entity: Role,\n where: buildScopedRoleFilter(id, scope),\n soft: false,\n })\n if (!deleted) throw new CrudHttpError(404, { error: 'Role not found' })\n }], {\n transaction: true,\n label: redoLogEntry ? 'auth.roles.delete.redo' : 'auth.roles.delete',\n })\n const deletedRole = deleted as Role | null\n if (!deletedRole) throw new CrudHttpError(404, { error: 'Role not found' })\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: deletedRole,\n identifiers: {\n id,\n organizationId: null,\n tenantId: deletedRole.tenantId ? String(deletedRole.tenantId) : null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n\n return deletedRole\n },\n buildLog: async ({ snapshots, input }) => {\n const { translate } = await resolveTranslations()\n const beforeSnapshots = snapshots.before as RoleSnapshots | undefined\n const before = beforeSnapshots?.view\n const beforeUndo = beforeSnapshots?.undo ?? null\n const id = requireId(input, 'Role id required')\n return {\n actionLabel: translate('auth.audit.roles.delete', 'Delete role'),\n resourceKind: 'auth.role',\n resourceId: id,\n tenantId: before?.tenantId ?? null,\n snapshotBefore: before ?? null,\n payload: {\n undo: {\n before: beforeUndo,\n },\n },\n }\n },\n undo: async ({ input, logEntry, ctx }) => {\n const before = extractUndoPayload<RoleUndoPayload>(logEntry)?.before\n if (!before) return\n const em = resolveCommandEm(ctx)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n let role: Role | null = null\n await withAtomicFlush(em, [async () => {\n await authorizeRoleReplayAtMutation('delete', {\n operation: 'undo',\n input,\n ctx,\n logEntry,\n })\n role = await findOneWithDecryption(em, Role, { id: before.id }, {}, { tenantId: null, organizationId: null })\n if (role) {\n role.deletedAt = null\n role.name = before.name\n role.tenantId = before.tenantId\n await em.flush()\n } else {\n role = await de.createOrmEntity({\n entity: Role,\n data: {\n id: before.id,\n name: before.name,\n tenantId: before.tenantId,\n },\n })\n }\n await restoreRoleAcls(em, before.id, before.acls)\n const reset = buildCustomFieldResetMap(before.custom, undefined)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: before.id,\n organizationId: null,\n tenantId: before.tenantId ?? null,\n values: reset,\n notify: false,\n })\n }\n }], { transaction: true, label: 'auth.roles.delete.undo' })\n if (!role) throw new CrudHttpError(400, { error: '[internal] undo failed to restore role row' })\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: role,\n identifiers: {\n id: before.id,\n organizationId: null,\n tenantId: before.tenantId ?? null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n },\n redo: ({ input, ctx, logEntry }) => deleteRoleCommand.execute(input, withRoleRedoLog(ctx, logEntry)),\n}\n\ntype RoleReplayCommandKind = 'create' | 'update' | 'delete'\n\nfunction normalizeRoleAclSnapshots(acls: RoleAclSnapshot[]): RoleAclSnapshot[] {\n return acls\n .map((acl) => ({\n ...acl,\n features: acl.features ? [...acl.features].sort((left, right) => left.localeCompare(right)) : null,\n organizations: acl.organizations ? [...acl.organizations].sort((left, right) => left.localeCompare(right)) : null,\n }))\n .sort((left, right) => {\n const tenantOrder = left.tenantId.localeCompare(right.tenantId)\n if (tenantOrder !== 0) return tenantOrder\n return (left.id ?? '').localeCompare(right.id ?? '')\n })\n}\n\nfunction normalizeRoleReplaySnapshot(snapshot: RoleUndoSnapshot): RoleUndoSnapshot {\n return {\n ...snapshot,\n acls: normalizeRoleAclSnapshots(snapshot.acls),\n }\n}\n\nfunction toRoleUpdateReplayState(snapshot: RoleUndoSnapshot): SerializedRole {\n return {\n name: snapshot.name,\n tenantId: snapshot.tenantId,\n ...(snapshot.custom && Object.keys(snapshot.custom).length ? { custom: snapshot.custom } : {}),\n }\n}\n\nfunction deriveRoleUpdateReplayInput(\n before: RoleUndoSnapshot | null,\n after: RoleUndoSnapshot | null,\n): Record<string, unknown> {\n if (!before) return {}\n if (!after) return { id: before.id, name: before.name, tenantId: before.tenantId }\n const input: Record<string, unknown> = { id: after.id }\n if (before.name !== after.name) input.name = after.name\n if (before.tenantId !== after.tenantId) input.tenantId = after.tenantId\n return input\n}\n\nasync function authorizeRoleReplay(\n commandKind: RoleReplayCommandKind,\n params: CommandReplayAuthorizationArgs<unknown>,\n): Promise<void> {\n const { operation, ctx, logEntry } = params\n const em = requireTransactionalReplayEntityManager(ctx)\n await requireCurrentReplayFeature(ctx, 'auth.roles.manage')\n\n const undoPayload = extractUndoPayload<RoleUndoPayload>(logEntry)\n const before = undoPayload?.before ?? null\n const after = undoPayload?.after ?? null\n const roleId = logEntry.resourceId ?? before?.id ?? after?.id ?? null\n if (!roleId) {\n await assertReplaySnapshotMatches(null, { required: 'role replay snapshot' })\n return\n }\n\n const currentRole = await findOneWithDecryption(\n em,\n Role,\n { id: roleId, deletedAt: null },\n {},\n { tenantId: null, organizationId: null },\n )\n const rbacService = ctx.systemActor === true\n ? null\n : ctx.container.resolve('rbacService') as RbacService\n if (rbacService && currentRole) {\n await assertActorCanAccessRoleTarget({\n em,\n rbacService,\n requireBoundRbac: true,\n actorUserId: ctx.auth?.sub,\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.auth?.orgId ?? null,\n targetRoleId: roleId,\n })\n await assertActorCanModifySuperAdminRoleTarget({\n em,\n rbacService,\n requireBoundRbac: true,\n actorUserId: ctx.auth?.sub,\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.auth?.orgId ?? null,\n targetRoleId: roleId,\n })\n }\n\n let currentSnapshot: RoleUndoSnapshot | null = null\n if (currentRole) {\n const currentTenantId = currentRole.tenantId ? String(currentRole.tenantId) : null\n const [acls, custom] = await Promise.all([\n loadRoleAclSnapshots(em, roleId),\n loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: roleId,\n tenantId: currentTenantId,\n }),\n ])\n currentSnapshot = captureRoleSnapshots(currentRole, acls, custom).undo\n }\n\n const expectedCurrent = operation === 'undo'\n ? commandKind === 'create' ? after : commandKind === 'update' ? after : null\n : commandKind === 'create' ? null : before\n const storedReplayInput = extractStoredReplayInput(params.input, logEntry)\n const replayInput = Object.keys(storedReplayInput).length\n ? storedReplayInput\n : deriveRoleUpdateReplayInput(before, after)\n const movesTenant = commandKind === 'update'\n && 'tenantId' in replayInput\n && before?.tenantId !== after?.tenantId\n if (movesTenant) {\n await requireCurrentReplaySuperAdmin(ctx)\n }\n\n if (commandKind === 'update' && !movesTenant) {\n if (!currentSnapshot || !expectedCurrent) {\n await assertReplaySnapshotMatches(currentSnapshot, expectedCurrent)\n } else {\n await assertReplaySnapshotMatches(\n toRoleUpdateReplayState(currentSnapshot),\n toRoleUpdateReplayState(expectedCurrent),\n )\n }\n } else {\n await assertReplaySnapshotMatches(\n currentSnapshot ? normalizeRoleReplaySnapshot(currentSnapshot) : null,\n expectedCurrent ? normalizeRoleReplaySnapshot(expectedCurrent) : null,\n )\n }\n\n const desiredState = operation === 'undo'\n ? commandKind === 'create' ? null : before\n : commandKind === 'delete' ? null : after\n if (desiredState && !currentRole && rbacService) {\n const actorUserId = ctx.auth?.sub\n if (!actorUserId) throw forbidden()\n const actorAcl = await rbacService.loadAclWithEntityManager(em, actorUserId, {\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.auth?.orgId ?? null,\n })\n if (!actorAcl.isSuperAdmin) assertRoleTenantInScope(ctx.auth?.tenantId ?? null, desiredState.tenantId)\n }\n const restoresAcls = Boolean(\n desiredState\n && desiredState.acls.length > 0\n && (commandKind !== 'update' || movesTenant),\n )\n if (!restoresAcls || !desiredState || !rbacService) return\n\n await requireCurrentReplayFeature(ctx, 'auth.acl.manage')\n for (const acl of desiredState.acls) {\n await assertActorCanGrantAcl({\n em,\n rbacService,\n requireBoundRbac: true,\n actorUserId: ctx.auth?.sub,\n tenantId: acl.tenantId,\n organizationId: null,\n features: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n organizations: acl.organizations,\n })\n }\n}\n\nasync function authorizeRoleReplayAtMutation(\n commandKind: RoleReplayCommandKind,\n params: CommandReplayAuthorizationArgs<unknown>,\n): Promise<void> {\n const em = params.ctx.transactionalEm\n ?? params.ctx.container.resolve('em') as EntityManager\n const boundParams = params.ctx.transactionalEm\n ? params\n : { ...params, ctx: { ...params.ctx, transactionalEm: em } }\n await stabilizeRoleReplay(boundParams)\n await rerunReplayTransactionGuardAfterLocks(boundParams, em)\n await authorizeRoleReplay(commandKind, boundParams)\n}\n\nasync function stabilizeRoleReplay(\n params: CommandReplayAuthorizationArgs<unknown>,\n): Promise<void> {\n const undoPayload = extractUndoPayload<RoleUndoPayload>(params.logEntry)\n const before = undoPayload?.before ?? null\n const after = undoPayload?.after ?? null\n const targetRoleId = params.logEntry.resourceId ?? before?.id ?? after?.id ?? null\n const em = requireTransactionalReplayEntityManager(params.ctx)\n await lockReplayAuthorizationState(em, params.ctx, {\n targetRoleIds: targetRoleId ? [targetRoleId] : [],\n targetTenantIds: [\n ...(before?.tenantId ? [before.tenantId] : []),\n ...(after?.tenantId ? [after.tenantId] : []),\n ...(params.logEntry.tenantId ? [params.logEntry.tenantId] : []),\n ],\n })\n}\n\nregisterCommand(createRoleCommand)\nregisterCommand(updateRoleCommand)\nregisterCommand(deleteRoleCommand)\n\nfunction serializeRole(role: Role, custom?: Record<string, unknown> | null): SerializedRole {\n const payload: SerializedRole = {\n name: String(role.name ?? ''),\n tenantId: String(role.tenantId),\n }\n if (custom && Object.keys(custom).length) payload.custom = custom\n return payload\n}\n\nfunction captureRoleSnapshots(\n role: Role,\n acls: RoleAclSnapshot[] = [],\n custom?: Record<string, unknown> | null\n): RoleSnapshots {\n return {\n view: serializeRole(role, custom),\n undo: {\n id: String(role.id),\n name: String(role.name ?? ''),\n tenantId: String(role.tenantId),\n acls,\n ...(custom && Object.keys(custom).length ? { custom } : {}),\n },\n }\n}\n\nasync function loadRoleAclSnapshots(em: EntityManager, roleId: string): Promise<RoleAclSnapshot[]> {\n const entries = await findWithDecryption(em, RoleAcl, { role: roleId as unknown as Role, deletedAt: null }, {}, { tenantId: null, organizationId: null })\n return entries.map((entry) => ({\n id: entry.id ? String(entry.id) : null,\n tenantId: String(entry.tenantId),\n features: Array.isArray(entry.featuresJson) ? [...entry.featuresJson] : null,\n isSuperAdmin: Boolean(entry.isSuperAdmin),\n organizations: Array.isArray(entry.organizationsJson) ? [...entry.organizationsJson] : null,\n }))\n}\n\nasync function restoreRoleAcls(em: EntityManager, roleId: string, acls: RoleAclSnapshot[]) {\n await lockRoleWriterAuthorizationState(em, [roleId])\n await em.nativeDelete(RoleAcl, { role: roleId as unknown as Role })\n if (!acls.length) {\n await em.flush()\n return\n }\n const roleRef = em.getReference(Role, roleId)\n for (const acl of acls) {\n const entity = em.create(RoleAcl, {\n id: acl.id ?? undefined,\n role: roleRef,\n tenantId: acl.tenantId,\n featuresJson: acl.features ?? null,\n isSuperAdmin: acl.isSuperAdmin,\n organizationsJson: acl.organizations ?? null,\n createdAt: new Date(),\n })\n em.persist(entity)\n }\n await em.flush()\n}\n\ntype RoleUndoPayload = { before?: RoleUndoSnapshot | null; after?: RoleUndoSnapshot | null }\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1375,7 +1375,7 @@ async function syncUserRoles(em, user, desiredRoles, tenantId, options = {}) {
1375
1375
  const currentLinks = await findWithDecryption(
1376
1376
  em,
1377
1377
  UserRole,
1378
- { user },
1378
+ { user, deletedAt: null, role: { deletedAt: null } },
1379
1379
  { lockMode: LockMode.PESSIMISTIC_WRITE, orderBy: { id: "ASC" }, refresh: true },
1380
1380
  { tenantId: null, organizationId: null }
1381
1381
  );
@@ -1401,7 +1401,11 @@ async function loadUserRoleNames(em, userId) {
1401
1401
  const links = await findWithDecryption(
1402
1402
  em,
1403
1403
  UserRole,
1404
- { user: userId },
1404
+ {
1405
+ user: userId,
1406
+ deletedAt: null,
1407
+ role: { deletedAt: null }
1408
+ },
1405
1409
  { populate: ["role"] },
1406
1410
  { tenantId: null, organizationId: null }
1407
1411
  );
@@ -1438,7 +1442,7 @@ function captureUserSnapshots(user, roles, acls = [], custom) {
1438
1442
  };
1439
1443
  }
1440
1444
  async function loadUserAclSnapshots(em, userId) {
1441
- const list = await findWithDecryption(em, UserAcl, { user: userId }, {}, { tenantId: null, organizationId: null });
1445
+ const list = await findWithDecryption(em, UserAcl, { user: userId, deletedAt: null }, {}, { tenantId: null, organizationId: null });
1442
1446
  return list.map((acl) => ({
1443
1447
  tenantId: String(acl.tenantId),
1444
1448
  features: Array.isArray(acl.featuresJson) ? [...acl.featuresJson] : null,