@astram/identity 0.4.0 → 0.6.0

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 (184) hide show
  1. package/dist/company-membership.object.d.ts.map +1 -1
  2. package/dist/company-membership.object.js +3 -2
  3. package/dist/company.object.d.ts.map +1 -1
  4. package/dist/company.object.js +8 -3
  5. package/dist/features/access/index.d.ts.map +1 -1
  6. package/dist/features/access/index.js +1 -5
  7. package/dist/features/audit/audit-read.service.d.ts.map +1 -1
  8. package/dist/features/audit/audit-read.service.js +13 -6
  9. package/dist/features/audit/audit.controller.d.ts.map +1 -1
  10. package/dist/features/audit/audit.controller.js +2 -0
  11. package/dist/features/audit/mariadb-audit-reader.d.ts.map +1 -1
  12. package/dist/features/authz/access-bundle-reconcile.job.d.ts.map +1 -1
  13. package/dist/features/authz/catalog-reconcile.job.d.ts.map +1 -1
  14. package/dist/features/authz/catalog-reconcile.job.js +1 -3
  15. package/dist/features/authz/db-permission-facts.adapter.d.ts +16 -0
  16. package/dist/features/authz/db-permission-facts.adapter.d.ts.map +1 -1
  17. package/dist/features/authz/db-permission-facts.adapter.js +46 -2
  18. package/dist/features/authz/grant-admin.controller.d.ts +8 -5
  19. package/dist/features/authz/grant-admin.controller.d.ts.map +1 -1
  20. package/dist/features/authz/grant-admin.controller.js +20 -18
  21. package/dist/features/authz/grant-admin.service.d.ts +72 -16
  22. package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
  23. package/dist/features/authz/grant-admin.service.js +232 -86
  24. package/dist/features/authz/grant-ceiling.d.ts +100 -1
  25. package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
  26. package/dist/features/authz/grant-ceiling.js +264 -2
  27. package/dist/features/authz/mariadb-catalog-store.d.ts.map +1 -1
  28. package/dist/features/authz/mariadb-catalog-store.js +15 -3
  29. package/dist/features/authz/permission-version.bumper.d.ts +52 -1
  30. package/dist/features/authz/permission-version.bumper.d.ts.map +1 -1
  31. package/dist/features/authz/permission-version.bumper.js +119 -20
  32. package/dist/features/authz/record-authorization.d.ts +12 -0
  33. package/dist/features/authz/record-authorization.d.ts.map +1 -1
  34. package/dist/features/authz/record-authorization.js +49 -0
  35. package/dist/features/authz/role-binding.d.ts +36 -0
  36. package/dist/features/authz/role-binding.d.ts.map +1 -0
  37. package/dist/features/authz/role-binding.js +53 -0
  38. package/dist/features/company/company-hierarchy.service.d.ts +108 -0
  39. package/dist/features/company/company-hierarchy.service.d.ts.map +1 -0
  40. package/dist/features/company/company-hierarchy.service.js +277 -0
  41. package/dist/features/company/company.controller.d.ts +23 -5
  42. package/dist/features/company/company.controller.d.ts.map +1 -1
  43. package/dist/features/company/company.controller.js +47 -12
  44. package/dist/features/company/company.service.d.ts +28 -12
  45. package/dist/features/company/company.service.d.ts.map +1 -1
  46. package/dist/features/company/company.service.js +61 -14
  47. package/dist/features/cross-tenant/cross-tenant-grant.controller.d.ts.map +1 -1
  48. package/dist/features/cross-tenant/cross-tenant-grant.controller.js +2 -0
  49. package/dist/features/identity-feature.module.d.ts +2 -0
  50. package/dist/features/identity-feature.module.d.ts.map +1 -1
  51. package/dist/features/identity-feature.module.js +72 -5
  52. package/dist/features/membership/membership.controller.d.ts +6 -5
  53. package/dist/features/membership/membership.controller.d.ts.map +1 -1
  54. package/dist/features/membership/membership.controller.js +34 -22
  55. package/dist/features/membership/membership.service.d.ts +20 -14
  56. package/dist/features/membership/membership.service.d.ts.map +1 -1
  57. package/dist/features/membership/membership.service.js +62 -29
  58. package/dist/features/membership/posting-version-bump.d.ts +12 -0
  59. package/dist/features/membership/posting-version-bump.d.ts.map +1 -1
  60. package/dist/features/membership/posting.service.d.ts +20 -24
  61. package/dist/features/membership/posting.service.d.ts.map +1 -1
  62. package/dist/features/membership/posting.service.js +101 -64
  63. package/dist/features/org-structure/org-catalog.controller.d.ts.map +1 -1
  64. package/dist/features/org-structure/org-catalog.controller.js +2 -0
  65. package/dist/features/org-structure/org-catalog.service.d.ts.map +1 -1
  66. package/dist/features/org-structure/org-catalog.service.js +4 -1
  67. package/dist/features/policy/policy.controller.d.ts.map +1 -1
  68. package/dist/features/policy/policy.controller.js +2 -0
  69. package/dist/features/role/role.controller.d.ts +3 -2
  70. package/dist/features/role/role.controller.d.ts.map +1 -1
  71. package/dist/features/role/role.controller.js +15 -10
  72. package/dist/features/role/role.service.d.ts +39 -8
  73. package/dist/features/role/role.service.d.ts.map +1 -1
  74. package/dist/features/role/role.service.js +77 -10
  75. package/dist/features/scope/scope-closure.service.d.ts +54 -4
  76. package/dist/features/scope/scope-closure.service.d.ts.map +1 -1
  77. package/dist/features/scope/scope-closure.service.js +237 -85
  78. package/dist/features/service-account/service-account.controller.d.ts +3 -2
  79. package/dist/features/service-account/service-account.controller.d.ts.map +1 -1
  80. package/dist/features/service-account/service-account.controller.js +29 -15
  81. package/dist/features/service-account/service-account.service.d.ts +29 -7
  82. package/dist/features/service-account/service-account.service.d.ts.map +1 -1
  83. package/dist/features/service-account/service-account.service.js +73 -12
  84. package/dist/features/service-account/service-account.store.d.ts +15 -0
  85. package/dist/features/service-account/service-account.store.d.ts.map +1 -1
  86. package/dist/features/service-account/service-account.store.js +29 -0
  87. package/dist/features/share/share.controller.d.ts.map +1 -1
  88. package/dist/features/share/share.controller.js +2 -0
  89. package/dist/features/share/share.service.d.ts.map +1 -1
  90. package/dist/features/shared/identity-error.interceptor.d.ts +24 -0
  91. package/dist/features/shared/identity-error.interceptor.d.ts.map +1 -0
  92. package/dist/features/shared/identity-error.interceptor.js +82 -0
  93. package/dist/features/shared/identity-errors.d.ts +23 -0
  94. package/dist/features/shared/identity-errors.d.ts.map +1 -0
  95. package/dist/features/shared/identity-errors.js +41 -0
  96. package/dist/features/user/session-revocation-check.d.ts +11 -0
  97. package/dist/features/user/session-revocation-check.d.ts.map +1 -0
  98. package/dist/features/user/session-revocation-check.js +21 -0
  99. package/dist/features/user/user.controller.d.ts +11 -6
  100. package/dist/features/user/user.controller.d.ts.map +1 -1
  101. package/dist/features/user/user.controller.js +35 -17
  102. package/dist/features/user/user.service.d.ts +55 -14
  103. package/dist/features/user/user.service.d.ts.map +1 -1
  104. package/dist/features/user/user.service.js +143 -16
  105. package/dist/index.d.ts +7 -5
  106. package/dist/index.d.ts.map +1 -1
  107. package/dist/index.js +7 -6
  108. package/dist/internal.d.ts +10 -0
  109. package/dist/internal.d.ts.map +1 -0
  110. package/dist/internal.js +13 -0
  111. package/dist/membership-posting.object.d.ts.map +1 -1
  112. package/dist/membership-posting.object.js +4 -2
  113. package/dist/objects.d.ts.map +1 -1
  114. package/dist/objects.js +13 -3
  115. package/dist/role.object.d.ts.map +1 -1
  116. package/dist/role.object.js +3 -0
  117. package/dist/user.object.d.ts.map +1 -1
  118. package/dist/user.object.js +3 -0
  119. package/migrations/1710000000019-AddSessionMustReenroll.cjs +35 -0
  120. package/package.json +15 -4
  121. package/dist/auth-facts-keys.js.map +0 -1
  122. package/dist/branch.object.js.map +0 -1
  123. package/dist/company-branch.object.js.map +0 -1
  124. package/dist/company-department.object.js.map +0 -1
  125. package/dist/company-membership.object.js.map +0 -1
  126. package/dist/company.object.js.map +0 -1
  127. package/dist/cross-tenant-grant.object.js.map +0 -1
  128. package/dist/department.object.js.map +0 -1
  129. package/dist/features/access/access-admin.access.js.map +0 -1
  130. package/dist/features/access/company-admin.access.js.map +0 -1
  131. package/dist/features/access/index.js.map +0 -1
  132. package/dist/features/access/user-management.access.js.map +0 -1
  133. package/dist/features/audit/audit-read.service.js.map +0 -1
  134. package/dist/features/audit/audit.controller.js.map +0 -1
  135. package/dist/features/audit/directory-label-resolver.js.map +0 -1
  136. package/dist/features/audit/mariadb-audit-reader.js.map +0 -1
  137. package/dist/features/authz/access-bundle-reconcile.job.js.map +0 -1
  138. package/dist/features/authz/access-bundle-store.js.map +0 -1
  139. package/dist/features/authz/catalog-bootstrap.provider.js.map +0 -1
  140. package/dist/features/authz/catalog-reconcile.job.js.map +0 -1
  141. package/dist/features/authz/db-permission-facts.adapter.js.map +0 -1
  142. package/dist/features/authz/grant-admin.controller.js.map +0 -1
  143. package/dist/features/authz/grant-admin.service.js.map +0 -1
  144. package/dist/features/authz/grant-ceiling.js.map +0 -1
  145. package/dist/features/authz/mariadb-catalog-store.js.map +0 -1
  146. package/dist/features/authz/permission-version.bumper.js.map +0 -1
  147. package/dist/features/authz/record-authorization.js.map +0 -1
  148. package/dist/features/authz/session-facts.reader.js.map +0 -1
  149. package/dist/features/company/company.controller.js.map +0 -1
  150. package/dist/features/company/company.service.js.map +0 -1
  151. package/dist/features/cross-tenant/cross-tenant-grant-read.service.js.map +0 -1
  152. package/dist/features/cross-tenant/cross-tenant-grant.controller.js.map +0 -1
  153. package/dist/features/cross-tenant/grant-authorize.js.map +0 -1
  154. package/dist/features/identity-feature.module.js.map +0 -1
  155. package/dist/features/membership/membership-checker.js.map +0 -1
  156. package/dist/features/membership/membership.controller.js.map +0 -1
  157. package/dist/features/membership/membership.service.js.map +0 -1
  158. package/dist/features/membership/posting-version-bump.js.map +0 -1
  159. package/dist/features/membership/posting.service.js.map +0 -1
  160. package/dist/features/org-structure/org-catalog.controller.js.map +0 -1
  161. package/dist/features/org-structure/org-catalog.service.js.map +0 -1
  162. package/dist/features/policy/policy.controller.js.map +0 -1
  163. package/dist/features/policy/policy.service.js.map +0 -1
  164. package/dist/features/role/role.controller.js.map +0 -1
  165. package/dist/features/role/role.service.js.map +0 -1
  166. package/dist/features/scope/scope-closure.service.js.map +0 -1
  167. package/dist/features/scope/scope-subtree.resolver.js.map +0 -1
  168. package/dist/features/share/share.controller.js.map +0 -1
  169. package/dist/features/share/share.service.js.map +0 -1
  170. package/dist/features/shared/http.js.map +0 -1
  171. package/dist/features/shared/list-query.js.map +0 -1
  172. package/dist/features/shared/named-option.js.map +0 -1
  173. package/dist/features/shared/record-resolver.js.map +0 -1
  174. package/dist/features/user/user.controller.js.map +0 -1
  175. package/dist/features/user/user.service.js.map +0 -1
  176. package/dist/index.js.map +0 -1
  177. package/dist/membership-posting.object.js.map +0 -1
  178. package/dist/migrations-dir.js.map +0 -1
  179. package/dist/objects.js.map +0 -1
  180. package/dist/record-share.object.js.map +0 -1
  181. package/dist/role.object.js.map +0 -1
  182. package/dist/schema.js.map +0 -1
  183. package/dist/shared-resource.object.js.map +0 -1
  184. package/dist/user.object.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"grant-admin.service.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-admin.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"grant-admin.service.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-admin.service.ts"],"names":[],"mappings":"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"}
