@astram/identity 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/dist/company-membership.object.d.ts.map +1 -1
  2. package/dist/company-membership.object.js +3 -2
  3. package/dist/company.object.d.ts.map +1 -1
  4. package/dist/company.object.js +8 -3
  5. package/dist/features/access/index.d.ts.map +1 -1
  6. package/dist/features/access/index.js +1 -5
  7. package/dist/features/audit/audit-read.service.d.ts.map +1 -1
  8. package/dist/features/audit/audit-read.service.js +13 -6
  9. package/dist/features/audit/audit.controller.d.ts.map +1 -1
  10. package/dist/features/audit/audit.controller.js +2 -0
  11. package/dist/features/audit/mariadb-audit-reader.d.ts.map +1 -1
  12. package/dist/features/authz/access-bundle-reconcile.job.d.ts.map +1 -1
  13. package/dist/features/authz/catalog-reconcile.job.d.ts.map +1 -1
  14. package/dist/features/authz/catalog-reconcile.job.js +1 -3
  15. package/dist/features/authz/db-permission-facts.adapter.d.ts +16 -0
  16. package/dist/features/authz/db-permission-facts.adapter.d.ts.map +1 -1
  17. package/dist/features/authz/db-permission-facts.adapter.js +46 -2
  18. package/dist/features/authz/grant-admin.controller.d.ts +8 -5
  19. package/dist/features/authz/grant-admin.controller.d.ts.map +1 -1
  20. package/dist/features/authz/grant-admin.controller.js +20 -18
  21. package/dist/features/authz/grant-admin.service.d.ts +49 -16
  22. package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
  23. package/dist/features/authz/grant-admin.service.js +155 -96
  24. package/dist/features/authz/grant-ceiling.d.ts +52 -1
  25. package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
  26. package/dist/features/authz/grant-ceiling.js +156 -2
  27. package/dist/features/authz/mariadb-catalog-store.d.ts.map +1 -1
  28. package/dist/features/authz/mariadb-catalog-store.js +15 -3
  29. package/dist/features/authz/permission-version.bumper.d.ts +45 -1
  30. package/dist/features/authz/permission-version.bumper.d.ts.map +1 -1
  31. package/dist/features/authz/permission-version.bumper.js +106 -16
  32. package/dist/features/authz/record-authorization.d.ts.map +1 -1
  33. package/dist/features/authz/role-binding.d.ts +36 -0
  34. package/dist/features/authz/role-binding.d.ts.map +1 -0
  35. package/dist/features/authz/role-binding.js +53 -0
  36. package/dist/features/company/company-hierarchy.service.d.ts +46 -0
  37. package/dist/features/company/company-hierarchy.service.d.ts.map +1 -0
  38. package/dist/features/company/company-hierarchy.service.js +100 -0
  39. package/dist/features/company/company.controller.d.ts +23 -5
  40. package/dist/features/company/company.controller.d.ts.map +1 -1
  41. package/dist/features/company/company.controller.js +47 -12
  42. package/dist/features/company/company.service.d.ts +28 -12
  43. package/dist/features/company/company.service.d.ts.map +1 -1
  44. package/dist/features/company/company.service.js +61 -14
  45. package/dist/features/cross-tenant/cross-tenant-grant.controller.d.ts.map +1 -1
  46. package/dist/features/cross-tenant/cross-tenant-grant.controller.js +2 -0
  47. package/dist/features/identity-feature.module.d.ts.map +1 -1
  48. package/dist/features/identity-feature.module.js +60 -3
  49. package/dist/features/membership/membership.controller.d.ts +6 -5
  50. package/dist/features/membership/membership.controller.d.ts.map +1 -1
  51. package/dist/features/membership/membership.controller.js +34 -22
  52. package/dist/features/membership/membership.service.d.ts +20 -14
  53. package/dist/features/membership/membership.service.d.ts.map +1 -1
  54. package/dist/features/membership/membership.service.js +62 -29
  55. package/dist/features/membership/posting-version-bump.d.ts +5 -0
  56. package/dist/features/membership/posting-version-bump.d.ts.map +1 -1
  57. package/dist/features/membership/posting.service.d.ts +20 -24
  58. package/dist/features/membership/posting.service.d.ts.map +1 -1
  59. package/dist/features/membership/posting.service.js +101 -64
  60. package/dist/features/org-structure/org-catalog.controller.d.ts.map +1 -1
  61. package/dist/features/org-structure/org-catalog.controller.js +2 -0
  62. package/dist/features/org-structure/org-catalog.service.d.ts.map +1 -1
  63. package/dist/features/org-structure/org-catalog.service.js +4 -1
  64. package/dist/features/policy/policy.controller.d.ts.map +1 -1
  65. package/dist/features/policy/policy.controller.js +2 -0
  66. package/dist/features/role/role.controller.d.ts +3 -2
  67. package/dist/features/role/role.controller.d.ts.map +1 -1
  68. package/dist/features/role/role.controller.js +15 -10
  69. package/dist/features/role/role.service.d.ts +16 -8
  70. package/dist/features/role/role.service.d.ts.map +1 -1
  71. package/dist/features/role/role.service.js +24 -10
  72. package/dist/features/scope/scope-closure.service.d.ts +44 -4
  73. package/dist/features/scope/scope-closure.service.d.ts.map +1 -1
  74. package/dist/features/scope/scope-closure.service.js +215 -82
  75. package/dist/features/service-account/service-account.controller.d.ts +3 -2
  76. package/dist/features/service-account/service-account.controller.d.ts.map +1 -1
  77. package/dist/features/service-account/service-account.controller.js +29 -15
  78. package/dist/features/service-account/service-account.service.d.ts +14 -7
  79. package/dist/features/service-account/service-account.service.d.ts.map +1 -1
  80. package/dist/features/service-account/service-account.service.js +56 -11
  81. package/dist/features/share/share.controller.d.ts.map +1 -1
  82. package/dist/features/share/share.controller.js +2 -0
  83. package/dist/features/share/share.service.d.ts.map +1 -1
  84. package/dist/features/shared/identity-error.interceptor.d.ts +24 -0
  85. package/dist/features/shared/identity-error.interceptor.d.ts.map +1 -0
  86. package/dist/features/shared/identity-error.interceptor.js +82 -0
  87. package/dist/features/shared/identity-errors.d.ts +23 -0
  88. package/dist/features/shared/identity-errors.d.ts.map +1 -0
  89. package/dist/features/shared/identity-errors.js +41 -0
  90. package/dist/features/user/session-revocation-check.d.ts +11 -0
  91. package/dist/features/user/session-revocation-check.d.ts.map +1 -0
  92. package/dist/features/user/session-revocation-check.js +21 -0
  93. package/dist/features/user/user.controller.d.ts +11 -6
  94. package/dist/features/user/user.controller.d.ts.map +1 -1
  95. package/dist/features/user/user.controller.js +35 -17
  96. package/dist/features/user/user.service.d.ts +55 -14
  97. package/dist/features/user/user.service.d.ts.map +1 -1
  98. package/dist/features/user/user.service.js +143 -16
  99. package/dist/membership-posting.object.d.ts.map +1 -1
  100. package/dist/membership-posting.object.js +4 -2
  101. package/dist/objects.d.ts.map +1 -1
  102. package/dist/objects.js +13 -3
  103. package/dist/role.object.d.ts.map +1 -1
  104. package/dist/role.object.js +3 -0
  105. package/dist/user.object.d.ts.map +1 -1
  106. package/dist/user.object.js +3 -0
  107. package/migrations/1710000000019-AddSessionMustReenroll.cjs +35 -0
  108. package/package.json +4 -4
  109. package/dist/auth-facts-keys.js.map +0 -1
  110. package/dist/branch.object.js.map +0 -1
  111. package/dist/company-branch.object.js.map +0 -1
  112. package/dist/company-department.object.js.map +0 -1
  113. package/dist/company-membership.object.js.map +0 -1
  114. package/dist/company.object.js.map +0 -1
  115. package/dist/cross-tenant-grant.object.js.map +0 -1
  116. package/dist/department.object.js.map +0 -1
  117. package/dist/features/access/access-admin.access.js.map +0 -1
  118. package/dist/features/access/company-admin.access.js.map +0 -1
  119. package/dist/features/access/index.js.map +0 -1
  120. package/dist/features/access/user-management.access.js.map +0 -1
  121. package/dist/features/audit/audit-read.service.js.map +0 -1
  122. package/dist/features/audit/audit.controller.js.map +0 -1
  123. package/dist/features/audit/directory-label-resolver.js.map +0 -1
  124. package/dist/features/audit/mariadb-audit-reader.js.map +0 -1
  125. package/dist/features/authz/access-bundle-reconcile.job.js.map +0 -1
  126. package/dist/features/authz/access-bundle-store.js.map +0 -1
  127. package/dist/features/authz/catalog-bootstrap.provider.js.map +0 -1
  128. package/dist/features/authz/catalog-reconcile.job.js.map +0 -1
  129. package/dist/features/authz/db-permission-facts.adapter.js.map +0 -1
  130. package/dist/features/authz/grant-admin.controller.js.map +0 -1
  131. package/dist/features/authz/grant-admin.service.js.map +0 -1
  132. package/dist/features/authz/grant-ceiling.js.map +0 -1
  133. package/dist/features/authz/mariadb-catalog-store.js.map +0 -1
  134. package/dist/features/authz/permission-version.bumper.js.map +0 -1
  135. package/dist/features/authz/record-authorization.js.map +0 -1
  136. package/dist/features/authz/session-facts.reader.js.map +0 -1
  137. package/dist/features/company/company.controller.js.map +0 -1
  138. package/dist/features/company/company.service.js.map +0 -1
  139. package/dist/features/cross-tenant/cross-tenant-grant-read.service.js.map +0 -1
  140. package/dist/features/cross-tenant/cross-tenant-grant.controller.js.map +0 -1
  141. package/dist/features/cross-tenant/grant-authorize.js.map +0 -1
  142. package/dist/features/identity-feature.module.js.map +0 -1
  143. package/dist/features/membership/membership-checker.js.map +0 -1
  144. package/dist/features/membership/membership.controller.js.map +0 -1
  145. package/dist/features/membership/membership.service.js.map +0 -1
  146. package/dist/features/membership/posting-version-bump.js.map +0 -1
  147. package/dist/features/membership/posting.service.js.map +0 -1
  148. package/dist/features/org-structure/org-catalog.controller.js.map +0 -1
  149. package/dist/features/org-structure/org-catalog.service.js.map +0 -1
  150. package/dist/features/policy/policy.controller.js.map +0 -1
  151. package/dist/features/policy/policy.service.js.map +0 -1
  152. package/dist/features/role/role.controller.js.map +0 -1
  153. package/dist/features/role/role.service.js.map +0 -1
  154. package/dist/features/scope/scope-closure.service.js.map +0 -1
  155. package/dist/features/scope/scope-subtree.resolver.js.map +0 -1
  156. package/dist/features/share/share.controller.js.map +0 -1
  157. package/dist/features/share/share.service.js.map +0 -1
  158. package/dist/features/shared/http.js.map +0 -1
  159. package/dist/features/shared/list-query.js.map +0 -1
  160. package/dist/features/shared/named-option.js.map +0 -1
  161. package/dist/features/shared/record-resolver.js.map +0 -1
  162. package/dist/features/user/user.controller.js.map +0 -1
  163. package/dist/features/user/user.service.js.map +0 -1
  164. package/dist/index.js.map +0 -1
  165. package/dist/membership-posting.object.js.map +0 -1
  166. package/dist/migrations-dir.js.map +0 -1
  167. package/dist/objects.js.map +0 -1
  168. package/dist/record-share.object.js.map +0 -1
  169. package/dist/role.object.js.map +0 -1
  170. package/dist/schema.js.map +0 -1
  171. package/dist/shared-resource.object.js.map +0 -1
  172. package/dist/user.object.js.map +0 -1
