@astram/identity 0.16.0 → 0.17.1
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
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.OrgBootstrapError = void 0;
|
|
3
|
+
exports.CLAIM_UNOWNED_NOTICE = exports.OrgBootstrapError = void 0;
|
|
4
4
|
exports.runOrgBootstrap = runOrgBootstrap;
|
|
5
5
|
/**
|
|
6
6
|
* G-009 T10: the org bootstrap runner (SPEC R2, R3, R5-R11, R13; ADR-0042).
|
|
7
7
|
*
|
|
8
8
|
* `runOrgBootstrap(app, opts)` applies a declarative org file against the consumer's own application
|
|
9
|
-
* context, through the framework's existing services, never raw SQL (the
|
|
10
|
-
*
|
|
9
|
+
* context, through the framework's existing services, never raw SQL (the exception is the root flag,
|
|
10
|
+
* under the run lock and audited). Each step runs as its own principal,
|
|
11
11
|
* bound to the declared operation `shield:org-bootstrap` (Shield #9): control-plane steps root-flagged,
|
|
12
12
|
* people steps non-root and anchored at one company. The principal reaches the request-scoped services
|
|
13
13
|
* through `registerRequestByContextId`, so no service signature changes.
|
|
@@ -27,7 +27,6 @@ const framework_schemas_token_js_1 = require("../framework-schemas.token.js");
|
|
|
27
27
|
const membership_service_js_1 = require("../membership/membership.service.js");
|
|
28
28
|
const posting_service_js_1 = require("../membership/posting.service.js");
|
|
29
29
|
const org_catalog_service_js_1 = require("../org-structure/org-catalog.service.js");
|
|
30
|
-
const role_owner_transition_js_1 = require("../role/role-owner-transition.js");
|
|
31
30
|
const role_service_js_1 = require("../role/role.service.js");
|
|
32
31
|
const credential_provisioner_token_js_1 = require("../user/credential-provisioner.token.js");
|
|
33
32
|
const user_service_js_1 = require("../user/user.service.js");
|
|
@@ -35,6 +34,7 @@ const invite_message_js_1 = require("../user/invite-message.js");
|
|
|
35
34
|
const org_bootstrap_file_js_1 = require("./org-bootstrap-file.js");
|
|
36
35
|
const org_bootstrap_error_js_1 = require("./org-bootstrap-error.js");
|
|
37
36
|
const org_bootstrap_plan_js_1 = require("./org-bootstrap-plan.js");
|
|
37
|
+
const org_unit_name_js_1 = require("../org-structure/org-unit-name.js");
|
|
38
38
|
const org_bootstrap_manifest_js_1 = require("./org-bootstrap.manifest.js");
|
|
39
39
|
const org_bootstrap_tokens_js_1 = require("./org-bootstrap.tokens.js");
|
|
40
40
|
const org_names_js_1 = require("./org-names.js");
|
|
@@ -51,6 +51,8 @@ const STEP = {
|
|
|
51
51
|
users: 'users',
|
|
52
52
|
postings: 'postings',
|
|
53
53
|
};
|
|
54
|
+
/** Printed when `claimUnownedRoles` is passed (G-013, spec §5.E): the flag now does nothing. */
|
|
55
|
+
exports.CLAIM_UNOWNED_NOTICE = '--claim-unowned-roles is no longer needed: roles belong to the company that declares them.';
|
|
54
56
|
// ---------------------------------------------------------------------------------------------
|
|
55
57
|
function loadFile(file) {
|
|
56
58
|
if (typeof file !== 'string')
|
|
@@ -84,11 +86,20 @@ function flatten(file) {
|
|
|
84
86
|
company: u.company === undefined ? c.path : `${c.tenant}/${u.company}`,
|
|
85
87
|
tenant: c.tenant,
|
|
86
88
|
postings: u.postings ?? [],
|
|
89
|
+
...(u.loginName !== undefined ? { loginName: u.loginName } : {}),
|
|
87
90
|
});
|
|
88
91
|
}
|
|
89
92
|
}
|
|
90
93
|
return { companies, users };
|
|
91
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* A department/branch name as stored (the shared rule, spec §10). The planner already refused a
|
|
97
|
+
* name the rule refuses, so the fallback is never written.
|
|
98
|
+
*/
|
|
99
|
+
function orgUnitName(raw) {
|
|
100
|
+
const n = (0, org_unit_name_js_1.normaliseOrgUnitName)(raw);
|
|
101
|
+
return n.ok ? n.value : raw;
|
|
102
|
+
}
|
|
92
103
|
const postingPath = (user, company, p) => `user:${user}@${company} role:${p.role}${p.department ? ` dept:${p.department}` : ''}${p.branch ? ` branch:${p.branch}` : ''}`;
|
|
93
104
|
function printPlan(plan, out) {
|
|
94
105
|
for (const e of plan.entries) {
|
|
@@ -140,7 +151,9 @@ async function runOrgBootstrap(app, opts) {
|
|
|
140
151
|
query: async (sql, params) => (await pool.query(sql, params)),
|
|
141
152
|
schemas,
|
|
142
153
|
}).read(file);
|
|
143
|
-
const planFor = (state) => (0, org_bootstrap_plan_js_1.planOrgBootstrap)(file, state
|
|
154
|
+
const planFor = (state) => (0, org_bootstrap_plan_js_1.planOrgBootstrap)(file, state);
|
|
155
|
+
if (opts.claimUnownedRoles === true)
|
|
156
|
+
out(exports.CLAIM_UNOWNED_NOTICE);
|
|
144
157
|
// ---- dry run: plan only, no lock, no write ----
|
|
145
158
|
if (!apply) {
|
|
146
159
|
const state = await readState();
|
|
@@ -348,81 +361,53 @@ async function runOrgBootstrap(app, opts) {
|
|
|
348
361
|
count('company');
|
|
349
362
|
}
|
|
350
363
|
});
|
|
351
|
-
// 2. org units:
|
|
364
|
+
// 2. org units (G-012): each company's OWN departments and branches. `ensure*` creates the row
|
|
365
|
+
// with its owner and its one junction row in one transaction, so both are counted.
|
|
352
366
|
await step(STEP.orgUnits, async () => {
|
|
353
|
-
const seenDept = new Set();
|
|
354
|
-
const seenBranch = new Set();
|
|
355
367
|
for (const c of flat.companies) {
|
|
356
368
|
const actor = await rootAt(STEP.orgUnits, c.tenant);
|
|
357
369
|
const catalogSvc = await service(org_catalog_service_js_1.OrgCatalogService, actor);
|
|
358
370
|
for (const d of c.node.departments ?? []) {
|
|
359
|
-
if (
|
|
371
|
+
if (st('department', `${c.path}#dept:${d}`) !== 'create')
|
|
360
372
|
continue;
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
ids.set('department', d, String(await catalogSvc.ensureDepartment(d)));
|
|
373
|
+
const companyUuid = await ids.company(c.path);
|
|
374
|
+
const id = await catalogSvc.ensureDepartment(companyUuid, orgUnitName(d));
|
|
375
|
+
ids.setUnit('department', companyUuid, d, String(id));
|
|
365
376
|
count('department');
|
|
366
|
-
}
|
|
367
|
-
for (const b of c.node.branches ?? []) {
|
|
368
|
-
if (seenBranch.has((0, org_names_js_1.nameKey)(b)))
|
|
369
|
-
continue;
|
|
370
|
-
seenBranch.add((0, org_names_js_1.nameKey)(b));
|
|
371
|
-
if (st('branch', `branch:${b}`) !== 'create')
|
|
372
|
-
continue;
|
|
373
|
-
ids.set('branch', b, String(await catalogSvc.ensureBranch(b)));
|
|
374
|
-
count('branch');
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
for (const c of flat.companies) {
|
|
378
|
-
const actor = await rootAt(STEP.orgUnits, c.tenant);
|
|
379
|
-
const catalogSvc = await service(org_catalog_service_js_1.OrgCatalogService, actor);
|
|
380
|
-
for (const d of c.node.departments ?? []) {
|
|
381
|
-
if (st('company_department', `${c.path}#dept:${d}`) !== 'create')
|
|
382
|
-
continue;
|
|
383
|
-
await catalogSvc.attachDepartment(await ids.company(c.path), await ids.department(d));
|
|
384
377
|
count('company_department');
|
|
385
378
|
}
|
|
386
379
|
for (const b of c.node.branches ?? []) {
|
|
387
|
-
if (st('
|
|
380
|
+
if (st('branch', `${c.path}#branch:${b}`) !== 'create')
|
|
388
381
|
continue;
|
|
389
|
-
|
|
382
|
+
const companyUuid = await ids.company(c.path);
|
|
383
|
+
const id = await catalogSvc.ensureBranch(companyUuid, orgUnitName(b));
|
|
384
|
+
ids.setUnit('branch', companyUuid, b, String(id));
|
|
385
|
+
count('branch');
|
|
390
386
|
count('company_branch');
|
|
391
387
|
}
|
|
392
388
|
}
|
|
393
389
|
});
|
|
394
|
-
// 3. roles
|
|
390
|
+
// 3. roles, then grants through GrantAdminService. G-013 (spec §5.E): each role is created OWNED
|
|
391
|
+
// by the company that declares it (passed explicitly: the `roles` step holds no company key).
|
|
395
392
|
await step(STEP.roles, async () => {
|
|
396
|
-
for (const
|
|
397
|
-
const
|
|
393
|
+
for (const c of flat.companies) {
|
|
394
|
+
const declared = c.node.roles ?? [];
|
|
395
|
+
if (declared.length === 0)
|
|
396
|
+
continue;
|
|
397
|
+
const actor = await rootAt(STEP.roles, c.tenant);
|
|
398
398
|
const roles = await service(role_service_js_1.RoleService, actor);
|
|
399
|
-
const
|
|
400
|
-
const seen = new Set();
|
|
401
|
-
for (const c of flat.companies.filter((x) => x.tenant === tenant.name)) {
|
|
402
|
-
for (const r of c.node.roles ?? []) {
|
|
403
|
-
if (seen.has((0, org_names_js_1.nameKey)(r.name)))
|
|
404
|
-
continue;
|
|
405
|
-
seen.add((0, org_names_js_1.nameKey)(r.name));
|
|
406
|
-
declared.push(r);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
399
|
+
const companyUuid = await ids.company(c.path);
|
|
409
400
|
for (const r of declared) {
|
|
410
|
-
if (st('role',
|
|
401
|
+
if (st('role', (0, org_bootstrap_plan_js_1.rolePath)(c.path, r.name)) !== 'create')
|
|
411
402
|
continue;
|
|
412
|
-
ids.
|
|
403
|
+
ids.setRole(companyUuid, r.name, String(await roles.createRole(orgUnitName(r.name), actor, companyUuid)));
|
|
413
404
|
count('role');
|
|
414
405
|
}
|
|
415
|
-
for (const r of declared) {
|
|
416
|
-
if (st('role_claim', `role:${r.name}`) !== 'create')
|
|
417
|
-
continue;
|
|
418
|
-
await claimRole(await ids.role(r.name), await ids.company(tenant.name));
|
|
419
|
-
count('role_claim');
|
|
420
|
-
}
|
|
421
406
|
for (const r of declared) {
|
|
422
407
|
for (const key of r.permissions) {
|
|
423
|
-
if (st('grant',
|
|
408
|
+
if (st('grant', `${(0, org_bootstrap_plan_js_1.rolePath)(c.path, r.name)}#${key}`) !== 'create')
|
|
424
409
|
continue;
|
|
425
|
-
await grantAdmin.grant(await ids.role(r.name), key, actor);
|
|
410
|
+
await grantAdmin.grant(await ids.role(companyUuid, r.name), key, actor);
|
|
426
411
|
count('grant');
|
|
427
412
|
}
|
|
428
413
|
}
|
|
@@ -440,6 +425,8 @@ async function runOrgBootstrap(app, opts) {
|
|
|
440
425
|
const created = await users.createUserWithCredential(actor, {
|
|
441
426
|
username: r.username,
|
|
442
427
|
email: r.email,
|
|
428
|
+
// Auth capabilities R1.7: the service validates it again and audits it.
|
|
429
|
+
...(r.loginName !== undefined ? { loginName: r.loginName } : {}),
|
|
443
430
|
});
|
|
444
431
|
ids.set('user', r.username, String(created.id));
|
|
445
432
|
count('user');
|
|
@@ -454,6 +441,7 @@ async function runOrgBootstrap(app, opts) {
|
|
|
454
441
|
username: u.username,
|
|
455
442
|
email: u.email,
|
|
456
443
|
companyUuid,
|
|
444
|
+
...(u.loginName !== undefined ? { loginName: u.loginName } : {}),
|
|
457
445
|
});
|
|
458
446
|
ids.set('user', u.username, String(created.id));
|
|
459
447
|
count('user');
|
|
@@ -480,7 +468,7 @@ async function runOrgBootstrap(app, opts) {
|
|
|
480
468
|
}
|
|
481
469
|
});
|
|
482
470
|
// 5. postings: add-only, as the anchored non-root principal, with the roles the file declares
|
|
483
|
-
//
|
|
471
|
+
// on the posting's own company (G-013: the only roles assignable there) as `operationRoleIds`.
|
|
484
472
|
await step(STEP.postings, async () => {
|
|
485
473
|
for (const u of flat.users) {
|
|
486
474
|
const toAdd = u.postings.filter((p) => st('posting', postingPath(u.username, u.company, p)) === 'create');
|
|
@@ -492,23 +480,26 @@ async function runOrgBootstrap(app, opts) {
|
|
|
492
480
|
// A read-only lookup, not `listMemberships`: list SQL scoping does not consult gate 7, so an
|
|
493
481
|
// operation-bound non-root principal could see no rows there (spec P1 "Matching").
|
|
494
482
|
const membershipUuid = await ids.membership(userUuid, companyUuid);
|
|
495
|
-
// R1-7: the roles declared on THIS company
|
|
496
|
-
//
|
|
483
|
+
// R1-7, G-013: the roles declared on THIS company in the file (the resolution the file
|
|
484
|
+
// parser uses for postings), never an ancestor's or another company's.
|
|
497
485
|
const operationRoleIds = new Set();
|
|
498
486
|
const here = (0, org_names_js_1.pathKey)(u.company);
|
|
499
487
|
for (const c of flat.companies) {
|
|
500
|
-
|
|
501
|
-
if (here !== there && !here.startsWith(`${there}/`))
|
|
488
|
+
if ((0, org_names_js_1.pathKey)(c.path) !== here)
|
|
502
489
|
continue;
|
|
503
|
-
for (const r of c.node.roles ?? [])
|
|
504
|
-
operationRoleIds.add(await ids.role(r.name));
|
|
490
|
+
for (const r of c.node.roles ?? []) {
|
|
491
|
+
operationRoleIds.add(await ids.role(companyUuid, r.name));
|
|
492
|
+
}
|
|
505
493
|
}
|
|
506
494
|
const inputs = [];
|
|
507
495
|
for (const p of toAdd) {
|
|
508
496
|
inputs.push({
|
|
509
|
-
roleUuid: await ids.role(p.role),
|
|
510
|
-
|
|
511
|
-
...(p.
|
|
497
|
+
roleUuid: await ids.role(companyUuid, p.role),
|
|
498
|
+
// G-012: resolved within the posting's OWN company (the planner refused any other).
|
|
499
|
+
...(p.department
|
|
500
|
+
? { departmentUuid: await ids.department(companyUuid, p.department) }
|
|
501
|
+
: {}),
|
|
502
|
+
...(p.branch ? { branchUuid: await ids.branch(companyUuid, p.branch) } : {}),
|
|
512
503
|
});
|
|
513
504
|
}
|
|
514
505
|
const postings = await service(posting_service_js_1.PostingService, actor);
|
|
@@ -666,7 +657,7 @@ async function runOrgBootstrap(app, opts) {
|
|
|
666
657
|
linksPrintedRoots,
|
|
667
658
|
rootIds,
|
|
668
659
|
};
|
|
669
|
-
// ----- raw
|
|
660
|
+
// ----- the raw write allowed by the spec (R5), under the run lock and audited -----
|
|
670
661
|
async function inTransaction(work) {
|
|
671
662
|
const conn = await pool.getConnection();
|
|
672
663
|
try {
|
|
@@ -684,24 +675,6 @@ async function runOrgBootstrap(app, opts) {
|
|
|
684
675
|
await conn.release();
|
|
685
676
|
}
|
|
686
677
|
}
|
|
687
|
-
async function claimRole(roleUuid, tenantUuid) {
|
|
688
|
-
const tenantUuids = await Promise.all(file.tenants.map((t) => ids.company(t.name)));
|
|
689
|
-
await inTransaction(async (conn) => {
|
|
690
|
-
// R1-8: serialise on the role row, then re-check inside the transaction that nobody outside the
|
|
691
|
-
// file's tenants holds it (the plan-time read is not enough: it ran before other writers).
|
|
692
|
-
await conn.query(`SELECT r_uuid FROM \`${schemas.app}\`.\`roles\` WHERE r_uuid = ? FOR UPDATE`, [roleUuid]);
|
|
693
|
-
const held = (await conn.query(`SELECT 1 AS held FROM \`${schemas.app}\`.\`membership_posting\` mp ` +
|
|
694
|
-
`JOIN \`${schemas.app}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid ` +
|
|
695
|
-
`WHERE mp.mp_role_uuid = ? AND cm.cm_company_uuid NOT IN (` +
|
|
696
|
-
`SELECT sc.descendant_uuid FROM \`${schemas.authz}\`.\`scope_closure\` sc WHERE sc.ancestor_uuid IN (${tenantUuids.map(() => '?').join(', ')})) LIMIT 1`, [roleUuid, ...tenantUuids]));
|
|
697
|
-
if (held.length > 0) {
|
|
698
|
-
const refusal = new Error('role is held outside the file tenants');
|
|
699
|
-
refusal.name = 'RoleHeldOutsideError';
|
|
700
|
-
throw refusal;
|
|
701
|
-
}
|
|
702
|
-
await (0, role_owner_transition_js_1.transitionRoleOwner)((sql, params) => conn.query(sql, params), roleUuid, null, tenantUuid, (action, attributes, query) => auditOn(query, tenantUuid, action, 'roles-claim', { ...attributes }), schemas);
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
678
|
async function setPlatformSuperuser(userUuid, tenantUuid) {
|
|
706
679
|
await inTransaction(async (conn) => {
|
|
707
680
|
await conn.query(`UPDATE \`${schemas.app}\`.\`users\` SET u_is_platform_superuser = 1 WHERE u_uuid = ?`, [userUuid]);
|
|
@@ -725,7 +698,9 @@ class Ids {
|
|
|
725
698
|
this.schemas = schemas;
|
|
726
699
|
}
|
|
727
700
|
key(kind, name) {
|
|
728
|
-
|
|
701
|
+
// membership, department, branch and role names are already composite keys (see `unitName`)
|
|
702
|
+
const raw = kind === 'membership' || kind === 'department' || kind === 'branch' || kind === 'role';
|
|
703
|
+
return `${kind}\u0000${kind === 'company' ? (0, org_names_js_1.pathKey)(name) : raw ? name : (0, org_names_js_1.nameKey)(name)}`;
|
|
729
704
|
}
|
|
730
705
|
set(kind, name, id) {
|
|
731
706
|
this.known.set(this.key(kind, name), id);
|
|
@@ -756,14 +731,28 @@ class Ids {
|
|
|
756
731
|
const parent = segments.length > 1 ? await this.company(segments.slice(0, -1).join('/')) : null;
|
|
757
732
|
return this.lookup('company', path, `SELECT c_uuid FROM \`${this.schemas.app}\`.\`companies\` WHERE c_name = ? AND c_parent_uuid <=> ? LIMIT 2`, [name, parent], 'c_uuid');
|
|
758
733
|
}
|
|
759
|
-
department(name)
|
|
760
|
-
|
|
734
|
+
/** G-012: the cache name of a company's own department/branch (owner + normalised name key). */
|
|
735
|
+
unitName(companyUuid, name) {
|
|
736
|
+
return `${companyUuid}\u0000${(0, org_bootstrap_plan_js_1.orgUnitKey)(name)}`;
|
|
737
|
+
}
|
|
738
|
+
setUnit(kind, companyUuid, name, id) {
|
|
739
|
+
this.set(kind, this.unitName(companyUuid, name), id);
|
|
740
|
+
}
|
|
741
|
+
/** `companyUuid`'s own department named `name` (normalised; the column collation matches). */
|
|
742
|
+
department(companyUuid, name) {
|
|
743
|
+
return this.lookup('department', this.unitName(companyUuid, name), `SELECT d_uuid FROM \`${this.schemas.app}\`.\`departments\` WHERE d_company_uuid = ? AND d_name = ? LIMIT 2`, [companyUuid, orgUnitName(name)], 'd_uuid');
|
|
744
|
+
}
|
|
745
|
+
/** `companyUuid`'s own branch named `name`. */
|
|
746
|
+
branch(companyUuid, name) {
|
|
747
|
+
return this.lookup('branch', this.unitName(companyUuid, name), `SELECT b_uuid FROM \`${this.schemas.app}\`.\`branches\` WHERE b_company_uuid = ? AND b_name = ? LIMIT 2`, [companyUuid, orgUnitName(name)], 'b_uuid');
|
|
761
748
|
}
|
|
762
|
-
|
|
763
|
-
|
|
749
|
+
/** G-013: cache a created role of `companyUuid` (owner + normalised name key). */
|
|
750
|
+
setRole(companyUuid, name, id) {
|
|
751
|
+
this.set('role', this.unitName(companyUuid, name), id);
|
|
764
752
|
}
|
|
765
|
-
role(
|
|
766
|
-
|
|
753
|
+
/** `companyUuid`'s own role named `name` (normalised; the column collation matches). */
|
|
754
|
+
role(companyUuid, name) {
|
|
755
|
+
return this.lookup('role', this.unitName(companyUuid, name), `SELECT r_uuid FROM \`${this.schemas.app}\`.\`roles\` WHERE r_owner_company_uuid = ? AND r_name = ? LIMIT 2`, [companyUuid, orgUnitName(name)], 'r_uuid');
|
|
767
756
|
}
|
|
768
757
|
user(username) {
|
|
769
758
|
return this.lookup('user', username, `SELECT u_uuid FROM \`${this.schemas.app}\`.\`users\` WHERE u_username = ? LIMIT 2`, [username], 'u_uuid');
|
|
@@ -1,14 +1,37 @@
|
|
|
1
|
+
import type { ListQueryDto } from '../shared/list-query.js';
|
|
1
2
|
import { OrgCatalogService } from './org-catalog.service.js';
|
|
2
3
|
export declare class OrgCatalogController {
|
|
3
4
|
private readonly catalog;
|
|
4
5
|
constructor(catalog: OrgCatalogService);
|
|
5
|
-
/** A company's departments
|
|
6
|
-
listCompanyDepartments(companyUuid: string): Promise<
|
|
7
|
-
|
|
6
|
+
/** A company's departments — gated on `identity:company_department:read`. */
|
|
7
|
+
listCompanyDepartments(companyUuid: string, query: ListQueryDto): Promise<import("./org-catalog.service.js").OrgUnitList>;
|
|
8
|
+
/** A company's branches — gated on `identity:company_branch:read`. */
|
|
9
|
+
listCompanyBranches(companyUuid: string, query: ListQueryDto): Promise<import("./org-catalog.service.js").OrgUnitList>;
|
|
10
|
+
/** Create a department in a company — `identity:department:create` ⇒ 201 `{ uuid, name }`. */
|
|
11
|
+
createDepartment(companyUuid: string, body: unknown): Promise<{
|
|
12
|
+
uuid: string;
|
|
13
|
+
name: string;
|
|
8
14
|
}>;
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
/** Create a branch in a company — `identity:branch:create` ⇒ 201 `{ uuid, name }`. */
|
|
16
|
+
createBranch(companyUuid: string, body: unknown): Promise<{
|
|
17
|
+
uuid: string;
|
|
18
|
+
name: string;
|
|
19
|
+
}>;
|
|
20
|
+
/** Rename a department — `identity:department:update`. */
|
|
21
|
+
renameDepartment(uuid: string, body: unknown): Promise<{
|
|
22
|
+
success: boolean;
|
|
23
|
+
}>;
|
|
24
|
+
/** Rename a branch — `identity:branch:update`. */
|
|
25
|
+
renameBranch(uuid: string, body: unknown): Promise<{
|
|
26
|
+
success: boolean;
|
|
27
|
+
}>;
|
|
28
|
+
/** Delete a department (409 while any posting references it) — `identity:department:delete`. */
|
|
29
|
+
deleteDepartment(uuid: string): Promise<{
|
|
30
|
+
success: boolean;
|
|
31
|
+
}>;
|
|
32
|
+
/** Delete a branch (409 while any posting references it) — `identity:branch:delete`. */
|
|
33
|
+
deleteBranch(uuid: string): Promise<{
|
|
34
|
+
success: boolean;
|
|
12
35
|
}>;
|
|
13
36
|
}
|
|
14
37
|
//# sourceMappingURL=org-catalog.controller.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"org-catalog.controller.d.ts","sourceRoot":"","sources":["../../../src/features/org-structure/org-catalog.controller.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"org-catalog.controller.d.ts","sourceRoot":"","sources":["../../../src/features/org-structure/org-catalog.controller.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAiB7D,qBAEa,oBAAoB;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,iBAAiB;IAEvD,6EAA6E;IAG7E,sBAAsB,CACY,WAAW,EAAE,MAAM,EAC1C,KAAK,EAAE,YAAY;IAK9B,sEAAsE;IAGtE,mBAAmB,CACe,WAAW,EAAE,MAAM,EAC1C,KAAK,EAAE,YAAY;IAK9B,8FAA8F;IAGxF,gBAAgB,CACY,WAAW,EAAE,MAAM,EAC3C,IAAI,EAAE,OAAO;;;;IAKvB,sFAAsF;IAGhF,YAAY,CAAiC,WAAW,EAAE,MAAM,EAAU,IAAI,EAAE,OAAO;;;;IAI7F,0DAA0D;IAGpD,gBAAgB,CAA0B,IAAI,EAAE,MAAM,EAAU,IAAI,EAAE,OAAO;;;IAKnF,kDAAkD;IAG5C,YAAY,CAA0B,IAAI,EAAE,MAAM,EAAU,IAAI,EAAE,OAAO;;;IAK/E,gGAAgG;IAG1F,gBAAgB,CAA0B,IAAI,EAAE,MAAM;;;IAK5D,wFAAwF;IAGlF,YAAY,CAA0B,IAAI,EAAE,MAAM;;;CAIzD"}
|
|
@@ -15,29 +15,63 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.OrgCatalogController = void 0;
|
|
16
16
|
const common_1 = require("@nestjs/common");
|
|
17
17
|
const identity_error_interceptor_js_1 = require("../shared/identity-error.interceptor.js");
|
|
18
|
+
const identity_errors_js_1 = require("../shared/identity-errors.js");
|
|
18
19
|
const guard_1 = require("@astram/guard");
|
|
19
20
|
const org_catalog_service_js_1 = require("./org-catalog.service.js");
|
|
20
|
-
// ──
|
|
21
|
+
// ── A company's own departments and branches (G-012, spec §3) ──
|
|
21
22
|
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
23
|
+
// The two GETs back the posting editor's dropdowns and the template's Org structure page:
|
|
24
|
+
// `{ rows: [{ uuid, name, usedBy? }], total, page, limit }` — every row (up to the per-company cap)
|
|
25
|
+
// without `?page&limit&search`, one page with them. The writes create, rename and delete a
|
|
26
|
+
// company's own rows. The service reaches the company or row through the subtree wall first (404
|
|
27
|
+
// out of reach) and stamps the owner from the reached company, never from the body.
|
|
28
|
+
/** The body's `name`, as a string only (the service normalises and validates it). */
|
|
29
|
+
function nameFrom(body, kind) {
|
|
30
|
+
const name = body?.name;
|
|
31
|
+
if (typeof name !== 'string')
|
|
32
|
+
throw new identity_errors_js_1.IdentityBadRequestError(`Enter a ${kind} name.`);
|
|
33
|
+
return name;
|
|
34
|
+
}
|
|
27
35
|
let OrgCatalogController = class OrgCatalogController {
|
|
28
36
|
catalog;
|
|
29
37
|
constructor(catalog) {
|
|
30
38
|
this.catalog = catalog;
|
|
31
39
|
}
|
|
32
|
-
/** A company's departments
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return { rows };
|
|
40
|
+
/** A company's departments — gated on `identity:company_department:read`. */
|
|
41
|
+
listCompanyDepartments(companyUuid, query) {
|
|
42
|
+
return this.catalog.listCompanyDepartments(companyUuid, query);
|
|
36
43
|
}
|
|
37
|
-
/** A company's branches
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
/** A company's branches — gated on `identity:company_branch:read`. */
|
|
45
|
+
listCompanyBranches(companyUuid, query) {
|
|
46
|
+
return this.catalog.listCompanyBranches(companyUuid, query);
|
|
47
|
+
}
|
|
48
|
+
/** Create a department in a company — `identity:department:create` ⇒ 201 `{ uuid, name }`. */
|
|
49
|
+
async createDepartment(companyUuid, body) {
|
|
50
|
+
return this.catalog.createDepartment(companyUuid, nameFrom(body, 'department'));
|
|
51
|
+
}
|
|
52
|
+
/** Create a branch in a company — `identity:branch:create` ⇒ 201 `{ uuid, name }`. */
|
|
53
|
+
async createBranch(companyUuid, body) {
|
|
54
|
+
return this.catalog.createBranch(companyUuid, nameFrom(body, 'branch'));
|
|
55
|
+
}
|
|
56
|
+
/** Rename a department — `identity:department:update`. */
|
|
57
|
+
async renameDepartment(uuid, body) {
|
|
58
|
+
await this.catalog.renameDepartment(uuid, nameFrom(body, 'department'));
|
|
59
|
+
return { success: true };
|
|
60
|
+
}
|
|
61
|
+
/** Rename a branch — `identity:branch:update`. */
|
|
62
|
+
async renameBranch(uuid, body) {
|
|
63
|
+
await this.catalog.renameBranch(uuid, nameFrom(body, 'branch'));
|
|
64
|
+
return { success: true };
|
|
65
|
+
}
|
|
66
|
+
/** Delete a department (409 while any posting references it) — `identity:department:delete`. */
|
|
67
|
+
async deleteDepartment(uuid) {
|
|
68
|
+
await this.catalog.deleteDepartment(uuid);
|
|
69
|
+
return { success: true };
|
|
70
|
+
}
|
|
71
|
+
/** Delete a branch (409 while any posting references it) — `identity:branch:delete`. */
|
|
72
|
+
async deleteBranch(uuid) {
|
|
73
|
+
await this.catalog.deleteBranch(uuid);
|
|
74
|
+
return { success: true };
|
|
41
75
|
}
|
|
42
76
|
};
|
|
43
77
|
exports.OrgCatalogController = OrgCatalogController;
|
|
@@ -45,18 +79,72 @@ __decorate([
|
|
|
45
79
|
(0, common_1.Get)('companies/:companyUuid/departments'),
|
|
46
80
|
(0, guard_1.Classified)({ typeKey: 'company_department', action: 'read' }),
|
|
47
81
|
__param(0, (0, common_1.Param)('companyUuid', guard_1.UuidPipe)),
|
|
82
|
+
__param(1, (0, common_1.Query)()),
|
|
48
83
|
__metadata("design:type", Function),
|
|
49
|
-
__metadata("design:paramtypes", [String]),
|
|
50
|
-
__metadata("design:returntype",
|
|
84
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
85
|
+
__metadata("design:returntype", void 0)
|
|
51
86
|
], OrgCatalogController.prototype, "listCompanyDepartments", null);
|
|
52
87
|
__decorate([
|
|
53
88
|
(0, common_1.Get)('companies/:companyUuid/branches'),
|
|
54
89
|
(0, guard_1.Classified)({ typeKey: 'company_branch', action: 'read' }),
|
|
55
90
|
__param(0, (0, common_1.Param)('companyUuid', guard_1.UuidPipe)),
|
|
91
|
+
__param(1, (0, common_1.Query)()),
|
|
92
|
+
__metadata("design:type", Function),
|
|
93
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
94
|
+
__metadata("design:returntype", void 0)
|
|
95
|
+
], OrgCatalogController.prototype, "listCompanyBranches", null);
|
|
96
|
+
__decorate([
|
|
97
|
+
(0, common_1.Post)('companies/:companyUuid/departments'),
|
|
98
|
+
(0, guard_1.Classified)({ typeKey: 'department', action: 'create' }),
|
|
99
|
+
__param(0, (0, common_1.Param)('companyUuid', guard_1.UuidPipe)),
|
|
100
|
+
__param(1, (0, common_1.Body)()),
|
|
101
|
+
__metadata("design:type", Function),
|
|
102
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
103
|
+
__metadata("design:returntype", Promise)
|
|
104
|
+
], OrgCatalogController.prototype, "createDepartment", null);
|
|
105
|
+
__decorate([
|
|
106
|
+
(0, common_1.Post)('companies/:companyUuid/branches'),
|
|
107
|
+
(0, guard_1.Classified)({ typeKey: 'branch', action: 'create' }),
|
|
108
|
+
__param(0, (0, common_1.Param)('companyUuid', guard_1.UuidPipe)),
|
|
109
|
+
__param(1, (0, common_1.Body)()),
|
|
110
|
+
__metadata("design:type", Function),
|
|
111
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
112
|
+
__metadata("design:returntype", Promise)
|
|
113
|
+
], OrgCatalogController.prototype, "createBranch", null);
|
|
114
|
+
__decorate([
|
|
115
|
+
(0, common_1.Put)('departments/:uuid'),
|
|
116
|
+
(0, guard_1.Classified)({ typeKey: 'department', action: 'update' }),
|
|
117
|
+
__param(0, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
118
|
+
__param(1, (0, common_1.Body)()),
|
|
119
|
+
__metadata("design:type", Function),
|
|
120
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
121
|
+
__metadata("design:returntype", Promise)
|
|
122
|
+
], OrgCatalogController.prototype, "renameDepartment", null);
|
|
123
|
+
__decorate([
|
|
124
|
+
(0, common_1.Put)('branches/:uuid'),
|
|
125
|
+
(0, guard_1.Classified)({ typeKey: 'branch', action: 'update' }),
|
|
126
|
+
__param(0, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
127
|
+
__param(1, (0, common_1.Body)()),
|
|
128
|
+
__metadata("design:type", Function),
|
|
129
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
130
|
+
__metadata("design:returntype", Promise)
|
|
131
|
+
], OrgCatalogController.prototype, "renameBranch", null);
|
|
132
|
+
__decorate([
|
|
133
|
+
(0, common_1.Delete)('departments/:uuid'),
|
|
134
|
+
(0, guard_1.Classified)({ typeKey: 'department', action: 'delete' }),
|
|
135
|
+
__param(0, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
56
136
|
__metadata("design:type", Function),
|
|
57
137
|
__metadata("design:paramtypes", [String]),
|
|
58
138
|
__metadata("design:returntype", Promise)
|
|
59
|
-
], OrgCatalogController.prototype, "
|
|
139
|
+
], OrgCatalogController.prototype, "deleteDepartment", null);
|
|
140
|
+
__decorate([
|
|
141
|
+
(0, common_1.Delete)('branches/:uuid'),
|
|
142
|
+
(0, guard_1.Classified)({ typeKey: 'branch', action: 'delete' }),
|
|
143
|
+
__param(0, (0, common_1.Param)('uuid', guard_1.UuidPipe)),
|
|
144
|
+
__metadata("design:type", Function),
|
|
145
|
+
__metadata("design:paramtypes", [String]),
|
|
146
|
+
__metadata("design:returntype", Promise)
|
|
147
|
+
], OrgCatalogController.prototype, "deleteBranch", null);
|
|
60
148
|
exports.OrgCatalogController = OrgCatalogController = __decorate([
|
|
61
149
|
(0, common_1.Controller)('identity'),
|
|
62
150
|
(0, common_1.UseInterceptors)(identity_error_interceptor_js_1.IdentityErrorInterceptor),
|