@astram/identity 0.7.0 → 0.7.2
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/features/authz/grant-admin.controller.d.ts.map +1 -1
- package/dist/features/authz/grant-admin.controller.js +5 -5
- package/dist/features/authz/grant-admin.service.d.ts +8 -0
- package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
- package/dist/features/authz/grant-admin.service.js +18 -0
- package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
- package/dist/features/authz/grant-ceiling.js +9 -4
- package/dist/features/company/company-hierarchy.service.d.ts +7 -0
- package/dist/features/company/company-hierarchy.service.d.ts.map +1 -1
- package/dist/features/company/company-hierarchy.service.js +17 -2
- package/dist/features/company/company.controller.d.ts.map +1 -1
- package/dist/features/company/company.controller.js +9 -5
- package/dist/features/company/company.service.d.ts +5 -0
- package/dist/features/company/company.service.d.ts.map +1 -1
- package/dist/features/company/company.service.js +12 -3
- package/dist/features/cross-tenant/cross-tenant-grant.controller.d.ts.map +1 -1
- package/dist/features/cross-tenant/cross-tenant-grant.controller.js +5 -10
- package/dist/features/membership/membership.controller.d.ts.map +1 -1
- package/dist/features/membership/membership.controller.js +25 -8
- package/dist/features/org-structure/org-catalog.controller.d.ts.map +1 -1
- package/dist/features/org-structure/org-catalog.controller.js +2 -2
- package/dist/features/policy/policy.controller.d.ts.map +1 -1
- package/dist/features/policy/policy.controller.js +1 -1
- package/dist/features/role/role.controller.d.ts.map +1 -1
- package/dist/features/role/role.controller.js +3 -3
- package/dist/features/service-account/service-account.controller.d.ts.map +1 -1
- package/dist/features/service-account/service-account.controller.js +12 -18
- package/dist/features/service-account/service-account.service.d.ts.map +1 -1
- package/dist/features/service-account/service-account.service.js +27 -8
- package/dist/features/share/share.controller.d.ts.map +1 -1
- package/dist/features/share/share.controller.js +7 -7
- package/dist/features/shared/http.d.ts +14 -2
- package/dist/features/shared/http.d.ts.map +1 -1
- package/dist/features/shared/http.js +22 -1
- package/dist/features/user/user.controller.d.ts.map +1 -1
- package/dist/features/user/user.controller.js +5 -6
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grant-admin.controller.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-admin.controller.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAe,MAAM,0BAA0B,CAAC;AAI1E,qFAAqF;AACrF,UAAU,aAAa;IACrB,SAAS,EAAE,SAAS,CAAC;CACtB;AAaD;;;;;;;;;;;;;;GAcG;AACH,qBAEa,oBAAoB;IACnB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,iBAAiB;IAEtD;;;;;;;;OAQG;IAGG,aAAa,CACV,GAAG,EAAE,aAAa,EAEzB,KAAK,EAAE;QACL,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;;;;;;IAcH,sDAAsD;IAGhD,OAAO;;;;;;;;;;IAIb;kGAC8F;IAGxF,IAAI,CAAQ,GAAG,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"grant-admin.controller.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-admin.controller.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAe,MAAM,0BAA0B,CAAC;AAI1E,qFAAqF;AACrF,UAAU,aAAa;IACrB,SAAS,EAAE,SAAS,CAAC;CACtB;AAaD;;;;;;;;;;;;;;GAcG;AACH,qBAEa,oBAAoB;IACnB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,iBAAiB;IAEtD;;;;;;;;OAQG;IAGG,aAAa,CACV,GAAG,EAAE,aAAa,EAEzB,KAAK,EAAE;QACL,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;;;;;;IAcH,sDAAsD;IAGhD,OAAO;;;;;;;;;;IAIb;kGAC8F;IAGxF,IAAI,CAAQ,GAAG,EAAE,aAAa,EAA2B,IAAI,EAAE,MAAM;;;IAI3E,0CAA0C;IAGpC,KAAK,CACF,GAAG,EAAE,aAAa,EACA,IAAI,EAAE,MAAM,EAC7B,IAAI,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE;;;IAahC,mFAAmF;IAG7E,MAAM,CACH,GAAG,EAAE,aAAa,EACA,IAAI,EAAE,MAAM,EACvB,GAAG,EAAE,MAAM;;;IAQ3B;;;;OAIG;IAGG,OAAO;;;IAIb;;;;OAIG;IAGG,WAAW,CACR,GAAG,EAAE,aAAa,EACA,IAAI,EAAE,MAAM,EAC7B,IAAI,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE;;;IAahC;;;;OAIG;IAGG,YAAY,CACT,GAAG,EAAE,aAAa,EACA,IAAI,EAAE,MAAM,EACvB,GAAG,EAAE,MAAM;;;CAU5B"}
|
|
@@ -170,7 +170,7 @@ __decorate([
|
|
|
170
170
|
(0, common_1.Get)('roles/:uuid/permissions'),
|
|
171
171
|
(0, guard_1.Classified)({ typeKey: 'role', action: 'read' }),
|
|
172
172
|
__param(0, (0, common_1.Req)()),
|
|
173
|
-
__param(1, (0, common_1.Param)('uuid')),
|
|
173
|
+
__param(1, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
174
174
|
__metadata("design:type", Function),
|
|
175
175
|
__metadata("design:paramtypes", [Object, String]),
|
|
176
176
|
__metadata("design:returntype", Promise)
|
|
@@ -179,7 +179,7 @@ __decorate([
|
|
|
179
179
|
(0, common_1.Post)('roles/:uuid/permissions'),
|
|
180
180
|
(0, guard_1.Classified)({ typeKey: 'role', action: 'update' }),
|
|
181
181
|
__param(0, (0, common_1.Req)()),
|
|
182
|
-
__param(1, (0, common_1.Param)('uuid')),
|
|
182
|
+
__param(1, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
183
183
|
__param(2, (0, common_1.Body)()),
|
|
184
184
|
__metadata("design:type", Function),
|
|
185
185
|
__metadata("design:paramtypes", [Object, String, Object]),
|
|
@@ -189,7 +189,7 @@ __decorate([
|
|
|
189
189
|
(0, common_1.Delete)('roles/:uuid/permissions/:key'),
|
|
190
190
|
(0, guard_1.Classified)({ typeKey: 'role', action: 'update' }),
|
|
191
191
|
__param(0, (0, common_1.Req)()),
|
|
192
|
-
__param(1, (0, common_1.Param)('uuid')),
|
|
192
|
+
__param(1, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
193
193
|
__param(2, (0, common_1.Param)('key')),
|
|
194
194
|
__metadata("design:type", Function),
|
|
195
195
|
__metadata("design:paramtypes", [Object, String, String]),
|
|
@@ -206,7 +206,7 @@ __decorate([
|
|
|
206
206
|
(0, common_1.Post)('roles/:uuid/access-bundles'),
|
|
207
207
|
(0, guard_1.Classified)({ typeKey: 'role', action: 'update' }),
|
|
208
208
|
__param(0, (0, common_1.Req)()),
|
|
209
|
-
__param(1, (0, common_1.Param)('uuid')),
|
|
209
|
+
__param(1, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
210
210
|
__param(2, (0, common_1.Body)()),
|
|
211
211
|
__metadata("design:type", Function),
|
|
212
212
|
__metadata("design:paramtypes", [Object, String, Object]),
|
|
@@ -216,7 +216,7 @@ __decorate([
|
|
|
216
216
|
(0, common_1.Delete)('roles/:uuid/access-bundles/:key'),
|
|
217
217
|
(0, guard_1.Classified)({ typeKey: 'role', action: 'update' }),
|
|
218
218
|
__param(0, (0, common_1.Req)()),
|
|
219
|
-
__param(1, (0, common_1.Param)('uuid')),
|
|
219
|
+
__param(1, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
220
220
|
__param(2, (0, common_1.Param)('key')),
|
|
221
221
|
__metadata("design:type", Function),
|
|
222
222
|
__metadata("design:paramtypes", [Object, String, String]),
|
|
@@ -148,6 +148,14 @@ export declare class GrantAdminService {
|
|
|
148
148
|
* role after the candidate read is not seen; the outcome equals "this change, then that edit"
|
|
149
149
|
* (a role delete leaves that agent an inert uuid: the role and its grants are gone).
|
|
150
150
|
*/
|
|
151
|
+
/**
|
|
152
|
+
* The role's uuid as stored (Phase 10 #1): `r_uuid` is `utf8mb4_unicode_ci`, so a route uuid in
|
|
153
|
+
* another letter case reaches the row, but `JSON_CONTAINS(sa_role_ids, …)` and the `sa_role_ids`
|
|
154
|
+
* rewrite are exact — an upper-cased uuid would find no holding agent (skipping their check) and
|
|
155
|
+
* write grants under a second spelling. Unknown → the input unchanged (later steps 404). A
|
|
156
|
+
* consistent read: `purgeRole` runs while the caller's delete of this row is uncommitted.
|
|
157
|
+
*/
|
|
158
|
+
private storedRoleUuid;
|
|
151
159
|
private lockAccountsHoldingRole;
|
|
152
160
|
/** All active bundles + their resolved atomic keys, for the Access UI. */
|
|
153
161
|
listBundles(): Promise<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grant-admin.service.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-admin.service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAc,SAAS,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EAAE,YAAY,EAAa,MAAM,oBAAoB,CAAC;AAuC7D;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED;6EAC6E;AAC7E,qBAAa,WAAY,SAAQ,KAAK;gBACxB,GAAG,EAAE,MAAM;CAIxB;AAED,KAAK,UAAU,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElG,sFAAsF;AACtF,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,qBAAa,iBAAiB;IAa1B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAEvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IAhBzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAE5C;;;;;;;;OAQG;gBAEgB,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,uBAAuB,EAChD,WAAW,CAAC,EAAE,iBAAiB,EACd,OAAO,CAAC,EAAE,YAAY,YAAA,EACtB,KAAK,CAAC,EAAE,SAAS,YAAA;IAKpC,+EAA+E;IACzE,WAAW,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,UAAU,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAmC5E;;;;OAIG;IACG,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,YAAY,GACzB,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IA0BhC,0FAA0F;YAC5E,cAAc;IAM5B;;;;;;OAMG;YACW,qBAAqB;IAUnC;;;;;OAKG;YACW,WAAW;IA0DzB;;;;;;OAMG;YACW,gBAAgB;IA0D9B;;;;;OAKG;IACG,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"grant-admin.service.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-admin.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -242,6 +242,7 @@ class GrantAdminService {
|
|
|
242
242
|
* (404/403) → ceiling (403) → write. Root bypasses the binding rule and the ceiling.
|
|
243
243
|
*/
|
|
244
244
|
async grant(roleUuid, key, grantor) {
|
|
245
|
+
roleUuid = await this.storedRoleUuid(roleUuid);
|
|
245
246
|
await this.assertValidKey(key); // throws BadKeyError (400) before touching the store
|
|
246
247
|
await this.assertMayChangeGrants(roleUuid, [key], grantor);
|
|
247
248
|
const actorUuid = grantor.id;
|
|
@@ -263,6 +264,7 @@ class GrantAdminService {
|
|
|
263
264
|
* role lock in `writeGrants`). Root bypasses.
|
|
264
265
|
*/
|
|
265
266
|
async revoke(roleUuid, key, actor) {
|
|
267
|
+
roleUuid = await this.storedRoleUuid(roleUuid);
|
|
266
268
|
await this.assertMayChangeGrants(roleUuid, [key], actor);
|
|
267
269
|
await this.writeGrants('role.permission_revoked', roleUuid, [key], actor, async (conn, k) => {
|
|
268
270
|
await conn.query(`DELETE FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?`, [roleUuid, k]);
|
|
@@ -291,6 +293,7 @@ class GrantAdminService {
|
|
|
291
293
|
* agents: fewer permissions, never live grants for a deleted role (fail closed).
|
|
292
294
|
*/
|
|
293
295
|
async purgeRole(roleUuid, actor) {
|
|
296
|
+
roleUuid = await this.storedRoleUuid(roleUuid);
|
|
294
297
|
const conn = await this.pool.getConnection();
|
|
295
298
|
try {
|
|
296
299
|
await conn.beginTransaction();
|
|
@@ -336,6 +339,19 @@ class GrantAdminService {
|
|
|
336
339
|
* role after the candidate read is not seen; the outcome equals "this change, then that edit"
|
|
337
340
|
* (a role delete leaves that agent an inert uuid: the role and its grants are gone).
|
|
338
341
|
*/
|
|
342
|
+
/**
|
|
343
|
+
* The role's uuid as stored (Phase 10 #1): `r_uuid` is `utf8mb4_unicode_ci`, so a route uuid in
|
|
344
|
+
* another letter case reaches the row, but `JSON_CONTAINS(sa_role_ids, …)` and the `sa_role_ids`
|
|
345
|
+
* rewrite are exact — an upper-cased uuid would find no holding agent (skipping their check) and
|
|
346
|
+
* write grants under a second spelling. Unknown → the input unchanged (later steps 404). A
|
|
347
|
+
* consistent read: `purgeRole` runs while the caller's delete of this row is uncommitted.
|
|
348
|
+
*/
|
|
349
|
+
async storedRoleUuid(roleUuid) {
|
|
350
|
+
if (typeof roleUuid !== 'string' || roleUuid.length === 0)
|
|
351
|
+
return roleUuid;
|
|
352
|
+
const rows = (await this.pool.query(`SELECT r_uuid FROM \`${APP}\`.\`roles\` WHERE r_uuid = ? LIMIT 1`, [roleUuid]));
|
|
353
|
+
return rows[0]?.r_uuid ?? roleUuid;
|
|
354
|
+
}
|
|
339
355
|
lockAccountsHoldingRole(conn, roleUuid) {
|
|
340
356
|
return (0, lock_service_accounts_js_1.lockServiceAccountsWhere)(conn, 'sa_id, sa_tenant_id, sa_role_ids, sa_updated_at_ms', {
|
|
341
357
|
sql: 'JSON_CONTAINS(sa_role_ids, JSON_QUOTE(?))',
|
|
@@ -361,6 +377,7 @@ class GrantAdminService {
|
|
|
361
377
|
* to; otherwise `GrantCeilingError` (403), checked before any write. Root bypasses.
|
|
362
378
|
*/
|
|
363
379
|
async grantBundle(roleUuid, bundleKey, grantor) {
|
|
380
|
+
roleUuid = await this.storedRoleUuid(roleUuid);
|
|
364
381
|
const keys = await this.resolveBundleKeysOrThrow(bundleKey);
|
|
365
382
|
// Binding rule + ADR-0029 ceiling: a non-root grantor must hold EVERY atomic key the bundle
|
|
366
383
|
// expands to — checked before any write (no partial bundle escalation).
|
|
@@ -384,6 +401,7 @@ class GrantAdminService {
|
|
|
384
401
|
* #1: the binding rule + "only revoke what you hold" apply (root bypasses).
|
|
385
402
|
*/
|
|
386
403
|
async revokeBundle(roleUuid, bundleKey, actor) {
|
|
404
|
+
roleUuid = await this.storedRoleUuid(roleUuid);
|
|
387
405
|
const keys = await this.resolveBundleKeysOrThrow(bundleKey);
|
|
388
406
|
await this.assertMayChangeGrants(roleUuid, [...keys], actor);
|
|
389
407
|
await this.writeGrants('role.bundle_revoked', roleUuid, keys, actor, async (conn, key) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grant-ceiling.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-ceiling.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"grant-ceiling.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-ceiling.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAI9D,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAetE,6FAA6F;AAC7F,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;CAI1C;AAED,sGAAsG;AACtG,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,MAAM,EAAE,CAE7D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,EAC9B,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,OAAO,EAAE,MAAM,GACd,OAAO,CAST;AAED;;;GAGG;AACH,qBAAa,YAAY;IAIrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAQzB,+FAA+F;IAC/F,OAAO,CAAC,QAAQ,CAAC,GAAG;IAbtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;gBAGzB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,cAAc;IACzC;;;;;OAKG;IACH,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IACjC,+FAA+F;IAC9E,GAAG,GAAE,MAAM,MAAiB;IAU/C,+FAA+F;IAC/F,MAAM,CAAC,kBAAkB,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI;IAS/C,qFAAqF;IACrF,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI;IAQ5C,oEAAoE;IACpE,OAAO,CAAC,cAAc;IAItB,+FAA+F;YACjF,cAAc;IAU5B,+FAA+F;YACjF,KAAK;IASnB;;;;;OAKG;IACG,aAAa,CAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAkB7D;;;;OAIG;IACG,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAahF;;;;;;OAMG;IACG,oBAAoB,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBvF;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAQtB;;;;;;OAMG;IACG,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+D5E;;;;;;;;;;;;OAYG;IACG,0BAA0B,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BnF;;;;;;;;;OASG;IACG,qCAAqC,CACzC,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,SAAS,MAAM,EAAE,EACnC,SAAS,GAAE,QAAQ,GAAG,QAAmB,GACxC,OAAO,CAAC,IAAI,CAAC;IAsBhB;;;;;;;;OAQG;IACG,8BAA8B,CAClC,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,aAAa,CAAC;QACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;QAChD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;KACrC,CAAC,GACD,OAAO,CAAC,IAAI,CAAC;IA6BhB;;;;;;;;OAQG;IACG,uBAAuB,CAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,KAAK,EAAE;QAAE,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,GAC9F,OAAO,CAAC,IAAI,CAAC;IAuChB,+FAA+F;IACzF,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAI3E;;;;;OAKG;IACG,uBAAuB,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKhF;;;;OAIG;IACG,oBAAoB,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAI9E"}
|
|
@@ -4,6 +4,7 @@ exports.GrantCeiling = exports.GrantCeilingError = void 0;
|
|
|
4
4
|
exports.subtreeOf = subtreeOf;
|
|
5
5
|
exports.coversKey = coversKey;
|
|
6
6
|
const core_1 = require("@astram/core");
|
|
7
|
+
const http_js_1 = require("../shared/http.js");
|
|
7
8
|
const db_permission_facts_adapter_js_1 = require("./db-permission-facts.adapter.js");
|
|
8
9
|
const role_binding_js_1 = require("./role-binding.js");
|
|
9
10
|
const identity_errors_js_1 = require("../shared/identity-errors.js");
|
|
@@ -192,12 +193,12 @@ class GrantCeiling {
|
|
|
192
193
|
async assertDominates(actor, targetUserId) {
|
|
193
194
|
if (actor.isRoot === true)
|
|
194
195
|
return;
|
|
195
|
-
if (this.rootIds.
|
|
196
|
+
if (this.rootIds.some((r) => (0, http_js_1.sameUuid)(r, targetUserId))) {
|
|
196
197
|
throw new identity_errors_js_1.IdentityForbiddenError('You cannot change this user: they are a platform administrator.');
|
|
197
198
|
}
|
|
198
199
|
const subtree = new Set(subtreeOf(actor));
|
|
199
|
-
const { companies, roleIds, superuser } = await this.facts('target memberships', async () => {
|
|
200
|
-
const su = (await this.pool.query(`SELECT u_is_platform_superuser AS su FROM \`${APP}\`.\`users\` WHERE u_uuid = ? LIMIT 1`, [targetUserId]));
|
|
200
|
+
const { companies, roleIds, superuser, storedId } = await this.facts('target memberships', async () => {
|
|
201
|
+
const su = (await this.pool.query(`SELECT u_uuid, u_is_platform_superuser AS su FROM \`${APP}\`.\`users\` WHERE u_uuid = ? LIMIT 1`, [targetUserId]));
|
|
201
202
|
const memberships = (await this.pool.query(`SELECT DISTINCT cm_company_uuid AS company
|
|
202
203
|
FROM \`${APP}\`.\`company_membership\`
|
|
203
204
|
WHERE cm_user_uuid = ?`, [targetUserId]));
|
|
@@ -209,9 +210,13 @@ class GrantCeiling {
|
|
|
209
210
|
companies: memberships.map((m) => m.company),
|
|
210
211
|
roleIds: postings.map((p) => p.role_uuid),
|
|
211
212
|
superuser: Number(su[0]?.su ?? 0) === 1,
|
|
213
|
+
storedId: typeof su[0]?.u_uuid === 'string' ? su[0].u_uuid : targetUserId,
|
|
212
214
|
};
|
|
213
215
|
});
|
|
214
|
-
|
|
216
|
+
// Phase 11 #1: `u_uuid` is `utf8mb4_unicode_ci` (case, trailing spaces and character width are
|
|
217
|
+
// ignored), so the caller's spelling may reach a platform administrator's row without matching
|
|
218
|
+
// `rootIds`. Judge the STORED uuid too.
|
|
219
|
+
if (superuser || this.rootIds.some((r) => (0, http_js_1.sameUuid)(r, storedId))) {
|
|
215
220
|
throw new identity_errors_js_1.IdentityForbiddenError('You cannot change this user: they are a platform administrator.');
|
|
216
221
|
}
|
|
217
222
|
// Security review F5: a user with NO membership is in nobody's subtree — refuse like the
|
|
@@ -54,6 +54,13 @@ export declare class CompanyHierarchyService {
|
|
|
54
54
|
ceiling: MemberDominance, audit?: AuditPort | undefined, now?: () => number);
|
|
55
55
|
/** The actor's own root node (see {@link ownRootOf}). */
|
|
56
56
|
private ownRoot;
|
|
57
|
+
/**
|
|
58
|
+
* The company's uuid as stored (Phase 10 #1): `c_uuid` is `utf8mb4_unicode_ci`, so a route uuid
|
|
59
|
+
* in another letter case still reaches the row, while JS-side comparisons (own root, agent
|
|
60
|
+
* tenants, `JSON_CONTAINS` over scope lists, name maps) are exact. Every entry resolves the
|
|
61
|
+
* stored spelling once and passes that down. Unknown → the input unchanged (later steps 404).
|
|
62
|
+
*/
|
|
63
|
+
private storedUuid;
|
|
57
64
|
/** Names of the given companies (unknown → absent). */
|
|
58
65
|
private names;
|
|
59
66
|
private recordAudit;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"company-hierarchy.service.d.ts","sourceRoot":"","sources":["../../../src/features/company/company-hierarchy.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"company-hierarchy.service.d.ts","sourceRoot":"","sources":["../../../src/features/company/company-hierarchy.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAAE,mBAAmB,EAAkB,MAAM,mCAAmC,CAAC;AAWxF,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,8BAA8B,CAC5B,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,aAAa,CAAC;QACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;QAChD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;KACrC,CAAC,GACD,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAuCD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAElD;AAED,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,2FAA2F;IAC3F,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;gBALH,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,mBAAmB;IAC7C,2FAA2F;IAC1E,OAAO,EAAE,eAAe,EACxB,KAAK,CAAC,EAAE,SAAS,YAAA,EACjB,GAAG,GAAE,MAAM,MAAiB;IAG/C,yDAAyD;IACzD,OAAO,CAAC,OAAO;IAIf;;;;;OAKG;YACW,UAAU;IAQxB,uDAAuD;YACzC,KAAK;YAUL,WAAW;IAqBzB,iGAAiG;IAC3F,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAoD3F;;;;;;;;;;;;;;;;;OAiBG;IACG,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB/D;;;;;;;;;;;;;;;;;;;;;;;OAuBG;YACW,gBAAgB;IAiF9B,0FAA0F;YAC5E,kBAAkB;IAmDhC,wFAAwF;YAC1E,sBAAsB;IA+BpC,wFAAwF;IAClF,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGhD"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CompanyHierarchyService = void 0;
|
|
4
4
|
exports.ownRootOf = ownRootOf;
|
|
5
|
+
const http_js_1 = require("../shared/http.js");
|
|
5
6
|
const grant_ceiling_js_1 = require("../authz/grant-ceiling.js");
|
|
6
7
|
const lock_service_accounts_js_1 = require("../service-account/lock-service-accounts.js");
|
|
7
8
|
const identity_errors_js_1 = require("../shared/identity-errors.js");
|
|
@@ -46,6 +47,16 @@ class CompanyHierarchyService {
|
|
|
46
47
|
ownRoot(actor) {
|
|
47
48
|
return ownRootOf(actor);
|
|
48
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* The company's uuid as stored (Phase 10 #1): `c_uuid` is `utf8mb4_unicode_ci`, so a route uuid
|
|
52
|
+
* in another letter case still reaches the row, while JS-side comparisons (own root, agent
|
|
53
|
+
* tenants, `JSON_CONTAINS` over scope lists, name maps) are exact. Every entry resolves the
|
|
54
|
+
* stored spelling once and passes that down. Unknown → the input unchanged (later steps 404).
|
|
55
|
+
*/
|
|
56
|
+
async storedUuid(uuid) {
|
|
57
|
+
const rows = (await this.pool.query(`SELECT c_uuid FROM \`${APP}\`.\`companies\` WHERE c_uuid = ? LIMIT 1`, [uuid]));
|
|
58
|
+
return rows[0]?.c_uuid ?? uuid;
|
|
59
|
+
}
|
|
49
60
|
/** Names of the given companies (unknown → absent). */
|
|
50
61
|
async names(q, ids) {
|
|
51
62
|
const unique = [...new Set(ids)];
|
|
@@ -75,11 +86,14 @@ class CompanyHierarchyService {
|
|
|
75
86
|
(typeof newParentUuid !== 'string' || newParentUuid.length === 0)) {
|
|
76
87
|
throw new identity_errors_js_1.IdentityBadRequestError('parentUuid must be a company or null.');
|
|
77
88
|
}
|
|
89
|
+
nodeUuid = await this.storedUuid(nodeUuid);
|
|
90
|
+
if (newParentUuid !== null)
|
|
91
|
+
newParentUuid = await this.storedUuid(newParentUuid);
|
|
78
92
|
if (actor.isRoot !== true) {
|
|
79
93
|
const subtree = new Set((0, grant_ceiling_js_1.subtreeOf)(actor));
|
|
80
94
|
if (!subtree.has(nodeUuid))
|
|
81
95
|
throw new identity_errors_js_1.IdentityNotFoundError('Company not found.');
|
|
82
|
-
if (nodeUuid
|
|
96
|
+
if ((0, http_js_1.sameUuid)(nodeUuid, this.ownRoot(actor))) {
|
|
83
97
|
throw new identity_errors_js_1.IdentityForbiddenError('You cannot move the organisation your access is rooted at.');
|
|
84
98
|
}
|
|
85
99
|
if (newParentUuid === null || !subtree.has(newParentUuid)) {
|
|
@@ -125,9 +139,10 @@ class CompanyHierarchyService {
|
|
|
125
139
|
* depending on it as they were; a commit leaves a deleted company with no live agent or grant.
|
|
126
140
|
*/
|
|
127
141
|
async remove(actor, nodeUuid) {
|
|
142
|
+
nodeUuid = await this.storedUuid(nodeUuid); // Phase 10 #1: one spelling downstream
|
|
128
143
|
// Security review F6: the same own-root guard as `move` — a sub-admin never deletes the
|
|
129
144
|
// organisation its access is rooted at.
|
|
130
|
-
if (actor.isRoot !== true && nodeUuid
|
|
145
|
+
if (actor.isRoot !== true && (0, http_js_1.sameUuid)(nodeUuid, this.ownRoot(actor))) {
|
|
131
146
|
throw new identity_errors_js_1.IdentityForbiddenError('You cannot delete the organisation your access is rooted at.');
|
|
132
147
|
}
|
|
133
148
|
const name = (await this.names(this.pool, [nodeUuid])).get(nodeUuid) ?? '(unknown)';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"company.controller.d.ts","sourceRoot":"","sources":["../../../src/features/company/company.controller.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvD,qBAEa,iBAAiB;IAChB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,cAAc;IAEtD;;;;OAIG;IAGG,aAAa,CAAU,KAAK,EAAE,YAAY;;;;;;IAMhD;;;OAGG;IAGG,aAAa,CAAQ,GAAG,EAAE,aAAa,EAAU,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;;;IAK9E,2DAA2D;IAG3D,UAAU,
|
|
1
|
+
{"version":3,"file":"company.controller.d.ts","sourceRoot":"","sources":["../../../src/features/company/company.controller.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvD,qBAEa,iBAAiB;IAChB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,cAAc;IAEtD;;;;OAIG;IAGG,aAAa,CAAU,KAAK,EAAE,YAAY;;;;;;IAMhD;;;OAGG;IAGG,aAAa,CAAQ,GAAG,EAAE,aAAa,EAAU,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;;;IAK9E,2DAA2D;IAG3D,UAAU,CAA0B,IAAI,EAAE,MAAM;IAIhD;;;;;OAKG;IAGG,WAAW,CACR,GAAG,EAAE,aAAa,EACA,IAAI,EAAE,MAAM,EAC7B,IAAI,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;;;IAiB9C;;;OAGG;IAGG,aAAa,CACV,GAAG,EAAE,aAAa,EACA,IAAI,EAAE,MAAM,EAC7B,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;;;IASlD,iGAAiG;IAG3F,aAAa,CAAQ,GAAG,EAAE,aAAa,EAA2B,IAAI,EAAE,MAAM;;;CAIrF"}
|
|
@@ -57,7 +57,11 @@ let CompanyController = class CompanyController {
|
|
|
57
57
|
(body.parentUuid !== null && typeof body.parentUuid !== 'string')) {
|
|
58
58
|
throw new common_1.BadRequestException('parentUuid (a company id, or null) is required');
|
|
59
59
|
}
|
|
60
|
-
|
|
60
|
+
const parentUuid = (0, guard_1.optionalUuidField)(body.parentUuid, 'parentUuid', { keepNull: true });
|
|
61
|
+
if (parentUuid === undefined) {
|
|
62
|
+
throw new common_1.BadRequestException('parentUuid (a company id, or null) is required');
|
|
63
|
+
}
|
|
64
|
+
await this.companies.moveCompany(req.principal, uuid, parentUuid);
|
|
61
65
|
return { success: true };
|
|
62
66
|
}
|
|
63
67
|
/**
|
|
@@ -98,7 +102,7 @@ __decorate([
|
|
|
98
102
|
__decorate([
|
|
99
103
|
(0, common_1.Get)('companies/:uuid'),
|
|
100
104
|
(0, guard_1.Classified)({ typeKey: 'company', action: 'read' }),
|
|
101
|
-
__param(0, (0, common_1.Param)('uuid')),
|
|
105
|
+
__param(0, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
102
106
|
__metadata("design:type", Function),
|
|
103
107
|
__metadata("design:paramtypes", [String]),
|
|
104
108
|
__metadata("design:returntype", void 0)
|
|
@@ -107,7 +111,7 @@ __decorate([
|
|
|
107
111
|
(0, common_1.Put)('companies/:uuid/parent'),
|
|
108
112
|
(0, guard_1.Classified)({ typeKey: 'company', action: 'update' }),
|
|
109
113
|
__param(0, (0, common_1.Req)()),
|
|
110
|
-
__param(1, (0, common_1.Param)('uuid')),
|
|
114
|
+
__param(1, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
111
115
|
__param(2, (0, common_1.Body)()),
|
|
112
116
|
__metadata("design:type", Function),
|
|
113
117
|
__metadata("design:paramtypes", [Object, String, Object]),
|
|
@@ -117,7 +121,7 @@ __decorate([
|
|
|
117
121
|
(0, common_1.Put)('companies/:uuid'),
|
|
118
122
|
(0, guard_1.Classified)({ typeKey: 'company', action: 'update' }),
|
|
119
123
|
__param(0, (0, common_1.Req)()),
|
|
120
|
-
__param(1, (0, common_1.Param)('uuid')),
|
|
124
|
+
__param(1, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
121
125
|
__param(2, (0, common_1.Body)()),
|
|
122
126
|
__metadata("design:type", Function),
|
|
123
127
|
__metadata("design:paramtypes", [Object, String, Object]),
|
|
@@ -127,7 +131,7 @@ __decorate([
|
|
|
127
131
|
(0, common_1.Delete)('companies/:uuid'),
|
|
128
132
|
(0, guard_1.Classified)({ typeKey: 'company', action: 'delete' }),
|
|
129
133
|
__param(0, (0, common_1.Req)()),
|
|
130
|
-
__param(1, (0, common_1.Param)('uuid')),
|
|
134
|
+
__param(1, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
131
135
|
__metadata("design:type", Function),
|
|
132
136
|
__metadata("design:paramtypes", [Object, String]),
|
|
133
137
|
__metadata("design:returntype", Promise)
|
|
@@ -33,6 +33,11 @@ export declare class CompanyService {
|
|
|
33
33
|
* rooted at (the same own-root guard as move and delete, F6): deactivating it would act on the
|
|
34
34
|
* whole tenant above the actor's authority. Re-sending the current status (the company form
|
|
35
35
|
* always sends both fields) stays allowed, so the own root can still be renamed.
|
|
36
|
+
*
|
|
37
|
+
* Phase 11 #1: the guard compares the STORED uuid, never the caller's spelling. `c_uuid` is
|
|
38
|
+
* `utf8mb4_unicode_ci`, which also ignores trailing spaces and character width, so a padded or
|
|
39
|
+
* fullwidth route uuid reaches the same row. A non-root status change therefore reads the company
|
|
40
|
+
* first (the walled read; out of reach → 404) and judges and writes by the id that read returns.
|
|
36
41
|
*/
|
|
37
42
|
updateCompany(actor: Principal, uuid: string, patch: Readonly<Record<string, unknown>>): Promise<void>;
|
|
38
43
|
/** Move a company under a new parent (hierarchy rules + cycle check, see the hierarchy service). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"company.service.d.ts","sourceRoot":"","sources":["../../../src/features/company/company.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EAAE,uBAAuB,EAAa,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"company.service.d.ts","sourceRoot":"","sources":["../../../src/features/company/company.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EAAE,uBAAuB,EAAa,MAAM,gCAAgC,CAAC;AAUpF;;;;;;;GAOG;AACH,qBACa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBADT,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,uBAAuB;IAGrD;;;;;OAKG;IACG,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAetE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAE1D,2EAA2E;IAC3E,aAAa,CAAC,MAAM,CAAC,EAAE,UAAU;IAOjC,6BAA6B;IAC7B,UAAU,CAAC,IAAI,EAAE,MAAM;IAIvB;;;;;;;;;;;;OAYG;IACG,aAAa,CACjB,KAAK,EAAE,SAAS,EAChB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACvC,OAAO,CAAC,IAAI,CAAC;IAsBhB,oGAAoG;IACpG,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrF;;;;;OAKG;IACG,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAKnE"}
|
|
@@ -15,6 +15,7 @@ const common_1 = require("@nestjs/common");
|
|
|
15
15
|
const engine_1 = require("@astram/engine");
|
|
16
16
|
const list_query_js_1 = require("../shared/list-query.js");
|
|
17
17
|
const company_hierarchy_service_js_1 = require("./company-hierarchy.service.js");
|
|
18
|
+
const http_js_1 = require("../shared/http.js");
|
|
18
19
|
const identity_errors_js_1 = require("../shared/identity-errors.js");
|
|
19
20
|
const COMPANY_SEARCH_FIELDS = ['name'];
|
|
20
21
|
const COMPANY_SORT_FIELDS = ['name', 'status', 'createdAt'];
|
|
@@ -73,6 +74,11 @@ let CompanyService = CompanyService_1 = class CompanyService {
|
|
|
73
74
|
* rooted at (the same own-root guard as move and delete, F6): deactivating it would act on the
|
|
74
75
|
* whole tenant above the actor's authority. Re-sending the current status (the company form
|
|
75
76
|
* always sends both fields) stays allowed, so the own root can still be renamed.
|
|
77
|
+
*
|
|
78
|
+
* Phase 11 #1: the guard compares the STORED uuid, never the caller's spelling. `c_uuid` is
|
|
79
|
+
* `utf8mb4_unicode_ci`, which also ignores trailing spaces and character width, so a padded or
|
|
80
|
+
* fullwidth route uuid reaches the same row. A non-root status change therefore reads the company
|
|
81
|
+
* first (the walled read; out of reach → 404) and judges and writes by the id that read returns.
|
|
76
82
|
*/
|
|
77
83
|
async updateCompany(actor, uuid, patch) {
|
|
78
84
|
const fields = {};
|
|
@@ -80,15 +86,18 @@ let CompanyService = CompanyService_1 = class CompanyService {
|
|
|
80
86
|
if (typeof patch?.[key] === 'string')
|
|
81
87
|
fields[key] = patch[key];
|
|
82
88
|
}
|
|
83
|
-
|
|
89
|
+
let target = uuid;
|
|
90
|
+
if (actor.isRoot !== true && fields.status !== undefined) {
|
|
84
91
|
const current = await this.records.get('company', uuid);
|
|
85
92
|
if (current === null)
|
|
86
93
|
throw new identity_errors_js_1.IdentityNotFoundError('Company not found.');
|
|
87
|
-
|
|
94
|
+
target = current.id;
|
|
95
|
+
if ((0, http_js_1.sameUuid)(String(current.id), (0, company_hierarchy_service_js_1.ownRootOf)(actor)) &&
|
|
96
|
+
current.fields.status !== fields.status) {
|
|
88
97
|
throw new identity_errors_js_1.IdentityForbiddenError('You cannot change the status of the organisation your access is rooted at.');
|
|
89
98
|
}
|
|
90
99
|
}
|
|
91
|
-
return this.records.update('company',
|
|
100
|
+
return this.records.update('company', target, { fields });
|
|
92
101
|
}
|
|
93
102
|
/** Move a company under a new parent (hierarchy rules + cycle check, see the hierarchy service). */
|
|
94
103
|
moveCompany(actor, uuid, parentUuid) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cross-tenant-grant.controller.d.ts","sourceRoot":"","sources":["../../../src/features/cross-tenant/cross-tenant-grant.controller.ts"],"names":[],"mappings":"AA8CA,OAAO,EAGL,sBAAsB,
|
|
1
|
+
{"version":3,"file":"cross-tenant-grant.controller.d.ts","sourceRoot":"","sources":["../../../src/features/cross-tenant/cross-tenant-grant.controller.ts"],"names":[],"mappings":"AA8CA,OAAO,EAGL,sBAAsB,EAOvB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,2BAA2B,EAAE,MAAM,sCAAsC,CAAC;AAEnF,qFAAqF;AACrF,UAAU,aAAa;IACrB,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,gGAAgG;AAChG,UAAU,iBAAiB;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAgDD,qBAEa,0BAA0B;IAEO,OAAO,CAAC,QAAQ,CAAC,WAAW;IACtE,OAAO,CAAC,QAAQ,CAAC,KAAK;gBADqC,WAAW,EAAE,sBAAsB,EAC7E,KAAK,EAAE,2BAA2B;IAGrD;;;;OAIG;IAGG,KAAK,CAAQ,GAAG,EAAE,aAAa,EAAU,IAAI,EAAE,iBAAiB;;;;IA+BtE,yFAAyF;IAGnF,MAAM,CAAQ,GAAG,EAAE,aAAa,EAA2B,IAAI,EAAE,MAAM;;;IAS7E;;;;;OAKG;IAGG,MAAM,CAAQ,GAAG,EAAE,aAAa,EAA2B,IAAI,EAAE,MAAM;;;IAS7E;;;OAGG;IAGG,KAAK,CAAQ,GAAG,EAAE,aAAa;;;IAKrC;;;;OAIG;IAGG,IAAI,CAAQ,GAAG,EAAE,aAAa;;;;IAQpC,wFAAwF;IAGlF,cAAc;;;CAIrB"}
|
|
@@ -52,18 +52,13 @@ const cross_tenant_grant_read_service_js_1 = require("./cross-tenant-grant-read.
|
|
|
52
52
|
const VALID_GRANTEE_KINDS = ['user', 'role'];
|
|
53
53
|
/** Validate + narrow the offer body, throwing 400 on any missing/malformed field. */
|
|
54
54
|
function parseOfferBody(body) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
throw new common_1.BadRequestException('granteeTenantId is required');
|
|
58
|
-
}
|
|
55
|
+
// Phase 11 #1: ids are canonical uuids (400 otherwise) before they reach the writer.
|
|
56
|
+
const granteeTenantId = (0, guard_1.uuidField)(body?.granteeTenantId, 'granteeTenantId');
|
|
59
57
|
const granteeKind = body?.granteeKind;
|
|
60
58
|
if (!VALID_GRANTEE_KINDS.includes(granteeKind)) {
|
|
61
59
|
throw new common_1.BadRequestException("granteeKind must be 'user' or 'role'");
|
|
62
60
|
}
|
|
63
|
-
const granteeUuid = body?.granteeUuid;
|
|
64
|
-
if (typeof granteeUuid !== 'string' || granteeUuid.length === 0) {
|
|
65
|
-
throw new common_1.BadRequestException('granteeUuid is required');
|
|
66
|
-
}
|
|
61
|
+
const granteeUuid = (0, guard_1.uuidField)(body?.granteeUuid, 'granteeUuid');
|
|
67
62
|
const typeKey = body?.typeKey;
|
|
68
63
|
if (typeof typeKey !== 'string' || typeKey.length === 0) {
|
|
69
64
|
throw new common_1.BadRequestException('typeKey is required');
|
|
@@ -193,7 +188,7 @@ __decorate([
|
|
|
193
188
|
(0, common_1.Post)('cross-tenant-grants/:uuid/accept'),
|
|
194
189
|
(0, guard_1.Classified)({ typeKey: 'cross_tenant_grant', action: 'accept' }),
|
|
195
190
|
__param(0, (0, common_1.Req)()),
|
|
196
|
-
__param(1, (0, common_1.Param)('uuid')),
|
|
191
|
+
__param(1, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
197
192
|
__metadata("design:type", Function),
|
|
198
193
|
__metadata("design:paramtypes", [Object, String]),
|
|
199
194
|
__metadata("design:returntype", Promise)
|
|
@@ -202,7 +197,7 @@ __decorate([
|
|
|
202
197
|
(0, common_1.Post)('cross-tenant-grants/:uuid/revoke'),
|
|
203
198
|
(0, guard_1.Classified)({ typeKey: 'cross_tenant_grant', action: 'revoke' }),
|
|
204
199
|
__param(0, (0, common_1.Req)()),
|
|
205
|
-
__param(1, (0, common_1.Param)('uuid')),
|
|
200
|
+
__param(1, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
206
201
|
__metadata("design:type", Function),
|
|
207
202
|
__metadata("design:paramtypes", [Object, String]),
|
|
208
203
|
__metadata("design:returntype", Promise)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"membership.controller.d.ts","sourceRoot":"","sources":["../../../src/features/membership/membership.controller.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAG1E,iFAAiF;AACjF,UAAU,iBAAiB;IACzB,uFAAuF;IACvF,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED,4EAA4E;AAC5E,UAAU,cAAc;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,kFAAkF;IAClF,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAYD,qBAEa,oBAAoB;IAE7B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAFR,WAAW,EAAE,iBAAiB,EAC9B,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,qBAAqB;IAGlD;;;;;OAKG;IAGG,mBAAmB,
|
|
1
|
+
{"version":3,"file":"membership.controller.d.ts","sourceRoot":"","sources":["../../../src/features/membership/membership.controller.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAG1E,iFAAiF;AACjF,UAAU,iBAAiB;IACzB,uFAAuF;IACvF,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED,4EAA4E;AAC5E,UAAU,cAAc;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,kFAAkF;IAClF,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAYD,qBAEa,oBAAoB;IAE7B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAFR,WAAW,EAAE,iBAAiB,EAC9B,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,qBAAqB;IAGlD;;;;;OAKG;IAGG,mBAAmB,CAA8B,QAAQ,EAAE,MAAM;;;IA0CvE,uEAAuE;IAGjE,iBAAiB,CACd,GAAG,EAAE,aAAa,EACI,QAAQ,EAAE,MAAM,EACrC,IAAI,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE;;;IAUxC;;;;OAIG;IAGG,gBAAgB,CACb,GAAG,EAAE,aAAa,EACU,cAAc,EAAE,MAAM,EACjD,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE;;;IAoB1E,2FAA2F;IAGrF,gBAAgB,CACb,GAAG,EAAE,aAAa,EACU,cAAc,EAAE,MAAM;;;IAM3D,0EAA0E;IAGpE,eAAe,CACZ,GAAG,EAAE,aAAa,EACU,cAAc,EAAE,MAAM,EACjD,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAA;KAAE;;;IA4B7C;;;;OAIG;YACW,WAAW;CAM1B"}
|
|
@@ -83,7 +83,7 @@ let MembershipController = class MembershipController {
|
|
|
83
83
|
}
|
|
84
84
|
/** Add the user to a company (creates an inactive membership card). */
|
|
85
85
|
async addUserMembership(req, userUuid, body) {
|
|
86
|
-
const id = await this.memberships.addMembership(req.principal, userUuid, body?.companyUuid
|
|
86
|
+
const id = await this.memberships.addMembership(req.principal, userUuid, (0, guard_1.uuidField)(body?.companyUuid, 'companyUuid'));
|
|
87
87
|
return { id };
|
|
88
88
|
}
|
|
89
89
|
/**
|
|
@@ -96,7 +96,7 @@ let MembershipController = class MembershipController {
|
|
|
96
96
|
if (!body.userUuid) {
|
|
97
97
|
throw new common_1.ForbiddenException('userUuid is required to activate a membership');
|
|
98
98
|
}
|
|
99
|
-
await this.memberships.setActive(req.principal, membershipUuid, body.userUuid);
|
|
99
|
+
await this.memberships.setActive(req.principal, membershipUuid, (0, guard_1.uuidField)(body.userUuid, 'userUuid'));
|
|
100
100
|
}
|
|
101
101
|
if (typeof body?.status === 'string') {
|
|
102
102
|
await this.memberships.updateMembership(req.principal, membershipUuid, {
|
|
@@ -112,7 +112,24 @@ let MembershipController = class MembershipController {
|
|
|
112
112
|
}
|
|
113
113
|
/** Atomically replace a membership's full posting set (the diff sync). */
|
|
114
114
|
async replacePostings(req, membershipUuid, body) {
|
|
115
|
-
const
|
|
115
|
+
const raw = Array.isArray(body?.postings) ? body.postings : [];
|
|
116
|
+
// Phase 11 #1: every posting id is a canonical uuid (400 otherwise); the window bounds and any
|
|
117
|
+
// other key pass through to the service's own whitelist.
|
|
118
|
+
const postings = raw.map((p, i) => {
|
|
119
|
+
const entry = (p ?? {});
|
|
120
|
+
const departmentUuid = (0, guard_1.optionalUuidField)(entry.departmentUuid, `postings[${i}].departmentUuid`, {
|
|
121
|
+
keepNull: true,
|
|
122
|
+
});
|
|
123
|
+
const branchUuid = (0, guard_1.optionalUuidField)(entry.branchUuid, `postings[${i}].branchUuid`, {
|
|
124
|
+
keepNull: true,
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
...entry,
|
|
128
|
+
roleUuid: (0, guard_1.uuidField)(entry.roleUuid, `postings[${i}].roleUuid`),
|
|
129
|
+
...(departmentUuid !== undefined ? { departmentUuid } : {}),
|
|
130
|
+
...(branchUuid !== undefined ? { branchUuid } : {}),
|
|
131
|
+
};
|
|
132
|
+
});
|
|
116
133
|
await this.postings.replacePostings(req.principal, membershipUuid, postings);
|
|
117
134
|
return { success: true };
|
|
118
135
|
}
|
|
@@ -133,7 +150,7 @@ exports.MembershipController = MembershipController;
|
|
|
133
150
|
__decorate([
|
|
134
151
|
(0, common_1.Get)('users/:userUuid/memberships'),
|
|
135
152
|
(0, guard_1.Classified)({ typeKey: 'company_membership', action: 'read' }),
|
|
136
|
-
__param(0, (0, common_1.Param)('userUuid')),
|
|
153
|
+
__param(0, (0, common_1.Param)('userUuid', guard_1.UuidPipe)),
|
|
137
154
|
__metadata("design:type", Function),
|
|
138
155
|
__metadata("design:paramtypes", [String]),
|
|
139
156
|
__metadata("design:returntype", Promise)
|
|
@@ -142,7 +159,7 @@ __decorate([
|
|
|
142
159
|
(0, common_1.Post)('users/:userUuid/memberships'),
|
|
143
160
|
(0, guard_1.Classified)({ typeKey: 'company_membership', action: 'create' }),
|
|
144
161
|
__param(0, (0, common_1.Req)()),
|
|
145
|
-
__param(1, (0, common_1.Param)('userUuid')),
|
|
162
|
+
__param(1, (0, common_1.Param)('userUuid', guard_1.UuidPipe)),
|
|
146
163
|
__param(2, (0, common_1.Body)()),
|
|
147
164
|
__metadata("design:type", Function),
|
|
148
165
|
__metadata("design:paramtypes", [Object, String, Object]),
|
|
@@ -152,7 +169,7 @@ __decorate([
|
|
|
152
169
|
(0, common_1.Put)('memberships/:membershipUuid'),
|
|
153
170
|
(0, guard_1.Classified)({ typeKey: 'company_membership', action: 'update' }),
|
|
154
171
|
__param(0, (0, common_1.Req)()),
|
|
155
|
-
__param(1, (0, common_1.Param)('membershipUuid')),
|
|
172
|
+
__param(1, (0, common_1.Param)('membershipUuid', guard_1.UuidPipe)),
|
|
156
173
|
__param(2, (0, common_1.Body)()),
|
|
157
174
|
__metadata("design:type", Function),
|
|
158
175
|
__metadata("design:paramtypes", [Object, String, Object]),
|
|
@@ -162,7 +179,7 @@ __decorate([
|
|
|
162
179
|
(0, common_1.Delete)('memberships/:membershipUuid'),
|
|
163
180
|
(0, guard_1.Classified)({ typeKey: 'company_membership', action: 'delete' }),
|
|
164
181
|
__param(0, (0, common_1.Req)()),
|
|
165
|
-
__param(1, (0, common_1.Param)('membershipUuid')),
|
|
182
|
+
__param(1, (0, common_1.Param)('membershipUuid', guard_1.UuidPipe)),
|
|
166
183
|
__metadata("design:type", Function),
|
|
167
184
|
__metadata("design:paramtypes", [Object, String]),
|
|
168
185
|
__metadata("design:returntype", Promise)
|
|
@@ -171,7 +188,7 @@ __decorate([
|
|
|
171
188
|
(0, common_1.Put)('memberships/:membershipUuid/postings'),
|
|
172
189
|
(0, guard_1.Classified)({ typeKey: 'membership_posting', action: 'update' }),
|
|
173
190
|
__param(0, (0, common_1.Req)()),
|
|
174
|
-
__param(1, (0, common_1.Param)('membershipUuid')),
|
|
191
|
+
__param(1, (0, common_1.Param)('membershipUuid', guard_1.UuidPipe)),
|
|
175
192
|
__param(2, (0, common_1.Body)()),
|
|
176
193
|
__metadata("design:type", Function),
|
|
177
194
|
__metadata("design:paramtypes", [Object, String, Object]),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"org-catalog.controller.d.ts","sourceRoot":"","sources":["../../../src/features/org-structure/org-catalog.controller.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAU7D,qBAEa,oBAAoB;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,iBAAiB;IAEvD,kFAAkF;IAG5E,sBAAsB,
|
|
1
|
+
{"version":3,"file":"org-catalog.controller.d.ts","sourceRoot":"","sources":["../../../src/features/org-structure/org-catalog.controller.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAU7D,qBAEa,oBAAoB;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,iBAAiB;IAEvD,kFAAkF;IAG5E,sBAAsB,CAAiC,WAAW,EAAE,MAAM;;;IAKhF,+EAA+E;IAGzE,mBAAmB,CAAiC,WAAW,EAAE,MAAM;;;CAI9E"}
|
|
@@ -44,7 +44,7 @@ exports.OrgCatalogController = OrgCatalogController;
|
|
|
44
44
|
__decorate([
|
|
45
45
|
(0, common_1.Get)('companies/:companyUuid/departments'),
|
|
46
46
|
(0, guard_1.Classified)({ typeKey: 'company_department', action: 'read' }),
|
|
47
|
-
__param(0, (0, common_1.Param)('companyUuid')),
|
|
47
|
+
__param(0, (0, common_1.Param)('companyUuid', guard_1.UuidPipe)),
|
|
48
48
|
__metadata("design:type", Function),
|
|
49
49
|
__metadata("design:paramtypes", [String]),
|
|
50
50
|
__metadata("design:returntype", Promise)
|
|
@@ -52,7 +52,7 @@ __decorate([
|
|
|
52
52
|
__decorate([
|
|
53
53
|
(0, common_1.Get)('companies/:companyUuid/branches'),
|
|
54
54
|
(0, guard_1.Classified)({ typeKey: 'company_branch', action: 'read' }),
|
|
55
|
-
__param(0, (0, common_1.Param)('companyUuid')),
|
|
55
|
+
__param(0, (0, common_1.Param)('companyUuid', guard_1.UuidPipe)),
|
|
56
56
|
__metadata("design:type", Function),
|
|
57
57
|
__metadata("design:paramtypes", [String]),
|
|
58
58
|
__metadata("design:returntype", Promise)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy.controller.d.ts","sourceRoot":"","sources":["../../../src/features/policy/policy.controller.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EACL,aAAa,EAId,MAAM,qBAAqB,CAAC;AAE7B,qFAAqF;AACrF,UAAU,aAAa;IACrB,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,qBAEa,gBAAgB;IACf,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,aAAa;IAEpD;;;;;;;;OAQG;IAGG,MAAM,CAAmB,OAAO,CAAC,EAAE,MAAM;IAY/C;;;OAGG;IAGG,IAAI,CAAQ,GAAG,EAAE,aAAa,EAAoB,OAAO,CAAC,EAAE,MAAM;;;IAQxE;;;;;OAKG;IAGG,MAAM,CAAQ,GAAG,EAAE,aAAa,EAAU,IAAI,EAAE,YAAY;;;IAWlE;;;;OAIG;IAGG,MAAM,CAAQ,GAAG,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"policy.controller.d.ts","sourceRoot":"","sources":["../../../src/features/policy/policy.controller.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EACL,aAAa,EAId,MAAM,qBAAqB,CAAC;AAE7B,qFAAqF;AACrF,UAAU,aAAa;IACrB,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,qBAEa,gBAAgB;IACf,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,aAAa;IAEpD;;;;;;;;OAQG;IAGG,MAAM,CAAmB,OAAO,CAAC,EAAE,MAAM;IAY/C;;;OAGG;IAGG,IAAI,CAAQ,GAAG,EAAE,aAAa,EAAoB,OAAO,CAAC,EAAE,MAAM;;;IAQxE;;;;;OAKG;IAGG,MAAM,CAAQ,GAAG,EAAE,aAAa,EAAU,IAAI,EAAE,YAAY;;;IAWlE;;;;OAIG;IAGG,MAAM,CAAQ,GAAG,EAAE,aAAa,EAAyB,EAAE,EAAE,MAAM;;;CAS1E"}
|
|
@@ -140,7 +140,7 @@ __decorate([
|
|
|
140
140
|
(0, common_1.Delete)('record-policies/:id'),
|
|
141
141
|
(0, guard_1.Classified)({ typeKey: 'role', action: 'update' }),
|
|
142
142
|
__param(0, (0, common_1.Req)()),
|
|
143
|
-
__param(1, (0, common_1.Param)('id')),
|
|
143
|
+
__param(1, (0, common_1.Param)('id', guard_1.UuidPipe)),
|
|
144
144
|
__metadata("design:type", Function),
|
|
145
145
|
__metadata("design:paramtypes", [Object, String]),
|
|
146
146
|
__metadata("design:returntype", Promise)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role.controller.d.ts","sourceRoot":"","sources":["../../../src/features/role/role.controller.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAOvD,qBAEa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,WAAW;IAE/C;;;;OAIG;IAGG,SAAS,CAAU,KAAK,EAAE,YAAY;;;;;;IAM5C,uDAAuD;IAGjD,UAAU,CAAS,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;;;IAKhD;0CACsC;IAGtC,OAAO,
|
|
1
|
+
{"version":3,"file":"role.controller.d.ts","sourceRoot":"","sources":["../../../src/features/role/role.controller.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAOvD,qBAEa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,WAAW;IAE/C;;;;OAIG;IAGG,SAAS,CAAU,KAAK,EAAE,YAAY;;;;;;IAM5C,uDAAuD;IAGjD,UAAU,CAAS,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;;;IAKhD;0CACsC;IAGtC,OAAO,CAA0B,IAAI,EAAE,MAAM;IAI7C,uDAAuD;IAGjD,UAAU,CACP,GAAG,EAAE,aAAa,EACA,IAAI,EAAE,MAAM,EAC7B,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;;;IAMjC,uDAAuD;IAGjD,UAAU,CAAQ,GAAG,EAAE,aAAa,EAA2B,IAAI,EAAE,MAAM;;;CAIlF"}
|
|
@@ -78,7 +78,7 @@ __decorate([
|
|
|
78
78
|
__decorate([
|
|
79
79
|
(0, common_1.Get)('roles/:uuid'),
|
|
80
80
|
(0, guard_1.Classified)({ typeKey: 'role', action: 'read' }),
|
|
81
|
-
__param(0, (0, common_1.Param)('uuid')),
|
|
81
|
+
__param(0, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
82
82
|
__metadata("design:type", Function),
|
|
83
83
|
__metadata("design:paramtypes", [String]),
|
|
84
84
|
__metadata("design:returntype", void 0)
|
|
@@ -87,7 +87,7 @@ __decorate([
|
|
|
87
87
|
(0, common_1.Put)('roles/:uuid'),
|
|
88
88
|
(0, guard_1.Classified)({ typeKey: 'role', action: 'update' }),
|
|
89
89
|
__param(0, (0, common_1.Req)()),
|
|
90
|
-
__param(1, (0, common_1.Param)('uuid')),
|
|
90
|
+
__param(1, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
91
91
|
__param(2, (0, common_1.Body)()),
|
|
92
92
|
__metadata("design:type", Function),
|
|
93
93
|
__metadata("design:paramtypes", [Object, String, Object]),
|
|
@@ -97,7 +97,7 @@ __decorate([
|
|
|
97
97
|
(0, common_1.Delete)('roles/:uuid'),
|
|
98
98
|
(0, guard_1.Classified)({ typeKey: 'role', action: 'delete' }),
|
|
99
99
|
__param(0, (0, common_1.Req)()),
|
|
100
|
-
__param(1, (0, common_1.Param)('uuid')),
|
|
100
|
+
__param(1, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
101
101
|
__metadata("design:type", Function),
|
|
102
102
|
__metadata("design:paramtypes", [Object, String]),
|
|
103
103
|
__metadata("design:returntype", Promise)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-account.controller.d.ts","sourceRoot":"","sources":["../../../src/features/service-account/service-account.controller.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EACL,qBAAqB,EAOtB,MAAM,8BAA8B,CAAC;AAGtC,qFAAqF;AACrF,UAAU,aAAa;IACrB,SAAS,EAAE,SAAS,CAAC;CACtB;
|
|
1
|
+
{"version":3,"file":"service-account.controller.d.ts","sourceRoot":"","sources":["../../../src/features/service-account/service-account.controller.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EACL,qBAAqB,EAOtB,MAAM,8BAA8B,CAAC;AAGtC,qFAAqF;AACrF,UAAU,aAAa;IACrB,SAAS,EAAE,SAAS,CAAC;CACtB;AAmDD,qBAEa,wBAAwB;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,qBAAqB;IAEvD,8FAA8F;IAGxF,IAAI,CAAQ,GAAG,EAAE,aAAa;;;IAQpC;;;;;OAKG;IAGG,UAAU,CAAQ,GAAG,EAAE,aAAa;IAQ1C,8FAA8F;IAGxF,GAAG,CAAQ,GAAG,EAAE,aAAa,EAAyB,EAAE,EAAE,MAAM;;;IAQtE;;;OAGG;IAGG,MAAM,CAAQ,GAAG,EAAE,aAAa,EAAU,IAAI,EAAE,OAAO;;;IAiB7D;;;OAGG;IAGG,MAAM,CACH,GAAG,EAAE,aAAa,EACF,EAAE,EAAE,MAAM,EACzB,IAAI,EAAE,OAAO;;;IAwBvB;;;;OAIG;IAGG,QAAQ,CACL,GAAG,EAAE,aAAa,EACF,EAAE,EAAE,MAAM,EACzB,IAAI,EAAE,OAAO;;;IAWvB;;;OAGG;IAGG,SAAS,CACN,GAAG,EAAE,aAAa,EACF,EAAE,EAAE,MAAM,EACzB,IAAI,EAAE,OAAO;;;IAWvB;;;OAGG;IAGG,UAAU,CAAQ,GAAG,EAAE,aAAa,EAAyB,EAAE,EAAE,MAAM;;;CAQ9E"}
|
|
@@ -54,15 +54,9 @@ function mapError(e) {
|
|
|
54
54
|
throw new common_1.ConflictException(e.message);
|
|
55
55
|
throw e;
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return undefined;
|
|
61
|
-
if (!Array.isArray(v) || v.some((x) => typeof x !== 'string')) {
|
|
62
|
-
throw new common_1.BadRequestException(`${field} must be an array of strings`);
|
|
63
|
-
}
|
|
64
|
-
return v;
|
|
65
|
-
}
|
|
57
|
+
// The OPTIONAL id-array fields of a body (scope/roles) go through `uuidArrayField`: each item a
|
|
58
|
+
// canonical uuid (Phase 11 #1), 400 otherwise. The service re-checks (service-level callers bypass
|
|
59
|
+
// this controller).
|
|
66
60
|
/**
|
|
67
61
|
* Narrow a key-issue/rotate body to its expiry choice (ADR-0033 §4): `expiresAtMs` (epoch-ms) or
|
|
68
62
|
* `neverExpires: true`. Shape errors are 400 here; "neither", "both" and "not in the future" are
|
|
@@ -130,8 +124,8 @@ let ServiceAccountController = class ServiceAccountController {
|
|
|
130
124
|
const b = (body ?? {});
|
|
131
125
|
if (typeof b.label !== 'string')
|
|
132
126
|
throw new common_1.BadRequestException('label is required');
|
|
133
|
-
const scope =
|
|
134
|
-
const roles =
|
|
127
|
+
const scope = (0, guard_1.uuidArrayField)(b.scopeSubtreeNodeIds, 'scopeSubtreeNodeIds');
|
|
128
|
+
const roles = (0, guard_1.uuidArrayField)(b.roleIds, 'roleIds');
|
|
135
129
|
try {
|
|
136
130
|
const account = await this.svc.create(adminOf(req), {
|
|
137
131
|
label: b.label,
|
|
@@ -156,8 +150,8 @@ let ServiceAccountController = class ServiceAccountController {
|
|
|
156
150
|
if (b.status !== undefined && b.status !== 'active' && b.status !== 'disabled') {
|
|
157
151
|
throw new common_1.BadRequestException("status must be 'active' or 'disabled'");
|
|
158
152
|
}
|
|
159
|
-
const scope =
|
|
160
|
-
const roles =
|
|
153
|
+
const scope = (0, guard_1.uuidArrayField)(b.scopeSubtreeNodeIds, 'scopeSubtreeNodeIds');
|
|
154
|
+
const roles = (0, guard_1.uuidArrayField)(b.roleIds, 'roleIds');
|
|
161
155
|
try {
|
|
162
156
|
const account = await this.svc.update(adminOf(req), id, {
|
|
163
157
|
...(b.label !== undefined ? { label: b.label } : {}),
|
|
@@ -235,7 +229,7 @@ __decorate([
|
|
|
235
229
|
(0, common_1.Get)('service-accounts/:id'),
|
|
236
230
|
(0, guard_1.Classified)({ typeKey: 'service_account', action: 'read' }),
|
|
237
231
|
__param(0, (0, common_1.Req)()),
|
|
238
|
-
__param(1, (0, common_1.Param)('id')),
|
|
232
|
+
__param(1, (0, common_1.Param)('id', guard_1.UuidPipe)),
|
|
239
233
|
__metadata("design:type", Function),
|
|
240
234
|
__metadata("design:paramtypes", [Object, String]),
|
|
241
235
|
__metadata("design:returntype", Promise)
|
|
@@ -253,7 +247,7 @@ __decorate([
|
|
|
253
247
|
(0, common_1.Patch)('service-accounts/:id'),
|
|
254
248
|
(0, guard_1.Classified)({ typeKey: 'service_account', action: 'update' }),
|
|
255
249
|
__param(0, (0, common_1.Req)()),
|
|
256
|
-
__param(1, (0, common_1.Param)('id')),
|
|
250
|
+
__param(1, (0, common_1.Param)('id', guard_1.UuidPipe)),
|
|
257
251
|
__param(2, (0, common_1.Body)()),
|
|
258
252
|
__metadata("design:type", Function),
|
|
259
253
|
__metadata("design:paramtypes", [Object, String, Object]),
|
|
@@ -263,7 +257,7 @@ __decorate([
|
|
|
263
257
|
(0, common_1.Post)('service-accounts/:id/keys'),
|
|
264
258
|
(0, guard_1.Classified)({ typeKey: 'service_account', action: 'manage_keys' }),
|
|
265
259
|
__param(0, (0, common_1.Req)()),
|
|
266
|
-
__param(1, (0, common_1.Param)('id')),
|
|
260
|
+
__param(1, (0, common_1.Param)('id', guard_1.UuidPipe)),
|
|
267
261
|
__param(2, (0, common_1.Body)()),
|
|
268
262
|
__metadata("design:type", Function),
|
|
269
263
|
__metadata("design:paramtypes", [Object, String, Object]),
|
|
@@ -273,7 +267,7 @@ __decorate([
|
|
|
273
267
|
(0, common_1.Post)('service-accounts/:id/keys/rotate'),
|
|
274
268
|
(0, guard_1.Classified)({ typeKey: 'service_account', action: 'manage_keys' }),
|
|
275
269
|
__param(0, (0, common_1.Req)()),
|
|
276
|
-
__param(1, (0, common_1.Param)('id')),
|
|
270
|
+
__param(1, (0, common_1.Param)('id', guard_1.UuidPipe)),
|
|
277
271
|
__param(2, (0, common_1.Body)()),
|
|
278
272
|
__metadata("design:type", Function),
|
|
279
273
|
__metadata("design:paramtypes", [Object, String, Object]),
|
|
@@ -283,7 +277,7 @@ __decorate([
|
|
|
283
277
|
(0, common_1.Post)('service-accounts/:id/keys/revoke'),
|
|
284
278
|
(0, guard_1.Classified)({ typeKey: 'service_account', action: 'manage_keys' }),
|
|
285
279
|
__param(0, (0, common_1.Req)()),
|
|
286
|
-
__param(1, (0, common_1.Param)('id')),
|
|
280
|
+
__param(1, (0, common_1.Param)('id', guard_1.UuidPipe)),
|
|
287
281
|
__metadata("design:type", Function),
|
|
288
282
|
__metadata("design:paramtypes", [Object, String]),
|
|
289
283
|
__metadata("design:returntype", Promise)
|
|
@@ -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;
|
|
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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"}
|
|
@@ -23,6 +23,7 @@ exports.ceilingAllowsRole = ceilingAllowsRole;
|
|
|
23
23
|
*/
|
|
24
24
|
const node_crypto_1 = require("node:crypto");
|
|
25
25
|
const core_1 = require("@astram/core");
|
|
26
|
+
const engine_1 = require("@astram/engine");
|
|
26
27
|
/** The account id was not in the caller's subtree (unknown or cross-tenant) → 404 (no leak). */
|
|
27
28
|
class ServiceAccountNotFoundError extends Error {
|
|
28
29
|
constructor() {
|
|
@@ -65,23 +66,39 @@ function scopeOf(admin) {
|
|
|
65
66
|
const nodeIds = admin.scopeSubtreeNodeIds.length ? admin.scopeSubtreeNodeIds : [admin.tenantId];
|
|
66
67
|
return { nodeIds, isRoot: admin.isRoot === true };
|
|
67
68
|
}
|
|
69
|
+
/** An id in the ceiling's comparison form: canonical when it is a uuid (Phase 11 #1). */
|
|
70
|
+
const ceilingKey = (id) => (0, engine_1.canonicalUuid)(id) ?? id;
|
|
68
71
|
function ceilingOf(admin, heldRoleIds) {
|
|
69
72
|
return {
|
|
70
73
|
isRoot: admin.isRoot === true,
|
|
71
|
-
nodeIds: new Set(scopeOf(admin).nodeIds),
|
|
72
|
-
roleIds: new Set(heldRoleIds),
|
|
74
|
+
nodeIds: new Set(scopeOf(admin).nodeIds.map(ceilingKey)),
|
|
75
|
+
roleIds: new Set(heldRoleIds.map(ceilingKey)),
|
|
73
76
|
};
|
|
74
77
|
}
|
|
75
78
|
function ceilingAllowsNode(c, nodeId) {
|
|
76
|
-
return c.isRoot || c.nodeIds.has(nodeId);
|
|
79
|
+
return c.isRoot || c.nodeIds.has(ceilingKey(nodeId));
|
|
77
80
|
}
|
|
78
81
|
function ceilingAllowsRole(c, roleId) {
|
|
79
|
-
return c.isRoot || c.roleIds.has(roleId);
|
|
82
|
+
return c.isRoot || c.roleIds.has(ceilingKey(roleId));
|
|
80
83
|
}
|
|
81
84
|
/** Dedupe an id list keeping first-seen order (drops empty strings). */
|
|
82
85
|
function uniqueIds(ids) {
|
|
83
86
|
return [...new Set(ids.filter((x) => typeof x === 'string' && x.length > 0))];
|
|
84
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* REQUESTED ids (create/update input) → canonical uuids, deduped (Phase 11 #1). The id columns
|
|
90
|
+
* compare under `utf8mb4_unicode_ci` (case, trailing spaces and width ignored) while the ceiling
|
|
91
|
+
* Sets, `sa_role_ids` / `sa_scope_subtree` JSON and every `JSON_CONTAINS` are exact, so a padded or
|
|
92
|
+
* fullwidth id would be judged one way and stored another. Anything but a strict uuid → 400. This
|
|
93
|
+
* holds for service-level callers too (the controller's `uuidArrayField` is the HTTP edge).
|
|
94
|
+
*/
|
|
95
|
+
function requestIds(ids, field) {
|
|
96
|
+
const canonical = ids.map((x) => (0, engine_1.canonicalUuid)(x));
|
|
97
|
+
if (canonical.some((x) => x === null)) {
|
|
98
|
+
throw new ServiceAccountInputError(`${field} must be a list of ids`);
|
|
99
|
+
}
|
|
100
|
+
return uniqueIds(canonical);
|
|
101
|
+
}
|
|
85
102
|
/** `requested` then the `preserved` ids not already in it (first-seen order, no duplicates). */
|
|
86
103
|
function mergeIds(requested, preserved) {
|
|
87
104
|
return uniqueIds([...requested, ...preserved]);
|
|
@@ -313,13 +330,13 @@ class ServiceAccountService {
|
|
|
313
330
|
if (!label)
|
|
314
331
|
throw new ServiceAccountInputError('a label is required');
|
|
315
332
|
// Default the scope to the admin's own subtree (least surprise; still least-privilege-bounded).
|
|
316
|
-
const requestedScope =
|
|
333
|
+
const requestedScope = requestIds(input.scopeSubtreeNodeIds ?? [], 'scopeSubtreeNodeIds');
|
|
317
334
|
const scopeSubtree = requestedScope.length > 0
|
|
318
335
|
? requestedScope
|
|
319
336
|
: admin.scopeSubtreeNodeIds.length
|
|
320
337
|
? uniqueIds(admin.scopeSubtreeNodeIds)
|
|
321
338
|
: [admin.tenantId];
|
|
322
|
-
const roleIds =
|
|
339
|
+
const roleIds = requestIds(input.roleIds ?? [], 'roleIds');
|
|
323
340
|
await this.assertWithinCeiling(await this.ceilingFor(admin), scopeSubtree, roleIds);
|
|
324
341
|
const id = (0, node_crypto_1.randomUUID)();
|
|
325
342
|
const createdAtMs = this.now();
|
|
@@ -371,8 +388,10 @@ class ServiceAccountService {
|
|
|
371
388
|
if (patch.status !== undefined && patch.status !== 'active' && patch.status !== 'disabled') {
|
|
372
389
|
throw new ServiceAccountInputError("status must be 'active' or 'disabled'");
|
|
373
390
|
}
|
|
374
|
-
const requestedScope = patch.scopeSubtreeNodeIds !== undefined
|
|
375
|
-
|
|
391
|
+
const requestedScope = patch.scopeSubtreeNodeIds !== undefined
|
|
392
|
+
? requestIds(patch.scopeSubtreeNodeIds, 'scopeSubtreeNodeIds')
|
|
393
|
+
: undefined;
|
|
394
|
+
const requestedRoles = patch.roleIds !== undefined ? requestIds(patch.roleIds, 'roleIds') : undefined;
|
|
376
395
|
if (requestedScope !== undefined && requestedScope.length === 0) {
|
|
377
396
|
throw new ServiceAccountInputError('choose at least one scope node');
|
|
378
397
|
}
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAuBD,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;;;IAatC;;;;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"}
|
|
@@ -35,20 +35,19 @@ function toShareRequest(body) {
|
|
|
35
35
|
const b = (body ?? {});
|
|
36
36
|
if (!b.typeKey || typeof b.typeKey !== 'string')
|
|
37
37
|
throw new common_1.BadRequestException('typeKey required');
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
// Phase 11 #1: `rs_record_uuid` / `rs_grantee_uuid` are uuid columns — canonical or 400.
|
|
39
|
+
const recordId = (0, guard_1.uuidField)(b.recordId, 'recordId');
|
|
40
40
|
if (b.granteeKind !== 'user' && b.granteeKind !== 'role') {
|
|
41
41
|
throw new common_1.BadRequestException("granteeKind must be 'user' or 'role'");
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
throw new common_1.BadRequestException('granteeUuid required');
|
|
43
|
+
const granteeUuid = (0, guard_1.uuidField)(b.granteeUuid, 'granteeUuid');
|
|
45
44
|
if (!b.action || typeof b.action !== 'string')
|
|
46
45
|
throw new common_1.BadRequestException('action required');
|
|
47
46
|
return {
|
|
48
47
|
typeKey: b.typeKey,
|
|
49
|
-
recordId
|
|
48
|
+
recordId,
|
|
50
49
|
granteeKind: b.granteeKind,
|
|
51
|
-
granteeUuid
|
|
50
|
+
granteeUuid,
|
|
52
51
|
action: b.action,
|
|
53
52
|
};
|
|
54
53
|
}
|
|
@@ -65,8 +64,9 @@ let ShareController = class ShareController {
|
|
|
65
64
|
async list(req, typeKey, recordId) {
|
|
66
65
|
if (!typeKey || !recordId)
|
|
67
66
|
throw new common_1.BadRequestException('typeKey and recordId required');
|
|
67
|
+
const record = (0, guard_1.uuidField)(recordId, 'recordId'); // Phase 11 #1
|
|
68
68
|
try {
|
|
69
|
-
const shares = await this.shares.listShares(req.principal, typeKey,
|
|
69
|
+
const shares = await this.shares.listShares(req.principal, typeKey, record);
|
|
70
70
|
return { shares };
|
|
71
71
|
}
|
|
72
72
|
catch (e) {
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Principal } from '@astram/engine';
|
|
2
2
|
/** The authenticated request shape: the framework guard attaches `principal`. */
|
|
3
3
|
export interface AuthedRequest {
|
|
4
4
|
principal: Principal;
|
|
5
5
|
}
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Same id: identical strings, or the same uuid in canonical form (`canonicalUuid`, so letter case
|
|
8
|
+
* is ignored and anything padded or fullwidth is never "the same"). This is NOT how MariaDB
|
|
9
|
+
* compares: the uuid columns are `utf8mb4_unicode_ci`, which also ignores trailing spaces (PAD
|
|
10
|
+
* SPACE) and character width, so a caller's spelling can reach a row this function says differs
|
|
11
|
+
* (Phase 11 #1). Only compare ids that are already STORED or canonical (resolve the caller's id to
|
|
12
|
+
* the stored one first; route params and body fields are canonicalised by `UuidPipe`/`uuidField`).
|
|
13
|
+
*/
|
|
14
|
+
export declare function sameUuid(a: string | null | undefined, b: string | null | undefined): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Coerce a STORED field value (a record view read back from the engine) to a string uuid or null.
|
|
17
|
+
* Not an input validator: request ids go through `UuidPipe` / `uuidField` (Phase 11 #1).
|
|
18
|
+
*/
|
|
7
19
|
export declare function asUuid(v: unknown): string | null;
|
|
8
20
|
/**
|
|
9
21
|
* Coerce a stored effective-window bound to an epoch-ms number, or null. The
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/features/shared/http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/features/shared/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE/D,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAK5F;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAEhD;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAQnD"}
|
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sameUuid = sameUuid;
|
|
3
4
|
exports.asUuid = asUuid;
|
|
4
5
|
exports.asEpochMs = asEpochMs;
|
|
5
|
-
|
|
6
|
+
const engine_1 = require("@astram/engine");
|
|
7
|
+
/**
|
|
8
|
+
* Same id: identical strings, or the same uuid in canonical form (`canonicalUuid`, so letter case
|
|
9
|
+
* is ignored and anything padded or fullwidth is never "the same"). This is NOT how MariaDB
|
|
10
|
+
* compares: the uuid columns are `utf8mb4_unicode_ci`, which also ignores trailing spaces (PAD
|
|
11
|
+
* SPACE) and character width, so a caller's spelling can reach a row this function says differs
|
|
12
|
+
* (Phase 11 #1). Only compare ids that are already STORED or canonical (resolve the caller's id to
|
|
13
|
+
* the stored one first; route params and body fields are canonicalised by `UuidPipe`/`uuidField`).
|
|
14
|
+
*/
|
|
15
|
+
function sameUuid(a, b) {
|
|
16
|
+
if (typeof a !== 'string' || typeof b !== 'string')
|
|
17
|
+
return false;
|
|
18
|
+
if (a === b)
|
|
19
|
+
return true;
|
|
20
|
+
const canonical = (0, engine_1.canonicalUuid)(a);
|
|
21
|
+
return canonical !== null && canonical === (0, engine_1.canonicalUuid)(b);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Coerce a STORED field value (a record view read back from the engine) to a string uuid or null.
|
|
25
|
+
* Not an input validator: request ids go through `UuidPipe` / `uuidField` (Phase 11 #1).
|
|
26
|
+
*/
|
|
6
27
|
function asUuid(v) {
|
|
7
28
|
return typeof v === 'string' && v.length > 0 ? v : null;
|
|
8
29
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user.controller.d.ts","sourceRoot":"","sources":["../../../src/features/user/user.controller.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAOvD,qBAEa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,WAAW;IAE/C;;;;OAIG;IAGG,SAAS,CAAU,KAAK,EAAE,YAAY;;;;;;IAM5C;;;OAGG;IAGG,UAAU,CACP,GAAG,EAAE,aAAa,EACjB,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE;;;
|
|
1
|
+
{"version":3,"file":"user.controller.d.ts","sourceRoot":"","sources":["../../../src/features/user/user.controller.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAOvD,qBAEa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,WAAW;IAE/C;;;;OAIG;IAGG,SAAS,CAAU,KAAK,EAAE,YAAY;;;;;;IAM5C;;;OAGG;IAGG,UAAU,CACP,GAAG,EAAE,aAAa,EACjB,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE;;;IAU3D;0CACsC;IAGtC,OAAO,CAA0B,IAAI,EAAE,MAAM;IAI7C,iGAAiG;IAG3F,UAAU,CACP,GAAG,EAAE,aAAa,EACA,IAAI,EAAE,MAAM,EAC7B,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;;;IAUtE,0FAA0F;IAGpF,UAAU,CAAQ,GAAG,EAAE,aAAa,EAA2B,IAAI,EAAE,MAAM;;;CAIlF"}
|
|
@@ -42,11 +42,10 @@ let UserController = class UserController {
|
|
|
42
42
|
* first (active) membership in the same transaction; it must be a company the caller manages.
|
|
43
43
|
*/
|
|
44
44
|
async createUser(req, body) {
|
|
45
|
+
const companyUuid = (0, guard_1.optionalUuidField)(body?.companyUuid, 'companyUuid');
|
|
45
46
|
const id = await this.users.createUser(req.principal, {
|
|
46
47
|
username: body?.username ?? 'Untitled',
|
|
47
|
-
...(
|
|
48
|
-
? { companyUuid: body.companyUuid }
|
|
49
|
-
: {}),
|
|
48
|
+
...(companyUuid !== undefined ? { companyUuid } : {}),
|
|
50
49
|
});
|
|
51
50
|
return { id };
|
|
52
51
|
}
|
|
@@ -91,7 +90,7 @@ __decorate([
|
|
|
91
90
|
__decorate([
|
|
92
91
|
(0, common_1.Get)('users/:uuid'),
|
|
93
92
|
(0, guard_1.Classified)({ typeKey: 'user', action: 'read' }),
|
|
94
|
-
__param(0, (0, common_1.Param)('uuid')),
|
|
93
|
+
__param(0, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
95
94
|
__metadata("design:type", Function),
|
|
96
95
|
__metadata("design:paramtypes", [String]),
|
|
97
96
|
__metadata("design:returntype", void 0)
|
|
@@ -100,7 +99,7 @@ __decorate([
|
|
|
100
99
|
(0, common_1.Put)('users/:uuid'),
|
|
101
100
|
(0, guard_1.Classified)({ typeKey: 'user', action: 'update' }),
|
|
102
101
|
__param(0, (0, common_1.Req)()),
|
|
103
|
-
__param(1, (0, common_1.Param)('uuid')),
|
|
102
|
+
__param(1, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
104
103
|
__param(2, (0, common_1.Body)()),
|
|
105
104
|
__metadata("design:type", Function),
|
|
106
105
|
__metadata("design:paramtypes", [Object, String, Object]),
|
|
@@ -110,7 +109,7 @@ __decorate([
|
|
|
110
109
|
(0, common_1.Delete)('users/:uuid'),
|
|
111
110
|
(0, guard_1.Classified)({ typeKey: 'user', action: 'delete' }),
|
|
112
111
|
__param(0, (0, common_1.Req)()),
|
|
113
|
-
__param(1, (0, common_1.Param)('uuid')),
|
|
112
|
+
__param(1, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
114
113
|
__metadata("design:type", Function),
|
|
115
114
|
__metadata("design:paramtypes", [Object, String]),
|
|
116
115
|
__metadata("design:returntype", Promise)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astram/identity",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Astram framework — the framework-owned identity + tenancy model. Hosts the identity @ObjectType classes (user, company, membership, posting), framework-authored identity migrations, and the identity resolver. Top of the dependency graph: depends on @astram/engine (for @ObjectType/@Field decorators) + @astram/guard / @astram/core (for the auth ports).",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"mariadb": "^3.5.4",
|
|
39
39
|
"@astram/core": "1.4.0",
|
|
40
|
-
"@astram/
|
|
41
|
-
"@astram/
|
|
40
|
+
"@astram/guard": "0.10.2",
|
|
41
|
+
"@astram/engine": "0.5.2"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@nestjs/common": "^11",
|