@astram/identity 0.4.0 → 0.5.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 (172) 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 +49 -16
  22. package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
  23. package/dist/features/authz/grant-admin.service.js +155 -96
  24. package/dist/features/authz/grant-ceiling.d.ts +52 -1
  25. package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
  26. package/dist/features/authz/grant-ceiling.js +156 -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 +45 -1
  30. package/dist/features/authz/permission-version.bumper.d.ts.map +1 -1
  31. package/dist/features/authz/permission-version.bumper.js +106 -16
  32. package/dist/features/authz/record-authorization.d.ts.map +1 -1
  33. package/dist/features/authz/role-binding.d.ts +36 -0
  34. package/dist/features/authz/role-binding.d.ts.map +1 -0
  35. package/dist/features/authz/role-binding.js +53 -0
  36. package/dist/features/company/company-hierarchy.service.d.ts +46 -0
  37. package/dist/features/company/company-hierarchy.service.d.ts.map +1 -0
  38. package/dist/features/company/company-hierarchy.service.js +100 -0
  39. package/dist/features/company/company.controller.d.ts +23 -5
  40. package/dist/features/company/company.controller.d.ts.map +1 -1
  41. package/dist/features/company/company.controller.js +47 -12
  42. package/dist/features/company/company.service.d.ts +28 -12
  43. package/dist/features/company/company.service.d.ts.map +1 -1
  44. package/dist/features/company/company.service.js +61 -14
  45. package/dist/features/cross-tenant/cross-tenant-grant.controller.d.ts.map +1 -1
  46. package/dist/features/cross-tenant/cross-tenant-grant.controller.js +2 -0
  47. package/dist/features/identity-feature.module.d.ts.map +1 -1
  48. package/dist/features/identity-feature.module.js +60 -3
  49. package/dist/features/membership/membership.controller.d.ts +6 -5
  50. package/dist/features/membership/membership.controller.d.ts.map +1 -1
  51. package/dist/features/membership/membership.controller.js +34 -22
  52. package/dist/features/membership/membership.service.d.ts +20 -14
  53. package/dist/features/membership/membership.service.d.ts.map +1 -1
  54. package/dist/features/membership/membership.service.js +62 -29
  55. package/dist/features/membership/posting-version-bump.d.ts +5 -0
  56. package/dist/features/membership/posting-version-bump.d.ts.map +1 -1
  57. package/dist/features/membership/posting.service.d.ts +20 -24
  58. package/dist/features/membership/posting.service.d.ts.map +1 -1
  59. package/dist/features/membership/posting.service.js +101 -64
  60. package/dist/features/org-structure/org-catalog.controller.d.ts.map +1 -1
  61. package/dist/features/org-structure/org-catalog.controller.js +2 -0
  62. package/dist/features/org-structure/org-catalog.service.d.ts.map +1 -1
  63. package/dist/features/org-structure/org-catalog.service.js +4 -1
  64. package/dist/features/policy/policy.controller.d.ts.map +1 -1
  65. package/dist/features/policy/policy.controller.js +2 -0
  66. package/dist/features/role/role.controller.d.ts +3 -2
  67. package/dist/features/role/role.controller.d.ts.map +1 -1
  68. package/dist/features/role/role.controller.js +15 -10
  69. package/dist/features/role/role.service.d.ts +16 -8
  70. package/dist/features/role/role.service.d.ts.map +1 -1
  71. package/dist/features/role/role.service.js +24 -10
  72. package/dist/features/scope/scope-closure.service.d.ts +44 -4
  73. package/dist/features/scope/scope-closure.service.d.ts.map +1 -1
  74. package/dist/features/scope/scope-closure.service.js +215 -82
  75. package/dist/features/service-account/service-account.controller.d.ts +3 -2
  76. package/dist/features/service-account/service-account.controller.d.ts.map +1 -1
  77. package/dist/features/service-account/service-account.controller.js +29 -15
  78. package/dist/features/service-account/service-account.service.d.ts +14 -7
  79. package/dist/features/service-account/service-account.service.d.ts.map +1 -1
  80. package/dist/features/service-account/service-account.service.js +56 -11
  81. package/dist/features/share/share.controller.d.ts.map +1 -1
  82. package/dist/features/share/share.controller.js +2 -0
  83. package/dist/features/share/share.service.d.ts.map +1 -1
  84. package/dist/features/shared/identity-error.interceptor.d.ts +24 -0
  85. package/dist/features/shared/identity-error.interceptor.d.ts.map +1 -0
  86. package/dist/features/shared/identity-error.interceptor.js +82 -0
  87. package/dist/features/shared/identity-errors.d.ts +23 -0
  88. package/dist/features/shared/identity-errors.d.ts.map +1 -0
  89. package/dist/features/shared/identity-errors.js +41 -0
  90. package/dist/features/user/session-revocation-check.d.ts +11 -0
  91. package/dist/features/user/session-revocation-check.d.ts.map +1 -0
  92. package/dist/features/user/session-revocation-check.js +21 -0
  93. package/dist/features/user/user.controller.d.ts +11 -6
  94. package/dist/features/user/user.controller.d.ts.map +1 -1
  95. package/dist/features/user/user.controller.js +35 -17
  96. package/dist/features/user/user.service.d.ts +55 -14
  97. package/dist/features/user/user.service.d.ts.map +1 -1
  98. package/dist/features/user/user.service.js +143 -16
  99. package/dist/membership-posting.object.d.ts.map +1 -1
  100. package/dist/membership-posting.object.js +4 -2
  101. package/dist/objects.d.ts.map +1 -1
  102. package/dist/objects.js +13 -3
  103. package/dist/role.object.d.ts.map +1 -1
  104. package/dist/role.object.js +3 -0
  105. package/dist/user.object.d.ts.map +1 -1
  106. package/dist/user.object.js +3 -0
  107. package/migrations/1710000000019-AddSessionMustReenroll.cjs +35 -0
  108. package/package.json +4 -4
  109. package/dist/auth-facts-keys.js.map +0 -1
  110. package/dist/branch.object.js.map +0 -1
  111. package/dist/company-branch.object.js.map +0 -1
  112. package/dist/company-department.object.js.map +0 -1
  113. package/dist/company-membership.object.js.map +0 -1
  114. package/dist/company.object.js.map +0 -1
  115. package/dist/cross-tenant-grant.object.js.map +0 -1
  116. package/dist/department.object.js.map +0 -1
  117. package/dist/features/access/access-admin.access.js.map +0 -1
  118. package/dist/features/access/company-admin.access.js.map +0 -1
  119. package/dist/features/access/index.js.map +0 -1
  120. package/dist/features/access/user-management.access.js.map +0 -1
  121. package/dist/features/audit/audit-read.service.js.map +0 -1
  122. package/dist/features/audit/audit.controller.js.map +0 -1
  123. package/dist/features/audit/directory-label-resolver.js.map +0 -1
  124. package/dist/features/audit/mariadb-audit-reader.js.map +0 -1
  125. package/dist/features/authz/access-bundle-reconcile.job.js.map +0 -1
  126. package/dist/features/authz/access-bundle-store.js.map +0 -1
  127. package/dist/features/authz/catalog-bootstrap.provider.js.map +0 -1
  128. package/dist/features/authz/catalog-reconcile.job.js.map +0 -1
  129. package/dist/features/authz/db-permission-facts.adapter.js.map +0 -1
  130. package/dist/features/authz/grant-admin.controller.js.map +0 -1
  131. package/dist/features/authz/grant-admin.service.js.map +0 -1
  132. package/dist/features/authz/grant-ceiling.js.map +0 -1
  133. package/dist/features/authz/mariadb-catalog-store.js.map +0 -1
  134. package/dist/features/authz/permission-version.bumper.js.map +0 -1
  135. package/dist/features/authz/record-authorization.js.map +0 -1
  136. package/dist/features/authz/session-facts.reader.js.map +0 -1
  137. package/dist/features/company/company.controller.js.map +0 -1
  138. package/dist/features/company/company.service.js.map +0 -1
  139. package/dist/features/cross-tenant/cross-tenant-grant-read.service.js.map +0 -1
  140. package/dist/features/cross-tenant/cross-tenant-grant.controller.js.map +0 -1
  141. package/dist/features/cross-tenant/grant-authorize.js.map +0 -1
  142. package/dist/features/identity-feature.module.js.map +0 -1
  143. package/dist/features/membership/membership-checker.js.map +0 -1
  144. package/dist/features/membership/membership.controller.js.map +0 -1
  145. package/dist/features/membership/membership.service.js.map +0 -1
  146. package/dist/features/membership/posting-version-bump.js.map +0 -1
  147. package/dist/features/membership/posting.service.js.map +0 -1
  148. package/dist/features/org-structure/org-catalog.controller.js.map +0 -1
  149. package/dist/features/org-structure/org-catalog.service.js.map +0 -1
  150. package/dist/features/policy/policy.controller.js.map +0 -1
  151. package/dist/features/policy/policy.service.js.map +0 -1
  152. package/dist/features/role/role.controller.js.map +0 -1
  153. package/dist/features/role/role.service.js.map +0 -1
  154. package/dist/features/scope/scope-closure.service.js.map +0 -1
  155. package/dist/features/scope/scope-subtree.resolver.js.map +0 -1
  156. package/dist/features/share/share.controller.js.map +0 -1
  157. package/dist/features/share/share.service.js.map +0 -1
  158. package/dist/features/shared/http.js.map +0 -1
  159. package/dist/features/shared/list-query.js.map +0 -1
  160. package/dist/features/shared/named-option.js.map +0 -1
  161. package/dist/features/shared/record-resolver.js.map +0 -1
  162. package/dist/features/user/user.controller.js.map +0 -1
  163. package/dist/features/user/user.service.js.map +0 -1
  164. package/dist/index.js.map +0 -1
  165. package/dist/membership-posting.object.js.map +0 -1
  166. package/dist/migrations-dir.js.map +0 -1
  167. package/dist/objects.js.map +0 -1
  168. package/dist/record-share.object.js.map +0 -1
  169. package/dist/role.object.js.map +0 -1
  170. package/dist/schema.js.map +0 -1
  171. package/dist/shared-resource.object.js.map +0 -1
  172. package/dist/user.object.js.map +0 -1
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NodeHasChildrenError = exports.ScopeClosureService = void 0;
4
+ exports.quarantineCycles = quarantineCycles;
4
5
  // astram-framework/packages/identity/src/features/scope/scope-closure.service.ts
