@astram/identity 0.13.0 → 0.15.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 +7 -1
- package/dist/features/identity-feature.module.d.ts.map +1 -1
- package/dist/features/identity-feature.module.js +46 -1
- 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.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 +61 -0
- package/dist/features/org-bootstrap/run-org-bootstrap.d.ts.map +1 -0
- package/dist/features/org-bootstrap/run-org-bootstrap.js +774 -0
- package/dist/features/org-bootstrap/set-password-links.d.ts +42 -0
- package/dist/features/org-bootstrap/set-password-links.d.ts.map +1 -0
- package/dist/features/org-bootstrap/set-password-links.js +76 -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/invite-message.d.ts +8 -0
- package/dist/features/user/invite-message.d.ts.map +1 -0
- package/dist/features/user/invite-message.js +23 -0
- package/dist/features/user/invite.config.d.ts +15 -0
- package/dist/features/user/invite.config.d.ts.map +1 -0
- package/dist/features/user/invite.config.js +55 -0
- package/dist/features/user/invite.service.d.ts +62 -0
- package/dist/features/user/invite.service.d.ts.map +1 -0
- package/dist/features/user/invite.service.js +254 -0
- package/dist/features/user/invite.tokens.d.ts +11 -0
- package/dist/features/user/invite.tokens.d.ts.map +1 -0
- package/dist/features/user/invite.tokens.js +13 -0
- package/dist/features/user/user.controller.d.ts +13 -6
- package/dist/features/user/user.controller.d.ts.map +1 -1
- package/dist/features/user/user.controller.js +30 -4
- package/dist/features/user/user.service.d.ts +51 -10
- package/dist/features/user/user.service.d.ts.map +1 -1
- package/dist/features/user/user.service.js +96 -27
- package/dist/index.d.ts +13 -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/dist/user.object.d.ts.map +1 -1
- package/dist/user.object.js +3 -0
- package/migrations/1710000000101-AddRoleOwnerCompany.cjs +98 -0
- package/package.json +4 -4
|
@@ -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,6 +1,7 @@
|
|
|
1
1
|
import { type DynamicModule } from '@nestjs/common';
|
|
2
2
|
import { type FrameworkSchemas } from '@astram/core';
|
|
3
3
|
import { type EngineDbConfig, type ObjectTypeInput } from '@astram/engine';
|
|
4
|
+
import { type InviteOptions } from './user/invite.config.js';
|
|
4
5
|
/**
|
|
5
6
|
* The boot check behind {@link IDENTITY_DECISION_AUTHORITY_CHECK}: with this module wired, the
|
|
6
7
|
* request path must decide with the authority guard built from this module's inputs. A missing
|
|
@@ -67,11 +68,16 @@ export declare class IdentityFeatureModule {
|
|
|
67
68
|
* throws). The one set is handed to the engine (registrations + its audit sink), the seven
|
|
68
69
|
* audit sinks, the audit reader and every identity store; a boot check refuses names that
|
|
69
70
|
* differ from guard's `FRAMEWORK_SCHEMAS`. Never read per request.
|
|
71
|
+
* @param invite administrator invite (G-010 R11, `POST /identity/users/:uuid/invite`).
|
|
72
|
+
* `returnLinkToAdmin: true` also returns the link once in the response (default false: the
|
|
73
|
+
* link is only emailed). `INVITE_LINK_TTL_HOURS` and `INVITE_RATE_PER_ADMIN` are read here,
|
|
74
|
+
* once; a bad value refuses boot.
|
|
70
75
|
*/
|
|
71
|
-
static forRoot({ rootIds, extraObjects, schemas: givenSchemas, }?: {
|
|
76
|
+
static forRoot({ rootIds, extraObjects, schemas: givenSchemas, invite, }?: {
|
|
72
77
|
rootIds?: readonly string[];
|
|
73
78
|
extraObjects?: readonly ObjectTypeInput[];
|
|
74
79
|
schemas?: FrameworkSchemas;
|
|
80
|
+
invite?: InviteOptions;
|
|
75
81
|
}): DynamicModule;
|
|
76
82
|
}
|
|
77
83
|
//# sourceMappingURL=identity-feature.module.d.ts.map
|
|
@@ -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;AAiDxB,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AA0E/E;;;;;;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;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,MAAM,CAAC,OAAO,CAAC,EACb,OAAO,EACP,YAAiB,EACjB,OAAO,EAAE,YAAY,EACrB,MAAM,GACP,GAAE;QACD,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5B,YAAY,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;QAC1C,OAAO,CAAC,EAAE,gBAAgB,CAAC;QAC3B,MAAM,CAAC,EAAE,aAAa,CAAC;KACnB,GAAG,aAAa;CA+lBvB"}
|
|
@@ -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,8 +27,13 @@ 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");
|
|
34
|
+
const invite_service_js_1 = require("./user/invite.service.js");
|
|
35
|
+
const invite_config_js_1 = require("./user/invite.config.js");
|
|
36
|
+
const invite_tokens_js_1 = require("./user/invite.tokens.js");
|
|
31
37
|
const membership_controller_js_1 = require("./membership/membership.controller.js");
|
|
32
38
|
const membership_service_js_1 = require("./membership/membership.service.js");
|
|
33
39
|
const posting_service_js_1 = require("./membership/posting.service.js");
|
|
@@ -201,8 +207,16 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
201
207
|
* throws). The one set is handed to the engine (registrations + its audit sink), the seven
|
|
202
208
|
* audit sinks, the audit reader and every identity store; a boot check refuses names that
|
|
203
209
|
* differ from guard's `FRAMEWORK_SCHEMAS`. Never read per request.
|
|
210
|
+
* @param invite administrator invite (G-010 R11, `POST /identity/users/:uuid/invite`).
|
|
211
|
+
* `returnLinkToAdmin: true` also returns the link once in the response (default false: the
|
|
212
|
+
* link is only emailed). `INVITE_LINK_TTL_HOURS` and `INVITE_RATE_PER_ADMIN` are read here,
|
|
213
|
+
* once; a bad value refuses boot.
|
|
204
214
|
*/
|
|
205
|
-
static forRoot({ rootIds, extraObjects = [], schemas: givenSchemas, } = {}) {
|
|
215
|
+
static forRoot({ rootIds, extraObjects = [], schemas: givenSchemas, invite, } = {}) {
|
|
216
|
+
// G-009 N4: a reserved operation principal is never a root, whatever variable `rootIds` came from.
|
|
217
|
+
(0, guard_1.assertNoReservedRootId)(rootIds);
|
|
218
|
+
// G-010 T9: invite settings, read ONCE (boot); a bad value refuses boot naming the variable.
|
|
219
|
+
const inviteConfig = (0, invite_config_js_1.loadInviteConfig)(invite, process.env);
|
|
206
220
|
// The framework database names, resolved ONCE (boot) for every SQL path built below.
|
|
207
221
|
// Caller-supplied names are checked before any SQL is built (fail closed, Shield #1).
|
|
208
222
|
const schemas = givenSchemas !== undefined
|
|
@@ -585,9 +599,17 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
585
599
|
provide: email_address_changer_token_js_1.IDENTITY_EMAIL_ADDRESS_CHANGER,
|
|
586
600
|
useValue: new guard_1.MariaDbEmailAddressChanger(authzPool, Date.now, schemas),
|
|
587
601
|
},
|
|
602
|
+
// G-009 T7 (OD-6): the locked-credential writer UserService runs on the create transaction.
|
|
603
|
+
{
|
|
604
|
+
provide: credential_provisioner_token_js_1.IDENTITY_CREDENTIAL_PROVISIONER,
|
|
605
|
+
useValue: new internal_1.MariaDbCredentialProvisioner(authzPool, Date.now, schemas),
|
|
606
|
+
},
|
|
588
607
|
// The same resolved names for identity services Nest constructs (UserService's
|
|
589
608
|
// email-change audit sink), never a second env read.
|
|
590
609
|
{ provide: framework_schemas_token_js_1.IDENTITY_FRAMEWORK_SCHEMAS, useValue: schemas },
|
|
610
|
+
// G-009 T10: the ONE shared authz pool, for the org bootstrap runner (named lock, state reads,
|
|
611
|
+
// claim and root-flag writes). Not exported; the runner reads it with `app.get({ strict: false })`.
|
|
612
|
+
{ provide: org_bootstrap_tokens_js_1.IDENTITY_AUTHZ_POOL, useValue: authzPool },
|
|
591
613
|
// ADR-0035 (GAPS #6): attach the guard's Redis permission-version cache to the ONE shared
|
|
592
614
|
// bumper, so every committed bump (postings, grants, hierarchy) is published to Redis,
|
|
593
615
|
// where gate 5 reads it. Provided by the auth surface under `auth: true` (through the
|
|
@@ -691,6 +713,29 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
691
713
|
catalog_bootstrap_provider_js_1.CatalogBootstrapProvider,
|
|
692
714
|
// Stored files (file-storage Phase 5): the request-principal RecordsService + the guard's
|
|
693
715
|
// object store (`null`/absent ⇒ every method answers 501) + the file_access audit bridge.
|
|
716
|
+
// G-010 T9 (P-5): the invite, its settings, its audit writer and its two Redis limiters
|
|
717
|
+
// (built by guard's SHIELD_ATTEMPT_LIMITER_FACTORY over guard's one Redis client; `null`
|
|
718
|
+
// without auth, and InviteService then refuses invites with 503: fail closed).
|
|
719
|
+
invite_service_js_1.InviteService,
|
|
720
|
+
{ provide: invite_tokens_js_1.IDENTITY_INVITE_CONFIG, useValue: inviteConfig },
|
|
721
|
+
// `user.invited` / `user.invite_throttled`: the same audit-schema writer over the shared
|
|
722
|
+
// authz pool as `file_access` (one sink, no new one; best-effort in InviteService).
|
|
723
|
+
{ provide: invite_tokens_js_1.IDENTITY_INVITE_AUDIT, useValue: filesAudit },
|
|
724
|
+
{
|
|
725
|
+
provide: invite_tokens_js_1.IDENTITY_INVITE_LIMITERS,
|
|
726
|
+
// Guard's limiter factory over its own Redis (G-010 P-5); absent (auth off) ⇒ null, and
|
|
727
|
+
// the invite fails closed (503) rather than running unthrottled.
|
|
728
|
+
useFactory: (makeLimiter) => makeLimiter
|
|
729
|
+
? {
|
|
730
|
+
user: makeLimiter({ maxAttempts: invite_service_js_1.INVITES_PER_USER, windowMs: invite_service_js_1.INVITE_WINDOW_MS }),
|
|
731
|
+
admin: makeLimiter({
|
|
732
|
+
maxAttempts: inviteConfig.perAdminPerHour,
|
|
733
|
+
windowMs: invite_service_js_1.INVITE_WINDOW_MS,
|
|
734
|
+
}),
|
|
735
|
+
}
|
|
736
|
+
: null,
|
|
737
|
+
inject: [{ token: internal_1.SHIELD_ATTEMPT_LIMITER_FACTORY, optional: true }],
|
|
738
|
+
},
|
|
694
739
|
{
|
|
695
740
|
provide: files_service_js_1.FilesService,
|
|
696
741
|
useFactory: (records, store) => new files_service_js_1.FilesService(records, store ?? null, filesAudit),
|
|
@@ -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"}
|