@astram/identity 0.13.0 → 0.14.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/features/authz/grant-admin.service.d.ts +1 -1
- package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
- package/dist/features/authz/grant-admin.service.js +8 -0
- package/dist/features/authz/grant-ceiling.d.ts +11 -3
- package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
- package/dist/features/authz/grant-ceiling.js +20 -3
- package/dist/features/authz/record-authorization.d.ts +1 -1
- package/dist/features/authz/record-authorization.d.ts.map +1 -1
- package/dist/features/authz/record-authorization.js +3 -2
- package/dist/features/company/company-hierarchy.service.d.ts +6 -0
- package/dist/features/company/company-hierarchy.service.d.ts.map +1 -1
- package/dist/features/company/company-hierarchy.service.js +19 -0
- package/dist/features/identity-feature.module.d.ts.map +1 -1
- package/dist/features/identity-feature.module.js +13 -0
- package/dist/features/membership/membership.service.d.ts +9 -0
- package/dist/features/membership/membership.service.d.ts.map +1 -1
- package/dist/features/membership/membership.service.js +40 -0
- package/dist/features/membership/posting.service.d.ts +18 -0
- package/dist/features/membership/posting.service.d.ts.map +1 -1
- package/dist/features/membership/posting.service.js +85 -27
- package/dist/features/org-bootstrap/org-bootstrap-error.d.ts +20 -0
- package/dist/features/org-bootstrap/org-bootstrap-error.d.ts.map +1 -0
- package/dist/features/org-bootstrap/org-bootstrap-error.js +24 -0
- package/dist/features/org-bootstrap/org-bootstrap-file.d.ts +58 -0
- package/dist/features/org-bootstrap/org-bootstrap-file.d.ts.map +1 -0
- package/dist/features/org-bootstrap/org-bootstrap-file.js +323 -0
- package/dist/features/org-bootstrap/org-bootstrap-plan.d.ts +79 -0
- package/dist/features/org-bootstrap/org-bootstrap-plan.d.ts.map +1 -0
- package/dist/features/org-bootstrap/org-bootstrap-plan.js +288 -0
- package/dist/features/org-bootstrap/org-bootstrap-runner.harness.d.ts +259 -0
- package/dist/features/org-bootstrap/org-bootstrap-runner.harness.d.ts.map +1 -0
- package/dist/features/org-bootstrap/org-bootstrap-runner.harness.js +604 -0
- package/dist/features/org-bootstrap/org-bootstrap-runner.mocks.d.ts +24 -0
- package/dist/features/org-bootstrap/org-bootstrap-runner.mocks.d.ts.map +1 -0
- package/dist/features/org-bootstrap/org-bootstrap-runner.mocks.js +39 -0
- package/dist/features/org-bootstrap/org-bootstrap.manifest.d.ts +19 -0
- package/dist/features/org-bootstrap/org-bootstrap.manifest.d.ts.map +1 -0
- package/dist/features/org-bootstrap/org-bootstrap.manifest.js +30 -0
- package/dist/features/org-bootstrap/org-bootstrap.tokens.d.ts +8 -0
- package/dist/features/org-bootstrap/org-bootstrap.tokens.d.ts.map +1 -0
- package/dist/features/org-bootstrap/org-bootstrap.tokens.js +10 -0
- package/dist/features/org-bootstrap/org-names.d.ts +12 -0
- package/dist/features/org-bootstrap/org-names.d.ts.map +1 -0
- package/dist/features/org-bootstrap/org-names.js +26 -0
- package/dist/features/org-bootstrap/org-state.reader.d.ts +15 -0
- package/dist/features/org-bootstrap/org-state.reader.d.ts.map +1 -0
- package/dist/features/org-bootstrap/org-state.reader.js +225 -0
- package/dist/features/org-bootstrap/root-password-source.d.ts +9 -0
- package/dist/features/org-bootstrap/root-password-source.d.ts.map +1 -0
- package/dist/features/org-bootstrap/root-password-source.js +57 -0
- package/dist/features/org-bootstrap/run-org-bootstrap.d.ts +42 -0
- package/dist/features/org-bootstrap/run-org-bootstrap.d.ts.map +1 -0
- package/dist/features/org-bootstrap/run-org-bootstrap.js +688 -0
- package/dist/features/org-bootstrap/set-password-links.d.ts +30 -0
- package/dist/features/org-bootstrap/set-password-links.d.ts.map +1 -0
- package/dist/features/org-bootstrap/set-password-links.js +55 -0
- package/dist/features/org-structure/org-catalog.service.d.ts +13 -0
- package/dist/features/org-structure/org-catalog.service.d.ts.map +1 -1
- package/dist/features/org-structure/org-catalog.service.js +40 -0
- package/dist/features/role/role-owner-transition.d.ts +17 -0
- package/dist/features/role/role-owner-transition.d.ts.map +1 -0
- package/dist/features/role/role-owner-transition.js +23 -0
- package/dist/features/role/role.controller.d.ts +1 -1
- package/dist/features/role/role.controller.d.ts.map +1 -1
- package/dist/features/role/role.controller.js +6 -4
- package/dist/features/role/role.service.d.ts +8 -2
- package/dist/features/role/role.service.d.ts.map +1 -1
- package/dist/features/role/role.service.js +13 -3
- package/dist/features/user/credential-provisioner.token.d.ts +8 -0
- package/dist/features/user/credential-provisioner.token.d.ts.map +1 -0
- package/dist/features/user/credential-provisioner.token.js +10 -0
- package/dist/features/user/user.controller.d.ts +1 -3
- package/dist/features/user/user.controller.d.ts.map +1 -1
- package/dist/features/user/user.controller.js +2 -2
- package/dist/features/user/user.service.d.ts +28 -5
- package/dist/features/user/user.service.d.ts.map +1 -1
- package/dist/features/user/user.service.js +45 -12
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/internal.d.ts +2 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +3 -1
- package/dist/role.object.d.ts +1 -0
- package/dist/role.object.d.ts.map +1 -1
- package/dist/role.object.js +8 -0
- package/migrations/1710000000101-AddRoleOwnerCompany.cjs +98 -0
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Pool } from 'mariadb';
|
|
2
2
|
import type { Principal } from '@astram/core';
|
|
3
|
-
import type
|
|
3
|
+
import { type AuditPort } from '@astram/engine';
|
|
4
4
|
import { PermissionVersionBumper } from './permission-version.bumper.js';
|
|
5
5
|
import { AccessBundleStore } from './access-bundle-store.js';
|
|
6
6
|
import { GrantCeiling } from './grant-ceiling.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grant-admin.service.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-admin.service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAc,SAAS,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"grant-admin.service.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-admin.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.GrantAdminService = exports.BadKeyError = void 0;
|
|
4
4
|
const core_1 = require("@astram/core");
|
|
5
|
+
const engine_1 = require("@astram/engine");
|
|
5
6
|
const access_bundle_store_js_1 = require("./access-bundle-store.js");
|
|
6
7
|
const db_permission_facts_adapter_js_1 = require("./db-permission-facts.adapter.js");
|
|
7
8
|
const grant_ceiling_js_1 = require("./grant-ceiling.js");
|
|
@@ -216,6 +217,12 @@ class GrantAdminService {
|
|
|
216
217
|
return [labelOf(rowTenant)];
|
|
217
218
|
return actor.isRoot === true ? tenants.map(labelOf) : [];
|
|
218
219
|
};
|
|
220
|
+
// G-009 R6: a grantor bound to a declared operation (org bootstrap) is attributed in the row;
|
|
221
|
+
// an unbound grantor adds nothing, so today's attributes stay byte-identical.
|
|
222
|
+
const op = (0, engine_1.operationOf)(actor);
|
|
223
|
+
const operationAttributes = !op
|
|
224
|
+
? {}
|
|
225
|
+
: { operation: op.operationKey, step: op.stepKey, onBehalfOf: op.onBehalfOf };
|
|
219
226
|
const occurredAtMs = Date.now();
|
|
220
227
|
for (const tenantId of new Set([actor.tenantId, ...tenants])) {
|
|
221
228
|
const tenantNames = tenantsFor(tenantId);
|
|
@@ -231,6 +238,7 @@ class GrantAdminService {
|
|
|
231
238
|
keys: [...keys],
|
|
232
239
|
tenants: tenantNames,
|
|
233
240
|
...extra,
|
|
241
|
+
...operationAttributes,
|
|
234
242
|
},
|
|
235
243
|
}, query);
|
|
236
244
|
}
|
|
@@ -20,6 +20,14 @@ import { type IdentitySchemaNames } from '../../schema.js';
|
|
|
20
20
|
export declare class GrantCeilingError extends Error {
|
|
21
21
|
constructor(key: string, message?: string);
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* The org-bootstrap exception (G-009 AC-7): `operationRoleIds` is the allow-set the runner computed
|
|
25
|
+
* from the file's roles for the company. It relaxes a role check ONLY for a principal bound (engine
|
|
26
|
+
* WeakMap binding, unforgeable) to `shield:org-bootstrap`, and only when EVERY id of the call is in the set.
|
|
27
|
+
*/
|
|
28
|
+
export interface OperationRoleOptions {
|
|
29
|
+
readonly operationRoleIds?: readonly string[];
|
|
30
|
+
}
|
|
23
31
|
/** The actor's reachable subtree: the session's cached closure, else the self node (non-widening). */
|
|
24
32
|
export declare function subtreeOf(actor: Principal): readonly string[];
|
|
25
33
|
/**
|
|
@@ -89,7 +97,7 @@ export declare class GrantCeiling {
|
|
|
89
97
|
* A role holding nothing assigns nothing and passes; a role key the registry does not know
|
|
90
98
|
* counts as not held (Phase 8 #4). Root bypasses. Store outage → 503.
|
|
91
99
|
*/
|
|
92
|
-
assertCanAssignRoles(actor: Principal, roleIds: readonly string[]): Promise<void>;
|
|
100
|
+
assertCanAssignRoles(actor: Principal, roleIds: readonly string[], opts?: OperationRoleOptions): Promise<void>;
|
|
93
101
|
/**
|
|
94
102
|
* The first of a TARGET's raw keys (a role's `role_permission` rows) the actor does not cover,
|
|
95
103
|
* or `undefined` when all are covered. Phase 8 #4: judged per RAW key with `coversKey`, so a key
|
|
@@ -166,12 +174,12 @@ export declare class GrantCeiling {
|
|
|
166
174
|
* role not bound in the subtree is "not found" for the actor (no existence leak); a role also
|
|
167
175
|
* used outside (a shared role) is root-only. Root bypasses.
|
|
168
176
|
*/
|
|
169
|
-
assertRoleAdministrable(actor: Principal, roleUuid: string): Promise<void>;
|
|
177
|
+
assertRoleAdministrable(actor: Principal, roleUuid: string, opts?: OperationRoleOptions): Promise<void>;
|
|
170
178
|
/**
|
|
171
179
|
* Assert the actor may POST someone to the role (#11): the role is bound within the actor's
|
|
172
180
|
* subtree or not bound at all — never a role also used outside it (a shared role's assignment
|
|
173
181
|
* would widen another tenant's role). Pair with {@link assertCanAssignRoles}. Root bypasses.
|
|
174
182
|
*/
|
|
175
|
-
assertRoleAssignable(actor: Principal, roleUuid: string): Promise<void>;
|
|
183
|
+
assertRoleAssignable(actor: Principal, roleUuid: string, opts?: OperationRoleOptions): Promise<void>;
|
|
176
184
|
}
|
|
177
185
|
//# sourceMappingURL=grant-ceiling.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grant-ceiling.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-ceiling.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"grant-ceiling.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-ceiling.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,8 @@ exports.GrantCeiling = exports.GrantCeilingError = void 0;
|
|
|
4
4
|
exports.subtreeOf = subtreeOf;
|
|
5
5
|
exports.coversKey = coversKey;
|
|
6
6
|
const core_1 = require("@astram/core");
|
|
7
|
+
const engine_1 = require("@astram/engine");
|
|
8
|
+
const org_bootstrap_manifest_js_1 = require("../org-bootstrap/org-bootstrap.manifest.js");
|
|
7
9
|
const http_js_1 = require("../shared/http.js");
|
|
8
10
|
const db_permission_facts_adapter_js_1 = require("./db-permission-facts.adapter.js");
|
|
9
11
|
const role_binding_js_1 = require("./role-binding.js");
|
|
@@ -18,6 +20,15 @@ class GrantCeilingError extends Error {
|
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
22
|
exports.GrantCeilingError = GrantCeilingError;
|
|
23
|
+
function operationCovers(actor, roleIds, opts) {
|
|
24
|
+
const allowed = opts?.operationRoleIds;
|
|
25
|
+
if (allowed === undefined || allowed.length === 0 || roleIds.length === 0)
|
|
26
|
+
return false;
|
|
27
|
+
if ((0, engine_1.operationOf)(actor)?.operationKey !== org_bootstrap_manifest_js_1.ORG_BOOTSTRAP_OPERATION)
|
|
28
|
+
return false;
|
|
29
|
+
const set = new Set(allowed);
|
|
30
|
+
return roleIds.every((id) => set.has(id));
|
|
31
|
+
}
|
|
21
32
|
/** The actor's reachable subtree: the session's cached closure, else the self node (non-widening). */
|
|
22
33
|
function subtreeOf(actor) {
|
|
23
34
|
return actor.scopeSubtreeNodeIds?.length ? actor.scopeSubtreeNodeIds : [actor.tenantId];
|
|
@@ -160,9 +171,11 @@ class GrantCeiling {
|
|
|
160
171
|
* A role holding nothing assigns nothing and passes; a role key the registry does not know
|
|
161
172
|
* counts as not held (Phase 8 #4). Root bypasses. Store outage → 503.
|
|
162
173
|
*/
|
|
163
|
-
async assertCanAssignRoles(actor, roleIds) {
|
|
174
|
+
async assertCanAssignRoles(actor, roleIds, opts) {
|
|
164
175
|
if (actor.isRoot === true)
|
|
165
176
|
return;
|
|
177
|
+
if (operationCovers(actor, roleIds, opts))
|
|
178
|
+
return;
|
|
166
179
|
const ids = [...new Set(roleIds)];
|
|
167
180
|
if (ids.length === 0)
|
|
168
181
|
return;
|
|
@@ -372,9 +385,11 @@ class GrantCeiling {
|
|
|
372
385
|
* role not bound in the subtree is "not found" for the actor (no existence leak); a role also
|
|
373
386
|
* used outside (a shared role) is root-only. Root bypasses.
|
|
374
387
|
*/
|
|
375
|
-
async assertRoleAdministrable(actor, roleUuid) {
|
|
388
|
+
async assertRoleAdministrable(actor, roleUuid, opts) {
|
|
376
389
|
if (actor.isRoot === true)
|
|
377
390
|
return;
|
|
391
|
+
if (operationCovers(actor, [roleUuid], opts))
|
|
392
|
+
return;
|
|
378
393
|
GrantCeiling.checkAdministrable(await this.roleBinding(actor, roleUuid));
|
|
379
394
|
}
|
|
380
395
|
/**
|
|
@@ -382,9 +397,11 @@ class GrantCeiling {
|
|
|
382
397
|
* subtree or not bound at all — never a role also used outside it (a shared role's assignment
|
|
383
398
|
* would widen another tenant's role). Pair with {@link assertCanAssignRoles}. Root bypasses.
|
|
384
399
|
*/
|
|
385
|
-
async assertRoleAssignable(actor, roleUuid) {
|
|
400
|
+
async assertRoleAssignable(actor, roleUuid, opts) {
|
|
386
401
|
if (actor.isRoot === true)
|
|
387
402
|
return;
|
|
403
|
+
if (operationCovers(actor, [roleUuid], opts))
|
|
404
|
+
return;
|
|
388
405
|
GrantCeiling.checkAssignable(await this.roleBinding(actor, roleUuid));
|
|
389
406
|
}
|
|
390
407
|
}
|
|
@@ -41,7 +41,7 @@ export declare function createIdentityAuthzPool(schemas?: FrameworkSchemas): Poo
|
|
|
41
41
|
* - `registry` — bridged from `identityObjects` PLUS the consumer's `extraObjects` (their
|
|
42
42
|
* `<module>:<type>:<action>` keys project into a principal's grants at gate 5; without them a
|
|
43
43
|
* consumer type is `resource_not_registered`),
|
|
44
|
-
* - `operationManifest` —
|
|
44
|
+
* - `operationManifest` — the `shield:org-bootstrap` steps (G-009); no other delegated operation.
|
|
45
45
|
*/
|
|
46
46
|
export declare function buildRecordAuthorizationInputs(pool: Pool, extraObjects?: readonly ObjectTypeInput[],
|
|
47
47
|
/** Resolved framework names for the facts adapter (absent ⇒ from the namespace, once). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record-authorization.d.ts","sourceRoot":"","sources":["../../../src/features/authz/record-authorization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAgB,EAAE,KAAK,IAAI,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAIL,KAAK,yBAAyB,EAC/B,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"record-authorization.d.ts","sourceRoot":"","sources":["../../../src/features/authz/record-authorization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAgB,EAAE,KAAK,IAAI,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAIL,KAAK,yBAAyB,EAC/B,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAKtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE3D;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMxF;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE3E;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAaxE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,IAAI,EACV,YAAY,GAAE,SAAS,eAAe,EAAO;AAC7C,0FAA0F;AAC1F,OAAO,CAAC,EAAE,mBAAmB,GAC5B,yBAAyB,CAS3B"}
|
|
@@ -22,6 +22,7 @@ exports.buildRecordAuthorizationInputs = buildRecordAuthorizationInputs;
|
|
|
22
22
|
*/
|
|
23
23
|
const mariadb_1 = __importDefault(require("mariadb"));
|
|
24
24
|
const guard_1 = require("@astram/guard");
|
|
25
|
+
const org_bootstrap_manifest_js_1 = require("../org-bootstrap/org-bootstrap.manifest.js");
|
|
25
26
|
const objects_js_1 = require("../../objects.js");
|
|
26
27
|
const db_permission_facts_adapter_js_1 = require("./db-permission-facts.adapter.js");
|
|
27
28
|
const session_facts_reader_js_1 = require("./session-facts.reader.js");
|
|
@@ -73,7 +74,7 @@ function createIdentityAuthzPool(schemas) {
|
|
|
73
74
|
* - `registry` — bridged from `identityObjects` PLUS the consumer's `extraObjects` (their
|
|
74
75
|
* `<module>:<type>:<action>` keys project into a principal's grants at gate 5; without them a
|
|
75
76
|
* consumer type is `resource_not_registered`),
|
|
76
|
-
* - `operationManifest` —
|
|
77
|
+
* - `operationManifest` — the `shield:org-bootstrap` steps (G-009); no other delegated operation.
|
|
77
78
|
*/
|
|
78
79
|
function buildRecordAuthorizationInputs(pool, extraObjects = [],
|
|
79
80
|
/** Resolved framework names for the facts adapter (absent ⇒ from the namespace, once). */
|
|
@@ -84,6 +85,6 @@ schemas) {
|
|
|
84
85
|
permissionFacts: new db_permission_facts_adapter_js_1.DbPermissionFactsAdapter(pool, registry, undefined, schemas),
|
|
85
86
|
sessionFacts: new session_facts_reader_js_1.AlsSessionFactsReader(),
|
|
86
87
|
registry,
|
|
87
|
-
operationManifest:
|
|
88
|
+
operationManifest: org_bootstrap_manifest_js_1.ORG_BOOTSTRAP_MANIFEST,
|
|
88
89
|
};
|
|
89
90
|
}
|
|
@@ -115,6 +115,12 @@ export declare class CompanyHierarchyService {
|
|
|
115
115
|
* Every change is audited on this transaction (names; ids only in structured attributes).
|
|
116
116
|
*/
|
|
117
117
|
private retireDependents;
|
|
118
|
+
/**
|
|
119
|
+
* Step 5 of {@link retireDependents} (G-009 T14, SPEC §5 v4): the roles the company owns become
|
|
120
|
+
* shared. Each owner is cleared through the audited `transitionRoleOwner` seam on the delete's
|
|
121
|
+
* connection (the FK's SET NULL would do it silently, without an audit row).
|
|
122
|
+
*/
|
|
123
|
+
private clearRoleOwners;
|
|
118
124
|
/** Step 4 of {@link retireDependents}: revoke and audit the grants naming the company. */
|
|
119
125
|
private revokeGrantsNaming;
|
|
120
126
|
/** Lock the company, then require dominance over each of its members (see `remove`). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"company-hierarchy.service.d.ts","sourceRoot":"","sources":["../../../src/features/company/company-hierarchy.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAAE,mBAAmB,EAAkB,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"company-hierarchy.service.d.ts","sourceRoot":"","sources":["../../../src/features/company/company-hierarchy.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAAE,mBAAmB,EAAkB,MAAM,mCAAmC,CAAC;AASxF,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,8BAA8B,CAC5B,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,aAAa,CAAC;QACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;QAChD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;KACrC,CAAC,GACD,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAqCD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAElD;AAED,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,2FAA2F;IAC3F,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,mFAAmF;IACnF,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAPP,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,mBAAmB;IAC7C,2FAA2F;IAC1E,OAAO,EAAE,eAAe,EACxB,KAAK,CAAC,EAAE,SAAS,YAAA,EACjB,GAAG,GAAE,MAAM,MAAiB;IAC7C,mFAAmF;IAClE,OAAO,GAAE,mBAA+C;IAG3E,yDAAyD;IACzD,OAAO,CAAC,OAAO;IAIf;;;;;OAKG;YACW,UAAU;IAQxB,uDAAuD;YACzC,KAAK;YAUL,WAAW;IAqBzB,iGAAiG;IAC3F,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAoD3F;;;;;;;;;;;;;;;;;OAiBG;IACG,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB/D;;;;;;;;;;;;;;;;;;;;;;;OAuBG;YACW,gBAAgB;IAmF9B;;;;OAIG;YACW,eAAe;IA6B7B,0FAA0F;YAC5E,kBAAkB;IAmDhC,wFAAwF;YAC1E,sBAAsB;IAgCpC,wFAAwF;IAClF,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGhD"}
|
|
@@ -6,6 +6,7 @@ const http_js_1 = require("../shared/http.js");
|
|
|
6
6
|
const grant_ceiling_js_1 = require("../authz/grant-ceiling.js");
|
|
7
7
|
const lock_service_accounts_js_1 = require("../service-account/lock-service-accounts.js");
|
|
8
8
|
const identity_errors_js_1 = require("../shared/identity-errors.js");
|
|
9
|
+
const role_owner_transition_js_1 = require("../role/role-owner-transition.js");
|
|
9
10
|
const schema_js_1 = require("../../schema.js");
|
|
10
11
|
/** A JSON-array column (the driver returns an array OR a string) → its string items; else []. */
|
|
11
12
|
function jsonIdArray(v) {
|
|
@@ -233,8 +234,26 @@ class CompanyHierarchyService {
|
|
|
233
234
|
});
|
|
234
235
|
}
|
|
235
236
|
await this.revokeGrantsNaming(conn, actor, nodeUuid, companyName);
|
|
237
|
+
await this.clearRoleOwners(conn, actor, nodeUuid, companyName);
|
|
236
238
|
return [...touched];
|
|
237
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Step 5 of {@link retireDependents} (G-009 T14, SPEC §5 v4): the roles the company owns become
|
|
242
|
+
* shared. Each owner is cleared through the audited `transitionRoleOwner` seam on the delete's
|
|
243
|
+
* connection (the FK's SET NULL would do it silently, without an audit row).
|
|
244
|
+
*/
|
|
245
|
+
async clearRoleOwners(conn, actor, nodeUuid, companyName) {
|
|
246
|
+
const owned = (await conn.query(`SELECT r_uuid, r_name FROM \`${this.schemas.app}\`.\`roles\` WHERE r_owner_company_uuid = ? ORDER BY r_uuid FOR UPDATE`, [nodeUuid]));
|
|
247
|
+
for (const role of owned) {
|
|
248
|
+
await (0, role_owner_transition_js_1.transitionRoleOwner)((sql, params) => conn.query(sql, params), role.r_uuid, nodeUuid, null, (action, attributes, query) => this.recordAudit({ query }, actor, action, {
|
|
249
|
+
eventType: 'role',
|
|
250
|
+
...attributes,
|
|
251
|
+
role: role.r_name,
|
|
252
|
+
company: companyName,
|
|
253
|
+
reason: 'company_deleted',
|
|
254
|
+
}), this.schemas);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
238
257
|
/** Step 4 of {@link retireDependents}: revoke and audit the grants naming the company. */
|
|
239
258
|
async revokeGrantsNaming(conn, actor, nodeUuid, companyName) {
|
|
240
259
|
const grants = (await conn.query(`SELECT ctg_uuid, ctg_owning_tenant_uuid, ctg_grantee_tenant_uuid, ctg_grantee_kind,
|
|
@@ -1 +1 @@
|
|
|
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,EAGL,KAAK,gBAAgB,EAEtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAYL,KAAK,cAAc,EAGnB,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;
|
|
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,EAGL,KAAK,gBAAgB,EAEtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAYL,KAAK,cAAc,EAGnB,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AA+GxB;;;;;;GAMG;AACH,wBAAgB,0CAA0C,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAUnF;AAKD;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GACzC,IAAI,CAwBN;AAKD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,gBAAgB,EAAE,MAAM,EACxB,WAAW,EAAE;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GAC/E,IAAI,CAQN;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,gBAAgB,EACzB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,cAAc,CAqBhB;AAED;;;;;;;;;;GAUG;AACH,qBACa,qBAAqB;IAChC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CAAC,OAAO,CAAC,EACb,OAAO,EACP,YAAiB,EACjB,OAAO,EAAE,YAAY,GACtB,GAAE;QACD,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5B,YAAY,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;QAC1C,OAAO,CAAC,EAAE,gBAAgB,CAAC;KACvB,GAAG,aAAa;CAqkBvB"}
|
|
@@ -17,6 +17,7 @@ const core_1 = require("@nestjs/core");
|
|
|
17
17
|
const core_2 = require("@astram/core");
|
|
18
18
|
const engine_1 = require("@astram/engine");
|
|
19
19
|
const guard_1 = require("@astram/guard");
|
|
20
|
+
const internal_1 = require("@astram/guard/internal");
|
|
20
21
|
const objects_js_1 = require("../objects.js");
|
|
21
22
|
const index_js_1 = require("./access/index.js");
|
|
22
23
|
const company_service_js_1 = require("./company/company.service.js");
|
|
@@ -26,7 +27,9 @@ const role_service_js_1 = require("./role/role.service.js");
|
|
|
26
27
|
const role_controller_js_1 = require("./role/role.controller.js");
|
|
27
28
|
const user_service_js_1 = require("./user/user.service.js");
|
|
28
29
|
const email_address_changer_token_js_1 = require("./user/email-address-changer.token.js");
|
|
30
|
+
const credential_provisioner_token_js_1 = require("./user/credential-provisioner.token.js");
|
|
29
31
|
const framework_schemas_token_js_1 = require("./framework-schemas.token.js");
|
|
32
|
+
const org_bootstrap_tokens_js_1 = require("./org-bootstrap/org-bootstrap.tokens.js");
|
|
30
33
|
const user_controller_js_1 = require("./user/user.controller.js");
|
|
31
34
|
const membership_controller_js_1 = require("./membership/membership.controller.js");
|
|
32
35
|
const membership_service_js_1 = require("./membership/membership.service.js");
|
|
@@ -203,6 +206,8 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
203
206
|
* differ from guard's `FRAMEWORK_SCHEMAS`. Never read per request.
|
|
204
207
|
*/
|
|
205
208
|
static forRoot({ rootIds, extraObjects = [], schemas: givenSchemas, } = {}) {
|
|
209
|
+
// G-009 N4: a reserved operation principal is never a root, whatever variable `rootIds` came from.
|
|
210
|
+
(0, guard_1.assertNoReservedRootId)(rootIds);
|
|
206
211
|
// The framework database names, resolved ONCE (boot) for every SQL path built below.
|
|
207
212
|
// Caller-supplied names are checked before any SQL is built (fail closed, Shield #1).
|
|
208
213
|
const schemas = givenSchemas !== undefined
|
|
@@ -585,9 +590,17 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
585
590
|
provide: email_address_changer_token_js_1.IDENTITY_EMAIL_ADDRESS_CHANGER,
|
|
586
591
|
useValue: new guard_1.MariaDbEmailAddressChanger(authzPool, Date.now, schemas),
|
|
587
592
|
},
|
|
593
|
+
// G-009 T7 (OD-6): the locked-credential writer UserService runs on the create transaction.
|
|
594
|
+
{
|
|
595
|
+
provide: credential_provisioner_token_js_1.IDENTITY_CREDENTIAL_PROVISIONER,
|
|
596
|
+
useValue: new internal_1.MariaDbCredentialProvisioner(authzPool, Date.now, schemas),
|
|
597
|
+
},
|
|
588
598
|
// The same resolved names for identity services Nest constructs (UserService's
|
|
589
599
|
// email-change audit sink), never a second env read.
|
|
590
600
|
{ provide: framework_schemas_token_js_1.IDENTITY_FRAMEWORK_SCHEMAS, useValue: schemas },
|
|
601
|
+
// G-009 T10: the ONE shared authz pool, for the org bootstrap runner (named lock, state reads,
|
|
602
|
+
// claim and root-flag writes). Not exported; the runner reads it with `app.get({ strict: false })`.
|
|
603
|
+
{ provide: org_bootstrap_tokens_js_1.IDENTITY_AUTHZ_POOL, useValue: authzPool },
|
|
591
604
|
// ADR-0035 (GAPS #6): attach the guard's Redis permission-version cache to the ONE shared
|
|
592
605
|
// bumper, so every committed bump (postings, grants, hierarchy) is published to Redis,
|
|
593
606
|
// where gate 5 reads it. Provided by the auth surface under `auth: true` (through the
|
|
@@ -41,6 +41,15 @@ export declare class MembershipService {
|
|
|
41
41
|
* swallowed here). `createdBy` is stamped server-side (actorId-trusted).
|
|
42
42
|
*/
|
|
43
43
|
addMembership(actor: Principal, userUuid: string, companyUuid: string): Promise<RecordId>;
|
|
44
|
+
/**
|
|
45
|
+
* Add an ACTIVE membership for `(userUuid, companyUuid)` in one create (G-009 R4): a brand-new
|
|
46
|
+
* membership has no other active card to displace, so no create-then-activate pair. A
|
|
47
|
+
* membership for that pair already existing is an `IdentityConflictError`, from the pre-check
|
|
48
|
+
* and from the `(cm_user_uuid, cm_company_uuid)` unique index (the pre-check read is SQL-scoped
|
|
49
|
+
* and can miss a row for an operation-bound principal); the driver message is never echoed.
|
|
50
|
+
* Bumps the company's tenant. The reach checks of `addMembership` apply unchanged.
|
|
51
|
+
*/
|
|
52
|
+
addActiveMembership(actor: Principal, userUuid: string, companyUuid: string): Promise<RecordId>;
|
|
44
53
|
/** List a user's membership cards (tenant-scoped by the pipe). */
|
|
45
54
|
listMemberships(userUuid: string): Promise<readonly RecordView[]>;
|
|
46
55
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"membership.service.d.ts","sourceRoot":"","sources":["../../../src/features/membership/membership.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAChF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE1F,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"membership.service.d.ts","sourceRoot":"","sources":["../../../src/features/membership/membership.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAChF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE1F,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AASzD,qBACa,iBAAiB;IAE1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAGxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAQxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;gBAXZ,OAAO,EAAE,cAAc,EAGvB,OAAO,EAAE,YAAY,EAQrB,WAAW,CAAC,GAAE,kBAAkB,GAAG,IAAI,aAAA;IAG1D;;;;OAIG;YACW,WAAW;IAQzB;;;;;;OAMG;YACW,WAAW;IAYzB;;;;;;OAMG;IACG,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAkB/F;;;;;;;OAOG;IACG,mBAAmB,CACvB,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,QAAQ,CAAC;IAwBpB,kEAAkE;IAC5D,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC;IAKvE;;;;;;;;;;;;;;;;;OAiBG;IACG,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsC1F;;;;OAIG;IACG,gBAAgB,CACpB,KAAK,EAAE,SAAS,EAChB,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GACzB,OAAO,CAAC,IAAI,CAAC;IAchB;;;OAGG;IACG,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAYhF"}
|
|
@@ -30,6 +30,13 @@ const engine_1 = require("@astram/engine");
|
|
|
30
30
|
const posting_version_bump_js_1 = require("./posting-version-bump.js");
|
|
31
31
|
const identity_errors_js_1 = require("../shared/identity-errors.js");
|
|
32
32
|
const grant_ceiling_js_1 = require("../authz/grant-ceiling.js");
|
|
33
|
+
/** A driver duplicate-key error (errno 1062), recognised by errno/code only, never its message. */
|
|
34
|
+
function isDuplicateKey(err) {
|
|
35
|
+
if (typeof err !== 'object' || err === null)
|
|
36
|
+
return false;
|
|
37
|
+
const e = err;
|
|
38
|
+
return e.errno === 1062 || e.code === 'ER_DUP_ENTRY';
|
|
39
|
+
}
|
|
33
40
|
let MembershipService = class MembershipService {
|
|
34
41
|
records;
|
|
35
42
|
ceiling;
|
|
@@ -101,6 +108,39 @@ let MembershipService = class MembershipService {
|
|
|
101
108
|
fields: { userUuid, companyUuid, isActive: false, status: 'active' },
|
|
102
109
|
});
|
|
103
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Add an ACTIVE membership for `(userUuid, companyUuid)` in one create (G-009 R4): a brand-new
|
|
113
|
+
* membership has no other active card to displace, so no create-then-activate pair. A
|
|
114
|
+
* membership for that pair already existing is an `IdentityConflictError`, from the pre-check
|
|
115
|
+
* and from the `(cm_user_uuid, cm_company_uuid)` unique index (the pre-check read is SQL-scoped
|
|
116
|
+
* and can miss a row for an operation-bound principal); the driver message is never echoed.
|
|
117
|
+
* Bumps the company's tenant. The reach checks of `addMembership` apply unchanged.
|
|
118
|
+
*/
|
|
119
|
+
async addActiveMembership(actor, userUuid, companyUuid) {
|
|
120
|
+
if (actor.isRoot !== true) {
|
|
121
|
+
const user = await this.records.get('user', userUuid);
|
|
122
|
+
if (user === null)
|
|
123
|
+
throw new identity_errors_js_1.IdentityNotFoundError('User not found.');
|
|
124
|
+
}
|
|
125
|
+
const existing = await this.records.list('company_membership', { where: { userUuid } });
|
|
126
|
+
if (existing.rows.some((m) => m.fields?.companyUuid === companyUuid)) {
|
|
127
|
+
throw new identity_errors_js_1.IdentityConflictError('That user already has a membership in this company.');
|
|
128
|
+
}
|
|
129
|
+
let id;
|
|
130
|
+
try {
|
|
131
|
+
id = await this.records.create('company_membership', {
|
|
132
|
+
fields: { userUuid, companyUuid, isActive: true, status: 'active' },
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
if (isDuplicateKey(err)) {
|
|
137
|
+
throw new identity_errors_js_1.IdentityConflictError('That user already has a membership in this company.');
|
|
138
|
+
}
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
await this.bumpTenants([companyUuid]);
|
|
142
|
+
return id;
|
|
143
|
+
}
|
|
104
144
|
/** List a user's membership cards (tenant-scoped by the pipe). */
|
|
105
145
|
async listMemberships(userUuid) {
|
|
106
146
|
const page = await this.records.list('company_membership', { where: { userUuid } });
|
|
@@ -53,5 +53,23 @@ export declare class PostingService {
|
|
|
53
53
|
* runs BEFORE any write; the writes run in one transaction (see the file header).
|
|
54
54
|
*/
|
|
55
55
|
replacePostings(actor: Principal, membershipUuid: string, desired: readonly PostingInput[]): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Add postings to a membership WITHOUT touching the rest of its set (G-009 R4/R5): only tuples
|
|
58
|
+
* (role|department|branch) not already present are created; nothing is updated or deleted. The
|
|
59
|
+
* pre-checks, the in-transaction lock + re-check, the atomicity and the version bump are those
|
|
60
|
+
* of `replacePostings` (shared helpers). `opts.operationRoleIds` is forwarded to the ceiling
|
|
61
|
+
* calls (the org-bootstrap operation's declared roles). An inactive membership is refused.
|
|
62
|
+
*/
|
|
63
|
+
addPostings(actor: Principal, membershipUuid: string, toAdd: readonly PostingInput[], opts?: {
|
|
64
|
+
operationRoleIds?: ReadonlySet<string> | readonly string[];
|
|
65
|
+
}): Promise<void>;
|
|
66
|
+
private assertEveryRole;
|
|
67
|
+
/** The membership through the subtree read wall (404 when out of reach) and its company. */
|
|
68
|
+
private reachMembership;
|
|
69
|
+
/** Authority for ADDED postings (binding rule, ceiling, org units); returns the distinct roles. */
|
|
70
|
+
private assertMayAdd;
|
|
71
|
+
/** Lock each added role row on the transaction, then re-run the binding rule (review F2). */
|
|
72
|
+
private lockAndRecheck;
|
|
73
|
+
private bumpTenant;
|
|
56
74
|
}
|
|
57
75
|
//# sourceMappingURL=posting.service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"posting.service.d.ts","sourceRoot":"","sources":["../../../src/features/membership/posting.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC1F,OAAO,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"posting.service.d.ts","sourceRoot":"","sources":["../../../src/features/membership/posting.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC1F,OAAO,EAAE,YAAY,EAA6B,MAAM,2BAA2B,CAAC;AAOpF;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACvC,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CACzC;AA0ED,qBACa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAGxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAKxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;gBARZ,OAAO,EAAE,cAAc,EAGvB,OAAO,EAAE,YAAY,EAKrB,WAAW,CAAC,GAAE,kBAAkB,GAAG,IAAI,aAAA;IAG1D,gEAAgE;IAC1D,YAAY,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC;IAK1E;;;;;OAKG;IACG,eAAe,CACnB,KAAK,EAAE,SAAS,EAChB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,SAAS,YAAY,EAAE,GAC/B,OAAO,CAAC,IAAI,CAAC;IAyEhB;;;;;;OAMG;IACG,WAAW,CACf,KAAK,EAAE,SAAS,EAChB,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,IAAI,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,SAAS,MAAM,EAAE,CAAA;KAAE,GACpE,OAAO,CAAC,IAAI,CAAC;IA2ChB,OAAO,CAAC,eAAe;IAQvB,4FAA4F;YAC9E,eAAe;IAY7B,mGAAmG;YACrF,YAAY;IAqB1B,6FAA6F;YAC/E,cAAc;YAYd,UAAU;CAkBzB"}
|
|
@@ -134,18 +134,9 @@ let PostingService = class PostingService {
|
|
|
134
134
|
* runs BEFORE any write; the writes run in one transaction (see the file header).
|
|
135
135
|
*/
|
|
136
136
|
async replacePostings(actor, membershipUuid, desired) {
|
|
137
|
-
|
|
138
|
-
if (typeof d?.roleUuid !== 'string' || d.roleUuid.length === 0) {
|
|
139
|
-
throw new identity_errors_js_1.IdentityBadRequestError('Every assignment needs a role.');
|
|
140
|
-
}
|
|
141
|
-
}
|
|
137
|
+
this.assertEveryRole(desired);
|
|
142
138
|
// 1) Reach: the membership must be visible through the subtree read wall (else 404).
|
|
143
|
-
const membership = await this.
|
|
144
|
-
if (membership === null)
|
|
145
|
-
throw new identity_errors_js_1.IdentityNotFoundError('Membership not found.');
|
|
146
|
-
const tenantUuid = typeof membership.fields?.companyUuid === 'string' && membership.fields.companyUuid.length > 0
|
|
147
|
-
? membership.fields.companyUuid
|
|
148
|
-
: null;
|
|
139
|
+
const { membership, tenantUuid } = await this.reachMembership(membershipUuid);
|
|
149
140
|
const current = (await this.records.list('membership_posting', { where: { membershipUuid } }))
|
|
150
141
|
.rows;
|
|
151
142
|
const desiredKeys = new Set(desired.map(naturalKey));
|
|
@@ -160,18 +151,7 @@ let PostingService = class PostingService {
|
|
|
160
151
|
return true;
|
|
161
152
|
});
|
|
162
153
|
// 2) Authority for every ADDED role, before any write: binding rule, then the ceiling.
|
|
163
|
-
const addedRoles =
|
|
164
|
-
for (const roleUuid of addedRoles) {
|
|
165
|
-
await this.ceiling.assertRoleAssignable(actor, roleUuid);
|
|
166
|
-
}
|
|
167
|
-
await this.ceiling.assertCanAssignRoles(actor, addedRoles);
|
|
168
|
-
// Phase 9 #13b (review F4): an added posting's department/branch must be the membership
|
|
169
|
-
// company's own (junction rows), never another tenant's (400).
|
|
170
|
-
const present = (v) => typeof v === 'string' && v.length > 0;
|
|
171
|
-
await this.ceiling.assertOrgUnitsInCompany(tenantUuid, {
|
|
172
|
-
departmentUuids: added.map((d) => d.departmentUuid).filter(present),
|
|
173
|
-
branchUuids: added.map((d) => d.branchUuid).filter(present),
|
|
174
|
-
});
|
|
154
|
+
const addedRoles = await this.assertMayAdd(actor, tenantUuid, added, undefined);
|
|
175
155
|
// Removing a PEER's postings strips their authority: the actor must dominate the member
|
|
176
156
|
// (security review). Removing your OWN postings (self-demotion) stays allowed.
|
|
177
157
|
const memberUuid = typeof membership.fields?.userUuid === 'string' ? membership.fields.userUuid : '';
|
|
@@ -187,10 +167,7 @@ let PostingService = class PostingService {
|
|
|
187
167
|
// added role row on THIS transaction (grant administration locks the same row), then
|
|
188
168
|
// re-run the binding rule: a binding another tenant committed while we waited is now
|
|
189
169
|
// visible, and a role that became shared is refused before the INSERT (rolled back).
|
|
190
|
-
|
|
191
|
-
await tx.lockForUpdate('role', roleUuid);
|
|
192
|
-
await this.ceiling.assertRoleAssignable(actor, roleUuid);
|
|
193
|
-
}
|
|
170
|
+
await this.lockAndRecheck(tx, actor, addedRoles, undefined);
|
|
194
171
|
for (const d of added) {
|
|
195
172
|
await tx.create('membership_posting', { fields: { ...stripEmpty(d), membershipUuid } });
|
|
196
173
|
}
|
|
@@ -198,6 +175,87 @@ let PostingService = class PostingService {
|
|
|
198
175
|
await tx.delete('membership_posting', r.id);
|
|
199
176
|
}
|
|
200
177
|
});
|
|
178
|
+
await this.bumpTenant(tenantUuid);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Add postings to a membership WITHOUT touching the rest of its set (G-009 R4/R5): only tuples
|
|
182
|
+
* (role|department|branch) not already present are created; nothing is updated or deleted. The
|
|
183
|
+
* pre-checks, the in-transaction lock + re-check, the atomicity and the version bump are those
|
|
184
|
+
* of `replacePostings` (shared helpers). `opts.operationRoleIds` is forwarded to the ceiling
|
|
185
|
+
* calls (the org-bootstrap operation's declared roles). An inactive membership is refused.
|
|
186
|
+
*/
|
|
187
|
+
async addPostings(actor, membershipUuid, toAdd, opts) {
|
|
188
|
+
this.assertEveryRole(toAdd);
|
|
189
|
+
const { membership, tenantUuid } = await this.reachMembership(membershipUuid);
|
|
190
|
+
// TINYINT(1): the engine returns 1/0, not true/false (like `membership.controller.ts`).
|
|
191
|
+
if (!membership.fields?.isActive) {
|
|
192
|
+
throw new identity_errors_js_1.IdentityConflictError('That membership is not active.');
|
|
193
|
+
}
|
|
194
|
+
const ceilingOpts = opts?.operationRoleIds === undefined
|
|
195
|
+
? undefined
|
|
196
|
+
: { operationRoleIds: [...opts.operationRoleIds] };
|
|
197
|
+
const current = (await this.records.list('membership_posting', { where: { membershipUuid } }))
|
|
198
|
+
.rows;
|
|
199
|
+
const seen = new Set(current.map((r) => naturalKey(r.fields)));
|
|
200
|
+
const added = toAdd.filter((d) => {
|
|
201
|
+
const k = naturalKey(d);
|
|
202
|
+
if (seen.has(k))
|
|
203
|
+
return false;
|
|
204
|
+
seen.add(k);
|
|
205
|
+
return true;
|
|
206
|
+
});
|
|
207
|
+
if (added.length === 0)
|
|
208
|
+
return; // nothing missing: no write, no bump
|
|
209
|
+
const addedRoles = await this.assertMayAdd(actor, tenantUuid, added, ceilingOpts);
|
|
210
|
+
await this.records.transaction(async (tx) => {
|
|
211
|
+
await this.lockAndRecheck(tx, actor, addedRoles, ceilingOpts);
|
|
212
|
+
for (const d of added) {
|
|
213
|
+
await tx.create('membership_posting', { fields: { ...stripEmpty(d), membershipUuid } });
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
await this.bumpTenant(tenantUuid);
|
|
217
|
+
}
|
|
218
|
+
assertEveryRole(postings) {
|
|
219
|
+
for (const d of postings) {
|
|
220
|
+
if (typeof d?.roleUuid !== 'string' || d.roleUuid.length === 0) {
|
|
221
|
+
throw new identity_errors_js_1.IdentityBadRequestError('Every assignment needs a role.');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/** The membership through the subtree read wall (404 when out of reach) and its company. */
|
|
226
|
+
async reachMembership(membershipUuid) {
|
|
227
|
+
const membership = await this.records.get('company_membership', membershipUuid);
|
|
228
|
+
if (membership === null)
|
|
229
|
+
throw new identity_errors_js_1.IdentityNotFoundError('Membership not found.');
|
|
230
|
+
const tenantUuid = typeof membership.fields?.companyUuid === 'string' && membership.fields.companyUuid.length > 0
|
|
231
|
+
? membership.fields.companyUuid
|
|
232
|
+
: null;
|
|
233
|
+
return { membership, tenantUuid };
|
|
234
|
+
}
|
|
235
|
+
/** Authority for ADDED postings (binding rule, ceiling, org units); returns the distinct roles. */
|
|
236
|
+
async assertMayAdd(actor, tenantUuid, added, opts) {
|
|
237
|
+
const addedRoles = [...new Set(added.map((d) => d.roleUuid))];
|
|
238
|
+
for (const roleUuid of addedRoles) {
|
|
239
|
+
await this.ceiling.assertRoleAssignable(actor, roleUuid, opts);
|
|
240
|
+
}
|
|
241
|
+
await this.ceiling.assertCanAssignRoles(actor, addedRoles, opts);
|
|
242
|
+
// Phase 9 #13b (review F4): an added posting's department/branch must be the membership
|
|
243
|
+
// company's own (junction rows), never another tenant's (400).
|
|
244
|
+
const present = (v) => typeof v === 'string' && v.length > 0;
|
|
245
|
+
await this.ceiling.assertOrgUnitsInCompany(tenantUuid, {
|
|
246
|
+
departmentUuids: added.map((d) => d.departmentUuid).filter(present),
|
|
247
|
+
branchUuids: added.map((d) => d.branchUuid).filter(present),
|
|
248
|
+
});
|
|
249
|
+
return addedRoles;
|
|
250
|
+
}
|
|
251
|
+
/** Lock each added role row on the transaction, then re-run the binding rule (review F2). */
|
|
252
|
+
async lockAndRecheck(tx, actor, addedRoles, opts) {
|
|
253
|
+
for (const roleUuid of addedRoles) {
|
|
254
|
+
await tx.lockForUpdate('role', roleUuid);
|
|
255
|
+
await this.ceiling.assertRoleAssignable(actor, roleUuid, opts);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async bumpTenant(tenantUuid) {
|
|
201
259
|
// ADR-0011 STALENESS FIX: a posting add/remove changes the effective roles (→ coarse
|
|
202
260
|
// grants) of the membership's user in that company, so bump the tenant's permission
|
|
203
261
|
// version — every live session in that tenant then reloads its cached facts on the next
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PlanKind } from './org-bootstrap-plan.js';
|
|
2
|
+
export type OrgBootstrapErrorCode = 'operator_required' | 'plan_conflict' | 'root_password_missing' | 'root_password_literal' | 'root_password_weak' | 'root_password_collision' | 'link_base_url' | 'links_need_terminal' | 'invalid_link_ttl' | 'not_wired' | 'lock_not_acquired' | 'step_failed';
|
|
3
|
+
/** What a run wrote, counted per plan kind. */
|
|
4
|
+
export type OrgBootstrapWritten = Partial<Record<PlanKind, number>>;
|
|
5
|
+
/**
|
|
6
|
+
* A refusal or failure of the org bootstrap run. The message is fixed text plus names from the
|
|
7
|
+
* operator's own file; it never carries a password, a link or token, or a driver message.
|
|
8
|
+
*/
|
|
9
|
+
export declare class OrgBootstrapError extends Error {
|
|
10
|
+
readonly code: OrgBootstrapErrorCode;
|
|
11
|
+
/** `step_failed` only: the manifest step key that failed. */
|
|
12
|
+
readonly step?: string;
|
|
13
|
+
/** `step_failed` only: what the run had written before it stopped. */
|
|
14
|
+
readonly written?: OrgBootstrapWritten;
|
|
15
|
+
constructor(code: OrgBootstrapErrorCode, message: string, extra?: {
|
|
16
|
+
step?: string;
|
|
17
|
+
written?: OrgBootstrapWritten;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=org-bootstrap-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"org-bootstrap-error.d.ts","sourceRoot":"","sources":["../../../src/features/org-bootstrap/org-bootstrap-error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAExD,MAAM,MAAM,qBAAqB,GAC7B,mBAAmB,GACnB,eAAe,GACf,uBAAuB,GACvB,uBAAuB,GACvB,oBAAoB,GACpB,yBAAyB,GACzB,eAAe,GACf,qBAAqB,GACrB,kBAAkB,GAClB,WAAW,GACX,mBAAmB,GACnB,aAAa,CAAC;AAElB,+CAA+C;AAC/C,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;AAEpE;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,QAAQ,CAAC,OAAO,CAAC,EAAE,mBAAmB,CAAC;gBAGrC,IAAI,EAAE,qBAAqB,EAC3B,OAAO,EAAE,MAAM,EACf,KAAK,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,mBAAmB,CAAA;KAAO;CAQ/D"}
|