5
6
  /**
6
7
  * `ScopeClosureService` — the LIVE, I/O half of Task 1.2 (Phase 1 of the scoped-hierarchy
@@ -20,6 +21,12 @@ exports.NodeHasChildrenError = exports.ScopeClosureService = void 0;
20
21
  * the moved subtree so active sessions re-derive their (now-changed) subtree WITHOUT re-login
21
22
  * (ADR-0011). A failed rebuild rolls the whole move back — atomic.
22
23
  *
24
+ * • `attachNode(node)` — a node was just created: rebuild + bump its ancestors (security
25
+ * hardening D5), so the creator's subtree includes it without waiting for the hourly reconcile.
26
+ *
27
+ * Callers: `CompanyHierarchyService` (move / delete / attach, with an in-transaction audit hook)
28
+ * and the boot/hourly reconcile. A generic company patch can no longer move a node (ADR-0034).
29
+ *
23
30
  * • `deleteNode(node)` — remove a node (E-2), FAIL-CLOSED: it DENIES (throws) if the node has
24
31
  * children, never letting the DB's old `ON DELETE SET NULL` silently promote a sub-org to a
25
32
  * root (a hierarchy-boundary violation). The `...016` migration also flips the FK to
@@ -31,11 +38,16 @@ exports.NodeHasChildrenError = exports.ScopeClosureService = void 0;
31
38
  * invalidation. All SQL values are `?`-bound (never interpolated). `companies` lives in
32
39
  * `shield_app`; `scope_closure` lives in `shield_authz` — the shared authz pool reaches both.
33
40
  */
41
+ const core_1 = require("@astram/core");
34
42
  const guard_1 = require("@astram/guard");
35
43
  /** Schema the node tree (`companies`) lives in. */
36
44
  const APP = 'shield_app';
37
45
  /** Schema the closure table lives in (ADR-0011 / Task 1.1). */
38
46
  const AUTHZ = 'shield_authz';
