@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.
- package/dist/company-membership.object.d.ts.map +1 -1
- package/dist/company-membership.object.js +3 -2
- package/dist/company.object.d.ts.map +1 -1
- package/dist/company.object.js +8 -3
- package/dist/features/access/index.d.ts.map +1 -1
- package/dist/features/access/index.js +1 -5
- package/dist/features/audit/audit-read.service.d.ts.map +1 -1
- package/dist/features/audit/audit-read.service.js +13 -6
- package/dist/features/audit/audit.controller.d.ts.map +1 -1
- package/dist/features/audit/audit.controller.js +2 -0
- package/dist/features/audit/mariadb-audit-reader.d.ts.map +1 -1
- package/dist/features/authz/access-bundle-reconcile.job.d.ts.map +1 -1
- package/dist/features/authz/catalog-reconcile.job.d.ts.map +1 -1
- package/dist/features/authz/catalog-reconcile.job.js +1 -3
- package/dist/features/authz/db-permission-facts.adapter.d.ts +16 -0
- package/dist/features/authz/db-permission-facts.adapter.d.ts.map +1 -1
- package/dist/features/authz/db-permission-facts.adapter.js +46 -2
- package/dist/features/authz/grant-admin.controller.d.ts +8 -5
- package/dist/features/authz/grant-admin.controller.d.ts.map +1 -1
- package/dist/features/authz/grant-admin.controller.js +20 -18
- package/dist/features/authz/grant-admin.service.d.ts +49 -16
- package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
- package/dist/features/authz/grant-admin.service.js +155 -96
- package/dist/features/authz/grant-ceiling.d.ts +52 -1
- package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
- package/dist/features/authz/grant-ceiling.js +156 -2
- package/dist/features/authz/mariadb-catalog-store.d.ts.map +1 -1
- package/dist/features/authz/mariadb-catalog-store.js +15 -3
- package/dist/features/authz/permission-version.bumper.d.ts +45 -1
- package/dist/features/authz/permission-version.bumper.d.ts.map +1 -1
- package/dist/features/authz/permission-version.bumper.js +106 -16
- package/dist/features/authz/record-authorization.d.ts.map +1 -1
- package/dist/features/authz/role-binding.d.ts +36 -0
- package/dist/features/authz/role-binding.d.ts.map +1 -0
- package/dist/features/authz/role-binding.js +53 -0
- package/dist/features/company/company-hierarchy.service.d.ts +46 -0
- package/dist/features/company/company-hierarchy.service.d.ts.map +1 -0
- package/dist/features/company/company-hierarchy.service.js +100 -0
- package/dist/features/company/company.controller.d.ts +23 -5
- package/dist/features/company/company.controller.d.ts.map +1 -1
- package/dist/features/company/company.controller.js +47 -12
- package/dist/features/company/company.service.d.ts +28 -12
- package/dist/features/company/company.service.d.ts.map +1 -1
- package/dist/features/company/company.service.js +61 -14
- package/dist/features/cross-tenant/cross-tenant-grant.controller.d.ts.map +1 -1
- package/dist/features/cross-tenant/cross-tenant-grant.controller.js +2 -0
- package/dist/features/identity-feature.module.d.ts.map +1 -1
- package/dist/features/identity-feature.module.js +60 -3
- package/dist/features/membership/membership.controller.d.ts +6 -5
- package/dist/features/membership/membership.controller.d.ts.map +1 -1
- package/dist/features/membership/membership.controller.js +34 -22
- package/dist/features/membership/membership.service.d.ts +20 -14
- package/dist/features/membership/membership.service.d.ts.map +1 -1
- package/dist/features/membership/membership.service.js +62 -29
- package/dist/features/membership/posting-version-bump.d.ts +5 -0
- package/dist/features/membership/posting-version-bump.d.ts.map +1 -1
- package/dist/features/membership/posting.service.d.ts +20 -24
- package/dist/features/membership/posting.service.d.ts.map +1 -1
- package/dist/features/membership/posting.service.js +101 -64
- package/dist/features/org-structure/org-catalog.controller.d.ts.map +1 -1
- package/dist/features/org-structure/org-catalog.controller.js +2 -0
- package/dist/features/org-structure/org-catalog.service.d.ts.map +1 -1
- package/dist/features/org-structure/org-catalog.service.js +4 -1
- package/dist/features/policy/policy.controller.d.ts.map +1 -1
- package/dist/features/policy/policy.controller.js +2 -0
- package/dist/features/role/role.controller.d.ts +3 -2
- package/dist/features/role/role.controller.d.ts.map +1 -1
- package/dist/features/role/role.controller.js +15 -10
- package/dist/features/role/role.service.d.ts +16 -8
- package/dist/features/role/role.service.d.ts.map +1 -1
- package/dist/features/role/role.service.js +24 -10
- package/dist/features/scope/scope-closure.service.d.ts +44 -4
- package/dist/features/scope/scope-closure.service.d.ts.map +1 -1
- package/dist/features/scope/scope-closure.service.js +215 -82
- package/dist/features/service-account/service-account.controller.d.ts +3 -2
- package/dist/features/service-account/service-account.controller.d.ts.map +1 -1
- package/dist/features/service-account/service-account.controller.js +29 -15
- package/dist/features/service-account/service-account.service.d.ts +14 -7
- package/dist/features/service-account/service-account.service.d.ts.map +1 -1
- package/dist/features/service-account/service-account.service.js +56 -11
- package/dist/features/share/share.controller.d.ts.map +1 -1
- package/dist/features/share/share.controller.js +2 -0
- package/dist/features/share/share.service.d.ts.map +1 -1
- package/dist/features/shared/identity-error.interceptor.d.ts +24 -0
- package/dist/features/shared/identity-error.interceptor.d.ts.map +1 -0
- package/dist/features/shared/identity-error.interceptor.js +82 -0
- package/dist/features/shared/identity-errors.d.ts +23 -0
- package/dist/features/shared/identity-errors.d.ts.map +1 -0
- package/dist/features/shared/identity-errors.js +41 -0
- package/dist/features/user/session-revocation-check.d.ts +11 -0
- package/dist/features/user/session-revocation-check.d.ts.map +1 -0
- package/dist/features/user/session-revocation-check.js +21 -0
- package/dist/features/user/user.controller.d.ts +11 -6
- package/dist/features/user/user.controller.d.ts.map +1 -1
- package/dist/features/user/user.controller.js +35 -17
- package/dist/features/user/user.service.d.ts +55 -14
- package/dist/features/user/user.service.d.ts.map +1 -1
- package/dist/features/user/user.service.js +143 -16
- package/dist/membership-posting.object.d.ts.map +1 -1
- package/dist/membership-posting.object.js +4 -2
- package/dist/objects.d.ts.map +1 -1
- package/dist/objects.js +13 -3
- package/dist/role.object.d.ts.map +1 -1
- package/dist/role.object.js +3 -0
- package/dist/user.object.d.ts.map +1 -1
- package/dist/user.object.js +3 -0
- package/migrations/1710000000019-AddSessionMustReenroll.cjs +35 -0
- package/package.json +4 -4
- package/dist/auth-facts-keys.js.map +0 -1
- package/dist/branch.object.js.map +0 -1
- package/dist/company-branch.object.js.map +0 -1
- package/dist/company-department.object.js.map +0 -1
- package/dist/company-membership.object.js.map +0 -1
- package/dist/company.object.js.map +0 -1
- package/dist/cross-tenant-grant.object.js.map +0 -1
- package/dist/department.object.js.map +0 -1
- package/dist/features/access/access-admin.access.js.map +0 -1
- package/dist/features/access/company-admin.access.js.map +0 -1
- package/dist/features/access/index.js.map +0 -1
- package/dist/features/access/user-management.access.js.map +0 -1
- package/dist/features/audit/audit-read.service.js.map +0 -1
- package/dist/features/audit/audit.controller.js.map +0 -1
- package/dist/features/audit/directory-label-resolver.js.map +0 -1
- package/dist/features/audit/mariadb-audit-reader.js.map +0 -1
- package/dist/features/authz/access-bundle-reconcile.job.js.map +0 -1
- package/dist/features/authz/access-bundle-store.js.map +0 -1
- package/dist/features/authz/catalog-bootstrap.provider.js.map +0 -1
- package/dist/features/authz/catalog-reconcile.job.js.map +0 -1
- package/dist/features/authz/db-permission-facts.adapter.js.map +0 -1
- package/dist/features/authz/grant-admin.controller.js.map +0 -1
- package/dist/features/authz/grant-admin.service.js.map +0 -1
- package/dist/features/authz/grant-ceiling.js.map +0 -1
- package/dist/features/authz/mariadb-catalog-store.js.map +0 -1
- package/dist/features/authz/permission-version.bumper.js.map +0 -1
- package/dist/features/authz/record-authorization.js.map +0 -1
- package/dist/features/authz/session-facts.reader.js.map +0 -1
- package/dist/features/company/company.controller.js.map +0 -1
- package/dist/features/company/company.service.js.map +0 -1
- package/dist/features/cross-tenant/cross-tenant-grant-read.service.js.map +0 -1
- package/dist/features/cross-tenant/cross-tenant-grant.controller.js.map +0 -1
- package/dist/features/cross-tenant/grant-authorize.js.map +0 -1
- package/dist/features/identity-feature.module.js.map +0 -1
- package/dist/features/membership/membership-checker.js.map +0 -1
- package/dist/features/membership/membership.controller.js.map +0 -1
- package/dist/features/membership/membership.service.js.map +0 -1
- package/dist/features/membership/posting-version-bump.js.map +0 -1
- package/dist/features/membership/posting.service.js.map +0 -1
- package/dist/features/org-structure/org-catalog.controller.js.map +0 -1
- package/dist/features/org-structure/org-catalog.service.js.map +0 -1
- package/dist/features/policy/policy.controller.js.map +0 -1
- package/dist/features/policy/policy.service.js.map +0 -1
- package/dist/features/role/role.controller.js.map +0 -1
- package/dist/features/role/role.service.js.map +0 -1
- package/dist/features/scope/scope-closure.service.js.map +0 -1
- package/dist/features/scope/scope-subtree.resolver.js.map +0 -1
- package/dist/features/share/share.controller.js.map +0 -1
- package/dist/features/share/share.service.js.map +0 -1
- package/dist/features/shared/http.js.map +0 -1
- package/dist/features/shared/list-query.js.map +0 -1
- package/dist/features/shared/named-option.js.map +0 -1
- package/dist/features/shared/record-resolver.js.map +0 -1
- package/dist/features/user/user.controller.js.map +0 -1
- package/dist/features/user/user.service.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/membership-posting.object.js.map +0 -1
- package/dist/migrations-dir.js.map +0 -1
- package/dist/objects.js.map +0 -1
- package/dist/record-share.object.js.map +0 -1
- package/dist/role.object.js.map +0 -1
- package/dist/schema.js.map +0 -1
- package/dist/shared-resource.object.js.map +0 -1
- package/dist/user.object.js.map +0 -1
|
@@ -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":"
|
|
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.
|
|
88
|
-
__param(1, (0, common_1.
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
/**
|
|
18
|
-
updateRole(uuid: string, patch: {
|
|
19
|
-
name?:
|
|
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;
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
/**
|
|
41
|
-
updateRole(uuid, patch) {
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
31
|
-
*
|
|
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
|
|
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
|
|
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;
|
|
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"}
|