@astram/identity 0.16.0 → 0.17.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/branch.object.d.ts +9 -1
- package/dist/branch.object.d.ts.map +1 -1
- package/dist/branch.object.js +14 -1
- package/dist/company-branch.object.d.ts +5 -4
- package/dist/company-branch.object.d.ts.map +1 -1
- package/dist/company-branch.object.js +6 -4
- package/dist/company-department.object.d.ts +6 -6
- package/dist/company-department.object.d.ts.map +1 -1
- package/dist/company-department.object.js +7 -6
- package/dist/department.object.d.ts +9 -1
- package/dist/department.object.d.ts.map +1 -1
- package/dist/department.object.js +14 -1
- package/dist/features/access/access-admin.access.d.ts.map +1 -1
- package/dist/features/access/access-admin.access.js +8 -1
- package/dist/features/access/index.d.ts +1 -0
- package/dist/features/access/index.d.ts.map +1 -1
- package/dist/features/access/index.js +10 -2
- package/dist/features/access/org-structure.access.d.ts +3 -0
- package/dist/features/access/org-structure.access.d.ts.map +1 -0
- package/dist/features/access/org-structure.access.js +41 -0
- package/dist/features/access/user-management.access.d.ts.map +1 -1
- package/dist/features/access/user-management.access.js +5 -0
- package/dist/features/audit/audit-read.service.d.ts +32 -1
- package/dist/features/audit/audit-read.service.d.ts.map +1 -1
- package/dist/features/audit/audit-read.service.js +67 -14
- package/dist/features/audit/auth-audit-sink.d.ts +19 -0
- package/dist/features/audit/auth-audit-sink.d.ts.map +1 -0
- package/dist/features/audit/auth-audit-sink.js +24 -0
- package/dist/features/audit/directory-label-resolver.d.ts +10 -1
- package/dist/features/audit/directory-label-resolver.d.ts.map +1 -1
- package/dist/features/audit/directory-label-resolver.js +28 -1
- package/dist/features/authz/grant-admin.controller.d.ts +4 -4
- package/dist/features/authz/grant-admin.controller.js +4 -4
- package/dist/features/authz/grant-admin.service.d.ts +9 -8
- package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
- package/dist/features/authz/grant-admin.service.js +20 -24
- package/dist/features/authz/grant-ceiling.d.ts +35 -16
- package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
- package/dist/features/authz/grant-ceiling.js +44 -29
- package/dist/features/authz/role-binding.d.ts +25 -24
- package/dist/features/authz/role-binding.d.ts.map +1 -1
- package/dist/features/authz/role-binding.js +31 -44
- package/dist/features/identity-feature.module.d.ts +0 -11
- package/dist/features/identity-feature.module.d.ts.map +1 -1
- package/dist/features/identity-feature.module.js +90 -1
- package/dist/features/membership/membership.controller.d.ts.map +1 -1
- package/dist/features/membership/membership.controller.js +20 -6
- package/dist/features/membership/membership.service.d.ts +16 -10
- package/dist/features/membership/membership.service.d.ts.map +1 -1
- package/dist/features/membership/membership.service.js +61 -21
- package/dist/features/membership/posting.service.d.ts +25 -6
- package/dist/features/membership/posting.service.d.ts.map +1 -1
- package/dist/features/membership/posting.service.js +63 -16
- package/dist/features/org-bootstrap/org-bootstrap-file.d.ts +6 -2
- package/dist/features/org-bootstrap/org-bootstrap-file.d.ts.map +1 -1
- package/dist/features/org-bootstrap/org-bootstrap-file.js +54 -11
- package/dist/features/org-bootstrap/org-bootstrap-plan.d.ts +30 -11
- package/dist/features/org-bootstrap/org-bootstrap-plan.d.ts.map +1 -1
- package/dist/features/org-bootstrap/org-bootstrap-plan.js +172 -68
- package/dist/features/org-bootstrap/org-state.reader.d.ts.map +1 -1
- package/dist/features/org-bootstrap/org-state.reader.js +56 -49
- package/dist/features/org-bootstrap/run-org-bootstrap.d.ts +6 -1
- package/dist/features/org-bootstrap/run-org-bootstrap.d.ts.map +1 -1
- package/dist/features/org-bootstrap/run-org-bootstrap.js +81 -92
- package/dist/features/org-structure/org-catalog.controller.d.ts +29 -6
- package/dist/features/org-structure/org-catalog.controller.d.ts.map +1 -1
- package/dist/features/org-structure/org-catalog.controller.js +105 -17
- package/dist/features/org-structure/org-catalog.service.d.ts +99 -18
- package/dist/features/org-structure/org-catalog.service.d.ts.map +1 -1
- package/dist/features/org-structure/org-catalog.service.js +327 -51
- package/dist/features/org-structure/org-unit-name.d.ts +31 -0
- package/dist/features/org-structure/org-unit-name.d.ts.map +1 -0
- package/dist/features/org-structure/org-unit-name.js +50 -0
- package/dist/features/role/role.controller.d.ts +28 -13
- package/dist/features/role/role.controller.d.ts.map +1 -1
- package/dist/features/role/role.controller.js +54 -14
- package/dist/features/role/role.service.d.ts +76 -21
- package/dist/features/role/role.service.d.ts.map +1 -1
- package/dist/features/role/role.service.js +204 -27
- package/dist/features/security-policy/security-policy-version-bump.d.ts +41 -0
- package/dist/features/security-policy/security-policy-version-bump.d.ts.map +1 -0
- package/dist/features/security-policy/security-policy-version-bump.js +65 -0
- package/dist/features/security-policy/security-policy.controller.d.ts +51 -0
- package/dist/features/security-policy/security-policy.controller.d.ts.map +1 -0
- package/dist/features/security-policy/security-policy.controller.js +121 -0
- package/dist/features/security-policy/security-policy.service.d.ts +94 -0
- package/dist/features/security-policy/security-policy.service.d.ts.map +1 -0
- package/dist/features/security-policy/security-policy.service.js +429 -0
- package/dist/features/service-account/service-account.service.d.ts +13 -3
- package/dist/features/service-account/service-account.service.d.ts.map +1 -1
- package/dist/features/service-account/service-account.service.js +48 -10
- package/dist/features/service-account/service-account.store.d.ts +11 -1
- package/dist/features/service-account/service-account.store.d.ts.map +1 -1
- package/dist/features/service-account/service-account.store.js +20 -1
- package/dist/features/user/signin-admin.tokens.d.ts +11 -0
- package/dist/features/user/signin-admin.tokens.d.ts.map +1 -0
- package/dist/features/user/signin-admin.tokens.js +13 -0
- package/dist/features/user/user-mfa-status.d.ts +38 -0
- package/dist/features/user/user-mfa-status.d.ts.map +1 -0
- package/dist/features/user/user-mfa-status.js +47 -0
- package/dist/features/user/user-signin-admin.service.d.ts +48 -0
- package/dist/features/user/user-signin-admin.service.d.ts.map +1 -0
- package/dist/features/user/user-signin-admin.service.js +232 -0
- package/dist/features/user/user.controller.d.ts +29 -2
- package/dist/features/user/user.controller.d.ts.map +1 -1
- package/dist/features/user/user.controller.js +71 -3
- package/dist/features/user/user.service.d.ts +40 -14
- package/dist/features/user/user.service.d.ts.map +1 -1
- package/dist/features/user/user.service.js +143 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/objects.d.ts.map +1 -1
- package/dist/objects.js +2 -0
- package/dist/role.object.d.ts +1 -1
- package/dist/role.object.d.ts.map +1 -1
- package/dist/role.object.js +6 -15
- package/dist/security-policy.object.d.ts +43 -0
- package/dist/security-policy.object.d.ts.map +1 -0
- package/dist/security-policy.object.js +107 -0
- package/dist/user.object.d.ts +6 -0
- package/dist/user.object.d.ts.map +1 -1
- package/dist/user.object.js +13 -1
- package/migrations/1710000000103-AddUserLoginName.cjs +87 -0
- package/migrations/1710000000104-CreateSecurityPolicy.cjs +90 -0
- package/migrations/1710000000105-OwnDepartmentsAndBranches.cjs +327 -0
- package/migrations/1710000000106-OwnRolesPerCompany.cjs +622 -0
- package/package.json +4 -4
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* G-012 (spec §10): the ONE name rule for departments and branches, shared by the create and rename
|
|
4
|
+
* routes, `OrgCatalogService.ensure*` and the org bootstrap planner, so "Quality Assurance" in a
|
|
5
|
+
* bootstrap file and "Quality Assurance" typed in the UI are the same name.
|
|
6
|
+
*
|
|
7
|
+
* Order (spec §10, as written): Unicode NFC; trim and collapse every whitespace run (JS `\s`, so
|
|
8
|
+
* tab, CR and LF too) to one space; refuse any control character left (U+0000–U+001F,
|
|
9
|
+
* U+007F–U+009F, e.g. NUL or NEL); refuse an empty result or one longer than
|
|
10
|
+
* {@link ORG_UNIT_NAME_MAX} characters (code points, the column width). The template's
|
|
11
|
+
* `validateName` (`pages/org-structure/orgStructure.js`) applies the same order, so the client
|
|
12
|
+
* never accepts a name the server refuses.
|
|
13
|
+
*
|
|
14
|
+
* Case, accents and width are NOT folded here: the stored name keeps what the user typed; the
|
|
15
|
+
* column collation (`utf8mb4_unicode_ci`) decides uniqueness, and the bootstrap planner compares
|
|
16
|
+
* with `nameKey` on top of this.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.ORG_UNIT_NAME_MAX = void 0;
|
|
20
|
+
exports.normaliseOrgUnitName = normaliseOrgUnitName;
|
|
21
|
+
exports.orgUnitNameMessage = orgUnitNameMessage;
|
|
22
|
+
/** The column width of `d_name` / `b_name` (VARCHAR(150), counted in characters). */
|
|
23
|
+
exports.ORG_UNIT_NAME_MAX = 150;
|
|
24
|
+
// eslint-disable-next-line no-control-regex
|
|
25
|
+
const CONTROL = /[\u0000-\u001f\u007f-\u009f]/u;
|
|
26
|
+
/** Normalise a department or branch name, or say why it is refused. Pure. */
|
|
27
|
+
function normaliseOrgUnitName(raw) {
|
|
28
|
+
if (typeof raw !== 'string')
|
|
29
|
+
return { ok: false, reason: 'not_text' };
|
|
30
|
+
const nfc = raw.normalize('NFC');
|
|
31
|
+
const value = nfc.trim().replace(/\s+/gu, ' ');
|
|
32
|
+
if (CONTROL.test(value))
|
|
33
|
+
return { ok: false, reason: 'control_character' };
|
|
34
|
+
if (value.length === 0)
|
|
35
|
+
return { ok: false, reason: 'empty' };
|
|
36
|
+
if ([...value].length > exports.ORG_UNIT_NAME_MAX)
|
|
37
|
+
return { ok: false, reason: 'too_long' };
|
|
38
|
+
return { ok: true, value };
|
|
39
|
+
}
|
|
40
|
+
/** The fixed, user-facing 400 text for a refusal (`kind` is "department" or "branch"). */
|
|
41
|
+
function orgUnitNameMessage(kind, reason) {
|
|
42
|
+
switch (reason) {
|
|
43
|
+
case 'control_character':
|
|
44
|
+
return `A ${kind} name cannot contain control characters.`;
|
|
45
|
+
case 'too_long':
|
|
46
|
+
return `A ${kind} name can be at most ${exports.ORG_UNIT_NAME_MAX} characters.`;
|
|
47
|
+
default:
|
|
48
|
+
return `Enter a ${kind} name.`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -5,9 +5,26 @@ export declare class RoleController {
|
|
|
5
5
|
private readonly roles;
|
|
6
6
|
constructor(roles: RoleService);
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
* `?page&limit&search&sortBy&sortOrder
|
|
10
|
-
* `total
|
|
8
|
+
* One company's own roles — `identity:role:read`. The company is reached through the walled
|
|
9
|
+
* `company` read first (404 out of reach). `?page&limit&search&sortBy&sortOrder` as `GET /roles`
|
|
10
|
+
* ⇒ `{ total, rows, page, limit }`.
|
|
11
|
+
*/
|
|
12
|
+
listCompanyRoles(companyUuid: string, query: ListQueryDto): Promise<{
|
|
13
|
+
total: number;
|
|
14
|
+
rows: readonly import("packages/engine/dist/types.js").RecordView[];
|
|
15
|
+
page: number;
|
|
16
|
+
limit: number;
|
|
17
|
+
}>;
|
|
18
|
+
/** Create a role in a company — `identity:role:create` ⇒ 201 `{ uuid, name }`. */
|
|
19
|
+
createCompanyRole(companyUuid: string, body: unknown): Promise<{
|
|
20
|
+
uuid: string;
|
|
21
|
+
name: string;
|
|
22
|
+
}>;
|
|
23
|
+
/**
|
|
24
|
+
* List roles — gated on `identity:role:read` (see class doc): the roles owned in the caller's
|
|
25
|
+
* reachable subtree (root: every role, ownerless ones included). Server-side DataTable:
|
|
26
|
+
* `?page&limit&search&sortBy&sortOrder`. Returns `{ total, rows, page, limit }` — `total`
|
|
27
|
+
* reflects the FILTERED set so the client can page the true result count.
|
|
11
28
|
*/
|
|
12
29
|
listRoles(query: ListQueryDto): Promise<{
|
|
13
30
|
total: number;
|
|
@@ -15,19 +32,17 @@ export declare class RoleController {
|
|
|
15
32
|
page: number;
|
|
16
33
|
limit: number;
|
|
17
34
|
}>;
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Create a role in the caller's active company (else its tenant) — `identity:role:create`. Kept
|
|
37
|
+
* for existing clients; a caller with no company ⇒ 400. Any owner in the body is ignored.
|
|
38
|
+
*/
|
|
39
|
+
createRole(req: AuthedRequest, body: unknown): Promise<{
|
|
22
40
|
id: import("packages/engine/dist/types.js").RecordId;
|
|
23
41
|
}>;
|
|
24
|
-
/** Read one role — gated on `identity:role:read
|
|
25
|
-
* administrator, may read roles). */
|
|
42
|
+
/** Read one role — gated on `identity:role:read`; walled by owner (404 outside the subtree). */
|
|
26
43
|
getRole(uuid: string): Promise<import("packages/engine/dist/types.js").RecordView | null>;
|
|
27
|
-
/**
|
|
28
|
-
updateRole(req: AuthedRequest, uuid: string, body: {
|
|
29
|
-
name?: string;
|
|
30
|
-
}): Promise<{
|
|
44
|
+
/** Rename a role — gated on `identity:role:update`. */
|
|
45
|
+
updateRole(req: AuthedRequest, uuid: string, body: unknown): Promise<{
|
|
31
46
|
success: boolean;
|
|
32
47
|
}>;
|
|
33
48
|
/** Delete a role — gated on `identity:role:delete`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role.controller.d.ts","sourceRoot":"","sources":["../../../src/features/role/role.controller.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;
|
|
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;AAiBvD,qBAEa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,WAAW;IAE/C;;;;OAIG;IAGH,gBAAgB,CACkB,WAAW,EAAE,MAAM,EAC1C,KAAK,EAAE,YAAY;;;;;;IAK9B,kFAAkF;IAGlF,iBAAiB,CAAiC,WAAW,EAAE,MAAM,EAAU,IAAI,EAAE,OAAO;;;;IAI5F;;;;;OAKG;IAGG,SAAS,CAAU,KAAK,EAAE,YAAY;;;;;;IAM5C;;;OAGG;IAGG,UAAU,CAAQ,GAAG,EAAE,aAAa,EAAU,IAAI,EAAE,OAAO;;;IAKjE,gGAAgG;IAGhG,OAAO,CAA0B,IAAI,EAAE,MAAM;IAI7C,uDAAuD;IAGjD,UAAU,CACP,GAAG,EAAE,aAAa,EACA,IAAI,EAAE,MAAM,EAC7B,IAAI,EAAE,OAAO;;;IAMvB,uDAAuD;IAGjD,UAAU,CAAQ,GAAG,EAAE,aAAa,EAA2B,IAAI,EAAE,MAAM;;;CAIlF"}
|
|
@@ -18,39 +18,61 @@ 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
20
|
const identity_error_interceptor_js_1 = require("../shared/identity-error.interceptor.js");
|
|
21
|
-
// Role management is gated on `identity:role:<action>` grants. `role` is CONTROL-plane
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
21
|
+
// Role management is gated on `identity:role:<action>` grants. `role` is CONTROL-plane for ALL
|
|
22
|
+
// actions — including read: the capped root is the identity administrator, so it may list/read
|
|
23
|
+
// roles AND manage them. G-013 (ADR-0047): every role is OWNED by one company; reads and writes are
|
|
24
|
+
// walled by that owner (the role object's readScope: out of reach ⇒ 404, ownerless ⇒ root only),
|
|
25
|
+
// and rename/delete also pass the owner rule (`GrantCeiling.assertRoleAdministrable`). The owner is
|
|
26
|
+
// stamped server-side from the reached company (or the caller's active company on `POST /roles`),
|
|
27
|
+
// never from the body. The company sub-paths are static segments declared before `roles/:uuid`;
|
|
28
|
+
// this controller stays registered before `GrantAdminController`.
|
|
29
|
+
/** The body's `name`, untouched (the service normalises and validates it; non-text ⇒ 400). */
|
|
30
|
+
function nameFrom(body) {
|
|
31
|
+
return body?.name;
|
|
32
|
+
}
|
|
25
33
|
let RoleController = class RoleController {
|
|
26
34
|
roles;
|
|
27
35
|
constructor(roles) {
|
|
28
36
|
this.roles = roles;
|
|
29
37
|
}
|
|
30
38
|
/**
|
|
31
|
-
*
|
|
32
|
-
* `?page&limit&search&sortBy&sortOrder
|
|
33
|
-
* `total
|
|
39
|
+
* One company's own roles — `identity:role:read`. The company is reached through the walled
|
|
40
|
+
* `company` read first (404 out of reach). `?page&limit&search&sortBy&sortOrder` as `GET /roles`
|
|
41
|
+
* ⇒ `{ total, rows, page, limit }`.
|
|
42
|
+
*/
|
|
43
|
+
listCompanyRoles(companyUuid, query) {
|
|
44
|
+
return this.roles.listCompanyRoles(companyUuid, query);
|
|
45
|
+
}
|
|
46
|
+
/** Create a role in a company — `identity:role:create` ⇒ 201 `{ uuid, name }`. */
|
|
47
|
+
createCompanyRole(companyUuid, body) {
|
|
48
|
+
return this.roles.createCompanyRole(companyUuid, nameFrom(body));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* List roles — gated on `identity:role:read` (see class doc): the roles owned in the caller's
|
|
52
|
+
* reachable subtree (root: every role, ownerless ones included). Server-side DataTable:
|
|
53
|
+
* `?page&limit&search&sortBy&sortOrder`. Returns `{ total, rows, page, limit }` — `total`
|
|
54
|
+
* reflects the FILTERED set so the client can page the true result count.
|
|
34
55
|
*/
|
|
35
56
|
async listRoles(query) {
|
|
36
57
|
const { page, limit, params } = (0, list_query_js_1.parseListQuery)(query);
|
|
37
58
|
const result = await this.roles.listRoles(params);
|
|
38
59
|
return { total: result.total, rows: result.rows, page, limit };
|
|
39
60
|
}
|
|
40
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Create a role in the caller's active company (else its tenant) — `identity:role:create`. Kept
|
|
63
|
+
* for existing clients; a caller with no company ⇒ 400. Any owner in the body is ignored.
|
|
64
|
+
*/
|
|
41
65
|
async createRole(req, body) {
|
|
42
|
-
|
|
43
|
-
const id = await this.roles.createRole(body?.name ?? 'Untitled', req.principal);
|
|
66
|
+
const id = await this.roles.createRole(nameFrom(body), req.principal);
|
|
44
67
|
return { id };
|
|
45
68
|
}
|
|
46
|
-
/** Read one role — gated on `identity:role:read
|
|
47
|
-
* administrator, may read roles). */
|
|
69
|
+
/** Read one role — gated on `identity:role:read`; walled by owner (404 outside the subtree). */
|
|
48
70
|
getRole(uuid) {
|
|
49
71
|
return this.roles.getRole(uuid);
|
|
50
72
|
}
|
|
51
|
-
/**
|
|
73
|
+
/** Rename a role — gated on `identity:role:update`. */
|
|
52
74
|
async updateRole(req, uuid, body) {
|
|
53
|
-
await this.roles.updateRole(req.principal, uuid, { name: body
|
|
75
|
+
await this.roles.updateRole(req.principal, uuid, { name: nameFrom(body) });
|
|
54
76
|
return { success: true };
|
|
55
77
|
}
|
|
56
78
|
/** Delete a role — gated on `identity:role:delete`. */
|
|
@@ -60,6 +82,24 @@ let RoleController = class RoleController {
|
|
|
60
82
|
}
|
|
61
83
|
};
|
|
62
84
|
exports.RoleController = RoleController;
|
|
85
|
+
__decorate([
|
|
86
|
+
(0, common_1.Get)('companies/:companyUuid/roles'),
|
|
87
|
+
(0, guard_1.Classified)({ typeKey: 'role', action: 'read' }),
|
|
88
|
+
__param(0, (0, common_1.Param)('companyUuid', guard_1.UuidPipe)),
|
|
89
|
+
__param(1, (0, common_1.Query)()),
|
|
90
|
+
__metadata("design:type", Function),
|
|
91
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
92
|
+
__metadata("design:returntype", void 0)
|
|
93
|
+
], RoleController.prototype, "listCompanyRoles", null);
|
|
94
|
+
__decorate([
|
|
95
|
+
(0, common_1.Post)('companies/:companyUuid/roles'),
|
|
96
|
+
(0, guard_1.Classified)({ typeKey: 'role', action: 'create' }),
|
|
97
|
+
__param(0, (0, common_1.Param)('companyUuid', guard_1.UuidPipe)),
|
|
98
|
+
__param(1, (0, common_1.Body)()),
|
|
99
|
+
__metadata("design:type", Function),
|
|
100
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
101
|
+
__metadata("design:returntype", void 0)
|
|
102
|
+
], RoleController.prototype, "createCompanyRole", null);
|
|
63
103
|
__decorate([
|
|
64
104
|
(0, common_1.Get)('roles'),
|
|
65
105
|
(0, guard_1.Classified)({ typeKey: 'role', action: 'read' }),
|
|
@@ -1,18 +1,26 @@
|
|
|
1
|
-
import { RecordsService, type RecordId } from '@astram/engine';
|
|
1
|
+
import { RecordsService, type RecordId, type RecordView } from '@astram/engine';
|
|
2
2
|
import type { Principal } from '@astram/core';
|
|
3
|
-
import { type ListParams } from '../shared/list-query.js';
|
|
3
|
+
import { type ListParams, type ListQueryDto } from '../shared/list-query.js';
|
|
4
4
|
import { GrantCeiling } from '../authz/grant-ceiling.js';
|
|
5
5
|
import { GrantAdminService } from '../authz/grant-admin.service.js';
|
|
6
6
|
import { type PostingVersionBump } from '../membership/posting-version-bump.js';
|
|
7
|
+
import { type OrgUnitNameRefusal } from '../org-structure/org-unit-name.js';
|
|
8
|
+
/** The 400 of `POST /identity/roles` for a caller with no company to own the role (spec §10). */
|
|
9
|
+
export declare const NO_ROLE_COMPANY = "Choose a company for the role.";
|
|
10
|
+
/** The fixed, user-facing 400 text for a refused role name (the G-012 rule, spec §10). */
|
|
11
|
+
export declare function roleNameMessage(reason: OrgUnitNameRefusal): string;
|
|
7
12
|
/**
|
|
8
|
-
* RoleService — role CRUD on the paved road, via the framework's RecordsService
|
|
9
|
-
*
|
|
10
|
-
* no raw SQL, no RECORD_ENGINE.
|
|
13
|
+
* RoleService — role CRUD on the paved road, via the framework's RecordsService facade (the engine
|
|
14
|
+
* pipe: gate, wall, validate, write, audit). No raw SQL, no RECORD_ENGINE.
|
|
11
15
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
+
* G-013 (spec §5.A, ADR-0047): every role is OWNED by one company (`ownerCompanyUuid`, immutable),
|
|
17
|
+
* stamped here from the reached company (or the caller's active company on the legacy route), never
|
|
18
|
+
* from a request body (R1). The role object's `readScope` walls every read and write by that owner
|
|
19
|
+
* (R3: out of reach ⇒ 404; an ownerless legacy row is root only, R5). Names follow the G-012 rule
|
|
20
|
+
* (`normaliseOrgUnitName`) and are unique within the owner company under the column collation; the
|
|
21
|
+
* check runs under a lock inside one transaction and the per-company unique index is the backstop
|
|
22
|
+
* (a driver 1062 ⇒ the same 409, R6). Rename and delete first pass
|
|
23
|
+
* `GrantCeiling.assertRoleAdministrable` (owner in the actor's subtree, else 404).
|
|
16
24
|
*/
|
|
17
25
|
export declare class RoleService {
|
|
18
26
|
private readonly records;
|
|
@@ -21,26 +29,55 @@ export declare class RoleService {
|
|
|
21
29
|
private readonly grants?;
|
|
22
30
|
constructor(records: RecordsService, ceiling: GrantCeiling, versionBump?: (PostingVersionBump | null) | undefined, grants?: (GrantAdminService | null) | undefined);
|
|
23
31
|
/**
|
|
24
|
-
* Create a role
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
32
|
+
* Create a role (`POST /identity/roles`, kept for existing clients). Owner = the caller's active
|
|
33
|
+
* company, else its tenant; a caller with neither ⇒ 400 "Choose a company for the role." The
|
|
34
|
+
* company is then reached and locked exactly as {@link createCompanyRole} does.
|
|
35
|
+
*
|
|
36
|
+
* `companyUuid` is for the org bootstrap only (spec §5.E): the bound `roles` step names the
|
|
37
|
+
* DECLARING company explicitly and holds no company key (gate 7), so that company is not read or
|
|
38
|
+
* locked here; the engine's create wall checks the owner against the actor's reach, the duplicate
|
|
39
|
+
* check runs in the transaction and the unique index is the backstop.
|
|
40
|
+
*/
|
|
41
|
+
createRole(rawName: unknown, actor: Principal, companyUuid?: string): Promise<RecordId>;
|
|
42
|
+
/**
|
|
43
|
+
* Create a role in `companyUuid` (`POST /identity/companies/:companyUuid/roles`): normalise the
|
|
44
|
+
* name; in one transaction lock the company, reach it through the wall (404), refuse a name the
|
|
45
|
+
* company already holds (409 naming the stored role) and create the role owned by the REACHED
|
|
46
|
+
* company. Returns `{ uuid, name }` (the stored name).
|
|
29
47
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
48
|
+
createCompanyRole(companyUuid: string, rawName: unknown): Promise<{
|
|
49
|
+
uuid: string;
|
|
50
|
+
name: string;
|
|
51
|
+
}>;
|
|
52
|
+
/**
|
|
53
|
+
* One company's own roles (`GET /identity/companies/:companyUuid/roles`): the company is reached
|
|
54
|
+
* through the walled read first (404 when out of reach), then its roles are listed with the same
|
|
55
|
+
* paging, search and sort allowlists as {@link listRoles}.
|
|
56
|
+
*/
|
|
57
|
+
listCompanyRoles(companyUuid: string, query?: ListQueryDto): Promise<{
|
|
58
|
+
total: number;
|
|
59
|
+
rows: readonly RecordView[];
|
|
60
|
+
page: number;
|
|
61
|
+
limit: number;
|
|
62
|
+
}>;
|
|
63
|
+
/** List roles (walled by owner through the role object's readScope), with paging/search/sort. */
|
|
32
64
|
listRoles(params?: ListParams): Promise<import("@astram/engine").RecordPage>;
|
|
33
|
-
/** Read a single role. */
|
|
34
|
-
getRole(uuid: string): Promise<
|
|
35
|
-
/**
|
|
65
|
+
/** Read a single role (walled by owner). */
|
|
66
|
+
getRole(uuid: string): Promise<RecordView | null>;
|
|
67
|
+
/**
|
|
68
|
+
* Rename a role — only a role the actor may administer (owner in reach, else 404). The name goes
|
|
69
|
+
* through the name rule (400); in one transaction the role row is locked, re-read through the wall
|
|
70
|
+
* (404; ownerless ⇒ root only), and a name another role of the same company holds is refused (409
|
|
71
|
+
* naming the stored role). The same name ⇒ no write. Only `name` is ever written.
|
|
72
|
+
*/
|
|
36
73
|
updateRole(actor: Principal, uuid: string, patch: {
|
|
37
74
|
name?: unknown;
|
|
38
75
|
}): Promise<void>;
|
|
39
76
|
/**
|
|
40
|
-
* Delete a role — only a role the actor may administer (
|
|
77
|
+
* Delete a role — only a role the actor may administer (owner rule) AND whose every holder the
|
|
41
78
|
* actor dominates (Phase 8 #2: the delete cascades the holders' postings, stripping their
|
|
42
79
|
* authority). In ONE transaction: lock the role row (the posting and grant paths lock the same
|
|
43
|
-
* row first), re-check the
|
|
80
|
+
* row first), re-check the owner rule and holder dominance against what is committed now, read
|
|
44
81
|
* the holder tenants (before the cascade removes the postings), then delete. After the commit,
|
|
45
82
|
* every holder tenant's permission version is bumped so cached roles drop at the next request.
|
|
46
83
|
* The bump runs after the commit, never before: an earlier bump would let a session reload the
|
|
@@ -64,5 +101,23 @@ export declare class RoleService {
|
|
|
64
101
|
* (the engine mints it), so it can never pick up anything left behind.
|
|
65
102
|
*/
|
|
66
103
|
deleteRole(actor: Principal, uuid: string): Promise<void>;
|
|
104
|
+
private validName;
|
|
105
|
+
/** Lock the company FIRST, reach it through the wall (404), then create the role it owns. */
|
|
106
|
+
private createInCompany;
|
|
107
|
+
/** Duplicate check, then the role owned by `owner`, on the caller's transaction. */
|
|
108
|
+
private insertRole;
|
|
109
|
+
/**
|
|
110
|
+
* Refuse a name another role of the same company already holds. The engine's equality runs under
|
|
111
|
+
* the column collation (`utf8mb4_unicode_ci`: case, accents and width fold), so "manager" finds
|
|
112
|
+
* "Manager"; the 409 names the STORED clashing role.
|
|
113
|
+
*/
|
|
114
|
+
private assertNameFree;
|
|
115
|
+
private findByName;
|
|
116
|
+
/**
|
|
117
|
+
* Run a create/rename; a driver duplicate key (the per-company unique index, the backstop for a
|
|
118
|
+
* concurrent write) becomes the same 409 naming the stored role. Errno only: the driver's message
|
|
119
|
+
* holds the value and is never echoed (KI #87).
|
|
120
|
+
*/
|
|
121
|
+
private mappingDuplicates;
|
|
67
122
|
}
|
|
68
123
|
//# 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,
|
|
1
|
+
{"version":3,"file":"role.service.d.ts","sourceRoot":"","sources":["../../../src/features/role/role.service.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EAGd,KAAK,QAAQ,EACb,KAAK,UAAU,EAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,YAAY,EAClB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,uCAAuC,CAAC;AAM/C,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,mCAAmC,CAAC;AAK3C,iGAAiG;AACjG,eAAO,MAAM,eAAe,mCAAmC,CAAC;AAEhE,0FAA0F;AAC1F,wBAAgB,eAAe,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CASlE;AAmCD;;;;;;;;;;;;GAYG;AACH,qBACa,WAAW;IAEpB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAKxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAK7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAXP,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,YAAY,EAKrB,WAAW,CAAC,GAAE,kBAAkB,GAAG,IAAI,aAAA,EAKvC,MAAM,CAAC,GAAE,iBAAiB,GAAG,IAAI,aAAA;IAGpD;;;;;;;;;OASG;IACG,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAwB7F;;;;;OAKG;IACG,iBAAiB,CACrB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAI1C;;;;OAIG;IACG,gBAAgB,CACpB,WAAW,EAAE,MAAM,EACnB,KAAK,CAAC,EAAE,YAAY,GACnB,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,UAAU,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAYvF,iGAAiG;IACjG,SAAS,CAAC,MAAM,CAAC,EAAE,UAAU;IAI7B,4CAA4C;IAC5C,OAAO,CAAC,IAAI,EAAE,MAAM;IAIpB;;;;;OAKG;IACG,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB1F;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACG,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB/D,OAAO,CAAC,SAAS;IAMjB,6FAA6F;YAC/E,eAAe;IAoB7B,oFAAoF;YACtE,UAAU;IAKxB;;;;OAIG;YACW,cAAc;YAUd,UAAU;IAWxB;;;;OAIG;YACW,iBAAiB;CAsBhC"}
|