@@ -4,22 +4,28 @@ exports.GrantAdminService = exports.BadKeyError = void 0;
4
4
  const core_1 = require("@astram/core");
5
5
  const access_bundle_store_js_1 = require("./access-bundle-store.js");
6
6
  const db_permission_facts_adapter_js_1 = require("./db-permission-facts.adapter.js");
7
+ const grant_ceiling_js_1 = require("./grant-ceiling.js");
8
+ const role_binding_js_1 = require("./role-binding.js");
9
+ const identity_errors_js_1 = require("../shared/identity-errors.js");
7
10
  const AUTHZ = 'shield_authz';
8
11
  const APP = 'shield_app';
9
- /**
10
- * Build the shared EXISTS-subtree fragment for a non-root caller. A role qualifies only if it
11
- * is bound (`membership_posting → company_membership`) to a node in the caller's subtree. All
12
- * node ids are `?`-bound (never interpolated → injection-free); the caller splices the returned
13
- * `params` at the position where `sql` sits in the surrounding query. Root / empty scope callers
14
- * are handled by the caller (root ⇒ no fragment; the engine guarantees non-empty subtrees).
15
- */
16
- function subtreeExists(nodeIds) {
17
- const ph = nodeIds.map(() => '?').join(', ');
18
- const sql = ` AND EXISTS (` +
19
- `SELECT 1 FROM \`${APP}\`.\`membership_posting\` mp` +
20
- ` JOIN \`${APP}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid` +
21
- ` WHERE mp.mp_role_uuid = r.r_uuid AND cm.cm_company_uuid IN (${ph}))`;
22
- return { sql, params: [...nodeIds] };
12
+ /** The grant-admin actions that REMOVE authority from a role's holders (Phase 8 #2). */
13
+ const REVOKE_ACTIONS = new Set([
14
+ 'role.permission_revoked',
15
+ 'role.bundle_revoked',
16
+ ]);
17
+ /** A JSON-array column (the driver returns an array OR a string) → its string items; else []. */
18
+ function jsonIdArray(v) {
19
+ let arr = v;
20
+ if (typeof v === 'string') {
21
+ try {
22
+ arr = JSON.parse(v);
23
+ }
24
+ catch {
25
+ arr = [];
26
+ }
27
+ }
28
+ return Array.isArray(arr) ? arr.filter((x) => typeof x === 'string') : [];
23
29
  }
24
30
  /** A granted key was not in the catalog and is not a recognized wildcard. The
25
31
  * controller maps this to HTTP 400 (distinct from a store outage → 503). */
@@ -34,17 +40,22 @@ class GrantAdminService {
34
40
  pool;
35
41
  bumper;
36
42
  ceiling;
43
+ audit;
37
44
  bundles;
38
45
  /**
39
- * @param ceiling The ADR-0029 self-escalation ceiling ("only grant what you hold"). Enforced
40
- * on `grant()`/`grantBundle()` for a non-root grantor. OPTIONAL for back-compat: when absent
41
- * (legacy wiring / tests that don't exercise the ceiling), the ceiling is not applied — the
42
- * identity module ALWAYS wires it, so production always enforces. Root bypasses regardless.
46
+ * @param ceiling The ADR-0029 self-escalation ceiling ("only grant what you hold") plus the
47
+ * security-hardening role-binding rule. Enforced on grant/revoke/grantBundle/revokeBundle for a
48
+ * non-root actor. OPTIONAL for back-compat: when absent (legacy wiring / tests that don't
49
+ * exercise it), neither check runs — the identity module ALWAYS wires it, so production always
50
+ * enforces. Root bypasses regardless.
51
+ * @param audit The grant-administration audit sink (D6): written on the grant's own transaction
52
+ * connection, fail-closed. The identity module ALWAYS wires the engine `MariaDbAuditSink`.
43
53
  */
44
- constructor(pool, bumper, bundleStore, ceiling) {
54
+ constructor(pool, bumper, bundleStore, ceiling, audit) {
45
55
  this.pool = pool;
46
56
  this.bumper = bumper;
47
57
  this.ceiling = ceiling;
58
+ this.audit = audit;
48
59
  this.bundles = bundleStore ?? new access_bundle_store_js_1.AccessBundleStore(pool);
49
60
  }
50
61
  /** All grantable exact keys (from the catalog) + the computed wildcard set. */
@@ -60,8 +71,11 @@ class GrantAdminService {
60
71
  throw new core_1.AuthorizationUnavailableError('permission catalog unavailable', cause);
61
72
  }
62
73
  const catalog = rows.map((r) => ({
63
- key: r.perm_key, label: r.perm_label, module: r.perm_module,
64
- typeKey: r.perm_type_key, action: r.perm_action,
74
+ key: r.perm_key,
75
+ label: r.perm_label,
76
+ module: r.perm_module,
77
+ typeKey: r.perm_type_key,
78
+ action: r.perm_action,
65
79
  }));
66
80
  // Wildcards: module-wide + one per distinct type key.
67
81
  const modules = [...new Set(catalog.map((c) => c.module))];
@@ -106,52 +120,206 @@ class GrantAdminService {
106
120
  throw new BadKeyError(key);
107
121
  }
108
122
  /**
109
- * Grant a key to a role (idempotent) + bump the version, atomically. The `grantor` Principal
110
- * stamps `rp_granted_at`/`rp_granted_by` on a NEWLY-inserted row (a duplicate is a no-op and
111
- * its existing stamp is left as-is) AND is subjected to the ADR-0029 self-escalation ceiling:
112
- * a non-root grantor may only grant a key they themselves effectively hold. Order: catalog
113
- * validation (400) → ceiling (403) → write. Root bypasses the ceiling.
123
+ * Security hardening #1 (D1 interim rule) + ADR-0029, in this order, BEFORE any write: the role
124
+ * must be administrable by the actor (bound inside its subtree and nowhere outside — a shared
125
+ * role is root-only), then every key must be covered by the actor's own effective keys ("only
126
+ * grant — or revoke — what you hold"). Root bypasses both. No ceiling wired (legacy/tests) ⇒
127
+ * neither check runs; the identity module always wires it.
114
128
  */
115
- async grant(roleUuid, key, grantor) {
116
- await this.assertValidKey(key); // throws BadKeyError (400) before touching the store
117
- // ADR-0029 ceiling: throws GrantCeilingError (403) if the grantor exceeds their authority.
118
- await this.ceiling?.assertCanGrant(grantor, [key]);
119
- const actorUuid = grantor.id;
129
+ async assertMayChangeGrants(roleUuid, keys, actor) {
130
+ if (!this.ceiling)
131
+ return;
132
+ await this.ceiling.assertRoleAdministrable(actor, roleUuid);
133
+ await this.ceiling.assertCanGrant(actor, keys);
134
+ }
135
+ /**
136
+ * Run one grant-administration write in ONE transaction: the `role_permission` statements, the
137
+ * permission-version bump of every tenant using the role, and the audit row(s) (D6 — audited in
138
+ * the SAME transaction, fail-closed: an audit failure rolls the grant back). Store failures map
139
+ * to AuthorizationUnavailableError (503).
140
+ */
141
+ async writeGrants(action, roleUuid, keys, actor, statement, extra = {}) {
120
142
  const conn = await this.pool.getConnection();
121
143
  try {
122
144
  await conn.beginTransaction();
123
- // Idempotent insert — UNIQUE (rp_role_uuid, rp_perm_key); a duplicate is a no-op.
124
- await conn.query(`INSERT INTO \`${AUTHZ}\`.\`role_permission\`
125
- (rp_uuid, rp_role_uuid, rp_perm_key, rp_granted_at, rp_granted_by)
126
- SELECT UUID(), ?, ?, UTC_TIMESTAMP(), ?
127
- WHERE NOT EXISTS (
128
- SELECT 1 FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?
129
- )`, [roleUuid, key, actorUuid ?? null, roleUuid, key]);
130
- await this.bumper.bumpForRole(roleUuid, conn);
145
+ // Security review F2: serialise against a concurrent posting of this role (in any tenant).
146
+ // Lock the role row FIRST — the posting path locks the same row before it inserts — then
147
+ // re-run the binding rule on THIS connection, so a binding committed by a writer we waited
148
+ // for is seen, and a role that became shared in the meantime is refused (rolled back).
149
+ await conn.query(`SELECT r_uuid FROM \`${APP}\`.\`roles\` WHERE r_uuid = ? FOR UPDATE`, [
150
+ roleUuid,
151
+ ]);
152
+ if (this.ceiling && actor.isRoot !== true) {
153
+ grant_ceiling_js_1.GrantCeiling.checkAdministrable(await (0, role_binding_js_1.readRoleBinding)(conn, roleUuid, (0, grant_ceiling_js_1.subtreeOf)(actor)));
154
+ // Phase 8 #2: a revocation strips authority from every holder of the role, so the actor
155
+ // must dominate each of them — checked under the role lock (no holder can appear unseen).
156
+ if (REVOKE_ACTIONS.has(action)) {
157
+ await this.ceiling.assertDominatesRoleHolders(actor, roleUuid);
158
+ }
159
+ }
160
+ for (const key of keys)
161
+ await statement(conn, key);
162
+ const bumped = await this.bumper.bumpForRole(roleUuid, conn);
163
+ await this.auditGrantChange(conn, action, roleUuid, keys, actor, bumped, extra);
131
164
  await conn.commit();
165
+ // ADR-0035: the committed versions reach Redis now (gate 5 reads them there).
166
+ await this.bumper.publishCommitted(conn);
132
167
  }
133
168
  catch (cause) {
134
169
  await conn.rollback();
135
- if (cause instanceof BadKeyError)
170
+ this.bumper.discardPending(conn);
171
+ if (cause instanceof BadKeyError ||
172
+ cause instanceof identity_errors_js_1.IdentityForbiddenError ||
173
+ cause instanceof identity_errors_js_1.IdentityNotFoundError) {
136
174
  throw cause;
137
- throw new core_1.AuthorizationUnavailableError('grant failed', cause);
175
+ }
176
+ throw new core_1.AuthorizationUnavailableError(`${action} failed`, cause);
138
177
  }
139
178
  finally {
140
179
  await conn.release();
141
180
  }
142
181
  }
143
- /** Revoke a key from a role (no-op if absent) + bump the version, atomically. */
144
- async revoke(roleUuid, key) {
182
+ /**
183
+ * Write the grant-administration audit row(s) on the transaction connection (D6). One row per
184
+ * distinct tenant among the actor's tenant and every tenant the change bumped, so each affected
185
+ * tenant's auditor sees it. Names, never ids, in the detail (ADR-0012): the role name and the
186
+ * bumped tenants' names; the viewer resolves the actor and row tenant itself. No sink wired
187
+ * (legacy/tests) ⇒ nothing is written.
188
+ */
189
+ async auditGrantChange(conn, action, roleUuid, keys, actor, bumpedTenants, extra) {
190
+ if (!this.audit)
191
+ return;
192
+ const query = (sql, params) => conn.query(sql, params);
193
+ const roleRows = (await query(`SELECT r_name FROM \`${APP}\`.\`roles\` WHERE r_uuid = ? LIMIT 1`, [roleUuid]));
194
+ const tenants = [...new Set(bumpedTenants)];
195
+ const nameOf = new Map();
196
+ if (tenants.length > 0) {
197
+ const ph = tenants.map(() => '?').join(', ');
198
+ const rows = (await query(`SELECT c_uuid, c_name FROM \`${APP}\`.\`companies\` WHERE c_uuid IN (${ph})`, tenants));
199
+ for (const r of rows)
200
+ nameOf.set(r.c_uuid, r.c_name);
201
+ }
202
+ const labelOf = (t) => nameOf.get(t) ?? '(deleted organisation)';
203
+ const bumpedSet = new Set(tenants);
204
+ // Security review F1: each row is filed under ONE tenant and names ONLY that tenant — a
205
+ // tenant's auditor must never learn which OTHER tenants use the same (shared) role. The one
206
+ // exception is root's own row, filed under a tenant the change did NOT bump, which may carry
207
+ // the full list (root administers across tenants).
208
+ const tenantsFor = (rowTenant) => {
209
+ if (bumpedSet.has(rowTenant))
210
+ return [labelOf(rowTenant)];
211
+ return actor.isRoot === true ? tenants.map(labelOf) : [];
212
+ };
213
+ const occurredAtMs = Date.now();
214
+ for (const tenantId of new Set([actor.tenantId, ...tenants])) {
215
+ const tenantNames = tenantsFor(tenantId);
216
+ await this.audit.record({
217
+ type: 'admin_action',
218
+ occurredAtMs,
219
+ actorId: actor.id,
220
+ tenantId,
221
+ action: { key: action },
222
+ attributes: {
223
+ eventType: 'role_grant',
224
+ role: roleRows[0]?.r_name ?? '(deleted role)',
225
+ keys: [...keys],
226
+ tenants: tenantNames,
227
+ ...extra,
228
+ },
229
+ }, query);
230
+ }
231
+ }
232
+ /**
233
+ * Grant a key to a role (idempotent) + bump the version + audit, atomically. The `grantor`
234
+ * Principal stamps `rp_granted_at`/`rp_granted_by` on a NEWLY-inserted row (a duplicate is a
235
+ * no-op and its existing stamp is left as-is). Order: catalog validation (400) → role binding
236
+ * (404/403) → ceiling (403) → write. Root bypasses the binding rule and the ceiling.
237
+ */
238
+ async grant(roleUuid, key, grantor) {
239
+ await this.assertValidKey(key); // throws BadKeyError (400) before touching the store
240
+ await this.assertMayChangeGrants(roleUuid, [key], grantor);
241
+ const actorUuid = grantor.id;
242
+ await this.writeGrants('role.permission_granted', roleUuid, [key], grantor, async (conn, k) => {
243
+ // Idempotent insert — UNIQUE (rp_role_uuid, rp_perm_key); a duplicate is a no-op.
244
+ await conn.query(`INSERT INTO \`${AUTHZ}\`.\`role_permission\`
245
+ (rp_uuid, rp_role_uuid, rp_perm_key, rp_granted_at, rp_granted_by)
246
+ SELECT UUID(), ?, ?, UTC_TIMESTAMP(), ?
247
+ WHERE NOT EXISTS (
248
+ SELECT 1 FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?
249
+ )`, [roleUuid, k, actorUuid ?? null, roleUuid, k]);
250
+ });
251
+ }
252
+ /**
253
+ * Revoke a key from a role (no-op if absent) + bump + audit, atomically. Security hardening #1:
254
+ * the actor must be able to administer the role (binding rule) and must hold the key itself —
255
+ * "only revoke what you hold" — so a delegated admin can no longer strip a role of authority it
256
+ * never had. Phase 8 #2: it must also dominate every user holding the role (checked under the
257
+ * role lock in `writeGrants`). Root bypasses.
258
+ */
259
+ async revoke(roleUuid, key, actor) {
260
+ await this.assertMayChangeGrants(roleUuid, [key], actor);
261
+ await this.writeGrants('role.permission_revoked', roleUuid, [key], actor, async (conn, k) => {
262
+ await conn.query(`DELETE FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?`, [roleUuid, k]);
263
+ });
264
+ }
265
+ /**
266
+ * Remove a role that is being DELETED from authorization (Phase 8 #2 follow-up): every
267
+ * `role_permission` row of the role, and the role from every service account's `sa_role_ids`
268
+ * (guard's API-key store reads that column live, so a stale entry would keep the deleted role's
269
+ * keys on the agent). ONE transaction on the shared authz pool — the same pool, bump, audit and
270
+ * error mapping as {@link revoke}:
271
+ * 1. lock the holding accounts (`FOR UPDATE`), then check the actor may strip the role from
272
+ * each (`GrantCeiling.assertMayStripRoleFromServiceAccounts`, root bypasses);
273
+ * 2. bump every tenant using the role — postings AND the accounts' tenants — before the strip,
274
+ * so the account tenants are still found;
275
+ * 3. delete the role's grants; rewrite each account's role list without it (edit stamp moved
276
+ * forward, so a concurrent preserve-merge edit conflicts instead of re-adding it);
277
+ * 4. audit as `role.permission_revoked` (`roleDeleted: true`), commit, publish the versions.
278
+ * Returns the bumped tenants.
279
+ *
280
+ * The CALLER (`RoleService.deleteRole`) holds the role row lock on its own engine connection and
281
+ * has already run the binding rule and user-holder dominance; this method therefore does NOT
282
+ * lock the role row (that would wait on the caller). It commits BEFORE the caller deletes the
283
+ * role, so a failure in between leaves a role with no grants and no agents: fewer permissions,
284
+ * never live grants for a deleted role (fail closed).
285
+ */
286
+ async purgeRole(roleUuid, actor) {
145
287
  const conn = await this.pool.getConnection();
146
288
  try {
147
289
  await conn.beginTransaction();
148
- await conn.query(`DELETE FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?`, [roleUuid, key]);
149
- await this.bumper.bumpForRole(roleUuid, conn);
290
+ const accounts = (await conn.query(`SELECT sa_id, sa_tenant_id, sa_role_ids, sa_updated_at_ms
291
+ FROM \`${APP}\`.\`service_account\`
292
+ WHERE JSON_CONTAINS(sa_role_ids, JSON_QUOTE(?))
293
+ FOR UPDATE`, [roleUuid]));
294
+ if (this.ceiling && actor.isRoot !== true) {
295
+ await this.ceiling.assertMayStripRoleFromServiceAccounts(actor, roleUuid, accounts.map((a) => a.sa_tenant_id));
296
+ }
297
+ const keys = (await conn.query(`SELECT rp_perm_key FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid = ? ORDER BY rp_perm_key`, [roleUuid])).map((r) => r.rp_perm_key);
298
+ const bumped = await this.bumper.bumpForRole(roleUuid, conn);
299
+ await conn.query(`DELETE FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid = ?`, [
300
+ roleUuid,
301
+ ]);
302
+ const nowMs = Date.now();
303
+ for (const a of accounts) {
304
+ const remaining = jsonIdArray(a.sa_role_ids).filter((id) => id !== roleUuid);
305
+ const previous = a.sa_updated_at_ms === null ? 0 : Number(a.sa_updated_at_ms);
306
+ await conn.query(`UPDATE \`${APP}\`.\`service_account\` SET sa_role_ids = ?, sa_updated_at_ms = ? WHERE sa_id = ?`, [JSON.stringify(remaining), Math.max(nowMs, previous + 1), a.sa_id]);
307
+ }
308
+ await this.auditGrantChange(conn, 'role.permission_revoked', roleUuid, keys, actor, bumped, {
309
+ roleDeleted: true,
310
+ serviceAccountsUpdated: accounts.length,
311
+ });
150
312
  await conn.commit();
313
+ await this.bumper.publishCommitted(conn);
314
+ return bumped;
151
315
  }
152
316
  catch (cause) {
153
317
  await conn.rollback();
154
- throw new core_1.AuthorizationUnavailableError('revoke failed', cause);
318
+ this.bumper.discardPending(conn);
319
+ if (cause instanceof identity_errors_js_1.IdentityForbiddenError || cause instanceof identity_errors_js_1.IdentityNotFoundError) {
320
+ throw cause;
321
+ }
322
+ throw new core_1.AuthorizationUnavailableError('role grant purge failed', cause);
155
323
  }
156
324
  finally {
157
325
  await conn.release();
@@ -177,57 +345,33 @@ class GrantAdminService {
177
345
  */
178
346
  async grantBundle(roleUuid, bundleKey, grantor) {
179
347
  const keys = await this.resolveBundleKeysOrThrow(bundleKey);
180
- // ADR-0029 ceiling: a non-root grantor must hold EVERY atomic key the bundle expands to,
181
- // else GrantCeilingError (403) — checked before any write (no partial bundle escalation).
182
- await this.ceiling?.assertCanGrant(grantor, [...keys]);
348
+ // Binding rule + ADR-0029 ceiling: a non-root grantor must hold EVERY atomic key the bundle
349
+ // expands to — checked before any write (no partial bundle escalation).
350
+ await this.assertMayChangeGrants(roleUuid, [...keys], grantor);
183
351
  const actorUuid = grantor.id;
184
- const conn = await this.pool.getConnection();
185
- try {
186
- await conn.beginTransaction();
187
- for (const key of keys) {
188
- // Idempotent — UNIQUE (rp_role_uuid, rp_perm_key); a duplicate is a no-op.
189
- await conn.query(`INSERT INTO \`${AUTHZ}\`.\`role_permission\`
352
+ await this.writeGrants('role.bundle_granted', roleUuid, keys, grantor, async (conn, key) => {
353
+ // Idempotent — UNIQUE (rp_role_uuid, rp_perm_key); a duplicate is a no-op.
354
+ await conn.query(`INSERT INTO \`${AUTHZ}\`.\`role_permission\`
190
355
  (rp_uuid, rp_role_uuid, rp_perm_key, rp_granted_at, rp_granted_by)
191
356
  SELECT UUID(), ?, ?, UTC_TIMESTAMP(), ?
192
357
  WHERE NOT EXISTS (
193
358
  SELECT 1 FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?
194
359
  )`, [roleUuid, key, actorUuid ?? null, roleUuid, key]);
195
- }
196
- await this.bumper.bumpForRole(roleUuid, conn);
197
- await conn.commit();
198
- }
199
- catch (cause) {
200
- await conn.rollback();
201
- throw new core_1.AuthorizationUnavailableError('grant bundle failed', cause);
202
- }
203
- finally {
204
- await conn.release();
205
- }
360
+ }, { bundle: bundleKey });
206
361
  }
207
362
  /**
208
363
  * Revoke a bundle from a role: resolve → DELETE each of the bundle's atomic keys, in
209
- * ONE transaction with ONE version bump. Caveat (design §5.1): this removes exactly the
364
+ * ONE transaction with ONE version bump + audit. Caveat (design §5.1): this removes exactly the
210
365
  * bundle's keys even if a manual grant or another bundle shares one — the model is
211
- * key-based, not bundle-membership-based. Unknown bundle → `BadKeyError`.
366
+ * key-based, not bundle-membership-based. Unknown bundle → `BadKeyError`. Security hardening
367
+ * #1: the binding rule + "only revoke what you hold" apply (root bypasses).
212
368
  */
213
- async revokeBundle(roleUuid, bundleKey) {
369
+ async revokeBundle(roleUuid, bundleKey, actor) {
214
370
  const keys = await this.resolveBundleKeysOrThrow(bundleKey);
215
- const conn = await this.pool.getConnection();
216
- try {
217
- await conn.beginTransaction();
218
- for (const key of keys) {
219
- await conn.query(`DELETE FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?`, [roleUuid, key]);
220
- }
221
- await this.bumper.bumpForRole(roleUuid, conn);
222
- await conn.commit();
223
- }
224
- catch (cause) {
225
- await conn.rollback();
226
- throw new core_1.AuthorizationUnavailableError('revoke bundle failed', cause);
227
- }
228
- finally {
229
- await conn.release();
230
- }
371
+ await this.assertMayChangeGrants(roleUuid, [...keys], actor);
372
+ await this.writeGrants('role.bundle_revoked', roleUuid, keys, actor, async (conn, key) => {
373
+ await conn.query(`DELETE FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?`, [roleUuid, key]);
374
+ }, { bundle: bundleKey });
231
375
  }
232
376
  /** Resolve a bundle key → its atomic keys; throw BadKeyError when unknown/orphaned. */
233
377
  async resolveBundleKeysOrThrow(bundleKey) {
@@ -270,7 +414,9 @@ class GrantAdminService {
270
414
  // subtree (the EXISTS predicate); root spans all tenants (no predicate). The scope clause
271
415
  // sits FIRST in the WHERE (before the LIKE), so its ?-bound node params lead the arg list —
272
416
  // identical on BOTH the count and page queries so they can never disagree.
273
- const scope = subtreeScope.isRoot ? { sql: '', params: [] } : subtreeExists(subtreeScope.nodeIds);
417
+ const scope = subtreeScope.isRoot
418
+ ? { sql: '', params: [] }
419
+ : (0, role_binding_js_1.subtreeExists)(subtreeScope.nodeIds);
274
420
  // Distinct granted roles (a role appears iff it has ≥1 role_permission row), filtered
275
421
  // by name search. Bind the LIKE term as a param (wildcards escaped).
276
422
  const likeClause = search ? `AND r.r_name LIKE ?` : '';
@@ -14,10 +14,13 @@
14
14
  */
15
15
  import type { Pool } from 'mariadb';
16
16
  import type { ModuleRegistry, Principal } from '@astram/core';
17
+ import { type RoleBinding } from './role-binding.js';
17
18
  /** The grantor exceeds their own authority — cannot grant a key they do not hold (→ 403). */
18
19
  export declare class GrantCeilingError extends Error {
19
- constructor(key: string);
20
+ constructor(key: string, message?: string);
20
21
  }
22
+ /** The actor's reachable subtree: the session's cached closure, else the self node (non-widening). */
23
+ export declare function subtreeOf(actor: Principal): readonly string[];
21
24
  /**
22
25
  * Does `effective` (the grantor's held EXACT keys) cover `granted`?
23
26
  *
@@ -40,9 +43,27 @@ export declare class GrantCeiling {
40
43
  private readonly registry;
41
44
  /** Injectable clock (epoch-ms) for the posting effective-window read; defaults to Date.now. */
42
45
  private readonly now;
46
+ private readonly rootIds;
43
47
  constructor(pool: Pool, registry: ModuleRegistry,
48
+ /**
49
+ * The platform root allowlist (the SAME `rootIds` given to `ShieldModule.forRoot`) — REQUIRED,
50
+ * no default (security review F5): pass the array, or `null` to state explicitly that it is
51
+ * not configured. A root user is never dominated by a non-root actor; independently of this
52
+ * list, a user flagged `u_is_platform_superuser` in the database is never dominated either.
53
+ */
54
+ rootIds: readonly string[] | null,
44
55
  /** Injectable clock (epoch-ms) for the posting effective-window read; defaults to Date.now. */
45
56
  now?: () => number);
57
+ /** The D1 administer rule over a binding read: used inside and nowhere outside (404 / 403). */
58
+ static checkAdministrable(b: RoleBinding): void;
59
+ /** The #11 assign rule over a binding read: never a role also used outside (403). */
60
+ static checkAssignable(b: RoleBinding): void;
61
+ /** The registry's exact-key universe (wildcard expansion bound). */
62
+ private registeredKeys;
63
+ /** The raw `role_permission` keys of `roleIds` (exact or wildcard). Store errors propagate. */
64
+ private rawKeysOfRoles;
65
+ /** Run a store read, mapping any non-domain failure to AuthorizationUnavailableError (503). */
66
+ private facts;
46
67
  /**
47
68
  * The grantor's EFFECTIVE exact keys: the keys granted to the grantor's roles (active
48
69
  * membership's in-effect postings in the grantor's tenant), wildcards expanded against the
@@ -56,5 +77,83 @@ export declare class GrantCeiling {
56
77
  * `GrantCeilingError` on the FIRST key that exceeds the grantor's authority.
57
78
  */
58
79
  assertCanGrant(grantor: Principal, keys: readonly string[]): Promise<void>;
80
+ /**
81
+ * Assert the actor may ASSIGN every role in `roleIds` (post someone to it): the roles' effective
82
+ * keys (their `role_permission` rows, wildcards expanded against the registry) must all be held
83
+ * by the actor — the same "only grant what you hold" ceiling, applied to role assignment (#11).
84
+ * A role holding nothing assigns nothing and passes; a role key the registry does not know
85
+ * counts as not held (Phase 8 #4). Root bypasses. Store outage → 503.
86
+ */
87
+ assertCanAssignRoles(actor: Principal, roleIds: readonly string[]): Promise<void>;
88
+ /**
89
+ * The first of a TARGET's raw keys (a role's `role_permission` rows) the actor does not cover,
90
+ * or `undefined` when all are covered. Phase 8 #4: judged per RAW key with `coversKey`, so a key
91
+ * the registry does not know — an exact key that is not registered, or a wildcard expanding to
92
+ * nothing — counts as NOT held and denies. Expanding first would silently drop it and could
93
+ * pass vacuously (fail open) when this registry is narrower than the decision path's.
94
+ */
95
+ private firstUncovered;
96
+ /**
97
+ * Assert the actor DOMINATES the target user (#3, D2): every membership the target holds (in any
98
+ * tenant, active or not) is inside the actor's subtree, AND every key the target could hold
99
+ * through any of its postings (any membership, any window — potential authority, not just what is
100
+ * in effect now) is held by the actor. A platform root target is never dominated by a non-root.
101
+ * Root actors bypass. Store outage → 503.
102
+ */
103
+ assertDominates(actor: Principal, targetUserId: string): Promise<void>;
104
+ /**
105
+ * Assert the actor DOMINATES every user who holds the role (Phase 8 #2): deleting a role, or
106
+ * revoking a key from it, strips authority from each holder, so it needs the same
107
+ * {@link assertDominates} the actor would need to change any one of them directly. Holders are
108
+ * the users with a posting of the role in any membership and any window (potential authority).
109
+ * The actor itself is skipped: giving up your own authority stays allowed (self-demotion).
110
+ * Service-account holders: a revoke is covered by the binding rule (`assertRoleAdministrable`:
111
+ * no binding outside the actor's subtree) plus "only revoke what you hold"; a role DELETE also
112
+ * strips the role from each account and checks {@link assertMayStripRoleFromServiceAccounts}.
113
+ * Root bypasses. Store outage → 503.
114
+ *
115
+ * Callers serialise against a concurrent posting by locking the role row first (the posting
116
+ * path locks the same row before it inserts), then call this — so no holder appears unseen.
117
+ */
118
+ assertDominatesRoleHolders(actor: Principal, roleUuid: string): Promise<void>;
119
+ /**
120
+ * Assert the actor may REMOVE `roleUuid` from every service account holding it (Phase 8 #2
121
+ * follow-up: a role delete strips the role from each holding account). Judged by the SAME rules
122
+ * as editing the account directly (`ServiceAccountService.update`, ADR-0029 ceiling via
123
+ * {@link ceilingOf}): each account's tenant is inside the actor's subtree, and the role is one
124
+ * the actor holds itself — a non-root admin's edit preserves (cannot strip) a role it does not
125
+ * hold. `accountTenantIds` are the holding accounts' tenants, read by the caller under a row lock.
126
+ * Root bypasses. Refusals name no account. Store outage → 503.
127
+ */
128
+ assertMayStripRoleFromServiceAccounts(actor: Principal, roleUuid: string, accountTenantIds: readonly string[]): Promise<void>;
129
+ /**
130
+ * Assert the actor may RETIRE (disable + revoke the keys of) each of these service accounts
131
+ * (Phase 8 #3: a company delete retires the deleted company's own agents). Judged by the SAME
132
+ * ADR-0029 ceiling an admin needs to have made the agent ({@link ceilingOf}): the account's
133
+ * tenant and every node of its scope are inside the actor's subtree, and every role it holds is
134
+ * one the actor holds itself — so a sub-admin can never switch off an agent more powerful than
135
+ * itself by deleting its company. `accounts` are read by the caller under a row lock. Root
136
+ * bypasses. Refusals name no account. Store outage → 503.
137
+ */
138
+ assertMayRetireServiceAccounts(actor: Principal, accounts: ReadonlyArray<{
139
+ readonly tenantId: string;
140
+ readonly scopeSubtreeNodeIds: readonly string[];
141
+ readonly roleIds: readonly string[];
142
+ }>): Promise<void>;
143
+ /** Where `roleUuid` is bound relative to the actor's subtree (postings + service accounts). */
144
+ roleBinding(actor: Principal, roleUuid: string): Promise<RoleBinding>;
145
+ /**
146
+ * Assert the actor may ADMINISTER the role — edit its grants, rename or delete it (#1, D1
147
+ * interim rule): the role is bound inside the actor's subtree AND bound nowhere outside it. A
148
+ * role not bound in the subtree is "not found" for the actor (no existence leak); a role also
149
+ * used outside (a shared role) is root-only. Root bypasses.
150
+ */
151
+ assertRoleAdministrable(actor: Principal, roleUuid: string): Promise<void>;
152
+ /**
153
+ * Assert the actor may POST someone to the role (#11): the role is bound within the actor's
154
+ * subtree or not bound at all — never a role also used outside it (a shared role's assignment
155
+ * would widen another tenant's role). Pair with {@link assertCanAssignRoles}. Root bypasses.
156
+ */
157
+ assertRoleAssignable(actor: Principal, roleUuid: string): Promise<void>;
59
158
  }
60
159
  //# sourceMappingURL=grant-ceiling.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"grant-ceiling.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-ceiling.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAM9D,6FAA6F;AAC7F,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,GAAG,EAAE,MAAM;CAIxB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,EAC9B,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,OAAO,EAAE,MAAM,GACd,OAAO,CAST;AAED;;;GAGG;AACH,qBAAa,YAAY;IAErB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,+FAA+F;IAC/F,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAHH,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,cAAc;IACzC,+FAA+F;IAC9E,GAAG,GAAE,MAAM,MAAiB;IAG/C;;;;;OAKG;IACG,aAAa,CAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAkB7D;;;;OAIG;IACG,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAUjF"}
1
+ {"version":3,"file":"grant-ceiling.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-ceiling.ts"],"names":[],"mappings":"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"}