@@ -33,6 +33,7 @@ exports.PolicyController = void 0;
33
33
  */
34
34
  const common_1 = require("@nestjs/common");
35
35
  const guard_1 = require("@astram/guard");
36
+ const identity_error_interceptor_js_1 = require("../shared/identity-error.interceptor.js");
36
37
  const policy_service_js_1 = require("./policy.service.js");
37
38
  let PolicyController = class PolicyController {
38
39
  policies;
@@ -146,5 +147,6 @@ __decorate([
146
147
  ], PolicyController.prototype, "delete", null);
147
148
  exports.PolicyController = PolicyController = __decorate([
148
149
  (0, common_1.Controller)('identity'),
150
+ (0, common_1.UseInterceptors)(identity_error_interceptor_js_1.IdentityErrorInterceptor),
149
151
  __metadata("design:paramtypes", [policy_service_js_1.PolicyService])
150
152
  ], PolicyController);
@@ -1,5 +1,6 @@
1
1
  import { RoleService } from './role.service.js';
2
2
  import { type ListQueryDto } from '../shared/list-query.js';
3
+ import type { AuthedRequest } from '../shared/http.js';
3
4
  export declare class RoleController {
4
5
  private readonly roles;
5
6
  constructor(roles: RoleService);
@@ -24,13 +25,13 @@ export declare class RoleController {
24
25
  * administrator, may read roles). */
25
26
  getRole(uuid: string): Promise<import("packages/engine/dist/types.js").RecordView | null>;
26
27
  /** Update a role — gated on `identity:role:update`. */
27
- updateRole(uuid: string, body: {
28
+ updateRole(req: AuthedRequest, uuid: string, body: {
28
29
  name?: string;
29
30
  }): Promise<{
30
31
  success: boolean;
31
32
  }>;
32
33
  /** Delete a role — gated on `identity:role:delete`. */
33
- deleteRole(uuid: string): Promise<{
34
+ deleteRole(req: AuthedRequest, uuid: string): Promise<{
34
35
  success: boolean;
35
36
  }>;
36
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"role.controller.d.ts","sourceRoot":"","sources":["../../../src/features/role/role.controller.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAK5E,qBACa,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,CAAgB,IAAI,EAAE,MAAM;IAInC,uDAAuD;IAGjD,UAAU,CAAgB,IAAI,EAAE,MAAM,EAAU,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;;;IAK7E,uDAAuD;IAGjD,UAAU,CAAgB,IAAI,EAAE,MAAM;;;CAI7C"}
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,CAAgB,IAAI,EAAE,MAAM;IAInC,uDAAuD;IAGjD,UAAU,CACP,GAAG,EAAE,aAAa,EACV,IAAI,EAAE,MAAM,EACnB,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;;;IAMjC,uDAAuD;IAGjD,UAAU,CAAQ,GAAG,EAAE,aAAa,EAAiB,IAAI,EAAE,MAAM;;;CAIxE"}
@@ -17,9 +17,11 @@ const common_1 = require("@nestjs/common");
17
17
  const guard_1 = require("@astram/guard");
18
18
  const role_service_js_1 = require("./role.service.js");
19
19
  const list_query_js_1 = require("../shared/list-query.js");
20
+ const identity_error_interceptor_js_1 = require("../shared/identity-error.interceptor.js");
20
21
  // Role management is gated on `identity:role:<action>` grants. `role` is CONTROL-plane
21
22
  // for ALL actions — including read: the capped root is the identity administrator (the
22
- // identity-administrator model), so it may list/read roles AND manage them.
23
+ // identity-administrator model), so it may list/read roles AND manage them. Update/delete
24
+ // also pass the role-binding rule (a role used outside the caller's subtree is root-only).
23
25
  let RoleController = class RoleController {
24
26
  roles;
25
27
  constructor(roles) {
@@ -46,13 +48,13 @@ let RoleController = class RoleController {
46
48
  return this.roles.getRole(uuid);
47
49
  }
48
50
  /** Update a role — gated on `identity:role:update`. */
49
- async updateRole(uuid, body) {
50
- await this.roles.updateRole(uuid, body ?? {});
51
+ async updateRole(req, uuid, body) {
52
+ await this.roles.updateRole(req.principal, uuid, { name: body?.name });
51
53
  return { success: true };
52
54
  }
53
55
  /** Delete a role — gated on `identity:role:delete`. */
54
- async deleteRole(uuid) {
55
- await this.roles.deleteRole(uuid);
56
+ async deleteRole(req, uuid) {
57
+ await this.roles.deleteRole(req.principal, uuid);
56
58
  return { success: true };
57
59
  }
58
60
  };
@@ -84,21 +86,24 @@ __decorate([
84
86
  __decorate([
85
87
  (0, common_1.Put)('roles/:uuid'),
86
88
  (0, guard_1.Classified)({ typeKey: 'role', action: 'update' }),
87
- __param(0, (0, common_1.Param)('uuid')),
88
- __param(1, (0, common_1.Body)()),
89
+ __param(0, (0, common_1.Req)()),
90
+ __param(1, (0, common_1.Param)('uuid')),
91
+ __param(2, (0, common_1.Body)()),
89
92
  __metadata("design:type", Function),
90
- __metadata("design:paramtypes", [String, Object]),
93
+ __metadata("design:paramtypes", [Object, String, Object]),
91
94
  __metadata("design:returntype", Promise)
92
95
  ], RoleController.prototype, "updateRole", null);
93
96
  __decorate([
94
97
  (0, common_1.Delete)('roles/:uuid'),
95
98
  (0, guard_1.Classified)({ typeKey: 'role', action: 'delete' }),
96
- __param(0, (0, common_1.Param)('uuid')),
99
+ __param(0, (0, common_1.Req)()),
100
+ __param(1, (0, common_1.Param)('uuid')),
97
101
  __metadata("design:type", Function),
98
- __metadata("design:paramtypes", [String]),
102
+ __metadata("design:paramtypes", [Object, String]),
99
103
  __metadata("design:returntype", Promise)
100
104
  ], RoleController.prototype, "deleteRole", null);
101
105
  exports.RoleController = RoleController = __decorate([
102
106
  (0, common_1.Controller)('identity'),
107
+ (0, common_1.UseInterceptors)(identity_error_interceptor_js_1.IdentityErrorInterceptor),
103
108
  __metadata("design:paramtypes", [role_service_js_1.RoleService])
104
109
  ], RoleController);
@@ -1,24 +1,32 @@
1
1
  import { RecordsService, type RecordId } from '@astram/engine';
2
+ import type { Principal } from '@astram/core';
2
3
  import { type ListParams } from '../shared/list-query.js';
4
+ import { GrantCeiling } from '../authz/grant-ceiling.js';
3
5
  /**
4
6
  * RoleService — role CRUD on the paved road, via the framework's RecordsService
5
- * facade. The facade supplies the acting identity (the request principal) —
6
- * this service never threads a Principal. No raw SQL, no RECORD_ENGINE.
7
+ * facade. The facade supplies the acting identity (the request principal) for the pipe;
8
+ * no raw SQL, no RECORD_ENGINE.
9
+ *
10
+ * Roles are a GLOBAL catalog, so renaming or deleting one changes every tenant that uses it (a
11
+ * delete cascades its postings). Security hardening #1 (D1 interim rule): update/delete first pass
12
+ * `GrantCeiling.assertRoleAdministrable` — the role must be bound inside the actor's subtree and
13
+ * nowhere outside it (a shared role is root-only). The engine's write wall still applies after.
7
14
  */
8
15
  export declare class RoleService {
9
16
  private readonly records;
10
- constructor(records: RecordsService);
17
+ private readonly ceiling;
18
+ constructor(records: RecordsService, ceiling: GrantCeiling);
11
19
  /** Create a role. */
12
20
  createRole(name: string): Promise<RecordId>;
13
21
  /** List roles (tenant-scoped by the pipe), with paging/search/sort. */
14
22
  listRoles(params?: ListParams): Promise<import("@astram/engine").RecordPage>;
15
23
  /** Read a single role. */
16
24
  getRole(uuid: string): Promise<import("@astram/engine").RecordView | null>;
17
- /** Update a role. */
18
- updateRole(uuid: string, patch: {
19
- name?: string;
25
+ /** Rename a role — only a role the actor may administer (binding rule). Patch whitelisted. */
26
+ updateRole(actor: Principal, uuid: string, patch: {
27
+ name?: unknown;
20
28
  }): Promise<void>;
21
- /** Delete a role. */
22
- deleteRole(uuid: string): Promise<void>;
29
+ /** Delete a role — only a role the actor may administer (binding rule). */
30
+ deleteRole(actor: Principal, uuid: string): Promise<void>;
23
31
  }
24
32
  //# sourceMappingURL=role.service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"role.service.d.ts","sourceRoot":"","sources":["../../../src/features/role/role.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAK1E;;;;GAIG;AACH,qBACa,WAAW;IACV,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,cAAc;IAEpD,qBAAqB;IACrB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAI3C,uEAAuE;IACvE,SAAS,CAAC,MAAM,CAAC,EAAE,UAAU;IAI7B,0BAA0B;IAC1B,OAAO,CAAC,IAAI,EAAE,MAAM;IAIpB,qBAAqB;IACrB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjE,qBAAqB;IACrB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGxC"}
1
+ {"version":3,"file":"role.service.d.ts","sourceRoot":"","sources":["../../../src/features/role/role.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,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAKzD;;;;;;;;;GASG;AACH,qBACa,WAAW;IAEpB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,YAAY;IAGxC,qBAAqB;IACrB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAI3C,uEAAuE;IACvE,SAAS,CAAC,MAAM,CAAC,EAAE,UAAU;IAI7B,0BAA0B;IAC1B,OAAO,CAAC,IAAI,EAAE,MAAM;IAIpB,8FAA8F;IACxF,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAO1F,2EAA2E;IACrE,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAIhE"}
@@ -13,17 +13,25 @@ exports.RoleService = void 0;
13
13
  const common_1 = require("@nestjs/common");
14
14
  const engine_1 = require("@astram/engine");
15
15
  const list_query_js_1 = require("../shared/list-query.js");
16
+ const grant_ceiling_js_1 = require("../authz/grant-ceiling.js");
16
17
  const ROLE_SEARCH_FIELDS = ['name'];
17
18
  const ROLE_SORT_FIELDS = ['name', 'createdAt'];
18
19
  /**
19
20
  * RoleService — role CRUD on the paved road, via the framework's RecordsService
20
- * facade. The facade supplies the acting identity (the request principal) —
21
- * this service never threads a Principal. No raw SQL, no RECORD_ENGINE.
21
+ * facade. The facade supplies the acting identity (the request principal) for the pipe;
22
+ * no raw SQL, no RECORD_ENGINE.
23
+ *
24
+ * Roles are a GLOBAL catalog, so renaming or deleting one changes every tenant that uses it (a
25
+ * delete cascades its postings). Security hardening #1 (D1 interim rule): update/delete first pass
26
+ * `GrantCeiling.assertRoleAdministrable` — the role must be bound inside the actor's subtree and
27
+ * nowhere outside it (a shared role is root-only). The engine's write wall still applies after.
22
28
  */
23
29
  let RoleService = class RoleService {
24
30
  records;
25
- constructor(records) {
31
+ ceiling;
32
+ constructor(records, ceiling) {
26
33
  this.records = records;
34
+ this.ceiling = ceiling;
27
35
  }
28
36
  /** Create a role. */
29
37
  createRole(name) {
@@ -37,17 +45,23 @@ let RoleService = class RoleService {
37
45
  getRole(uuid) {
38
46
  return this.records.get('role', uuid);
39
47
  }
40
- /** Update a role. */
41
- updateRole(uuid, patch) {
42
- return this.records.update('role', uuid, { fields: patch });
48
+ /** Rename a role — only a role the actor may administer (binding rule). Patch whitelisted. */
49
+ async updateRole(actor, uuid, patch) {
50
+ await this.ceiling.assertRoleAdministrable(actor, uuid);
51
+ const fields = {};
52
+ if (typeof patch?.name === 'string')
53
+ fields.name = patch.name;
54
+ await this.records.update('role', uuid, { fields });
43
55
  }
44
- /** Delete a role. */
45
- deleteRole(uuid) {
46
- return this.records.delete('role', uuid);
56
+ /** Delete a role — only a role the actor may administer (binding rule). */
57
+ async deleteRole(actor, uuid) {
58
+ await this.ceiling.assertRoleAdministrable(actor, uuid);
59
+ await this.records.delete('role', uuid);
47
60
  }
48
61
  };
49
62
  exports.RoleService = RoleService;
50
63
  exports.RoleService = RoleService = __decorate([
51
64
  (0, common_1.Injectable)(),
52
- __metadata("design:paramtypes", [engine_1.RecordsService])
65
+ __metadata("design:paramtypes", [engine_1.RecordsService,
66
+ grant_ceiling_js_1.GrantCeiling])
53
67
  ], RoleService);
@@ -1,3 +1,4 @@
1
+ import { type ScopeEdge } from '@astram/guard';
1
2
  /**
2
3
  * The minimal query surface a transaction connection must expose. A `mariadb`
3
4
  * `PoolConnection` satisfies it structurally; kept framework-agnostic (mirrors `Queryable`
@@ -14,11 +15,17 @@ export interface ScopePool {
14
15
  commit(): Promise<void>;
15
16
  rollback(): Promise<void>;
16
17
  release(): Promise<void>;
18
+ /** Drop the session instead of pooling it (used if a named lock could not be released). */
19
+ destroy?(): void;
17
20
  }>;
18
21
  }
19
22
  /** The version-bump surface moveNode needs — satisfied by `PermissionVersionBumper`. */
20
23
  export interface ScopeVersionBumper {
21
24
  bumpForTenant(tenantUuid: string, conn: ScopeConn): Promise<void>;
25
+ /** ADR-0035: publish this transaction's bumped versions to Redis — called after `commit()`. */
26
+ publishCommitted?(conn: ScopeConn): Promise<void>;
27
+ /** ADR-0035: drop this transaction's bumped versions — called after `rollback()`. */
28
+ discardPending?(conn: ScopeConn): void;
22
29
  }
23
30
  export declare class ScopeClosureService {
24
31
  private readonly pool;
@@ -27,13 +34,32 @@ export declare class ScopeClosureService {
27
34
  /**
28
35
  * Rebuild `scope_closure` from the current `companies.c_parent_uuid` edges. Idempotent
29
36
  * (delete-then-insert in a tx → a second run over the same tree writes the identical set).
30
- * FAIL-LOUD: an orphaned/cyclic/too-deep edge makes `buildClosureRows` throw and that
31
- * propagates (the tx rolls back — never a partial closure). Returns a small summary + logs.
37
+ * A CYCLIC component is quarantined (no rows for it; logged loudly) so the rest of the forest
38
+ * still reconciles. FAIL-LOUD for the rest: an orphaned/too-deep edge makes `buildClosureRows`
39
+ * throw and that propagates (the tx rolls back — never a partial closure). Returns a summary.
32
40
  */
33
41
  reconcileClosure(): Promise<{
34
42
  nodes: number;
35
43
  rows: number;
44
+ quarantined: number;
36
45
  }>;
46
+ /**
47
+ * A node was just CREATED (security hardening D5): rebuild the closure so it has rows at once
48
+ * (not at the next hourly reconcile), and bump every ANCESTOR's permission version — their
49
+ * reachable subtree just grew — so their sessions pick the new node up at the next refresh.
50
+ * One transaction; fail-loud like `moveNode` (a corrupt tree aborts the rebuild). A node inside
51
+ * a quarantined cycle gets no rows and bumps nothing.
52
+ */
53
+ attachNode(nodeUuid: string): Promise<void>;
54
+ /**
55
+ * Run `fn` in ONE transaction while holding the tree's named lock (security review F3). Every
56
+ * hierarchy writer (move, delete, attach, reconcile) takes the SAME `GET_LOCK` on its own
57
+ * connection BEFORE `BEGIN`, so they run one at a time and each transaction's first read sees
58
+ * every move committed before it. Without it, two concurrent moves each pass the cycle check
59
+ * against the other's pre-move tree and together form a cycle. A lock that cannot be taken in
60
+ * time fails closed (503, nothing written). The lock is released after commit/rollback.
61
+ */
62
+ private inTreeLock;
37
63
  /**
38
64
  * Re-parent `nodeUuid` under `newParentUuid` (or null for a root). Cycle-checked FIRST via the
39
65
  * closure: if `newParentUuid` is already a descendant of `nodeUuid`, throw `ScopeCycleError`
@@ -51,7 +77,9 @@ export declare class ScopeClosureService {
51
77
  * HIGH stale-subtree-after-move leak: a parent admin kept reaching a moved-away tenant.
52
78
  * Nodes on neither side keep their exact set and are not bumped.
53
79
  */
54
- moveNode(nodeUuid: string, newParentUuid: string | null): Promise<void>;
80
+ moveNode(nodeUuid: string, newParentUuid: string | null,
81
+ /** Runs on the move's transaction after the bump (e.g. the audit row) — a throw rolls back. */
82
+ inTransaction?: (conn: ScopeConn) => Promise<void>): Promise<void>;
55
83
  /**
56
84
  * Delete `nodeUuid`, FAIL-CLOSED. Refuses (throws) if the node has ANY child company — never
57
85
  * silently orphans/promotes a sub-org (the `...016` FK RESTRICT enforces this at the DB too; the
@@ -65,7 +93,9 @@ export declare class ScopeClosureService {
65
93
  * the many tenant-scoped app tables that reference the deleted node) is out of scope for v1 and
66
94
  * is tracked as a follow-up; deleteNode v1 only guarantees the HIERARCHY stays consistent.
67
95
  */
68
- deleteNode(nodeUuid: string): Promise<void>;
96
+ deleteNode(nodeUuid: string,
97
+ /** Runs on the delete's transaction after the bump (e.g. the audit row) — a throw rolls back. */
98
+ inTransaction?: (conn: ScopeConn) => Promise<void>): Promise<void>;
69
99
  /** Read the node forest's parent edges (`c_uuid` → `c_parent_uuid`) as `ScopeEdge[]`. */
70
100
  private readEdges;
71
101
  /**
@@ -75,6 +105,16 @@ export declare class ScopeClosureService {
75
105
  */
76
106
  private rewriteClosure;
77
107
  }
108
+ /**
109
+ * Split the edge set into the CLEAN part and the nodes QUARANTINED because their parent chain
110
+ * reaches a cycle (the cycle's members and everything below them). PURE. Orphans and depth are
111
+ * left to `buildClosureRows` (still fail-loud): only a cycle is quarantined, because a cycle is
112
+ * the corruption a tenant admin could once create through the generic company patch (#2).
113
+ */
114
+ export declare function quarantineCycles(edges: readonly ScopeEdge[]): {
115
+ clean: ScopeEdge[];
116
+ quarantined: string[];
117
+ };
78
118
  /** deleteNode fail-closed guard: the node still has child companies (re-parent/cascade first). */
79
119
  export declare class NodeHasChildrenError extends Error {
80
120
  constructor(nodeUuid: string);
@@ -1 +1 @@
1
- {"version":3,"file":"scope-closure.service.d.ts","sourceRoot":"","sources":["../../../src/features/scope/scope-closure.service.ts"],"names":[],"mappings":"AAqCA;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED,iGAAiG;AACjG,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE,aAAa,IAAI,OAAO,CACtB,SAAS,GAAG;QACV,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QACxB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;KAC1B,CACF,CAAC;CACH;AAED,wFAAwF;AACxF,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE;AAED,qBAAa,mBAAmB;IAE5B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,kBAAkB;IAG7C;;;;;OAKG;IACG,gBAAgB,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAoBlE;;;;;;;;;;;;;;;;OAgBG;IACG,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IA2E7E;;;;;;;;;;;;OAYG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyCjD,yFAAyF;YAC3E,SAAS;IAOvB;;;;OAIG;YACW,cAAc;CAiB7B;AAED,kGAAkG;AAClG,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,QAAQ,EAAE,MAAM;CAQ7B"}
1
+ {"version":3,"file":"scope-closure.service.d.ts","sourceRoot":"","sources":["../../../src/features/scope/scope-closure.service.ts"],"names":[],"mappings":"AAqCA,OAAO,EAAqC,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAOlF;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED,iGAAiG;AACjG,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE,aAAa,IAAI,OAAO,CACtB,SAAS,GAAG;QACV,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QACxB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QACzB,2FAA2F;QAC3F,OAAO,CAAC,IAAI,IAAI,CAAC;KAClB,CACF,CAAC;CACH;AAOD,wFAAwF;AACxF,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,+FAA+F;IAC/F,gBAAgB,CAAC,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,qFAAqF;IACrF,cAAc,CAAC,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;CACxC;AAED,qBAAa,mBAAmB;IAE5B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,kBAAkB;IAG7C;;;;;;OAMG;IACG,gBAAgB,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAsBvF;;;;;;OAMG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcjD;;;;;;;OAOG;YACW,UAAU;IAwCxB;;;;;;;;;;;;;;;;OAgBG;IACG,QAAQ,CACZ,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,GAAG,IAAI;IAC5B,+FAA+F;IAC/F,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,GACjD,OAAO,CAAC,IAAI,CAAC;IAiFhB;;;;;;;;;;;;OAYG;IACG,UAAU,CACd,QAAQ,EAAE,MAAM;IAChB,iGAAiG;IACjG,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,GACjD,OAAO,CAAC,IAAI,CAAC;IAoChB,yFAAyF;YAC3E,SAAS;IAOvB;;;;OAIG;YACW,cAAc;CAiB7B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG;IAC7D,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CA8BA;AAED,kGAAkG;AAClG,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,QAAQ,EAAE,MAAM;CAQ7B"}