@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.
- package/dist/company-membership.object.d.ts.map +1 -1
- package/dist/company-membership.object.js +3 -2
- package/dist/company.object.d.ts.map +1 -1
- package/dist/company.object.js +8 -3
- package/dist/features/access/index.d.ts.map +1 -1
- package/dist/features/access/index.js +1 -5
- package/dist/features/audit/audit-read.service.d.ts.map +1 -1
- package/dist/features/audit/audit-read.service.js +13 -6
- package/dist/features/audit/audit.controller.d.ts.map +1 -1
- package/dist/features/audit/audit.controller.js +2 -0
- package/dist/features/audit/mariadb-audit-reader.d.ts.map +1 -1
- package/dist/features/authz/access-bundle-reconcile.job.d.ts.map +1 -1
- package/dist/features/authz/catalog-reconcile.job.d.ts.map +1 -1
- package/dist/features/authz/catalog-reconcile.job.js +1 -3
- package/dist/features/authz/db-permission-facts.adapter.d.ts +16 -0
- package/dist/features/authz/db-permission-facts.adapter.d.ts.map +1 -1
- package/dist/features/authz/db-permission-facts.adapter.js +46 -2
- package/dist/features/authz/grant-admin.controller.d.ts +8 -5
- package/dist/features/authz/grant-admin.controller.d.ts.map +1 -1
- package/dist/features/authz/grant-admin.controller.js +20 -18
- package/dist/features/authz/grant-admin.service.d.ts +72 -16
- package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
- package/dist/features/authz/grant-admin.service.js +232 -86
- package/dist/features/authz/grant-ceiling.d.ts +100 -1
- package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
- package/dist/features/authz/grant-ceiling.js +264 -2
- package/dist/features/authz/mariadb-catalog-store.d.ts.map +1 -1
- package/dist/features/authz/mariadb-catalog-store.js +15 -3
- package/dist/features/authz/permission-version.bumper.d.ts +52 -1
- package/dist/features/authz/permission-version.bumper.d.ts.map +1 -1
- package/dist/features/authz/permission-version.bumper.js +119 -20
- package/dist/features/authz/record-authorization.d.ts +12 -0
- package/dist/features/authz/record-authorization.d.ts.map +1 -1
- package/dist/features/authz/record-authorization.js +49 -0
- package/dist/features/authz/role-binding.d.ts +36 -0
- package/dist/features/authz/role-binding.d.ts.map +1 -0
- package/dist/features/authz/role-binding.js +53 -0
- package/dist/features/company/company-hierarchy.service.d.ts +108 -0
- package/dist/features/company/company-hierarchy.service.d.ts.map +1 -0
- package/dist/features/company/company-hierarchy.service.js +277 -0
- package/dist/features/company/company.controller.d.ts +23 -5
- package/dist/features/company/company.controller.d.ts.map +1 -1
- package/dist/features/company/company.controller.js +47 -12
- package/dist/features/company/company.service.d.ts +28 -12
- package/dist/features/company/company.service.d.ts.map +1 -1
- package/dist/features/company/company.service.js +61 -14
- package/dist/features/cross-tenant/cross-tenant-grant.controller.d.ts.map +1 -1
- package/dist/features/cross-tenant/cross-tenant-grant.controller.js +2 -0
- package/dist/features/identity-feature.module.d.ts +2 -0
- package/dist/features/identity-feature.module.d.ts.map +1 -1
- package/dist/features/identity-feature.module.js +72 -5
- package/dist/features/membership/membership.controller.d.ts +6 -5
- package/dist/features/membership/membership.controller.d.ts.map +1 -1
- package/dist/features/membership/membership.controller.js +34 -22
- package/dist/features/membership/membership.service.d.ts +20 -14
- package/dist/features/membership/membership.service.d.ts.map +1 -1
- package/dist/features/membership/membership.service.js +62 -29
- package/dist/features/membership/posting-version-bump.d.ts +12 -0
- package/dist/features/membership/posting-version-bump.d.ts.map +1 -1
- package/dist/features/membership/posting.service.d.ts +20 -24
- package/dist/features/membership/posting.service.d.ts.map +1 -1
- package/dist/features/membership/posting.service.js +101 -64
- package/dist/features/org-structure/org-catalog.controller.d.ts.map +1 -1
- package/dist/features/org-structure/org-catalog.controller.js +2 -0
- package/dist/features/org-structure/org-catalog.service.d.ts.map +1 -1
- package/dist/features/org-structure/org-catalog.service.js +4 -1
- package/dist/features/policy/policy.controller.d.ts.map +1 -1
- package/dist/features/policy/policy.controller.js +2 -0
- package/dist/features/role/role.controller.d.ts +3 -2
- package/dist/features/role/role.controller.d.ts.map +1 -1
- package/dist/features/role/role.controller.js +15 -10
- package/dist/features/role/role.service.d.ts +39 -8
- package/dist/features/role/role.service.d.ts.map +1 -1
- package/dist/features/role/role.service.js +77 -10
- package/dist/features/scope/scope-closure.service.d.ts +54 -4
- package/dist/features/scope/scope-closure.service.d.ts.map +1 -1
- package/dist/features/scope/scope-closure.service.js +237 -85
- package/dist/features/service-account/service-account.controller.d.ts +3 -2
- package/dist/features/service-account/service-account.controller.d.ts.map +1 -1
- package/dist/features/service-account/service-account.controller.js +29 -15
- package/dist/features/service-account/service-account.service.d.ts +29 -7
- package/dist/features/service-account/service-account.service.d.ts.map +1 -1
- package/dist/features/service-account/service-account.service.js +73 -12
- package/dist/features/service-account/service-account.store.d.ts +15 -0
- package/dist/features/service-account/service-account.store.d.ts.map +1 -1
- package/dist/features/service-account/service-account.store.js +29 -0
- package/dist/features/share/share.controller.d.ts.map +1 -1
- package/dist/features/share/share.controller.js +2 -0
- package/dist/features/share/share.service.d.ts.map +1 -1
- package/dist/features/shared/identity-error.interceptor.d.ts +24 -0
- package/dist/features/shared/identity-error.interceptor.d.ts.map +1 -0
- package/dist/features/shared/identity-error.interceptor.js +82 -0
- package/dist/features/shared/identity-errors.d.ts +23 -0
- package/dist/features/shared/identity-errors.d.ts.map +1 -0
- package/dist/features/shared/identity-errors.js +41 -0
- package/dist/features/user/session-revocation-check.d.ts +11 -0
- package/dist/features/user/session-revocation-check.d.ts.map +1 -0
- package/dist/features/user/session-revocation-check.js +21 -0
- package/dist/features/user/user.controller.d.ts +11 -6
- package/dist/features/user/user.controller.d.ts.map +1 -1
- package/dist/features/user/user.controller.js +35 -17
- package/dist/features/user/user.service.d.ts +55 -14
- package/dist/features/user/user.service.d.ts.map +1 -1
- package/dist/features/user/user.service.js +143 -16
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -6
- package/dist/internal.d.ts +10 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +13 -0
- package/dist/membership-posting.object.d.ts.map +1 -1
- package/dist/membership-posting.object.js +4 -2
- package/dist/objects.d.ts.map +1 -1
- package/dist/objects.js +13 -3
- package/dist/role.object.d.ts.map +1 -1
- package/dist/role.object.js +3 -0
- package/dist/user.object.d.ts.map +1 -1
- package/dist/user.object.js +3 -0
- package/migrations/1710000000019-AddSessionMustReenroll.cjs +35 -0
- package/package.json +15 -4
- package/dist/auth-facts-keys.js.map +0 -1
- package/dist/branch.object.js.map +0 -1
- package/dist/company-branch.object.js.map +0 -1
- package/dist/company-department.object.js.map +0 -1
- package/dist/company-membership.object.js.map +0 -1
- package/dist/company.object.js.map +0 -1
- package/dist/cross-tenant-grant.object.js.map +0 -1
- package/dist/department.object.js.map +0 -1
- package/dist/features/access/access-admin.access.js.map +0 -1
- package/dist/features/access/company-admin.access.js.map +0 -1
- package/dist/features/access/index.js.map +0 -1
- package/dist/features/access/user-management.access.js.map +0 -1
- package/dist/features/audit/audit-read.service.js.map +0 -1
- package/dist/features/audit/audit.controller.js.map +0 -1
- package/dist/features/audit/directory-label-resolver.js.map +0 -1
- package/dist/features/audit/mariadb-audit-reader.js.map +0 -1
- package/dist/features/authz/access-bundle-reconcile.job.js.map +0 -1
- package/dist/features/authz/access-bundle-store.js.map +0 -1
- package/dist/features/authz/catalog-bootstrap.provider.js.map +0 -1
- package/dist/features/authz/catalog-reconcile.job.js.map +0 -1
- package/dist/features/authz/db-permission-facts.adapter.js.map +0 -1
- package/dist/features/authz/grant-admin.controller.js.map +0 -1
- package/dist/features/authz/grant-admin.service.js.map +0 -1
- package/dist/features/authz/grant-ceiling.js.map +0 -1
- package/dist/features/authz/mariadb-catalog-store.js.map +0 -1
- package/dist/features/authz/permission-version.bumper.js.map +0 -1
- package/dist/features/authz/record-authorization.js.map +0 -1
- package/dist/features/authz/session-facts.reader.js.map +0 -1
- package/dist/features/company/company.controller.js.map +0 -1
- package/dist/features/company/company.service.js.map +0 -1
- package/dist/features/cross-tenant/cross-tenant-grant-read.service.js.map +0 -1
- package/dist/features/cross-tenant/cross-tenant-grant.controller.js.map +0 -1
- package/dist/features/cross-tenant/grant-authorize.js.map +0 -1
- package/dist/features/identity-feature.module.js.map +0 -1
- package/dist/features/membership/membership-checker.js.map +0 -1
- package/dist/features/membership/membership.controller.js.map +0 -1
- package/dist/features/membership/membership.service.js.map +0 -1
- package/dist/features/membership/posting-version-bump.js.map +0 -1
- package/dist/features/membership/posting.service.js.map +0 -1
- package/dist/features/org-structure/org-catalog.controller.js.map +0 -1
- package/dist/features/org-structure/org-catalog.service.js.map +0 -1
- package/dist/features/policy/policy.controller.js.map +0 -1
- package/dist/features/policy/policy.service.js.map +0 -1
- package/dist/features/role/role.controller.js.map +0 -1
- package/dist/features/role/role.service.js.map +0 -1
- package/dist/features/scope/scope-closure.service.js.map +0 -1
- package/dist/features/scope/scope-subtree.resolver.js.map +0 -1
- package/dist/features/share/share.controller.js.map +0 -1
- package/dist/features/share/share.service.js.map +0 -1
- package/dist/features/shared/http.js.map +0 -1
- package/dist/features/shared/list-query.js.map +0 -1
- package/dist/features/shared/named-option.js.map +0 -1
- package/dist/features/shared/record-resolver.js.map +0 -1
- package/dist/features/user/user.controller.js.map +0 -1
- package/dist/features/user/user.service.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/membership-posting.object.js.map +0 -1
- package/dist/migrations-dir.js.map +0 -1
- package/dist/objects.js.map +0 -1
- package/dist/record-share.object.js.map +0 -1
- package/dist/role.object.js.map +0 -1
- package/dist/schema.js.map +0 -1
- package/dist/shared-resource.object.js.map +0 -1
- 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
|
-
/**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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
|
|
166
|
-
const expiresAtMs = optExpiry(b.expiresAtMs);
|
|
180
|
+
const expiry = expiryOf((body ?? {}));
|
|
167
181
|
try {
|
|
168
|
-
const key = await this.svc.issueKey(adminOf(req), id,
|
|
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
|
|
181
|
-
const expiresAtMs = optExpiry(b.expiresAtMs);
|
|
194
|
+
const expiry = expiryOf((body ?? {}));
|
|
182
195
|
try {
|
|
183
|
-
const key = await this.svc.rotateKey(adminOf(req), id,
|
|
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
|
-
*
|
|
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
|
-
|
|
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":"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;
|
|
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 = {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
378
|
-
|
|
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
|
-
: {
|
|
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
|
-
*
|
|
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',
|
|
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',
|
|
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({
|
|
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":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;
|
|
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":"
|
|
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,
|
|
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"}
|