@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
@@ -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);
@@ -30,6 +30,21 @@ export interface ServiceAccountView {
30
30
  readonly createdByName: string | null;
31
31
  readonly createdAtMs: number;
32
32
  readonly updatedAtMs: number | null;
33
+ /** The account's current API key (the highest version), or `null` when none was ever issued. */
34
+ readonly currentKey: ServiceAccountKeyView | null;
35
+ }
36
+ /**
37
+ * The status of an account's current key (Phase 8 #13) — never the secret or its hash.
38
+ * `neverExpires` flags an ACTIVE key with no expiry: either issued before an expiry became
39
+ * mandatory (identity 0.5.0) or issued later with an explicit `neverExpires: true`. The stored row
40
+ * cannot tell the two apart, so both are flagged; review them and rotate with an expiry.
41
+ */
42
+ export interface ServiceAccountKeyView {
43
+ readonly version: number;
44
+ readonly issuedAtMs: number;
45
+ readonly expiresAtMs: number | null;
46
+ readonly revoked: boolean;
47
+ readonly neverExpires: boolean;
33
48
  }
34
49
  /** The ONE-TIME key reveal returned by issue/rotate — the only time the raw key exists. */
35
50
  export interface IssuedKey {
@@ -84,6 +99,11 @@ export interface AssignableAccess {
84
99
  readonly roles: readonly RoleOption[];
85
100
  readonly scopeNodes: readonly ScopeNodeOption[];
86
101
  }
102
+ /** Key expiry on issue/rotate: a future `expiresAtMs`, or an explicit `neverExpires: true`. */
103
+ export interface KeyExpiryOptions {
104
+ readonly expiresAtMs?: number;
105
+ readonly neverExpires?: boolean;
106
+ }
87
107
  export declare class ServiceAccountService {
88
108
  private readonly accounts;
89
109
  private readonly keys;
@@ -179,19 +199,21 @@ export declare class ServiceAccountService {
179
199
  * Issue a NEW key for an account (own-tenant). Mints `ask_<id>_<secret>`, hashes the secret, and
180
200
  * persists the hash as the next version — carrying the ACCOUNT's current scope/roles onto the key
181
201
  * (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).
202
+ * ADR-0033 §4 (security hardening): the caller must give a FUTURE `expiresAtMs`, or opt in to a
203
+ * non-expiring key explicitly with `neverExpires: true` — otherwise 400, nothing minted.
204
+ */
205
+ issueKey(admin: AdminPrincipal, id: string, opts?: KeyExpiryOptions): Promise<IssuedKey>;
206
+ /**
207
+ * The key's expiry from the request: a future epoch-ms, or `undefined` when the caller said
208
+ * `neverExpires: true`. Neither, both, or a past/invalid time → ServiceAccountInputError (400).
183
209
  */
184
- issueKey(admin: AdminPrincipal, id: string, opts?: {
185
- expiresAtMs?: number;
186
- }): Promise<IssuedKey>;
210
+ private resolveExpiry;
187
211
  /**
188
212
  * Rotate an account's key: mint a new version (the old version stops being the current one —
189
213
  * `getByAccountId` returns the highest, so the previous key no longer authenticates). Returns the
190
214
  * new raw key ONCE.
191
215
  */
192
- rotateKey(admin: AdminPrincipal, id: string, opts?: {
193
- expiresAtMs?: number;
194
- }): Promise<IssuedKey>;
216
+ rotateKey(admin: AdminPrincipal, id: string, opts?: KeyExpiryOptions): Promise<IssuedKey>;
195
217
  /** Revoke ALL of an account's keys (own-tenant) — fail-closed on the next request. Idempotent. */
196
218
  revokeKeys(admin: AdminPrincipal, id: string): Promise<void>;
197
219
  /** 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":"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"}
1
+ {"version":3,"file":"service-account.service.d.ts","sourceRoot":"","sources":["../../../src/features/service-account/service-account.service.ts"],"names":[],"mappings":"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"}
@@ -157,12 +157,27 @@ class ServiceAccountService {
157
157
  async toViews(rows) {
158
158
  if (rows.length === 0)
159
159
  return [];
160
- const [roles, nodes] = await Promise.all([
160
+ const [roles, nodes, keys] = await Promise.all([
161
161
  this.accounts.rolesByIds(rows.flatMap((r) => r.roleIds)),
162
162
  this.accounts.nodesByIds(rows.flatMap((r) => r.scopeSubtreeNodeIds)),
163
+ this.accounts.currentKeys(rows.map((r) => r.id)),
163
164
  ]);
164
165
  const roleName = new Map(roles.map((r) => [r.id, r.name]));
165
166
  const nodeName = new Map(nodes.map((n) => [n.id, n.name]));
167
+ const keyOf = new Map(keys.map((k) => [k.accountId, k]));
168
+ const keyView = (id) => {
169
+ const k = keyOf.get(id);
170
+ if (k === undefined)
171
+ return null;
172
+ const revoked = k.revokedAtMs !== null;
173
+ return {
174
+ version: k.version,
175
+ issuedAtMs: k.createdAtMs,
176
+ expiresAtMs: k.expiresAtMs,
177
+ revoked,
178
+ neverExpires: k.expiresAtMs === null && !revoked,
179
+ };
180
+ };
166
181
  return Promise.all(rows.map(async (row) => ({
167
182
  id: row.id,
168
183
  label: row.label,
@@ -174,6 +189,7 @@ class ServiceAccountService {
174
189
  createdByName: row.createdBy ? await this.resolveName(row.createdBy) : null,
175
190
  createdAtMs: row.createdAtMs,
176
191
  updatedAtMs: row.updatedAtMs,
192
+ currentKey: keyView(row.id),
177
193
  })));
178
194
  }
179
195
  /**
@@ -193,7 +209,10 @@ class ServiceAccountService {
193
209
  * described generically: the role/company catalog reads are not caller-scoped, so resolving a
194
210
  * name for an arbitrary id would turn the 403 into a cross-tenant name/existence oracle.
195
211
  */
196
- async assertWithinCeiling(ceiling, scopeSubtreeNodeIds, roleIds, onAgent = { nodes: [], roles: [] }) {
212
+ async assertWithinCeiling(ceiling, scopeSubtreeNodeIds, roleIds, onAgent = {
213
+ nodes: [],
214
+ roles: [],
215
+ }) {
197
216
  if (ceiling.isRoot)
198
217
  return;
199
218
  const badNodes = uniqueIds(scopeSubtreeNodeIds).filter((n) => !ceilingAllowsNode(ceiling, n));
@@ -213,13 +232,17 @@ class ServiceAccountService {
213
232
  async describeOffenders(bad, onAgent, lookup, noun, plural) {
214
233
  const known = new Set(onAgent);
215
234
  const nameable = bad.filter((id) => known.has(id));
216
- const found = nameable.length > 0 ? new Map((await lookup(nameable)).map((x) => [x.id, x.name])) : new Map();
235
+ const found = nameable.length > 0
236
+ ? new Map((await lookup(nameable)).map((x) => [x.id, x.name]))
237
+ : new Map();
217
238
  const names = uniqueIds(nameable.map((id) => found.get(id) ?? '').filter((n) => n.length > 0));
218
239
  const rest = bad.length - names.length;
219
240
  if (names.length === 0)
220
241
  return rest === 1 ? `a ${noun}` : `${rest} ${plural}`;
221
242
  const named = `the ${names.length === 1 ? noun : plural} ${quoteNames(names)}`;
222
- return rest === 0 ? named : `${named} and ${rest === 1 ? `1 other ${noun}` : `${rest} other ${plural}`}`;
243
+ return rest === 0
244
+ ? named
245
+ : `${named} and ${rest === 1 ? `1 other ${noun}` : `${rest} other ${plural}`}`;
223
246
  }
224
247
  /**
225
248
  * Resolve names for audit (ids already authorised for this caller: within ceiling, or on the
@@ -261,7 +284,9 @@ class ServiceAccountService {
261
284
  ]);
262
285
  return {
263
286
  // Defensive re-filter through the same predicates (a store that over-returns can't widen it).
264
- roles: roles.filter((r) => ceilingAllowsRole(ceiling, r.id)).map((r) => ({ id: r.id, name: r.name })),
287
+ roles: roles
288
+ .filter((r) => ceilingAllowsRole(ceiling, r.id))
289
+ .map((r) => ({ id: r.id, name: r.name })),
265
290
  scopeNodes: treeOrder(nodes.filter((n) => ceilingAllowsNode(ceiling, n.id))),
266
291
  };
267
292
  }
@@ -374,8 +399,12 @@ class ServiceAccountService {
374
399
  const updatedAtMs = Math.max(this.now(), (existing.updatedAtMs ?? 0) + 1);
375
400
  const normalized = {
376
401
  ...(patch.label !== undefined ? { label: patch.label.trim() } : {}),
377
- ...(requestedScope !== undefined ? { scopeSubtreeNodeIds: mergeIds(requestedScope, preservedScope) } : {}),
378
- ...(requestedRoles !== undefined ? { roleIds: mergeIds(requestedRoles, preservedRoles) } : {}),
402
+ ...(requestedScope !== undefined
403
+ ? { scopeSubtreeNodeIds: mergeIds(requestedScope, preservedScope) }
404
+ : {}),
405
+ ...(requestedRoles !== undefined
406
+ ? { roleIds: mergeIds(requestedRoles, preservedRoles) }
407
+ : {}),
379
408
  ...(patch.status !== undefined ? { status: patch.status } : {}),
380
409
  };
381
410
  // The preserve-merge is a read-modify-write: guard it so a concurrent edit (e.g. root revoking an
@@ -393,7 +422,10 @@ class ServiceAccountService {
393
422
  // WHAT changed, by name: diff the stored sets (added ⊆ ceiling or root; removed were on the agent).
394
423
  const diff = (before, after) => after === undefined
395
424
  ? { added: [], removed: [] }
396
- : { added: after.filter((x) => !before.includes(x)), removed: before.filter((x) => !after.includes(x)) };
425
+ : {
426
+ added: after.filter((x) => !before.includes(x)),
427
+ removed: before.filter((x) => !after.includes(x)),
428
+ };
397
429
  const roleDiff = diff(existing.roleIds, normalized.roleIds);
398
430
  const scopeDiff = diff(existing.scopeSubtreeNodeIds, normalized.scopeSubtreeNodeIds);
399
431
  const names = await this.auditNames([...roleDiff.added, ...roleDiff.removed], [...scopeDiff.added, ...scopeDiff.removed]);
@@ -414,13 +446,35 @@ class ServiceAccountService {
414
446
  * Issue a NEW key for an account (own-tenant). Mints `ask_<id>_<secret>`, hashes the secret, and
415
447
  * persists the hash as the next version — carrying the ACCOUNT's current scope/roles onto the key
416
448
  * (so the key's Principal reflects the account as it stands). Returns the raw key ONCE.
417
- * `expiresAtMs` is optional (a non-expiring key is discouraged; the caller opts in to no expiry).
449
+ * ADR-0033 §4 (security hardening): the caller must give a FUTURE `expiresAtMs`, or opt in to a
450
+ * non-expiring key explicitly with `neverExpires: true` — otherwise 400, nothing minted.
418
451
  */
419
452
  async issueKey(admin, id, opts = {}) {
453
+ const expiresAtMs = this.resolveExpiry(opts);
420
454
  const account = await this.accounts.getById(id, scopeOf(admin));
421
455
  if (account === null)
422
456
  throw new ServiceAccountNotFoundError();
423
- return this.mintFor(account, admin, 'service_account.key_issued', opts.expiresAtMs);
457
+ return this.mintFor(account, admin, 'service_account.key_issued', expiresAtMs);
458
+ }
459
+ /**
460
+ * The key's expiry from the request: a future epoch-ms, or `undefined` when the caller said
461
+ * `neverExpires: true`. Neither, both, or a past/invalid time → ServiceAccountInputError (400).
462
+ */
463
+ resolveExpiry(opts) {
464
+ const hasExpiry = opts.expiresAtMs !== undefined;
465
+ if (hasExpiry && opts.neverExpires === true) {
466
+ throw new ServiceAccountInputError('Give either an expiry or neverExpires: true, not both.');
467
+ }
468
+ if (opts.neverExpires === true)
469
+ return undefined;
470
+ if (!hasExpiry) {
471
+ throw new ServiceAccountInputError('Choose when the key expires (expiresAtMs), or set neverExpires: true for a key that never expires.');
472
+ }
473
+ const ms = opts.expiresAtMs;
474
+ if (typeof ms !== 'number' || !Number.isFinite(ms) || ms <= this.now()) {
475
+ throw new ServiceAccountInputError('expiresAtMs must be a time in the future (epoch-ms).');
476
+ }
477
+ return ms;
424
478
  }
425
479
  /**
426
480
  * Rotate an account's key: mint a new version (the old version stops being the current one —
@@ -428,10 +482,11 @@ class ServiceAccountService {
428
482
  * new raw key ONCE.
429
483
  */
430
484
  async rotateKey(admin, id, opts = {}) {
485
+ const expiresAtMs = this.resolveExpiry(opts);
431
486
  const account = await this.accounts.getById(id, scopeOf(admin));
432
487
  if (account === null)
433
488
  throw new ServiceAccountNotFoundError();
434
- return this.mintFor(account, admin, 'service_account.key_rotated', opts.expiresAtMs);
489
+ return this.mintFor(account, admin, 'service_account.key_rotated', expiresAtMs);
435
490
  }
436
491
  /** Revoke ALL of an account's keys (own-tenant) — fail-closed on the next request. Idempotent. */
437
492
  async revokeKeys(admin, id) {
@@ -473,7 +528,13 @@ class ServiceAccountService {
473
528
  /** Best-effort audit — a sink throw must never fail a legitimate operation. */
474
529
  async safeAudit(action, admin, occurredAtMs, attributes) {
475
530
  try {
476
- await this.audit.record({ action, actorId: admin.id, tenantId: admin.tenantId, occurredAtMs, attributes });
531
+ await this.audit.record({
532
+ action,
533
+ actorId: admin.id,
534
+ tenantId: admin.tenantId,
535
+ occurredAtMs,
536
+ attributes,
537
+ });
477
538
  }
478
539
  catch {
479
540
  // swallow — auditing is best-effort here (the durable sink already logs its own failures).
@@ -12,6 +12,14 @@
12
12
  * stamped `sa_tenant_id` (from the session) on create — never client input.
13
13
  */
14
14
  import type { Pool } from 'mariadb';
15
+ /** The status of an account's current key (no hash, no secret). Epoch-ms; `null` = not set. */
16
+ export interface ServiceAccountKeyRow {
17
+ readonly accountId: string;
18
+ readonly version: number;
19
+ readonly expiresAtMs: number | null;
20
+ readonly revokedAtMs: number | null;
21
+ readonly createdAtMs: number;
22
+ }
15
23
  /** The caller's reachable subtree (threaded from `req.principal`) — same shape as grant-admin. */
16
24
  export interface SubtreeScope {
17
25
  readonly nodeIds: readonly string[];
@@ -51,6 +59,13 @@ export declare class ServiceAccountStore {
51
59
  nodesByIds(ids: readonly string[]): Promise<ScopeNodeOption[]>;
52
60
  /** Every company (ROOT's assignable scope — root reaches all). */
53
61
  allNodes(): Promise<ScopeNodeOption[]>;
62
+ /**
63
+ * Phase 8 #13 — the CURRENT key (highest version: the only one the authenticator honours) of
64
+ * each account, from guard's `shield_authz.service_account_key`, in ONE batched read. Status
65
+ * columns only — never the hash. Read-only. Callers pass ids they already read through the
66
+ * own-tenant wall (`list`/`getById`); accounts with no key are simply absent.
67
+ */
68
+ currentKeys(accountIds: readonly string[]): Promise<ServiceAccountKeyRow[]>;
54
69
  /** List the caller's own-tenant accounts (newest first). Root sees all. */
55
70
  list(scope: SubtreeScope): Promise<ServiceAccountRow[]>;
56
71
  /** Fetch one account BY id, scoped to the caller's subtree. Cross-tenant/unknown → null (404). */
@@ -1 +1 @@
1
- {"version":3,"file":"service-account.store.d.ts","sourceRoot":"","sources":["../../../src/features/service-account/service-account.store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service-account.store.d.ts","sourceRoot":"","sources":["../../../src/features/service-account/service-account.store.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ServiceAccountStore = void 0;
4
4
  const APP = 'shield_app';
5
+ /** Guard's key table lives here (`1710000000017-CreateServiceAccountKey.cjs`); read-only here. */
6
+ const AUTHZ = 'shield_authz';
5
7
  /** Normalize a JSON-array column (driver returns array OR string) → string[]. Fail-safe to []. */
6
8
  function jsonArray(v) {
7
9
  const arr = Array.isArray(v)
@@ -83,6 +85,33 @@ class ServiceAccountStore {
83
85
  const rows = (await this.pool.query(`SELECT c_uuid AS id, c_name AS name, c_parent_uuid AS parentId FROM \`${APP}\`.\`companies\` ORDER BY c_name`, []));
84
86
  return rows.map((r) => ({ id: r.id, name: r.name, parentId: r.parentId ?? null }));
85
87
  }
88
+ /**
89
+ * Phase 8 #13 — the CURRENT key (highest version: the only one the authenticator honours) of
90
+ * each account, from guard's `shield_authz.service_account_key`, in ONE batched read. Status
91
+ * columns only — never the hash. Read-only. Callers pass ids they already read through the
92
+ * own-tenant wall (`list`/`getById`); accounts with no key are simply absent.
93
+ */
94
+ async currentKeys(accountIds) {
95
+ const u = uniqueIds(accountIds);
96
+ if (u.length === 0)
97
+ return [];
98
+ const rows = (await this.pool.query(`SELECT k.sak_account_id, k.sak_version, k.sak_expires_at_ms, k.sak_revoked_at_ms,
99
+ k.sak_created_at_ms
100
+ FROM \`${AUTHZ}\`.\`service_account_key\` k
101
+ JOIN (SELECT sak_account_id, MAX(sak_version) AS v
102
+ FROM \`${AUTHZ}\`.\`service_account_key\`
103
+ WHERE sak_account_id IN (${u.map(() => '?').join(', ')})
104
+ GROUP BY sak_account_id) m
105
+ ON m.sak_account_id = k.sak_account_id AND m.v = k.sak_version`, u));
106
+ const ms = (v) => (v === null || v === undefined ? null : Number(v));
107
+ return rows.map((r) => ({
108
+ accountId: r.sak_account_id,
109
+ version: Number(r.sak_version),
110
+ expiresAtMs: ms(r.sak_expires_at_ms),
111
+ revokedAtMs: ms(r.sak_revoked_at_ms),
112
+ createdAtMs: Number(r.sak_created_at_ms),
113
+ }));
114
+ }
86
115
  /** List the caller's own-tenant accounts (newest first). Root sees all. */
87
116
  async list(scope) {
88
117
  const t = tenantScope(scope);
@@ -1 +1 @@
1
- {"version":3,"file":"share.controller.d.ts","sourceRoot":"","sources":["../../../src/features/share/share.controller.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAqB,MAAM,oBAAoB,CAAC;AAErE,qFAAqF;AACrF,UAAU,aAAa;IACrB,SAAS,EAAE,SAAS,CAAC;CACtB;AAqBD,qBACa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,YAAY;IAEjD;;;;OAIG;IAGG,IAAI,CACD,GAAG,EAAE,aAAa,EACP,OAAO,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM;;;IAYtC;;;;OAIG;IAGG,KAAK,CAAQ,GAAG,EAAE,aAAa,EAAU,IAAI,EAAE,OAAO;;;IAW5D;;;OAGG;IAGG,OAAO,CAAQ,GAAG,EAAE,aAAa,EAAU,IAAI,EAAE,OAAO;;;CAU/D"}
1
+ {"version":3,"file":"share.controller.d.ts","sourceRoot":"","sources":["../../../src/features/share/share.controller.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAqB,MAAM,oBAAoB,CAAC;AAErE,qFAAqF;AACrF,UAAU,aAAa;IACrB,SAAS,EAAE,SAAS,CAAC;CACtB;AAwBD,qBAEa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,YAAY;IAEjD;;;;OAIG;IAGG,IAAI,CACD,GAAG,EAAE,aAAa,EACP,OAAO,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM;;;IAYtC;;;;OAIG;IAGG,KAAK,CAAQ,GAAG,EAAE,aAAa,EAAU,IAAI,EAAE,OAAO;;;IAW5D;;;OAGG;IAGG,OAAO,CAAQ,GAAG,EAAE,aAAa,EAAU,IAAI,EAAE,OAAO;;;CAU/D"}
@@ -28,6 +28,7 @@ exports.ShareController = void 0;
28
28
  */
29
29
  const common_1 = require("@nestjs/common");
30
30
  const guard_1 = require("@astram/guard");
31
+ const identity_error_interceptor_js_1 = require("../shared/identity-error.interceptor.js");
31
32
  const share_service_js_1 = require("./share.service.js");
32
33
  /** Validate + narrow a share/unshare body to a {@link ShareRequest} (grantee + action only). */
33
34
  function toShareRequest(body) {
@@ -139,5 +140,6 @@ __decorate([
139
140
  ], ShareController.prototype, "unshare", null);
140
141
  exports.ShareController = ShareController = __decorate([
141
142
  (0, common_1.Controller)('identity'),
143
+ (0, common_1.UseInterceptors)(identity_error_interceptor_js_1.IdentityErrorInterceptor),
142
144
  __metadata("design:paramtypes", [share_service_js_1.ShareService])
143
145
  ], ShareController);
@@ -1 +1 @@
1
- {"version":3,"file":"share.service.d.ts","sourceRoot":"","sources":["../../../src/features/share/share.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,eAAe,CAAC;AAEvB,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrF;AAED,4FAA4F;AAC5F,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,KAAK,EAAE;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,gBAAgB,CAAC;QAC9B,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,EAAE;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,gBAAgB,CAAC;QAC9B,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB,mBAAmB,CAAC,KAAK,EAAE;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CACT,aAAa,CAAC;QACZ,WAAW,EAAE,gBAAgB,CAAC;QAC9B,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC,CACH,CAAC;CACH;AAED,oFAAoF;AACpF,MAAM,WAAW,SAAS;IACxB,yDAAyD;IACzD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,gFAAgF;IAChF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,kGAAkG;AAClG,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,YAAY;IAErB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,MAAM,EAAE,iBAAiB,GAAG,eAAe,EAC3C,MAAM,EAAE,kBAAkB;IAG7C;;;;OAIG;IACG,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAY/D,qFAAqF;IAC/E,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAYjE;;;OAGG;IACG,UAAU,CACd,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC;CAoBjC"}
1
+ {"version":3,"file":"share.service.d.ts","sourceRoot":"","sources":["../../../src/features/share/share.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEzE,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrF;AAED,4FAA4F;AAC5F,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,KAAK,EAAE;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,gBAAgB,CAAC;QAC9B,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,EAAE;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,gBAAgB,CAAC;QAC9B,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB,mBAAmB,CAAC,KAAK,EAAE;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CACT,aAAa,CAAC;QACZ,WAAW,EAAE,gBAAgB,CAAC;QAC9B,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC,CACH,CAAC;CACH;AAED,oFAAoF;AACpF,MAAM,WAAW,SAAS;IACxB,yDAAyD;IACzD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,gFAAgF;IAChF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,kGAAkG;AAClG,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,YAAY;IAErB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,MAAM,EAAE,iBAAiB,GAAG,eAAe,EAC3C,MAAM,EAAE,kBAAkB;IAG7C;;;;OAIG;IACG,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAY/D,qFAAqF;IAC/E,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAYjE;;;OAGG;IACG,UAAU,CACd,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC;CAoBjC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * IdentityErrorInterceptor — maps engine, ceiling and identity domain errors to HTTP for every
3
+ * identity route, in ONE place (security hardening Phase 1, ADR-0034 follow-up).
4
+ *
5
+ * Before this, an engine `AuthorizationDeniedError` (for example the ADR-0034 create wall's
6
+ * `create_outside_write_scope`) escaped a controller as an unmapped error → HTTP 500. Each mapping
7
+ * below uses a fixed, id-free message (ADR-0012): the engine's own message carries reason codes and
8
+ * detail that are for logs, not clients.
9
+ *
10
+ * Applied with `@UseInterceptors(IdentityErrorInterceptor)` on each identity controller. An
11
+ * `HttpException` (a controller's own 400/403/404) passes through untouched; anything unmapped is
12
+ * rethrown as-is (Nest's default handling).
13
+ */
14
+ import { type CallHandler, type ExecutionContext, type NestInterceptor } from '@nestjs/common';
15
+ import { type Observable } from 'rxjs';
16
+ /**
17
+ * Map one error to the HTTP exception identity answers with, or return it unchanged when it is
18
+ * not an identity-mapped type. Exported for unit tests and for callers that map by hand.
19
+ */
20
+ export declare function toIdentityHttpError(err: unknown): unknown;
21
+ export declare class IdentityErrorInterceptor implements NestInterceptor {
22
+ intercept(_context: ExecutionContext, next: CallHandler): Observable<unknown>;
23
+ }
24
+ //# sourceMappingURL=identity-error.interceptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-error.interceptor.d.ts","sourceRoot":"","sources":["../../../src/features/shared/identity-error.interceptor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAQL,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAA0B,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAqB/D;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAwBzD;AAED,qBACa,wBAAyB,YAAW,eAAe;IAC9D,SAAS,CAAC,QAAQ,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC;CAK9E"}
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.IdentityErrorInterceptor = void 0;
10
+ exports.toIdentityHttpError = toIdentityHttpError;
11
+ /**
12
+ * IdentityErrorInterceptor — maps engine, ceiling and identity domain errors to HTTP for every
13
+ * identity route, in ONE place (security hardening Phase 1, ADR-0034 follow-up).
14
+ *
15
+ * Before this, an engine `AuthorizationDeniedError` (for example the ADR-0034 create wall's
16
+ * `create_outside_write_scope`) escaped a controller as an unmapped error → HTTP 500. Each mapping
17
+ * below uses a fixed, id-free message (ADR-0012): the engine's own message carries reason codes and
18
+ * detail that are for logs, not clients.
19
+ *
20
+ * Applied with `@UseInterceptors(IdentityErrorInterceptor)` on each identity controller. An
21
+ * `HttpException` (a controller's own 400/403/404) passes through untouched; anything unmapped is
22
+ * rethrown as-is (Nest's default handling).
23
+ */
24
+ const common_1 = require("@nestjs/common");
25
+ const rxjs_1 = require("rxjs");
26
+ const engine_1 = require("@astram/engine");
27
+ const core_1 = require("@astram/core");
28
+ const guard_1 = require("@astram/guard");
29
+ const grant_ceiling_js_1 = require("../authz/grant-ceiling.js");
30
+ const scope_closure_service_js_1 = require("../scope/scope-closure.service.js");
31
+ const identity_errors_js_1 = require("./identity-errors.js");
32
+ /** The fixed client message for an engine deny, by reason code (never the engine's own text). */
33
+ function deniedMessage(reason) {
34
+ if (reason === 'create_outside_write_scope') {
35
+ return 'You cannot create this record: it is outside the organisations you manage.';
36
+ }
37
+ return 'You do not have access to perform this action.';
38
+ }
39
+ /**
40
+ * Map one error to the HTTP exception identity answers with, or return it unchanged when it is
41
+ * not an identity-mapped type. Exported for unit tests and for callers that map by hand.
42
+ */
43
+ function toIdentityHttpError(err) {
44
+ if (err instanceof common_1.HttpException)
45
+ return err;
46
+ if (err instanceof engine_1.AuthorizationDeniedError)
47
+ return new common_1.ForbiddenException(deniedMessage(err.reason));
48
+ if (err instanceof grant_ceiling_js_1.GrantCeilingError)
49
+ return new common_1.ForbiddenException(err.message);
50
+ if (err instanceof identity_errors_js_1.IdentityForbiddenError)
51
+ return new common_1.ForbiddenException(err.message);
52
+ if (err instanceof identity_errors_js_1.IdentityNotFoundError)
53
+ return new common_1.NotFoundException(err.message);
54
+ if (err instanceof identity_errors_js_1.IdentityConflictError)
55
+ return new common_1.ConflictException(err.message);
56
+ if (err instanceof identity_errors_js_1.IdentityBadRequestError)
57
+ return new common_1.BadRequestException(err.message);
58
+ if (err instanceof guard_1.ScopeCycleError) {
59
+ return new common_1.ConflictException('That move would put an organisation inside its own sub-tree.');
60
+ }
61
+ if (err instanceof scope_closure_service_js_1.NodeHasChildrenError) {
62
+ return new common_1.ConflictException('This organisation still has sub-organisations. Move or delete them first.');
63
+ }
64
+ if (err instanceof engine_1.ValidationError) {
65
+ return new common_1.BadRequestException(`Invalid value for '${err.field}'.`);
66
+ }
67
+ if (err instanceof core_1.AuthorizationUnavailableError || err instanceof engine_1.EngineUnavailableError) {
68
+ return new common_1.ServiceUnavailableException('Authorization is temporarily unavailable. Try again.');
69
+ }
70
+ return err;
71
+ }
72
+ let IdentityErrorInterceptor = class IdentityErrorInterceptor {
73
+ intercept(_context, next) {
74
+ return next
75
+ .handle()
76
+ .pipe((0, rxjs_1.catchError)((err) => (0, rxjs_1.throwError)(() => toIdentityHttpError(err))));
77
+ }
78
+ };
79
+ exports.IdentityErrorInterceptor = IdentityErrorInterceptor;
80
+ exports.IdentityErrorInterceptor = IdentityErrorInterceptor = __decorate([
81
+ (0, common_1.Injectable)()
82
+ ], IdentityErrorInterceptor);
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Identity domain errors (security hardening Phase 1). Thrown by identity services, mapped to HTTP
3
+ * by {@link IdentityErrorInterceptor} in ONE place so every identity route answers the same way.
4
+ *
5
+ * Messages are user-facing: they name no raw ids (ADR-0012) and carry no store detail.
6
+ */
7
+ /** The caller may not perform this action on this target (→ 403). */
8
+ export declare class IdentityForbiddenError extends Error {
9
+ constructor(message: string);
10
+ }
11
+ /** The target does not exist or is outside the caller's reach (→ 404, no existence leak). */
12
+ export declare class IdentityNotFoundError extends Error {
13
+ constructor(message: string);
14
+ }
15
+ /** The request conflicts with the current state (cycle, children present, … → 409). */
16
+ export declare class IdentityConflictError extends Error {
17
+ constructor(message: string);
18
+ }
19
+ /** The request is malformed (→ 400). */
20
+ export declare class IdentityBadRequestError extends Error {
21
+ constructor(message: string);
22
+ }
23
+ //# sourceMappingURL=identity-errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-errors.d.ts","sourceRoot":"","sources":["../../../src/features/shared/identity-errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,qEAAqE;AACrE,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED,6FAA6F;AAC7F,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED,uFAAuF;AACvF,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED,wCAAwC;AACxC,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;CAI5B"}