@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
|
@@ -17,6 +17,7 @@ const common_1 = require("@nestjs/common");
|
|
|
17
17
|
const guard_1 = require("@astram/guard");
|
|
18
18
|
const company_service_js_1 = require("./company.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
|
let CompanyController = class CompanyController {
|
|
21
22
|
companies;
|
|
22
23
|
constructor(companies) {
|
|
@@ -32,23 +33,44 @@ let CompanyController = class CompanyController {
|
|
|
32
33
|
const result = await this.companies.listCompanies(params);
|
|
33
34
|
return { total: result.total, rows: result.rows, page, limit };
|
|
34
35
|
}
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Create a company — gated on `identity:company:create`. A non-root caller's company is created
|
|
38
|
+
* UNDER the caller's own organisation (D5); only root creates a top-level tenant.
|
|
39
|
+
*/
|
|
40
|
+
async createCompany(req, body) {
|
|
41
|
+
const id = await this.companies.createCompany(req.principal, body?.name ?? 'Untitled');
|
|
38
42
|
return { id };
|
|
39
43
|
}
|
|
40
44
|
/** Read one company — gated on `identity:company:read`. */
|
|
41
45
|
getCompany(uuid) {
|
|
42
46
|
return this.companies.getCompany(uuid);
|
|
43
47
|
}
|
|
44
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Move a company under a new parent — gated on `identity:company:update`. Body
|
|
50
|
+
* `{ parentUuid: string | null }`. Non-root: the company and the new parent must both be in the
|
|
51
|
+
* caller's subtree, the company is not the caller's own root, and the parent is not null. A move
|
|
52
|
+
* that would form a cycle → 409. Declared BEFORE `companies/:uuid` (static sub-path first).
|
|
53
|
+
*/
|
|
54
|
+
async moveCompany(req, uuid, body) {
|
|
55
|
+
if (!body ||
|
|
56
|
+
!('parentUuid' in body) ||
|
|
57
|
+
(body.parentUuid !== null && typeof body.parentUuid !== 'string')) {
|
|
58
|
+
throw new common_1.BadRequestException('parentUuid (a company id, or null) is required');
|
|
59
|
+
}
|
|
60
|
+
await this.companies.moveCompany(req.principal, uuid, body.parentUuid ?? null);
|
|
61
|
+
return { success: true };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Update a company's name/status — gated on `identity:company:update`. Any other key
|
|
65
|
+
* (parentUuid, type, …) is ignored: use `PUT companies/:uuid/parent` to move a company.
|
|
66
|
+
*/
|
|
45
67
|
async updateCompany(uuid, body) {
|
|
46
|
-
await this.companies.updateCompany(uuid, body
|
|
68
|
+
await this.companies.updateCompany(uuid, { name: body?.name, status: body?.status });
|
|
47
69
|
return { success: true };
|
|
48
70
|
}
|
|
49
|
-
/** Delete a company — gated on `identity:company:delete`. */
|
|
50
|
-
async deleteCompany(uuid) {
|
|
51
|
-
await this.companies.deleteCompany(uuid);
|
|
71
|
+
/** Delete a company — gated on `identity:company:delete`. A company with sub-companies → 409. */
|
|
72
|
+
async deleteCompany(req, uuid) {
|
|
73
|
+
await this.companies.deleteCompany(req.principal, uuid);
|
|
52
74
|
return { success: true };
|
|
53
75
|
}
|
|
54
76
|
};
|
|
@@ -64,9 +86,10 @@ __decorate([
|
|
|
64
86
|
__decorate([
|
|
65
87
|
(0, common_1.Post)('companies'),
|
|
66
88
|
(0, guard_1.Classified)({ typeKey: 'company', action: 'create' }),
|
|
67
|
-
__param(0, (0, common_1.
|
|
89
|
+
__param(0, (0, common_1.Req)()),
|
|
90
|
+
__param(1, (0, common_1.Body)()),
|
|
68
91
|
__metadata("design:type", Function),
|
|
69
|
-
__metadata("design:paramtypes", [Object]),
|
|
92
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
70
93
|
__metadata("design:returntype", Promise)
|
|
71
94
|
], CompanyController.prototype, "createCompany", null);
|
|
72
95
|
__decorate([
|
|
@@ -77,6 +100,16 @@ __decorate([
|
|
|
77
100
|
__metadata("design:paramtypes", [String]),
|
|
78
101
|
__metadata("design:returntype", void 0)
|
|
79
102
|
], CompanyController.prototype, "getCompany", null);
|
|
103
|
+
__decorate([
|
|
104
|
+
(0, common_1.Put)('companies/:uuid/parent'),
|
|
105
|
+
(0, guard_1.Classified)({ typeKey: 'company', action: 'update' }),
|
|
106
|
+
__param(0, (0, common_1.Req)()),
|
|
107
|
+
__param(1, (0, common_1.Param)('uuid')),
|
|
108
|
+
__param(2, (0, common_1.Body)()),
|
|
109
|
+
__metadata("design:type", Function),
|
|
110
|
+
__metadata("design:paramtypes", [Object, String, Object]),
|
|
111
|
+
__metadata("design:returntype", Promise)
|
|
112
|
+
], CompanyController.prototype, "moveCompany", null);
|
|
80
113
|
__decorate([
|
|
81
114
|
(0, common_1.Put)('companies/:uuid'),
|
|
82
115
|
(0, guard_1.Classified)({ typeKey: 'company', action: 'update' }),
|
|
@@ -89,12 +122,14 @@ __decorate([
|
|
|
89
122
|
__decorate([
|
|
90
123
|
(0, common_1.Delete)('companies/:uuid'),
|
|
91
124
|
(0, guard_1.Classified)({ typeKey: 'company', action: 'delete' }),
|
|
92
|
-
__param(0, (0, common_1.
|
|
125
|
+
__param(0, (0, common_1.Req)()),
|
|
126
|
+
__param(1, (0, common_1.Param)('uuid')),
|
|
93
127
|
__metadata("design:type", Function),
|
|
94
|
-
__metadata("design:paramtypes", [String]),
|
|
128
|
+
__metadata("design:paramtypes", [Object, String]),
|
|
95
129
|
__metadata("design:returntype", Promise)
|
|
96
130
|
], CompanyController.prototype, "deleteCompany", null);
|
|
97
131
|
exports.CompanyController = CompanyController = __decorate([
|
|
98
132
|
(0, common_1.Controller)('identity'),
|
|
133
|
+
(0, common_1.UseInterceptors)(identity_error_interceptor_js_1.IdentityErrorInterceptor),
|
|
99
134
|
__metadata("design:paramtypes", [company_service_js_1.CompanyService])
|
|
100
135
|
], CompanyController);
|
|
@@ -1,25 +1,41 @@
|
|
|
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 { CompanyHierarchyService } from './company-hierarchy.service.js';
|
|
3
5
|
/**
|
|
4
6
|
* CompanyService — company (tenant) CRUD on the paved road, via the framework's
|
|
5
|
-
* RecordsService facade
|
|
6
|
-
*
|
|
7
|
+
* RecordsService facade (the request principal acts). No raw SQL, no RECORD_ENGINE.
|
|
8
|
+
*
|
|
9
|
+
* Hierarchy changes never ride a generic write (security hardening #2): the patch is whitelisted
|
|
10
|
+
* to `{name, status}`; create, move and delete keep the scope closure consistent through
|
|
11
|
+
* {@link CompanyHierarchyService}.
|
|
7
12
|
*/
|
|
8
13
|
export declare class CompanyService {
|
|
9
14
|
private readonly records;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
private readonly hierarchy;
|
|
16
|
+
constructor(records: RecordsService, hierarchy: CompanyHierarchyService);
|
|
17
|
+
/**
|
|
18
|
+
* Create a company. A non-root creator's company is placed UNDER the creator's own node (D5) —
|
|
19
|
+
* never a new top-level tenant — and the closure is rebuilt + the ancestors bumped at once so
|
|
20
|
+
* the new node is inside the creator's subtree (their session picks it up at its next refresh).
|
|
21
|
+
* Root creates a top-level tenant.
|
|
22
|
+
*/
|
|
23
|
+
createCompany(actor: Principal, name: string): Promise<RecordId>;
|
|
24
|
+
private readonly logger;
|
|
13
25
|
/** List companies (tenant-scoped by the pipe), with paging/search/sort. */
|
|
14
26
|
listCompanies(params?: ListParams): Promise<import("@astram/engine").RecordPage>;
|
|
15
27
|
/** Read a single company. */
|
|
16
28
|
getCompany(uuid: string): Promise<import("@astram/engine").RecordView | null>;
|
|
17
|
-
/** Update a company. */
|
|
18
|
-
updateCompany(uuid: string, patch:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
/** Update a company's name/status. Any other key (parentUuid, type, …) is dropped. */
|
|
30
|
+
updateCompany(uuid: string, patch: Readonly<Record<string, unknown>>): Promise<void>;
|
|
31
|
+
/** Move a company under a new parent (hierarchy rules + cycle check, see the hierarchy service). */
|
|
32
|
+
moveCompany(actor: Principal, uuid: string, parentUuid: string | null): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Delete a company: reach is checked through the subtree read wall (out of reach → 404), then
|
|
35
|
+
* the node is removed through `deleteNode` (refuses a node with children; closure + ancestor
|
|
36
|
+
* bump + audit in one transaction) — never a raw engine delete that would leave the closure
|
|
37
|
+
* stale or silently promote children.
|
|
38
|
+
*/
|
|
39
|
+
deleteCompany(actor: Principal, uuid: string): Promise<void>;
|
|
24
40
|
}
|
|
25
41
|
//# sourceMappingURL=company.service.d.ts.map
|
|
@@ -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,EAAkB,KAAK,UAAU,EAAE,MAAM,yBAAyB,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,EAAE,MAAM,gCAAgC,CAAC;AASzE;;;;;;;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,sFAAsF;IACtF,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAQpF,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"}
|
|
@@ -8,27 +8,56 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
|
+
var CompanyService_1;
|
|
11
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
13
|
exports.CompanyService = void 0;
|
|
13
14
|
const common_1 = require("@nestjs/common");
|
|
14
15
|
const engine_1 = require("@astram/engine");
|
|
15
16
|
const list_query_js_1 = require("../shared/list-query.js");
|
|
17
|
+
const company_hierarchy_service_js_1 = require("./company-hierarchy.service.js");
|
|
18
|
+
const identity_errors_js_1 = require("../shared/identity-errors.js");
|
|
16
19
|
const COMPANY_SEARCH_FIELDS = ['name'];
|
|
17
20
|
const COMPANY_SORT_FIELDS = ['name', 'status', 'createdAt'];
|
|
21
|
+
/** The only company fields a generic patch may change (security hardening #2). */
|
|
22
|
+
const COMPANY_PATCH_KEYS = ['name', 'status'];
|
|
18
23
|
/**
|
|
19
24
|
* CompanyService — company (tenant) CRUD on the paved road, via the framework's
|
|
20
|
-
* RecordsService facade
|
|
21
|
-
*
|
|
25
|
+
* RecordsService facade (the request principal acts). No raw SQL, no RECORD_ENGINE.
|
|
26
|
+
*
|
|
27
|
+
* Hierarchy changes never ride a generic write (security hardening #2): the patch is whitelisted
|
|
28
|
+
* to `{name, status}`; create, move and delete keep the scope closure consistent through
|
|
29
|
+
* {@link CompanyHierarchyService}.
|
|
22
30
|
*/
|
|
23
|
-
let CompanyService = class CompanyService {
|
|
31
|
+
let CompanyService = CompanyService_1 = class CompanyService {
|
|
24
32
|
records;
|
|
25
|
-
|
|
33
|
+
hierarchy;
|
|
34
|
+
constructor(records, hierarchy) {
|
|
26
35
|
this.records = records;
|
|
36
|
+
this.hierarchy = hierarchy;
|
|
27
37
|
}
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Create a company. A non-root creator's company is placed UNDER the creator's own node (D5) —
|
|
40
|
+
* never a new top-level tenant — and the closure is rebuilt + the ancestors bumped at once so
|
|
41
|
+
* the new node is inside the creator's subtree (their session picks it up at its next refresh).
|
|
42
|
+
* Root creates a top-level tenant.
|
|
43
|
+
*/
|
|
44
|
+
async createCompany(actor, name) {
|
|
45
|
+
const fields = { name };
|
|
46
|
+
if (actor.isRoot !== true)
|
|
47
|
+
fields.parentUuid = actor.activeScopeNodeId ?? actor.tenantId;
|
|
48
|
+
const id = await this.records.create('company', { fields });
|
|
49
|
+
try {
|
|
50
|
+
await this.hierarchy.attached(String(id));
|
|
51
|
+
}
|
|
52
|
+
catch (cause) {
|
|
53
|
+
// The company row is committed; a failed closure rebuild only DELAYS the new node's reach
|
|
54
|
+
// (fail-closed: no closure row ⇒ nobody's subtree includes it) until the next reconcile.
|
|
55
|
+
// Answering an error here would invite a duplicate create on retry.
|
|
56
|
+
this.logger.error(`company closure attach failed: ${cause?.name ?? 'error'}`);
|
|
57
|
+
}
|
|
58
|
+
return id;
|
|
31
59
|
}
|
|
60
|
+
logger = new common_1.Logger(CompanyService_1.name);
|
|
32
61
|
/** List companies (tenant-scoped by the pipe), with paging/search/sort. */
|
|
33
62
|
listCompanies(params) {
|
|
34
63
|
return this.records.list('company', (0, list_query_js_1.buildListQuery)(params, COMPANY_SEARCH_FIELDS, COMPANY_SORT_FIELDS));
|
|
@@ -37,17 +66,35 @@ let CompanyService = class CompanyService {
|
|
|
37
66
|
getCompany(uuid) {
|
|
38
67
|
return this.records.get('company', uuid);
|
|
39
68
|
}
|
|
40
|
-
/** Update a company. */
|
|
69
|
+
/** Update a company's name/status. Any other key (parentUuid, type, …) is dropped. */
|
|
41
70
|
updateCompany(uuid, patch) {
|
|
42
|
-
|
|
71
|
+
const fields = {};
|
|
72
|
+
for (const key of COMPANY_PATCH_KEYS) {
|
|
73
|
+
if (typeof patch?.[key] === 'string')
|
|
74
|
+
fields[key] = patch[key];
|
|
75
|
+
}
|
|
76
|
+
return this.records.update('company', uuid, { fields });
|
|
43
77
|
}
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
return this.
|
|
78
|
+
/** Move a company under a new parent (hierarchy rules + cycle check, see the hierarchy service). */
|
|
79
|
+
moveCompany(actor, uuid, parentUuid) {
|
|
80
|
+
return this.hierarchy.move(actor, uuid, parentUuid);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Delete a company: reach is checked through the subtree read wall (out of reach → 404), then
|
|
84
|
+
* the node is removed through `deleteNode` (refuses a node with children; closure + ancestor
|
|
85
|
+
* bump + audit in one transaction) — never a raw engine delete that would leave the closure
|
|
86
|
+
* stale or silently promote children.
|
|
87
|
+
*/
|
|
88
|
+
async deleteCompany(actor, uuid) {
|
|
89
|
+
const company = await this.records.get('company', uuid);
|
|
90
|
+
if (company === null)
|
|
91
|
+
throw new identity_errors_js_1.IdentityNotFoundError('Company not found.');
|
|
92
|
+
await this.hierarchy.remove(actor, uuid);
|
|
47
93
|
}
|
|
48
94
|
};
|
|
49
95
|
exports.CompanyService = CompanyService;
|
|
50
|
-
exports.CompanyService = CompanyService = __decorate([
|
|
96
|
+
exports.CompanyService = CompanyService = CompanyService_1 = __decorate([
|
|
51
97
|
(0, common_1.Injectable)(),
|
|
52
|
-
__metadata("design:paramtypes", [engine_1.RecordsService
|
|
98
|
+
__metadata("design:paramtypes", [engine_1.RecordsService,
|
|
99
|
+
company_hierarchy_service_js_1.CompanyHierarchyService])
|
|
53
100
|
], CompanyService);
|
|
@@ -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":"
|
|
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,EAKvB,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;AAqDD,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,EAAiB,IAAI,EAAE,MAAM;;;IASnE;;;;;OAKG;IAGG,MAAM,CAAQ,GAAG,EAAE,aAAa,EAAiB,IAAI,EAAE,MAAM;;;IASnE;;;OAGG;IAGG,KAAK,CAAQ,GAAG,EAAE,aAAa;;;IAKrC;;;;OAIG;IAGG,IAAI,CAAQ,GAAG,EAAE,aAAa;;;;IAQpC,wFAAwF;IAGlF,cAAc;;;CAIrB"}
|
|
@@ -46,6 +46,7 @@ exports.CrossTenantGrantController = void 0;
|
|
|
46
46
|
* writer change).
|
|
47
47
|
*/
|
|
48
48
|
const common_1 = require("@nestjs/common");
|
|
49
|
+
const identity_error_interceptor_js_1 = require("../shared/identity-error.interceptor.js");
|
|
49
50
|
const guard_1 = require("@astram/guard");
|
|
50
51
|
const cross_tenant_grant_read_service_js_1 = require("./cross-tenant-grant-read.service.js");
|
|
51
52
|
const VALID_GRANTEE_KINDS = ['user', 'role'];
|
|
@@ -231,6 +232,7 @@ __decorate([
|
|
|
231
232
|
], CrossTenantGrantController.prototype, "offerableTypes", null);
|
|
232
233
|
exports.CrossTenantGrantController = CrossTenantGrantController = __decorate([
|
|
233
234
|
(0, common_1.Controller)('identity'),
|
|
235
|
+
(0, common_1.UseInterceptors)(identity_error_interceptor_js_1.IdentityErrorInterceptor),
|
|
234
236
|
__param(0, (0, common_1.Inject)(guard_1.SHIELD_CROSS_TENANT_GRANT_WRITER)),
|
|
235
237
|
__metadata("design:paramtypes", [guard_1.CrossTenantGrantWriter,
|
|
236
238
|
cross_tenant_grant_read_service_js_1.CrossTenantGrantReadService])
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity-feature.module.d.ts","sourceRoot":"","sources":["../../src/features/identity-feature.module.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"identity-feature.module.d.ts","sourceRoot":"","sources":["../../src/features/identity-feature.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAYL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AA6FxB;;;;;;;;;;GAUG;AACH,qBACa,qBAAqB;IAChC;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,OAAO,CAAC,EACb,OAAO,EACP,YAAiB,GAClB,GAAE;QACD,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5B,YAAY,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;KACtC,GAAG,aAAa;CAscvB"}
|
|
@@ -16,6 +16,7 @@ const objects_js_1 = require("../objects.js");
|
|
|
16
16
|
const index_js_1 = require("./access/index.js");
|
|
17
17
|
const company_service_js_1 = require("./company/company.service.js");
|
|
18
18
|
const company_controller_js_1 = require("./company/company.controller.js");
|
|
19
|
+
const company_hierarchy_service_js_1 = require("./company/company-hierarchy.service.js");
|
|
19
20
|
const role_service_js_1 = require("./role/role.service.js");
|
|
20
21
|
const role_controller_js_1 = require("./role/role.controller.js");
|
|
21
22
|
const user_service_js_1 = require("./user/user.service.js");
|
|
@@ -30,6 +31,7 @@ const posting_version_bump_js_1 = require("./membership/posting-version-bump.js"
|
|
|
30
31
|
const permission_version_bumper_js_1 = require("./authz/permission-version.bumper.js");
|
|
31
32
|
const record_authorization_js_1 = require("./authz/record-authorization.js");
|
|
32
33
|
const db_permission_facts_adapter_js_1 = require("./authz/db-permission-facts.adapter.js");
|
|
34
|
+
const session_revocation_check_js_1 = require("./user/session-revocation-check.js");
|
|
33
35
|
const access_bundle_store_js_1 = require("./authz/access-bundle-store.js");
|
|
34
36
|
const grant_admin_service_js_1 = require("./authz/grant-admin.service.js");
|
|
35
37
|
const grant_admin_controller_js_1 = require("./authz/grant-admin.controller.js");
|
|
@@ -52,6 +54,10 @@ const audit_controller_js_1 = require("./audit/audit.controller.js");
|
|
|
52
54
|
const cross_tenant_grant_read_service_js_1 = require("./cross-tenant/cross-tenant-grant-read.service.js");
|
|
53
55
|
const cross_tenant_grant_controller_js_1 = require("./cross-tenant/cross-tenant-grant.controller.js");
|
|
54
56
|
const grant_authorize_js_1 = require("./cross-tenant/grant-authorize.js");
|
|
57
|
+
/** DI token of the provider that links the guard's Redis version cache to the bumper (ADR-0035). */
|
|
58
|
+
const IDENTITY_VERSION_PUBLISHER_LINK = Symbol('IDENTITY_VERSION_PUBLISHER_LINK');
|
|
59
|
+
/** DI token of the boot check that warns when a user disable cannot revoke sessions. */
|
|
60
|
+
const IDENTITY_SESSION_REVOCATION_CHECK = Symbol('IDENTITY_SESSION_REVOCATION_CHECK');
|
|
55
61
|
/** Map guard's env DbConfig → the engine's EngineDbConfig (adds the pool cap the engine needs). */
|
|
56
62
|
function engineDbFromEnv() {
|
|
57
63
|
const db = (0, guard_1.loadDbConfig)();
|
|
@@ -101,6 +107,8 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
101
107
|
const bumper = new permission_version_bumper_js_1.PermissionVersionBumper();
|
|
102
108
|
const postingVersionBump = {
|
|
103
109
|
bumpTenant: (tenantUuid) => bumper.bumpForTenant(tenantUuid, authzPool),
|
|
110
|
+
// A user disabled/re-enabled: every membership tenant (autocommit pool → published at once).
|
|
111
|
+
bumpForUser: (userUuid) => bumper.bumpForUser(userUuid, authzPool),
|
|
104
112
|
};
|
|
105
113
|
// The core ModuleRegistry the catalog reconcile job needs, built from the identity
|
|
106
114
|
// object registrations (guard's bridge reflects decorated classes → registry). Built BEFORE
|
|
@@ -112,14 +120,33 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
112
120
|
// resolved over the SAME authz pool + registry the object-type gate uses (one source of
|
|
113
121
|
// truth for "what a principal holds"). ALWAYS wired here, so production always enforces.
|
|
114
122
|
const accessBundleStore = new access_bundle_store_js_1.AccessBundleStore(authzPool);
|
|
115
|
-
|
|
116
|
-
|
|
123
|
+
// The ceiling's key universe includes the consumer's `extraObjects` (the SAME registry shape
|
|
124
|
+
// `buildIdentityRecordAuthorization(pool, extraObjects)` gives the decision path): otherwise a
|
|
125
|
+
// role holding a consumer-type key could never be assigned or granted by a non-root admin who
|
|
126
|
+
// holds that key (review M5) — the security-hardening role-assignment ceiling makes that
|
|
127
|
+
// reachable from every posting edit. `rootIds` marks platform roots as never dominated.
|
|
128
|
+
const ceilingRegistry = (0, guard_1.buildIdentityModuleRegistry)([...objects_js_1.identityObjects, ...extraObjects]);
|
|
129
|
+
// rootIds is passed EXPLICITLY (review F5): `null` states "not configured". The dominance
|
|
130
|
+
// check still refuses any user flagged `u_is_platform_superuser`, so an unconfigured list
|
|
131
|
+
// never lets a sub-admin take over a platform administrator.
|
|
132
|
+
if (rootIds === undefined) {
|
|
133
|
+
new common_1.Logger('IdentityFeatureModule').warn('rootIds not passed to IdentityFeatureModule.forRoot — pass the same array as ' +
|
|
134
|
+
'ShieldModule.forRoot({ rootIds }). Platform administrators are still protected via ' +
|
|
135
|
+
'users.u_is_platform_superuser.');
|
|
136
|
+
}
|
|
137
|
+
const grantCeiling = new grant_ceiling_js_1.GrantCeiling(authzPool, ceilingRegistry, rootIds ?? null);
|
|
138
|
+
// Grant administration is audited on the grant's own transaction (D6, fail-closed).
|
|
139
|
+
const grantAdminAuditSink = new engine_1.MariaDbAuditSink({}); // shield_audit.audit_log defaults
|
|
140
|
+
const grantAdminService = new grant_admin_service_js_1.GrantAdminService(authzPool, bumper, accessBundleStore, grantCeiling, grantAdminAuditSink);
|
|
117
141
|
// Scope closure (Task 1.2 of the scoped-hierarchy program): the reconciler + node
|
|
118
142
|
// move/delete maintenance, over the SAME shared authzPool (it reaches BOTH shield_app —
|
|
119
143
|
// companies — and shield_authz — scope_closure). Reuses the SAME `bumper` instance so a
|
|
120
144
|
// moveNode's session-invalidation joins the move's transaction. reconcileClosure() is
|
|
121
145
|
// kicked post-boot by the CatalogBootstrapProvider (mirrors the catalog reconcile).
|
|
122
146
|
const scopeClosureService = new scope_closure_service_js_1.ScopeClosureService(authzPool, bumper);
|
|
147
|
+
// Company hierarchy (security hardening #2): the ONLY path that moves/deletes/attaches a company
|
|
148
|
+
// node — through the closure service above — audited on the move/delete transaction.
|
|
149
|
+
const companyHierarchyService = new company_hierarchy_service_js_1.CompanyHierarchyService(authzPool, scopeClosureService, new engine_1.MariaDbAuditSink({}));
|
|
123
150
|
// Audit viewer (Phase 5): the AuditReader + LabelResolver over the SAME shared authzPool
|
|
124
151
|
// (the reader queries shield_audit + the resolver/actor-lookup query shield_app, both
|
|
125
152
|
// reachable by that pool). `auditNameToId` is the actor-name -> uuid reverse lookup for the
|
|
@@ -398,9 +425,36 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
398
425
|
org_catalog_service_js_1.OrgCatalogService,
|
|
399
426
|
record_resolver_js_1.RecordResolverService,
|
|
400
427
|
{ provide: posting_version_bump_js_1.POSTING_VERSION_BUMP, useValue: postingVersionBump },
|
|
428
|
+
// ADR-0035 (GAPS #6): attach the guard's Redis permission-version cache to the ONE shared
|
|
429
|
+
// bumper, so every committed bump (postings, grants, hierarchy) is published to Redis,
|
|
430
|
+
// where gate 5 reads it. Provided by the auth surface under `auth: true` (through the
|
|
431
|
+
// @Global ShieldModule); absent → DB-only bumps, exactly as before.
|
|
432
|
+
// Phase 3 review P2: with auth on (the token binder is visible) but no session sweeper, a
|
|
433
|
+
// user disable cannot revoke sessions — warn at boot.
|
|
434
|
+
{
|
|
435
|
+
provide: IDENTITY_SESSION_REVOCATION_CHECK,
|
|
436
|
+
useFactory: (binder, sweeper) => (0, session_revocation_check_js_1.warnIfSessionsUnrevocable)(binder, sweeper, (m) => new common_1.Logger('IdentityFeatureModule').warn(m)),
|
|
437
|
+
inject: [
|
|
438
|
+
{ token: guard_1.SHIELD_SESSION_TOKEN_BINDER, optional: true },
|
|
439
|
+
{ token: guard_1.SHIELD_SESSION_SWEEPER, optional: true },
|
|
440
|
+
],
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
provide: IDENTITY_VERSION_PUBLISHER_LINK,
|
|
444
|
+
useFactory: (cache) => {
|
|
445
|
+
if (cache)
|
|
446
|
+
bumper.attachVersionPublisher(cache);
|
|
447
|
+
return cache !== null && cache !== undefined;
|
|
448
|
+
},
|
|
449
|
+
inject: [{ token: guard_1.SHIELD_PERMISSION_VERSION_CACHE, optional: true }],
|
|
450
|
+
},
|
|
401
451
|
// Grant-admin stack — all over the ONE shared authzPool.
|
|
402
452
|
{ provide: access_bundle_store_js_1.AccessBundleStore, useValue: accessBundleStore },
|
|
403
453
|
{ provide: grant_admin_service_js_1.GrantAdminService, useValue: grantAdminService },
|
|
454
|
+
// The ONE tenant-boundary authority (ADR-0029 ceiling + block A: role assignment, user
|
|
455
|
+
// dominance, role binding) — injected by RoleService, PostingService and UserService.
|
|
456
|
+
{ provide: grant_ceiling_js_1.GrantCeiling, useValue: grantCeiling },
|
|
457
|
+
{ provide: company_hierarchy_service_js_1.CompanyHierarchyService, useValue: companyHierarchyService },
|
|
404
458
|
// Audit read stack — built at forRoot() over the ONE shared authzPool, provided as a
|
|
405
459
|
// useValue (like GrantAdminService) so it carries the hand-built reader/resolver/lookup.
|
|
406
460
|
{ provide: audit_read_service_js_1.AuditReadService, useValue: auditReadService },
|
|
@@ -421,7 +475,10 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
421
475
|
{ provide: service_account_service_js_1.ServiceAccountService, useValue: serviceAccountService },
|
|
422
476
|
// The mutating half of auto-catalog: a single CatalogReconcileJob (post-boot pass +
|
|
423
477
|
// cron), over the shared pool + the identity registry.
|
|
424
|
-
{
|
|
478
|
+
{
|
|
479
|
+
provide: catalog_reconcile_job_js_1.CatalogReconcileJob,
|
|
480
|
+
useValue: new catalog_reconcile_job_js_1.CatalogReconcileJob(authzPool, identityRegistry),
|
|
481
|
+
},
|
|
425
482
|
// The bundle half: reconciles the merged AccessBundleSpec[] (ENGINE_ALL_ACCESS_BUNDLES,
|
|
426
483
|
// reflected by the engine from identityAccessBundles) into shield_authz.access_bundle.
|
|
427
484
|
{
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MembershipService } from './membership.service.js';
|
|
2
2
|
import { PostingService, type PostingInput } from './posting.service.js';
|
|
3
3
|
import { RecordResolverService } from '../shared/record-resolver.js';
|
|
4
|
+
import { type AuthedRequest } from '../shared/http.js';
|
|
4
5
|
/** A membership card for the API, with EVERY uuid resolved to a display name. */
|
|
5
6
|
interface MembershipCardDto {
|
|
6
7
|
/** The membership id — used as a route param / react key, NEVER as a display field. */
|
|
@@ -39,7 +40,7 @@ export declare class MembershipController {
|
|
|
39
40
|
memberships: MembershipCardDto[];
|
|
40
41
|
}>;
|
|
41
42
|
/** Add the user to a company (creates an inactive membership card). */
|
|
42
|
-
addUserMembership(userUuid: string, body: {
|
|
43
|
+
addUserMembership(req: AuthedRequest, userUuid: string, body: {
|
|
43
44
|
companyUuid?: string;
|
|
44
45
|
}): Promise<{
|
|
45
46
|
id: import("@astram/engine").RecordId;
|
|
@@ -49,19 +50,19 @@ export declare class MembershipController {
|
|
|
49
50
|
* invariant); `status` is a plain field update. Activating requires the owning user so
|
|
50
51
|
* the invariant can deactivate the user's OTHER memberships.
|
|
51
52
|
*/
|
|
52
|
-
updateMembership(membershipUuid: string, body: {
|
|
53
|
+
updateMembership(req: AuthedRequest, membershipUuid: string, body: {
|
|
53
54
|
isActive?: boolean;
|
|
54
55
|
status?: string;
|
|
55
56
|
userUuid?: string;
|
|
56
57
|
}): Promise<{
|
|
57
58
|
success: boolean;
|
|
58
59
|
}>;
|
|
59
|
-
/** Remove a membership card (its postings cascade at the DB). */
|
|
60
|
-
removeMembership(membershipUuid: string): Promise<{
|
|
60
|
+
/** Remove a membership card (its postings cascade at the DB). A peer's needs dominance. */
|
|
61
|
+
removeMembership(req: AuthedRequest, membershipUuid: string): Promise<{
|
|
61
62
|
success: boolean;
|
|
62
63
|
}>;
|
|
63
64
|
/** Atomically replace a membership's full posting set (the diff sync). */
|
|
64
|
-
replacePostings(membershipUuid: string, body: {
|
|
65
|
+
replacePostings(req: AuthedRequest, membershipUuid: string, body: {
|
|
65
66
|
postings?: PostingInput[];
|
|
66
67
|
}): Promise<{
|
|
67
68
|
success: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"membership.controller.d.ts","sourceRoot":"","sources":["../../../src/features/membership/membership.controller.ts"],"names":[],"mappings":"
|
|
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,CAAoB,QAAQ,EAAE,MAAM;;;IA0C7D,uEAAuE;IAGjE,iBAAiB,CACd,GAAG,EAAE,aAAa,EACN,QAAQ,EAAE,MAAM,EAC3B,IAAI,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE;;;IAUxC;;;;OAIG;IAGG,gBAAgB,CACb,GAAG,EAAE,aAAa,EACA,cAAc,EAAE,MAAM,EACvC,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE;;;IAgB1E,2FAA2F;IAGrF,gBAAgB,CACb,GAAG,EAAE,aAAa,EACA,cAAc,EAAE,MAAM;;;IAMjD,0EAA0E;IAGpE,eAAe,CACZ,GAAG,EAAE,aAAa,EACA,cAAc,EAAE,MAAM,EACvC,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAA;KAAE;;;IAO7C;;;;OAIG;YACW,WAAW;CAM1B"}
|
|
@@ -19,12 +19,16 @@ const membership_service_js_1 = require("./membership.service.js");
|
|
|
19
19
|
const posting_service_js_1 = require("./posting.service.js");
|
|
20
20
|
const record_resolver_js_1 = require("../shared/record-resolver.js");
|
|
21
21
|
const http_js_1 = require("../shared/http.js");
|
|
22
|
+
const identity_error_interceptor_js_1 = require("../shared/identity-error.interceptor.js");
|
|
22
23
|
// ── Memberships + postings ───────────────────────────────────────────────────
|
|
23
24
|
//
|
|
24
25
|
// Gated on the `company_membership` / `membership_posting` grants via `@Classified`
|
|
25
26
|
// + the engine's mandatory in-pipe gate. Both are DATA-plane types, so the capped root
|
|
26
27
|
// is denied them by construction — the membership admin surface is grant-driven, not
|
|
27
|
-
// root-driven.
|
|
28
|
+
// root-driven. Per-company-admin scoping is structural: reads, updates and deletes are walled
|
|
29
|
+
// to the caller's subtree (ADR-0027/0030), creates by the ADR-0034 create wall; adding an
|
|
30
|
+
// existing user needs that user already in the subtree (D4), and posting a role runs the
|
|
31
|
+
// role-assignment ceiling + binding rule (#11). Errors map via IdentityErrorInterceptor.
|
|
28
32
|
let MembershipController = class MembershipController {
|
|
29
33
|
memberships;
|
|
30
34
|
postings;
|
|
@@ -78,8 +82,8 @@ let MembershipController = class MembershipController {
|
|
|
78
82
|
return { memberships: cards };
|
|
79
83
|
}
|
|
80
84
|
/** Add the user to a company (creates an inactive membership card). */
|
|
81
|
-
async addUserMembership(userUuid, body) {
|
|
82
|
-
const id = await this.memberships.addMembership(userUuid, body?.companyUuid ?? '');
|
|
85
|
+
async addUserMembership(req, userUuid, body) {
|
|
86
|
+
const id = await this.memberships.addMembership(req.principal, userUuid, body?.companyUuid ?? '');
|
|
83
87
|
return { id };
|
|
84
88
|
}
|
|
85
89
|
/**
|
|
@@ -87,26 +91,29 @@ let MembershipController = class MembershipController {
|
|
|
87
91
|
* invariant); `status` is a plain field update. Activating requires the owning user so
|
|
88
92
|
* the invariant can deactivate the user's OTHER memberships.
|
|
89
93
|
*/
|
|
90
|
-
async updateMembership(membershipUuid, body) {
|
|
94
|
+
async updateMembership(req, membershipUuid, body) {
|
|
91
95
|
if (body?.isActive === true) {
|
|
92
96
|
if (!body.userUuid) {
|
|
93
97
|
throw new common_1.ForbiddenException('userUuid is required to activate a membership');
|
|
94
98
|
}
|
|
95
|
-
await this.memberships.setActive(membershipUuid, body.userUuid);
|
|
99
|
+
await this.memberships.setActive(req.principal, membershipUuid, body.userUuid);
|
|
96
100
|
}
|
|
97
101
|
if (typeof body?.status === 'string') {
|
|
98
|
-
await this.memberships.updateMembership(membershipUuid, {
|
|
102
|
+
await this.memberships.updateMembership(req.principal, membershipUuid, {
|
|
103
|
+
status: body.status,
|
|
104
|
+
});
|
|
99
105
|
}
|
|
100
106
|
return { success: true };
|
|
101
107
|
}
|
|
102
|
-
/** Remove a membership card (its postings cascade at the DB). */
|
|
103
|
-
async removeMembership(membershipUuid) {
|
|
104
|
-
await this.memberships.removeMembership(membershipUuid);
|
|
108
|
+
/** Remove a membership card (its postings cascade at the DB). A peer's needs dominance. */
|
|
109
|
+
async removeMembership(req, membershipUuid) {
|
|
110
|
+
await this.memberships.removeMembership(req.principal, membershipUuid);
|
|
105
111
|
return { success: true };
|
|
106
112
|
}
|
|
107
113
|
/** Atomically replace a membership's full posting set (the diff sync). */
|
|
108
|
-
async replacePostings(membershipUuid, body) {
|
|
109
|
-
|
|
114
|
+
async replacePostings(req, membershipUuid, body) {
|
|
115
|
+
const postings = Array.isArray(body?.postings) ? body.postings : [];
|
|
116
|
+
await this.postings.replacePostings(req.principal, membershipUuid, postings);
|
|
110
117
|
return { success: true };
|
|
111
118
|
}
|
|
112
119
|
/**
|
|
@@ -134,40 +141,45 @@ __decorate([
|
|
|
134
141
|
__decorate([
|
|
135
142
|
(0, common_1.Post)('users/:userUuid/memberships'),
|
|
136
143
|
(0, guard_1.Classified)({ typeKey: 'company_membership', action: 'create' }),
|
|
137
|
-
__param(0, (0, common_1.
|
|
138
|
-
__param(1, (0, common_1.
|
|
144
|
+
__param(0, (0, common_1.Req)()),
|
|
145
|
+
__param(1, (0, common_1.Param)('userUuid')),
|
|
146
|
+
__param(2, (0, common_1.Body)()),
|
|
139
147
|
__metadata("design:type", Function),
|
|
140
|
-
__metadata("design:paramtypes", [String, Object]),
|
|
148
|
+
__metadata("design:paramtypes", [Object, String, Object]),
|
|
141
149
|
__metadata("design:returntype", Promise)
|
|
142
150
|
], MembershipController.prototype, "addUserMembership", null);
|
|
143
151
|
__decorate([
|
|
144
152
|
(0, common_1.Put)('memberships/:membershipUuid'),
|
|
145
153
|
(0, guard_1.Classified)({ typeKey: 'company_membership', action: 'update' }),
|
|
146
|
-
__param(0, (0, common_1.
|
|
147
|
-
__param(1, (0, common_1.
|
|
154
|
+
__param(0, (0, common_1.Req)()),
|
|
155
|
+
__param(1, (0, common_1.Param)('membershipUuid')),
|
|
156
|
+
__param(2, (0, common_1.Body)()),
|
|
148
157
|
__metadata("design:type", Function),
|
|
149
|
-
__metadata("design:paramtypes", [String, Object]),
|
|
158
|
+
__metadata("design:paramtypes", [Object, String, Object]),
|
|
150
159
|
__metadata("design:returntype", Promise)
|
|
151
160
|
], MembershipController.prototype, "updateMembership", null);
|
|
152
161
|
__decorate([
|
|
153
162
|
(0, common_1.Delete)('memberships/:membershipUuid'),
|
|
154
163
|
(0, guard_1.Classified)({ typeKey: 'company_membership', action: 'delete' }),
|
|
155
|
-
__param(0, (0, common_1.
|
|
164
|
+
__param(0, (0, common_1.Req)()),
|
|
165
|
+
__param(1, (0, common_1.Param)('membershipUuid')),
|
|
156
166
|
__metadata("design:type", Function),
|
|
157
|
-
__metadata("design:paramtypes", [String]),
|
|
167
|
+
__metadata("design:paramtypes", [Object, String]),
|
|
158
168
|
__metadata("design:returntype", Promise)
|
|
159
169
|
], MembershipController.prototype, "removeMembership", null);
|
|
160
170
|
__decorate([
|
|
161
171
|
(0, common_1.Put)('memberships/:membershipUuid/postings'),
|
|
162
172
|
(0, guard_1.Classified)({ typeKey: 'membership_posting', action: 'update' }),
|
|
163
|
-
__param(0, (0, common_1.
|
|
164
|
-
__param(1, (0, common_1.
|
|
173
|
+
__param(0, (0, common_1.Req)()),
|
|
174
|
+
__param(1, (0, common_1.Param)('membershipUuid')),
|
|
175
|
+
__param(2, (0, common_1.Body)()),
|
|
165
176
|
__metadata("design:type", Function),
|
|
166
|
-
__metadata("design:paramtypes", [String, Object]),
|
|
177
|
+
__metadata("design:paramtypes", [Object, String, Object]),
|
|
167
178
|
__metadata("design:returntype", Promise)
|
|
168
179
|
], MembershipController.prototype, "replacePostings", null);
|
|
169
180
|
exports.MembershipController = MembershipController = __decorate([
|
|
170
181
|
(0, common_1.Controller)('identity'),
|
|
182
|
+
(0, common_1.UseInterceptors)(identity_error_interceptor_js_1.IdentityErrorInterceptor),
|
|
171
183
|
__metadata("design:paramtypes", [membership_service_js_1.MembershipService,
|
|
172
184
|
posting_service_js_1.PostingService,
|
|
173
185
|
record_resolver_js_1.RecordResolverService])
|