47
+ /** The named lock every hierarchy writer holds (security review F3). */
48
+ const TREE_LOCK = 'shield_scope_closure';
49
+ /** Seconds to wait for it before failing closed. */
50
+ const TREE_LOCK_TIMEOUT_S = 10;
39
51
  class ScopeClosureService {
40
52
  pool;
41
53
  bumper;
@@ -46,27 +58,99 @@ class ScopeClosureService {
46
58
  /**
47
59
  * Rebuild `scope_closure` from the current `companies.c_parent_uuid` edges. Idempotent
48
60
  * (delete-then-insert in a tx → a second run over the same tree writes the identical set).
49
- * FAIL-LOUD: an orphaned/cyclic/too-deep edge makes `buildClosureRows` throw and that
50
- * propagates (the tx rolls back — never a partial closure). Returns a small summary + logs.
61
+ * A CYCLIC component is quarantined (no rows for it; logged loudly) so the rest of the forest
62
+ * still reconciles. FAIL-LOUD for the rest: an orphaned/too-deep edge makes `buildClosureRows`
63
+ * throw and that propagates (the tx rolls back — never a partial closure). Returns a summary.
51
64
  */
52
65
  async reconcileClosure() {
53
- const edges = await this.readEdges(this.pool);
54
- const rows = (0, guard_1.buildClosureRows)(edges); // throws on cycle/depth/orphan — propagate (fail-loud).
66
+ // Security hardening #2: a cyclic component (and everything hanging under it) is QUARANTINED —
67
+ // left out of the closure, so its nodes get no rows (their admins fall back to the self node,
68
+ // non-widening) — instead of one corrupt component aborting the rebuild for EVERY tenant.
69
+ // Other corruption (orphan / too deep) still throws and propagates (fail-loud).
70
+ const { clean, quarantined, rows } = await this.inTreeLock(async (conn) => {
71
+ const split = quarantineCycles(await this.readEdges(conn));
72
+ const built = (0, guard_1.buildClosureRows)(split.clean);
73
+ await this.rewriteClosure(conn, built);
74
+ return { ...split, rows: built };
75
+ });
76
+ if (quarantined.length > 0) {
77
+ console.error(`[scope] closure: QUARANTINED ${quarantined.length} node(s) in or under a parent cycle — ` +
78
+ `they have no closure rows until the cycle is repaired`);
79
+ }
80
+ console.log(`[scope] closure: ${clean.length} nodes, ${rows.length} rows`);
81
+ return { nodes: clean.length, rows: rows.length, quarantined: quarantined.length };
82
+ }
83
+ /**
84
+ * A node was just CREATED (security hardening D5): rebuild the closure so it has rows at once
85
+ * (not at the next hourly reconcile), and bump every ANCESTOR's permission version — their
86
+ * reachable subtree just grew — so their sessions pick the new node up at the next refresh.
87
+ * One transaction; fail-loud like `moveNode` (a corrupt tree aborts the rebuild). A node inside
88
+ * a quarantined cycle gets no rows and bumps nothing.
89
+ */
90
+ async attachNode(nodeUuid) {
91
+ await this.inTreeLock(async (conn) => {
92
+ const { clean } = quarantineCycles(await this.readEdges(conn));
93
+ const rows = (0, guard_1.buildClosureRows)(clean);
94
+ await this.rewriteClosure(conn, rows);
95
+ const ancestors = rows
96
+ .filter((r) => r.descendant === nodeUuid && r.ancestor !== nodeUuid)
97
+ .map((r) => r.ancestor);
98
+ for (const tenantUuid of new Set(ancestors)) {
99
+ await this.bumper.bumpForTenant(tenantUuid, conn);
100
+ }
101
+ });
102
+ }
103
+ /**
104
+ * Run `fn` in ONE transaction while holding the tree's named lock (security review F3). Every
105
+ * hierarchy writer (move, delete, attach, reconcile) takes the SAME `GET_LOCK` on its own
106
+ * connection BEFORE `BEGIN`, so they run one at a time and each transaction's first read sees
107
+ * every move committed before it. Without it, two concurrent moves each pass the cycle check
108
+ * against the other's pre-move tree and together form a cycle. A lock that cannot be taken in
109
+ * time fails closed (503, nothing written). The lock is released after commit/rollback.
110
+ */
111
+ async inTreeLock(fn) {
55
112
  const conn = await this.pool.getConnection();
113
+ let locked = false;
56
114
  try {
115
+ const got = (await conn.query('SELECT GET_LOCK(?, ?) AS got', [
116
+ TREE_LOCK,
117
+ TREE_LOCK_TIMEOUT_S,
118
+ ]));
119
+ if (!Array.isArray(got) || Number(got[0]?.got) !== 1) {
120
+ throw new core_1.AuthorizationUnavailableError('scope hierarchy is busy; try again');
121
+ }
122
+ locked = true;
57
123
  await conn.beginTransaction();
58
- await this.rewriteClosure(conn, rows);
59
- await conn.commit();
60
- }
61
- catch (cause) {
62
- await conn.rollback();
63
- throw cause;
124
+ let out;
125
+ try {
126
+ out = await fn(conn);
127
+ await conn.commit();
128
+ }
129
+ catch (cause) {
130
+ await conn.rollback();
131
+ this.bumper.discardPending?.(conn);
132
+ throw cause;
133
+ }
134
+ // ADR-0035: the committed version bumps reach Redis (best-effort; never fails the write).
135
+ await this.bumper.publishCommitted?.(conn);
136
+ return out;
64
137
  }
65
138
  finally {
66
- await conn.release();
139
+ let releasedLock = !locked;
140
+ if (locked) {
141
+ try {
142
+ await conn.query('SELECT RELEASE_LOCK(?)', [TREE_LOCK]);
143
+ releasedLock = true;
144
+ }
145
+ catch {
146
+ // handled below: a pooled session that may still hold the lock is destroyed, not reused
147
+ }
148
+ }
149
+ if (!releasedLock && typeof conn.destroy === 'function')
150
+ conn.destroy();
151
+ else
152
+ await conn.release();
67
153
  }
68
- console.log(`[scope] closure: ${edges.length} nodes, ${rows.length} rows`);
69
- return { nodes: edges.length, rows: rows.length };
70
154
  }
71
155
  /**
72
156
  * Re-parent `nodeUuid` under `newParentUuid` (or null for a root). Cycle-checked FIRST via the
@@ -85,62 +169,74 @@ class ScopeClosureService {
85
169
  * HIGH stale-subtree-after-move leak: a parent admin kept reaching a moved-away tenant.
86
170
  * Nodes on neither side keep their exact set and are not bumped.
87
171
  */
88
- async moveNode(nodeUuid, newParentUuid) {
172
+ async moveNode(nodeUuid, newParentUuid,
173
+ /** Runs on the move's transaction after the bump (e.g. the audit row) — a throw rolls back. */
174
+ inTransaction) {
89
175
  if (newParentUuid === nodeUuid) {
90
176
  // A node cannot be its own parent (a trivial 1-cycle).
91
177
  throw new guard_1.ScopeCycleError(nodeUuid);
92
178
  }
93
- if (newParentUuid !== null) {
94
- // Cycle guard: the new parent must NOT be at/under the moved node. A closure hit means
95
- // newParent is a descendant of node → re-parenting node under it would form a cycle.
96
- const cyclic = (await this.pool.query(`SELECT 1 FROM \`${AUTHZ}\`.\`scope_closure\`
179
+ // Review F3: every read below runs INSIDE the tree lock + transaction, so a concurrent move
180
+ // that committed first is visible to the cycle check (two moves can no longer each pass the
181
+ // check against the other's pre-move tree).
182
+ await this.inTreeLock(async (conn) => {
183
+ if (newParentUuid !== null) {
184
+ // Cycle guard: the new parent must NOT be at/under the moved node. A closure hit means
185
+ // newParent is a descendant of node → re-parenting node under it would form a cycle.
186
+ const cyclic = (await conn.query(`SELECT 1 FROM \`${AUTHZ}\`.\`scope_closure\`
97
187
  WHERE ancestor_uuid = ? AND descendant_uuid = ? LIMIT 1`, [nodeUuid, newParentUuid]));
98
- if (cyclic.length > 0) {
99
- throw new guard_1.ScopeCycleError(nodeUuid);
188
+ if (cyclic.length > 0) {
189
+ throw new guard_1.ScopeCycleError(nodeUuid);
190
+ }
100
191
  }
101
- }
102
- // The moved subtree — the nodes whose ancestry changes (node + all descendants). Read
103
- // BEFORE the update (the move doesn't change the subtree's internal membership).
104
- const subtreeRows = (await this.pool.query(`SELECT descendant_uuid FROM \`${AUTHZ}\`.\`scope_closure\` WHERE ancestor_uuid = ?`, [nodeUuid]));
105
- // The FORMER-PARENT ANCESTRY — the nodes ABOVE `node` in the OLD tree (its ancestors,
106
- // excluding self). They just LOST `node`'s subtree from their reach, so their admins' cached
107
- // session subtrees are now stale (they'd otherwise keep reaching the moved-away tenant — the
108
- // HIGH stale-subtree-after-move leak). Read BEFORE the update (the pre-move closure has the
109
- // old ancestry). Bump these too so those sessions re-derive their (now-smaller) subtree at
110
- // their next refresh (ADR-0011 bound — no forced re-login).
111
- const formerAncestryRows = (await this.pool.query(`SELECT ancestor_uuid FROM \`${AUTHZ}\`.\`scope_closure\`
192
+ // The moved subtree — the nodes whose ancestry changes (node + all descendants). Read
193
+ // BEFORE the update (the move doesn't change the subtree's internal membership).
194
+ const subtreeRows = (await conn.query(`SELECT descendant_uuid FROM \`${AUTHZ}\`.\`scope_closure\` WHERE ancestor_uuid = ?`, [nodeUuid]));
195
+ // The FORMER-PARENT ANCESTRY — the nodes ABOVE `node` in the OLD tree (its ancestors,
196
+ // excluding self). They just LOST `node`'s subtree from their reach, so their admins' cached
197
+ // session subtrees are now stale (they'd otherwise keep reaching the moved-away tenant — the
198
+ // HIGH stale-subtree-after-move leak). Read BEFORE the update (the pre-move closure has the
199
+ // old ancestry). Bump these too so those sessions re-derive their (now-smaller) subtree at
200
+ // their next refresh (ADR-0011 bound — no forced re-login).
201
+ const formerAncestryRows = (await conn.query(`SELECT ancestor_uuid FROM \`${AUTHZ}\`.\`scope_closure\`
112
202
  WHERE descendant_uuid = ? AND ancestor_uuid <> ?`, [nodeUuid, nodeUuid]));
113
- // Fall back to the node itself if the closure has no self-row yet (pre-first-reconcile).
114
- const movedSubtree = subtreeRows.length > 0 ? subtreeRows.map((r) => r.descendant_uuid) : [nodeUuid];
115
- // The full bump set: the moved subtree (their ancestry grew/changed) UNION the former-parent
116
- // ancestry (they lost the moved subtree). Deduped.
117
- const affected = [
118
- ...new Set([...movedSubtree, ...formerAncestryRows.map((r) => r.ancestor_uuid)]),
119
- ];
120
- const conn = await this.pool.getConnection();
121
- try {
122
- await conn.beginTransaction();
123
- // 1) Re-parent the edge.
124
- await conn.query(`UPDATE \`${APP}\`.\`companies\` SET c_parent_uuid = ? WHERE c_uuid = ?`, [newParentUuid, nodeUuid]);
125
- // 2) Rebuild the whole closure from the now-updated edges (full rebuild — safe + simple;
126
- // a targeted subtree rebuild is a later optimization). Re-reads edges over the SAME tx
127
- // connection so it sees the UPDATE. Fail-loud propagates → the whole move rolls back.
128
- const edges = await this.readEdges(conn);
129
- const rows = (0, guard_1.buildClosureRows)(edges);
130
- await this.rewriteClosure(conn, rows);
131
- // 3) Session invalidation: bump each moved-subtree tenant so its cached subtree re-derives.
132
- for (const tenantUuid of affected) {
133
- await this.bumper.bumpForTenant(tenantUuid, conn);
203
+ // Fall back to the node itself if the closure has no self-row yet (pre-first-reconcile).
204
+ const movedSubtree = subtreeRows.length > 0 ? subtreeRows.map((r) => r.descendant_uuid) : [nodeUuid];
205
+ // The full bump set: the moved subtree (their ancestry grew/changed) UNION the former-parent
206
+ // ancestry (they lost the moved subtree). Deduped.
207
+ const affected = [
208
+ ...new Set([...movedSubtree, ...formerAncestryRows.map((r) => r.ancestor_uuid)]),
209
+ ];
210
+ {
211
+ // 1) Re-parent the edge.
212
+ await conn.query(`UPDATE \`${APP}\`.\`companies\` SET c_parent_uuid = ? WHERE c_uuid = ?`, [
213
+ newParentUuid,
214
+ nodeUuid,
215
+ ]);
216
+ // 2) Rebuild the whole closure from the now-updated edges (full rebuild — safe + simple;
217
+ // a targeted subtree rebuild is a later optimization). Re-reads edges over the SAME tx
218
+ // connection so it sees the UPDATE. Fail-loud propagates → the whole move rolls back.
219
+ // A pre-existing cycle elsewhere is quarantined (not this move's problem); but if THIS
220
+ // node lands in a cycle (the closure pre-check can miss one through quarantined nodes,
221
+ // which have no closure rows), refuse and roll back.
222
+ const { clean, quarantined } = quarantineCycles(await this.readEdges(conn));
223
+ if (quarantined.includes(nodeUuid))
224
+ throw new guard_1.ScopeCycleError(nodeUuid);
225
+ const rows = (0, guard_1.buildClosureRows)(clean);
226
+ await this.rewriteClosure(conn, rows);
227
+ // 3) Session invalidation: bump each moved-subtree tenant so its cached subtree re-derives.
228
+ // The NEW ancestry gained the subtree too: bump it so its sessions see the move.
229
+ const newAncestry = rows
230
+ .filter((r) => r.descendant === nodeUuid && r.ancestor !== nodeUuid)
231
+ .map((r) => r.ancestor);
232
+ for (const tenantUuid of new Set([...affected, ...newAncestry])) {
233
+ await this.bumper.bumpForTenant(tenantUuid, conn);
234
+ }
235
+ // 4) Caller hook on the SAME transaction (audit) — a throw rolls the whole move back.
236
+ if (inTransaction)
237
+ await inTransaction(conn);
134
238
  }
135
- await conn.commit();
136
- }
137
- catch (cause) {
138
- await conn.rollback();
139
- throw cause;
140
- }
141
- finally {
142
- await conn.release();
143
- }
239
+ });
144
240
  }
145
241
  /**
146
242
  * Delete `nodeUuid`, FAIL-CLOSED. Refuses (throws) if the node has ANY child company — never
@@ -155,18 +251,20 @@ class ScopeClosureService {
155
251
  * the many tenant-scoped app tables that reference the deleted node) is out of scope for v1 and
156
252
  * is tracked as a follow-up; deleteNode v1 only guarantees the HIERARCHY stays consistent.
157
253
  */
158
- async deleteNode(nodeUuid) {
159
- const children = (await this.pool.query(`SELECT 1 FROM \`${APP}\`.\`companies\` WHERE c_parent_uuid = ? LIMIT 1`, [nodeUuid]));
160
- if (children.length > 0) {
161
- throw new NodeHasChildrenError(nodeUuid);
162
- }
163
- // The former-parent ancestry — the deleted node's ancestors (excluding self). They lose the
164
- // node from their reach → bump so their sessions re-derive. Read BEFORE the delete.
165
- const formerAncestryRows = (await this.pool.query(`SELECT ancestor_uuid FROM \`${AUTHZ}\`.\`scope_closure\`
166
- WHERE descendant_uuid = ? AND ancestor_uuid <> ?`, [nodeUuid, nodeUuid]));
167
- const conn = await this.pool.getConnection();
168
- try {
169
- await conn.beginTransaction();
254
+ async deleteNode(nodeUuid,
255
+ /** Runs on the delete's transaction after the bump (e.g. the audit row) — a throw rolls back. */
256
+ inTransaction) {
257
+ // Under the tree lock (review F3): a concurrent move cannot hang a child under this node
258
+ // between the children check and the delete.
259
+ await this.inTreeLock(async (conn) => {
260
+ const children = (await conn.query(`SELECT 1 FROM \`${APP}\`.\`companies\` WHERE c_parent_uuid = ? LIMIT 1`, [nodeUuid]));
261
+ if (children.length > 0) {
262
+ throw new NodeHasChildrenError(nodeUuid);
263
+ }
264
+ // The former-parent ancestry — the deleted node's ancestors (excluding self). They lose the
265
+ // node from their reach → bump so their sessions re-derive. Read BEFORE the delete.
266
+ const formerAncestryRows = (await conn.query(`SELECT ancestor_uuid FROM \`${AUTHZ}\`.\`scope_closure\`
267
+ WHERE descendant_uuid = ? AND ancestor_uuid <> ?`, [nodeUuid, nodeUuid]));
170
268
  // Remove the node's closure rows (self-row + any it appears in as ancestor/descendant).
171
269
  await conn.query(`DELETE FROM \`${AUTHZ}\`.\`scope_closure\`
172
270
  WHERE ancestor_uuid = ? OR descendant_uuid = ?`, [nodeUuid, nodeUuid]);
@@ -176,15 +274,9 @@ class ScopeClosureService {
176
274
  for (const { ancestor_uuid } of formerAncestryRows) {
177
275
  await this.bumper.bumpForTenant(ancestor_uuid, conn);
178
276
  }
179
- await conn.commit();
180
- }
181
- catch (cause) {
182
- await conn.rollback();
183
- throw cause;
184
- }
185
- finally {
186
- await conn.release();
187
- }
277
+ if (inTransaction)
278
+ await inTransaction(conn);
279
+ });
188
280
  }
189
281
  /** Read the node forest's parent edges (`c_uuid` → `c_parent_uuid`) as `ScopeEdge[]`. */
190
282
  async readEdges(q) {
@@ -210,6 +302,47 @@ class ScopeClosureService {
210
302
  }
211
303
  }
212
304
  exports.ScopeClosureService = ScopeClosureService;
305
+ /**
306
+ * Split the edge set into the CLEAN part and the nodes QUARANTINED because their parent chain
307
+ * reaches a cycle (the cycle's members and everything below them). PURE. Orphans and depth are
308
+ * left to `buildClosureRows` (still fail-loud): only a cycle is quarantined, because a cycle is
309
+ * the corruption a tenant admin could once create through the generic company patch (#2).
310
+ */
311
+ function quarantineCycles(edges) {
312
+ const parentOf = new Map();
313
+ for (const e of edges)
314
+ parentOf.set(e.node, e.parent);
315
+ const state = new Map();
316
+ for (const e of edges) {
317
+ if (state.has(e.node))
318
+ continue;
319
+ const path = [];
320
+ const onPath = new Set();
321
+ let current = e.node;
322
+ let verdict = 'ok';
323
+ for (;;) {
324
+ if (current === null || current === undefined || !parentOf.has(current))
325
+ break; // root / orphan
326
+ const known = state.get(current);
327
+ if (known !== undefined) {
328
+ verdict = known;
329
+ break;
330
+ }
331
+ if (onPath.has(current)) {
332
+ verdict = 'bad'; // revisited a node on this walk → a cycle
333
+ break;
334
+ }
335
+ onPath.add(current);
336
+ path.push(current);
337
+ current = parentOf.get(current);
338
+ }
339
+ for (const n of path)
340
+ state.set(n, verdict);
341
+ }
342
+ const quarantined = edges.filter((e) => state.get(e.node) === 'bad').map((e) => e.node);
343
+ const bad = new Set(quarantined);
344
+ return { clean: edges.filter((e) => !bad.has(e.node)), quarantined };
345
+ }
213
346
  /** deleteNode fail-closed guard: the node still has child companies (re-parent/cascade first). */
214
347
  class NodeHasChildrenError extends Error {
215
348
  constructor(nodeUuid) {
@@ -37,7 +37,8 @@ export declare class ServiceAccountController {
37
37
  account: import("./service-account.service.js").ServiceAccountView;
38
38
  }>;
39
39
  /**
40
- * Issue a NEW key for an account. Gated `service_account:manage_keys`. Body: `{ expiresAtMs? }`.
40
+ * Issue a NEW key for an account. Gated `service_account:manage_keys`. Body: `{ expiresAtMs }`
41
+ * (a future epoch-ms) or `{ neverExpires: true }` — one of the two is required (400 otherwise).
41
42
  * Returns the raw `ask_…` key EXACTLY ONCE — copy it now, it is never retrievable again.
42
43
  */
43
44
  issueKey(req: AuthedRequest, id: string, body: unknown): Promise<{
@@ -45,7 +46,7 @@ export declare class ServiceAccountController {
45
46
  }>;
46
47
  /**
47
48
  * Rotate an account's key. Gated `service_account:manage_keys`. The previous key stops
48
- * authenticating; the new raw key is returned EXACTLY ONCE.
49
+ * authenticating; the new raw key is returned EXACTLY ONCE. Same expiry rule as issue.
49
50
  */
50
51
  rotateKey(req: AuthedRequest, id: string, body: unknown): Promise<{
51
52
  key: import("./service-account.service.js").IssuedKey;
@@ -1 +1 @@
1
- {"version":3,"file":"service-account.controller.d.ts","sourceRoot":"","sources":["../../../src/features/service-account/service-account.controller.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EACL,qBAAqB,EAMtB,MAAM,8BAA8B,CAAC;AAEtC,qFAAqF;AACrF,UAAU,aAAa;IACrB,SAAS,EAAE,SAAS,CAAC;CACtB;AA0CD,qBACa,wBAAwB;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,qBAAqB;IAEvD,8FAA8F;IAGxF,IAAI,CAAQ,GAAG,EAAE,aAAa;;;IAQpC;;;;;OAKG;IAGG,UAAU,CAAQ,GAAG,EAAE,aAAa;IAQ1C,8FAA8F;IAGxF,GAAG,CAAQ,GAAG,EAAE,aAAa,EAAe,EAAE,EAAE,MAAM;;;IAQ5D;;;OAGG;IAGG,MAAM,CAAQ,GAAG,EAAE,aAAa,EAAU,IAAI,EAAE,OAAO;;;IAiB7D;;;OAGG;IAGG,MAAM,CAAQ,GAAG,EAAE,aAAa,EAAe,EAAE,EAAE,MAAM,EAAU,IAAI,EAAE,OAAO;;;IAuBtF;;;OAGG;IAGG,QAAQ,CAAQ,GAAG,EAAE,aAAa,EAAe,EAAE,EAAE,MAAM,EAAU,IAAI,EAAE,OAAO;;;IAWxF;;;OAGG;IAGG,SAAS,CAAQ,GAAG,EAAE,aAAa,EAAe,EAAE,EAAE,MAAM,EAAU,IAAI,EAAE,OAAO;;;IAWzF;;;OAGG;IAGG,UAAU,CAAQ,GAAG,EAAE,aAAa,EAAe,EAAE,EAAE,MAAM;;;CAQpE"}
1
+ {"version":3,"file":"service-account.controller.d.ts","sourceRoot":"","sources":["../../../src/features/service-account/service-account.controller.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EACL,qBAAqB,EAOtB,MAAM,8BAA8B,CAAC;AAGtC,qFAAqF;AACrF,UAAU,aAAa;IACrB,SAAS,EAAE,SAAS,CAAC;CACtB;AAwDD,qBAEa,wBAAwB;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,qBAAqB;IAEvD,8FAA8F;IAGxF,IAAI,CAAQ,GAAG,EAAE,aAAa;;;IAQpC;;;;;OAKG;IAGG,UAAU,CAAQ,GAAG,EAAE,aAAa;IAQ1C,8FAA8F;IAGxF,GAAG,CAAQ,GAAG,EAAE,aAAa,EAAe,EAAE,EAAE,MAAM;;;IAQ5D;;;OAGG;IAGG,MAAM,CAAQ,GAAG,EAAE,aAAa,EAAU,IAAI,EAAE,OAAO;;;IAiB7D;;;OAGG;IAGG,MAAM,CAAQ,GAAG,EAAE,aAAa,EAAe,EAAE,EAAE,MAAM,EAAU,IAAI,EAAE,OAAO;;;IAuBtF;;;;OAIG;IAGG,QAAQ,CAAQ,GAAG,EAAE,aAAa,EAAe,EAAE,EAAE,MAAM,EAAU,IAAI,EAAE,OAAO;;;IAUxF;;;OAGG;IAGG,SAAS,CAAQ,GAAG,EAAE,aAAa,EAAe,EAAE,EAAE,MAAM,EAAU,IAAI,EAAE,OAAO;;;IAUzF;;;OAGG;IAGG,UAAU,CAAQ,GAAG,EAAE,aAAa,EAAe,EAAE,EAAE,MAAM;;;CAQpE"}
@@ -29,6 +29,7 @@ exports.ServiceAccountController = void 0;
29
29
  const common_1 = require("@nestjs/common");
30
30
  const guard_1 = require("@astram/guard");
31
31
  const service_account_service_js_1 = require("./service-account.service.js");
32
+ const identity_error_interceptor_js_1 = require("../shared/identity-error.interceptor.js");
32
33
  /** Project the resolved Principal to the AdminPrincipal the service consumes. */
33
34
  function adminOf(req) {
34
35
  const p = req.principal;
@@ -62,14 +63,27 @@ function optStringArray(v, field) {
62
63
  }
63
64
  return v;
64
65
  }
65
- /** Narrow an OPTIONAL positive-integer expiry (epoch-ms) from a key-issue body. */
66
- function optExpiry(v) {
67
- if (v === undefined || v === null)
68
- return undefined;
69
- if (typeof v !== 'number' || !Number.isFinite(v) || v <= 0) {
70
- throw new common_1.BadRequestException('expiresAtMs must be a positive epoch-ms number');
66
+ /**
67
+ * Narrow a key-issue/rotate body to its expiry choice (ADR-0033 §4): `expiresAtMs` (epoch-ms) or
68
+ * `neverExpires: true`. Shape errors are 400 here; "neither", "both" and "not in the future" are
69
+ * decided by the service (it owns the clock) and also answer 400.
70
+ */
71
+ function expiryOf(body) {
72
+ const out = {};
73
+ const v = body.expiresAtMs;
74
+ if (v !== undefined && v !== null) {
75
+ if (typeof v !== 'number' || !Number.isFinite(v) || v <= 0) {
76
+ throw new common_1.BadRequestException('expiresAtMs must be a positive epoch-ms number');
77
+ }
78
+ out.expiresAtMs = v;
71
79
  }
72
- return v;
80
+ if (body.neverExpires !== undefined) {
81
+ if (typeof body.neverExpires !== 'boolean') {
82
+ throw new common_1.BadRequestException('neverExpires must be true or false');
83
+ }
84
+ out.neverExpires = body.neverExpires;
85
+ }
86
+ return out;
73
87
  }
74
88
  let ServiceAccountController = class ServiceAccountController {
75
89
  svc;
@@ -158,14 +172,14 @@ let ServiceAccountController = class ServiceAccountController {
158
172
  }
159
173
  }
160
174
  /**
161
- * Issue a NEW key for an account. Gated `service_account:manage_keys`. Body: `{ expiresAtMs? }`.
175
+ * Issue a NEW key for an account. Gated `service_account:manage_keys`. Body: `{ expiresAtMs }`
176
+ * (a future epoch-ms) or `{ neverExpires: true }` — one of the two is required (400 otherwise).
162
177
  * Returns the raw `ask_…` key EXACTLY ONCE — copy it now, it is never retrievable again.
163
178
  */
164
179
  async issueKey(req, id, body) {
165
- const b = (body ?? {});
166
- const expiresAtMs = optExpiry(b.expiresAtMs);
180
+ const expiry = expiryOf((body ?? {}));
167
181
  try {
168
- const key = await this.svc.issueKey(adminOf(req), id, expiresAtMs !== undefined ? { expiresAtMs } : {});
182
+ const key = await this.svc.issueKey(adminOf(req), id, expiry);
169
183
  return { key };
170
184
  }
171
185
  catch (e) {
@@ -174,13 +188,12 @@ let ServiceAccountController = class ServiceAccountController {
174
188
  }
175
189
  /**
176
190
  * Rotate an account's key. Gated `service_account:manage_keys`. The previous key stops
177
- * authenticating; the new raw key is returned EXACTLY ONCE.
191
+ * authenticating; the new raw key is returned EXACTLY ONCE. Same expiry rule as issue.
178
192
  */
179
193
  async rotateKey(req, id, body) {
180
- const b = (body ?? {});
181
- const expiresAtMs = optExpiry(b.expiresAtMs);
194
+ const expiry = expiryOf((body ?? {}));
182
195
  try {
183
- const key = await this.svc.rotateKey(adminOf(req), id, expiresAtMs !== undefined ? { expiresAtMs } : {});
196
+ const key = await this.svc.rotateKey(adminOf(req), id, expiry);
184
197
  return { key };
185
198
  }
186
199
  catch (e) {
@@ -277,5 +290,6 @@ __decorate([
277
290
  ], ServiceAccountController.prototype, "revokeKeys", null);
278
291
  exports.ServiceAccountController = ServiceAccountController = __decorate([
279
292
  (0, common_1.Controller)('identity'),
293
+ (0, common_1.UseInterceptors)(identity_error_interceptor_js_1.IdentityErrorInterceptor),
280
294
  __metadata("design:paramtypes", [service_account_service_js_1.ServiceAccountService])
281
295
  ], ServiceAccountController);
@@ -84,6 +84,11 @@ export interface AssignableAccess {
84
84
  readonly roles: readonly RoleOption[];
85
85
  readonly scopeNodes: readonly ScopeNodeOption[];
86
86
  }
87
+ /** Key expiry on issue/rotate: a future `expiresAtMs`, or an explicit `neverExpires: true`. */
88
+ export interface KeyExpiryOptions {
89
+ readonly expiresAtMs?: number;
90
+ readonly neverExpires?: boolean;
91
+ }
87
92
  export declare class ServiceAccountService {
88
93
  private readonly accounts;
89
94
  private readonly keys;
@@ -179,19 +184,21 @@ export declare class ServiceAccountService {
179
184
  * Issue a NEW key for an account (own-tenant). Mints `ask_<id>_<secret>`, hashes the secret, and
180
185
  * persists the hash as the next version — carrying the ACCOUNT's current scope/roles onto the key
181
186
  * (so the key's Principal reflects the account as it stands). Returns the raw key ONCE.
182
- * `expiresAtMs` is optional (a non-expiring key is discouraged; the caller opts in to no expiry).
187
+ * ADR-0033 §4 (security hardening): the caller must give a FUTURE `expiresAtMs`, or opt in to a
188
+ * non-expiring key explicitly with `neverExpires: true` — otherwise 400, nothing minted.
189
+ */
190
+ issueKey(admin: AdminPrincipal, id: string, opts?: KeyExpiryOptions): Promise<IssuedKey>;
191
+ /**
192
+ * The key's expiry from the request: a future epoch-ms, or `undefined` when the caller said
193
+ * `neverExpires: true`. Neither, both, or a past/invalid time → ServiceAccountInputError (400).
183
194
  */
184
- issueKey(admin: AdminPrincipal, id: string, opts?: {
185
- expiresAtMs?: number;
186
- }): Promise<IssuedKey>;
195
+ private resolveExpiry;
187
196
  /**
188
197
  * Rotate an account's key: mint a new version (the old version stops being the current one —
189
198
  * `getByAccountId` returns the highest, so the previous key no longer authenticates). Returns the
190
199
  * new raw key ONCE.
191
200
  */
192
- rotateKey(admin: AdminPrincipal, id: string, opts?: {
193
- expiresAtMs?: number;
194
- }): Promise<IssuedKey>;
201
+ rotateKey(admin: AdminPrincipal, id: string, opts?: KeyExpiryOptions): Promise<IssuedKey>;
195
202
  /** Revoke ALL of an account's keys (own-tenant) — fail-closed on the next request. Idempotent. */
196
203
  revokeKeys(admin: AdminPrincipal, id: string): Promise<void>;
197
204
  /** Shared mint path for issue + rotate: version bump, hash, putVersion, audit, one-time reveal. */
@@ -1 +1 @@
1
- {"version":3,"file":"service-account.service.d.ts","sourceRoot":"","sources":["../../../src/features/service-account/service-account.service.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAO,KAAK,EACV,mBAAmB,EAGnB,UAAU,EACV,eAAe,EAChB,MAAM,4BAA4B,CAAC;AAEpC,gGAAgG;AAChG,qBAAa,2BAA4B,SAAQ,KAAK;;CAKrD;AAED,oFAAoF;AACpF,qBAAa,0BAA2B,SAAQ,KAAK;gBACvC,OAAO,EAAE,MAAM;CAI5B;AAED,4FAA4F;AAC5F,qBAAa,2BAA4B,SAAQ,KAAK;;CAKrD;AAED,0EAA0E;AAC1E,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,OAAO,EAAE,MAAM;CAI5B;AAED,kGAAkG;AAClG,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,2FAA2F;IAC3F,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,2FAA2F;AAC3F,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,yFAAyF;AACzF,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,KAAK,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KAC/C,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED,kGAAkG;AAClG,MAAM,MAAM,YAAY,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAElE,kGAAkG;AAClG,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;AAerG;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACvC;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,qBAAqB,CAMtG;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnF;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnF;AAgDD,0FAA0F;AAC1F,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;CACjD;AAWD,qBAAa,qBAAqB;IAE9B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAcrB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAnBH,QAAQ,EAAE,mBAAmB,EAC7B,IAAI,EAAE,WAAW,GAAG;QACnC,UAAU,CAAC,KAAK,EAAE;YAChB,SAAS,EAAE,MAAM,CAAC;YAClB,QAAQ,EAAE,MAAM,CAAC;YACjB,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;YACvC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;YAC3B,UAAU,EAAE,MAAM,CAAC;YACnB,OAAO,EAAE,MAAM,CAAC;YAChB,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,WAAW,EAAE,MAAM,CAAC;SACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAClB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QACnD,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACxD,EACgB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,mBAAmB,EAC1B,WAAW,EAAE,YAAY,EACzB,SAAS,EAAE,gBAAgB,EAC3B,GAAG,GAAE,MAAM,MAAiB;IAG/C;;;OAGG;YACW,OAAO;IAwBrB;;;OAGG;YACW,UAAU;IAKxB;;;;;;;OAOG;YACW,mBAAmB;IAuBjC;;OAEG;YACW,iBAAiB;IAiB/B;;;;OAIG;YACW,UAAU;IAmBxB;;;;;OAKG;IACG,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAelE,wEAAwE;IAClE,IAAI,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAKhE,gEAAgE;IAC1D,GAAG,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAOzE;;;;OAIG;IACG,MAAM,CACV,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,GAC7F,OAAO,CAAC,kBAAkB,CAAC;IAuC9B;;;;;;;;;;;;OAYG;IACG,MAAM,CACV,KAAK,EAAE,cAAc,EACrB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QACxC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5B,MAAM,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;KAChC,GACA,OAAO,CAAC,kBAAkB,CAAC;IAyF9B;;;;;OAKG;IACG,QAAQ,CACZ,KAAK,EAAE,cAAc,EACrB,EAAE,EAAE,MAAM,EACV,IAAI,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAO,GAClC,OAAO,CAAC,SAAS,CAAC;IAMrB;;;;OAIG;IACG,SAAS,CACb,KAAK,EAAE,cAAc,EACrB,EAAE,EAAE,MAAM,EACV,IAAI,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAO,GAClC,OAAO,CAAC,SAAS,CAAC;IAMrB,kGAAkG;IAC5F,UAAU,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQlE,mGAAmG;YACrF,OAAO;IAiCrB,+EAA+E;YACjE,SAAS;CAYxB"}
1
+ {"version":3,"file":"service-account.service.d.ts","sourceRoot":"","sources":["../../../src/features/service-account/service-account.service.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAO,KAAK,EACV,mBAAmB,EAGnB,UAAU,EACV,eAAe,EAChB,MAAM,4BAA4B,CAAC;AAEpC,gGAAgG;AAChG,qBAAa,2BAA4B,SAAQ,KAAK;;CAKrD;AAED,oFAAoF;AACpF,qBAAa,0BAA2B,SAAQ,KAAK;gBACvC,OAAO,EAAE,MAAM;CAI5B;AAED,4FAA4F;AAC5F,qBAAa,2BAA4B,SAAQ,KAAK;;CAKrD;AAED,0EAA0E;AAC1E,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,OAAO,EAAE,MAAM;CAI5B;AAED,kGAAkG;AAClG,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,2FAA2F;IAC3F,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,2FAA2F;AAC3F,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,yFAAyF;AACzF,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,KAAK,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KAC/C,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED,kGAAkG;AAClG,MAAM,MAAM,YAAY,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAElE,kGAAkG;AAClG,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;AAehC;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACvC;AAED,wBAAgB,SAAS,CACvB,KAAK,EAAE,cAAc,EACrB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,qBAAqB,CAMvB;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnF;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnF;AAgDD,0FAA0F;AAC1F,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;CACjD;AAWD,+FAA+F;AAC/F,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,qBAAa,qBAAqB;IAE9B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAcrB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAnBH,QAAQ,EAAE,mBAAmB,EAC7B,IAAI,EAAE,WAAW,GAAG;QACnC,UAAU,CAAC,KAAK,EAAE;YAChB,SAAS,EAAE,MAAM,CAAC;YAClB,QAAQ,EAAE,MAAM,CAAC;YACjB,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;YACvC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;YAC3B,UAAU,EAAE,MAAM,CAAC;YACnB,OAAO,EAAE,MAAM,CAAC;YAChB,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,WAAW,EAAE,MAAM,CAAC;SACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAClB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QACnD,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACxD,EACgB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,mBAAmB,EAC1B,WAAW,EAAE,YAAY,EACzB,SAAS,EAAE,gBAAgB,EAC3B,GAAG,GAAE,MAAM,MAAiB;IAG/C;;;OAGG;YACW,OAAO;IAwBrB;;;OAGG;YACW,UAAU;IAKxB;;;;;;;OAOG;YACW,mBAAmB;IAsCjC;;OAEG;YACW,iBAAiB;IAsB/B;;;;OAIG;YACW,UAAU;IAmBxB;;;;;OAKG;IACG,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAiBlE,wEAAwE;IAClE,IAAI,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAKhE,gEAAgE;IAC1D,GAAG,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAOzE;;;;OAIG;IACG,MAAM,CACV,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,GAC7F,OAAO,CAAC,kBAAkB,CAAC;IAuC9B;;;;;;;;;;;;OAYG;IACG,MAAM,CACV,KAAK,EAAE,cAAc,EACrB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QACxC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5B,MAAM,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;KAChC,GACA,OAAO,CAAC,kBAAkB,CAAC;IAuG9B;;;;;;OAMG;IACG,QAAQ,CACZ,KAAK,EAAE,cAAc,EACrB,EAAE,EAAE,MAAM,EACV,IAAI,GAAE,gBAAqB,GAC1B,OAAO,CAAC,SAAS,CAAC;IAOrB;;;OAGG;IACH,OAAO,CAAC,aAAa;IAkBrB;;;;OAIG;IACG,SAAS,CACb,KAAK,EAAE,cAAc,EACrB,EAAE,EAAE,MAAM,EACV,IAAI,GAAE,gBAAqB,GAC1B,OAAO,CAAC,SAAS,CAAC;IAOrB,kGAAkG;IAC5F,UAAU,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQlE,mGAAmG;YACrF,OAAO;IAiCrB,+EAA+E;YACjE,SAAS;CAkBxB"}