@astram/identity 0.10.0 → 0.12.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/auth-facts-keys.d.ts +16 -0
- package/dist/auth-facts-keys.d.ts.map +1 -1
- package/dist/auth-facts-keys.js +26 -10
- package/dist/cross-tenant-grant.object.d.ts +2 -2
- package/dist/cross-tenant-grant.object.d.ts.map +1 -1
- package/dist/cross-tenant-grant.object.js +8 -4
- package/dist/features/audit/directory-label-resolver.d.ts +4 -1
- package/dist/features/audit/directory-label-resolver.d.ts.map +1 -1
- package/dist/features/audit/directory-label-resolver.js +8 -5
- package/dist/features/audit/mariadb-audit-reader.d.ts +5 -1
- package/dist/features/audit/mariadb-audit-reader.d.ts.map +1 -1
- package/dist/features/audit/mariadb-audit-reader.js +10 -2
- package/dist/features/authz/access-bundle-reconcile.job.d.ts +4 -1
- package/dist/features/authz/access-bundle-reconcile.job.d.ts.map +1 -1
- package/dist/features/authz/access-bundle-reconcile.job.js +7 -4
- package/dist/features/authz/access-bundle-store.d.ts +4 -1
- package/dist/features/authz/access-bundle-store.d.ts.map +1 -1
- package/dist/features/authz/access-bundle-store.js +19 -16
- package/dist/features/authz/catalog-bootstrap.provider.d.ts +4 -1
- package/dist/features/authz/catalog-bootstrap.provider.d.ts.map +1 -1
- package/dist/features/authz/catalog-bootstrap.provider.js +37 -6
- package/dist/features/authz/catalog-reconcile.job.d.ts +3 -1
- package/dist/features/authz/catalog-reconcile.job.d.ts.map +1 -1
- package/dist/features/authz/catalog-reconcile.job.js +3 -2
- package/dist/features/authz/db-permission-facts.adapter.d.ts +9 -3
- package/dist/features/authz/db-permission-facts.adapter.d.ts.map +1 -1
- package/dist/features/authz/db-permission-facts.adapter.js +22 -16
- package/dist/features/authz/grant-admin.service.d.ts +4 -1
- package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
- package/dist/features/authz/grant-admin.service.js +32 -37
- package/dist/features/authz/grant-ceiling.d.ts +6 -1
- package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
- package/dist/features/authz/grant-ceiling.js +19 -16
- package/dist/features/authz/mariadb-catalog-store.d.ts +4 -1
- package/dist/features/authz/mariadb-catalog-store.d.ts.map +1 -1
- package/dist/features/authz/mariadb-catalog-store.js +10 -7
- package/dist/features/authz/permission-version.bumper.d.ts +6 -1
- package/dist/features/authz/permission-version.bumper.d.ts.map +1 -1
- package/dist/features/authz/permission-version.bumper.js +13 -11
- package/dist/features/authz/record-authorization.d.ts +8 -2
- package/dist/features/authz/record-authorization.d.ts.map +1 -1
- package/dist/features/authz/record-authorization.js +8 -4
- package/dist/features/authz/role-binding.d.ts +3 -2
- package/dist/features/authz/role-binding.d.ts.map +1 -1
- package/dist/features/authz/role-binding.js +7 -8
- package/dist/features/company/company-hierarchy.service.d.ts +6 -1
- package/dist/features/company/company-hierarchy.service.d.ts.map +1 -1
- package/dist/features/company/company-hierarchy.service.js +17 -16
- package/dist/features/cross-tenant/cross-tenant-grant-read.service.d.ts +11 -6
- package/dist/features/cross-tenant/cross-tenant-grant-read.service.d.ts.map +1 -1
- package/dist/features/cross-tenant/cross-tenant-grant-read.service.js +24 -19
- package/dist/features/files/files.controller.d.ts +27 -0
- package/dist/features/files/files.controller.d.ts.map +1 -0
- package/dist/features/files/files.controller.js +94 -0
- package/dist/features/files/files.service.d.ts +87 -0
- package/dist/features/files/files.service.d.ts.map +1 -0
- package/dist/features/files/files.service.js +311 -0
- package/dist/features/framework-schemas.token.d.ts +8 -0
- package/dist/features/framework-schemas.token.d.ts.map +1 -0
- package/dist/features/framework-schemas.token.js +10 -0
- package/dist/features/identity-feature.module.d.ts +29 -1
- package/dist/features/identity-feature.module.d.ts.map +1 -1
- package/dist/features/identity-feature.module.js +168 -41
- package/dist/features/membership/membership-checker.d.ts +7 -1
- package/dist/features/membership/membership-checker.d.ts.map +1 -1
- package/dist/features/membership/membership-checker.js +9 -4
- package/dist/features/migration-ledger-check.d.ts +15 -0
- package/dist/features/migration-ledger-check.d.ts.map +1 -0
- package/dist/features/migration-ledger-check.js +26 -0
- package/dist/features/policy/policy.service.d.ts +2 -1
- package/dist/features/policy/policy.service.d.ts.map +1 -1
- package/dist/features/policy/policy.service.js +15 -0
- package/dist/features/scope/scope-closure.service.d.ts +8 -1
- package/dist/features/scope/scope-closure.service.d.ts.map +1 -1
- package/dist/features/scope/scope-closure.service.js +22 -19
- package/dist/features/scope/scope-subtree.resolver.d.ts +7 -1
- package/dist/features/scope/scope-subtree.resolver.d.ts.map +1 -1
- package/dist/features/scope/scope-subtree.resolver.js +9 -4
- package/dist/features/service-account/lock-service-accounts.d.ts +3 -1
- package/dist/features/service-account/lock-service-accounts.d.ts.map +1 -1
- package/dist/features/service-account/lock-service-accounts.js +4 -4
- package/dist/features/service-account/no-expiry-key-report.d.ts +9 -3
- package/dist/features/service-account/no-expiry-key-report.d.ts.map +1 -1
- package/dist/features/service-account/no-expiry-key-report.js +17 -10
- package/dist/features/service-account/service-account.store.d.ts +8 -1
- package/dist/features/service-account/service-account.store.d.ts.map +1 -1
- package/dist/features/service-account/service-account.store.js +19 -14
- package/dist/features/share/share.service.d.ts +19 -2
- package/dist/features/share/share.service.d.ts.map +1 -1
- package/dist/features/share/share.service.js +36 -2
- package/dist/features/user/user.service.d.ts +7 -3
- package/dist/features/user/user.service.d.ts.map +1 -1
- package/dist/features/user/user.service.js +12 -4
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/objects.d.ts.map +1 -1
- package/dist/objects.js +2 -0
- package/dist/record-share.object.d.ts +1 -1
- package/dist/record-share.object.d.ts.map +1 -1
- package/dist/record-share.object.js +7 -3
- package/dist/schema.d.ts +15 -2
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +17 -2
- package/dist/service-account.object.d.ts +3 -2
- package/dist/service-account.object.d.ts.map +1 -1
- package/dist/service-account.object.js +4 -2
- package/dist/stored-file.object.d.ts +35 -0
- package/dist/stored-file.object.d.ts.map +1 -0
- package/dist/stored-file.object.js +94 -0
- package/migrations/1710000000001-CreateIdentityOrgModel.cjs +14 -4
- package/migrations/1710000000002-CreateMembershipAndPostings.cjs +14 -4
- package/migrations/1710000000003-CreateIdentityCredential.cjs +14 -4
- package/migrations/1710000000004-CreateSessionTables.cjs +14 -4
- package/migrations/1710000000005-CreateMfaAndTokenSurface.cjs +14 -4
- package/migrations/1710000000010-CreateAuditLog.cjs +14 -4
- package/migrations/1710000000011-CreateCrossTenantGrant.cjs +15 -4
- package/migrations/1710000000012-SeedCrossTenantGrantPerms.cjs +15 -4
- package/migrations/1710000000013-CreateSharedResource.cjs +14 -4
- package/migrations/1710000000018-CreateServiceAccount.cjs +13 -4
- package/migrations/1710000000019-AddSessionMustReenroll.cjs +14 -4
- package/migrations/1710000000020-CreateEmailChangeToken.cjs +15 -5
- package/migrations/1710000000021-AddUsersEmailUniqueIndex.cjs +24 -13
- package/migrations/1710000000022-CreateStoredFile.cjs +53 -0
- package/migrations/1710000000099-SeedDevIdentity.cjs +14 -6
- package/migrations/1710000000100-SeedDevTenantBAndRoot.cjs +14 -6
- package/package.json +8 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MariaDbCatalogStore = void 0;
|
|
4
|
-
const
|
|
4
|
+
const schema_js_1 = require("../../schema.js");
|
|
5
5
|
/**
|
|
6
6
|
* CatalogStore impl over shield_authz.permission (raw parameterized SQL, mirroring
|
|
7
7
|
* permission-facts.adapter). The reconciler (framework) calls this; guard does no DB
|
|
@@ -9,12 +9,15 @@ const AUTHZ = 'shield_authz';
|
|
|
9
9
|
*/
|
|
10
10
|
class MariaDbCatalogStore {
|
|
11
11
|
pool;
|
|
12
|
-
|
|
12
|
+
schemas;
|
|
13
|
+
/** @param schemas resolved framework names (absent ⇒ resolved from the namespace, once). */
|
|
14
|
+
constructor(pool, schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
|
|
13
15
|
this.pool = pool;
|
|
16
|
+
this.schemas = schemas;
|
|
14
17
|
}
|
|
15
18
|
async listPermissions() {
|
|
16
19
|
const rows = (await this.pool.query(`SELECT perm_key, perm_module, perm_type_key, perm_action, perm_label, perm_tier, perm_status
|
|
17
|
-
FROM \`${
|
|
20
|
+
FROM \`${this.schemas.authz}\`.\`permission\``));
|
|
18
21
|
return rows.map((r) => ({
|
|
19
22
|
permissionKey: r.perm_key,
|
|
20
23
|
moduleKey: r.perm_module,
|
|
@@ -26,10 +29,10 @@ class MariaDbCatalogStore {
|
|
|
26
29
|
}));
|
|
27
30
|
}
|
|
28
31
|
async upsertPermission(row) {
|
|
29
|
-
const res = (await this.pool.query(`INSERT INTO \`${
|
|
32
|
+
const res = (await this.pool.query(`INSERT INTO \`${this.schemas.authz}\`.\`permission\`
|
|
30
33
|
(perm_uuid, perm_key, perm_module, perm_type_key, perm_action, perm_label, perm_tier, perm_status)
|
|
31
34
|
SELECT UUID(), ?, ?, ?, ?, ?, ?, 'active'
|
|
32
|
-
WHERE NOT EXISTS (SELECT 1 FROM \`${
|
|
35
|
+
WHERE NOT EXISTS (SELECT 1 FROM \`${this.schemas.authz}\`.\`permission\` WHERE perm_key = ?)`, [
|
|
33
36
|
row.permissionKey,
|
|
34
37
|
row.moduleKey,
|
|
35
38
|
row.typeKey,
|
|
@@ -44,13 +47,13 @@ class MariaDbCatalogStore {
|
|
|
44
47
|
if (keys.length === 0)
|
|
45
48
|
return;
|
|
46
49
|
const ph = keys.map(() => '?').join(', ');
|
|
47
|
-
await this.pool.query(`UPDATE \`${
|
|
50
|
+
await this.pool.query(`UPDATE \`${this.schemas.authz}\`.\`permission\` SET perm_status = 'orphaned' WHERE perm_key IN (${ph})`, [...keys]);
|
|
48
51
|
}
|
|
49
52
|
async markActive(keys) {
|
|
50
53
|
if (keys.length === 0)
|
|
51
54
|
return;
|
|
52
55
|
const ph = keys.map(() => '?').join(', ');
|
|
53
|
-
await this.pool.query(`UPDATE \`${
|
|
56
|
+
await this.pool.query(`UPDATE \`${this.schemas.authz}\`.\`permission\` SET perm_status = 'active' WHERE perm_key IN (${ph})`, [...keys]);
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
exports.MariaDbCatalogStore = MariaDbCatalogStore;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type IdentitySchemaNames } from '../../schema.js';
|
|
1
2
|
/**
|
|
2
3
|
* The minimal query surface this writer needs — a `mariadb` `Pool` (or `PoolConnection`)
|
|
3
4
|
* satisfies it structurally, but the type itself stays framework-agnostic so this signature
|
|
@@ -29,6 +30,7 @@ export interface VersionPublisher {
|
|
|
29
30
|
}
|
|
30
31
|
export declare class PermissionVersionBumper {
|
|
31
32
|
private publisher?;
|
|
33
|
+
private readonly schemas;
|
|
32
34
|
/**
|
|
33
35
|
* Bumped versions waiting for their transaction to commit, per connection (ADR-0035). A
|
|
34
36
|
* transaction connection is a pooled object reused across requests, so callers either publish
|
|
@@ -39,8 +41,11 @@ export declare class PermissionVersionBumper {
|
|
|
39
41
|
/**
|
|
40
42
|
* @param publisher OPTIONAL Redis publisher (ADR-0035). Absent → DB-only bumps, exactly as
|
|
41
43
|
* before (no read-back). `IdentityFeatureModule` attaches the guard's cache through DI.
|
|
44
|
+
* @param schemas resolved framework names: `authz_permission_version` lives in `schemas.authz`
|
|
45
|
+
* (ADR-0011), the membership/posting join `bumpForRole` walks in `schemas.app`. Absent ⇒
|
|
46
|
+
* resolved from the namespace, once, here.
|
|
42
47
|
*/
|
|
43
|
-
constructor(publisher?: VersionPublisher | undefined);
|
|
48
|
+
constructor(publisher?: VersionPublisher | undefined, schemas?: IdentitySchemaNames);
|
|
44
49
|
/** Attach the Redis publisher once it is resolved through DI (first attach wins). */
|
|
45
50
|
attachVersionPublisher(publisher: VersionPublisher): void;
|
|
46
51
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-version.bumper.d.ts","sourceRoot":"","sources":["../../../src/features/authz/permission-version.bumper.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"permission-version.bumper.d.ts","sourceRoot":"","sources":["../../../src/features/authz/permission-version.bumper.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED;;;;;;;;;;;;;GAaG;AACH;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D;AAOD,qBAAa,uBAAuB;IAiBhC,OAAO,CAAC,SAAS,CAAC;IAClB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAjB1B;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8C;IACtE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyC;IAEhE;;;;;;OAMG;gBAEO,SAAS,CAAC,EAAE,gBAAgB,YAAA,EACnB,OAAO,GAAE,mBAA+C;IAG3E,qFAAqF;IACrF,sBAAsB,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI;IAIzD;;;OAGG;IACG,gBAAgB,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAOtD,kFAAkF;IAClF,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;YAIvB,WAAW;IAUzB;;;;OAIG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvE;;;;;;;;;;;;OAYG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAYvE;;;;;OAKG;IACG,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAmB1E;;;;;;OAMG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAgBvE;;;;;;OAMG;YACW,UAAU;CA4BzB"}
|
|
@@ -3,16 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PermissionVersionBumper = void 0;
|
|
4
4
|
const common_1 = require("@nestjs/common");
|
|
5
5
|
const core_1 = require("@astram/core");
|
|
6
|
-
|
|
7
|
-
const AUTHZ = 'shield_authz';
|
|
8
|
-
/** Schema the membership/posting tables live in — the join `bumpForRole` walks. */
|
|
9
|
-
const APP = 'shield_app';
|
|
6
|
+
const schema_js_1 = require("../../schema.js");
|
|
10
7
|
/** An autocommit pool (it hands out connections) rather than one transaction's connection. */
|
|
11
8
|
function isAutocommitPool(conn) {
|
|
12
9
|
return typeof conn.getConnection === 'function';
|
|
13
10
|
}
|
|
14
11
|
class PermissionVersionBumper {
|
|
15
12
|
publisher;
|
|
13
|
+
schemas;
|
|
16
14
|
/**
|
|
17
15
|
* Bumped versions waiting for their transaction to commit, per connection (ADR-0035). A
|
|
18
16
|
* transaction connection is a pooled object reused across requests, so callers either publish
|
|
@@ -23,9 +21,13 @@ class PermissionVersionBumper {
|
|
|
23
21
|
/**
|
|
24
22
|
* @param publisher OPTIONAL Redis publisher (ADR-0035). Absent → DB-only bumps, exactly as
|
|
25
23
|
* before (no read-back). `IdentityFeatureModule` attaches the guard's cache through DI.
|
|
24
|
+
* @param schemas resolved framework names: `authz_permission_version` lives in `schemas.authz`
|
|
25
|
+
* (ADR-0011), the membership/posting join `bumpForRole` walks in `schemas.app`. Absent ⇒
|
|
26
|
+
* resolved from the namespace, once, here.
|
|
26
27
|
*/
|
|
27
|
-
constructor(publisher) {
|
|
28
|
+
constructor(publisher, schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
|
|
28
29
|
this.publisher = publisher;
|
|
30
|
+
this.schemas = schemas;
|
|
29
31
|
}
|
|
30
32
|
/** Attach the Redis publisher once it is resolved through DI (first attach wins). */
|
|
31
33
|
attachVersionPublisher(publisher) {
|
|
@@ -100,13 +102,13 @@ class PermissionVersionBumper {
|
|
|
100
102
|
*/
|
|
101
103
|
async tenantsForRole(roleUuid, conn) {
|
|
102
104
|
const rows = (await conn.query(`SELECT DISTINCT cm.cm_company_uuid AS tenant
|
|
103
|
-
FROM \`${
|
|
104
|
-
JOIN \`${
|
|
105
|
+
FROM \`${this.schemas.app}\`.\`company_membership\` cm
|
|
106
|
+
JOIN \`${this.schemas.app}\`.\`membership_posting\` mp ON mp.mp_membership_uuid = cm.cm_uuid
|
|
105
107
|
WHERE mp.mp_role_uuid = ? AND cm.cm_is_active = 1`, [roleUuid]));
|
|
106
108
|
// Service accounts (ADR-0033) hold roles on `sa_role_ids`, not postings: a role held only by
|
|
107
109
|
// machine principals must still invalidate their tenants' cached grants (security hardening).
|
|
108
110
|
const saRows = (await conn.query(`SELECT DISTINCT sa.sa_tenant_id AS tenant
|
|
109
|
-
FROM \`${
|
|
111
|
+
FROM \`${this.schemas.app}\`.\`service_account\` sa
|
|
110
112
|
WHERE JSON_CONTAINS(sa.sa_role_ids, JSON_QUOTE(?))`, [roleUuid]));
|
|
111
113
|
return [...new Set([...rows, ...saRows].map((r) => r.tenant))];
|
|
112
114
|
}
|
|
@@ -120,7 +122,7 @@ class PermissionVersionBumper {
|
|
|
120
122
|
async bumpForUser(userUuid, conn) {
|
|
121
123
|
try {
|
|
122
124
|
const rows = (await conn.query(`SELECT DISTINCT cm.cm_company_uuid AS tenant
|
|
123
|
-
FROM \`${
|
|
125
|
+
FROM \`${this.schemas.app}\`.\`company_membership\` cm
|
|
124
126
|
WHERE cm.cm_user_uuid = ?`, [userUuid]));
|
|
125
127
|
const tenants = [...new Set(rows.map((r) => r.tenant))];
|
|
126
128
|
for (const tenant of tenants)
|
|
@@ -139,14 +141,14 @@ class PermissionVersionBumper {
|
|
|
139
141
|
* (the TTL bounds it) and never fails the bump.
|
|
140
142
|
*/
|
|
141
143
|
async bumpTenant(tenantUuid, conn) {
|
|
142
|
-
await conn.query(`INSERT INTO \`${
|
|
144
|
+
await conn.query(`INSERT INTO \`${this.schemas.authz}\`.\`authz_permission_version\` (apv_tenant_uuid, apv_version)
|
|
143
145
|
VALUES (?, 1)
|
|
144
146
|
ON DUPLICATE KEY UPDATE apv_version = apv_version + 1`, [tenantUuid]);
|
|
145
147
|
if (this.publisher === undefined)
|
|
146
148
|
return;
|
|
147
149
|
let version;
|
|
148
150
|
try {
|
|
149
|
-
const rows = (await conn.query(`SELECT apv_version FROM \`${
|
|
151
|
+
const rows = (await conn.query(`SELECT apv_version FROM \`${this.schemas.authz}\`.\`authz_permission_version\`
|
|
150
152
|
WHERE apv_tenant_uuid = ? LIMIT 1`, [tenantUuid]));
|
|
151
153
|
version = Number(rows[0]?.apv_version);
|
|
152
154
|
}
|
|
@@ -12,14 +12,18 @@
|
|
|
12
12
|
* adapted to `@astram/identity`'s own registrations (`identityObjects`) and env prefix (`APP_DB_*`).
|
|
13
13
|
*/
|
|
14
14
|
import { type Pool } from 'mariadb';
|
|
15
|
+
import type { FrameworkSchemas } from '@astram/core';
|
|
15
16
|
import { type RecordAuthorizationInputs } from '@astram/guard';
|
|
16
17
|
import type { ObjectTypeInput } from '@astram/engine';
|
|
18
|
+
import type { IdentitySchemaNames } from '../../schema.js';
|
|
17
19
|
/**
|
|
18
20
|
* A lean `mariadb` pool for the authz grant-reads (built from `APP_DB_*` env via guard's
|
|
19
21
|
* `loadDbConfig`). Kept small — these are pre-gate coarse-grant reads, not the main CRUD pool.
|
|
20
22
|
* Opens the pool at CALL time (never at import) — the caller owns closing it.
|
|
23
|
+
* `schemas` (the names `forRoot` resolved or was given) set the default database and user; absent
|
|
24
|
+
* ⇒ from the env namespace. An explicit `APP_DB_USER` / `APP_DB_NAME` wins (QA P2-5).
|
|
21
25
|
*/
|
|
22
|
-
export declare function createIdentityAuthzPool(): Pool;
|
|
26
|
+
export declare function createIdentityAuthzPool(schemas?: FrameworkSchemas): Pool;
|
|
23
27
|
/**
|
|
24
28
|
* Build the `RecordAuthorizationInputs` over `pool`. `@astram/identity` supplies only the
|
|
25
29
|
* app-schema ports:
|
|
@@ -31,5 +35,7 @@ export declare function createIdentityAuthzPool(): Pool;
|
|
|
31
35
|
* consumer type is `resource_not_registered`),
|
|
32
36
|
* - `operationManifest` — empty (identity declares no trusted delegated operations).
|
|
33
37
|
*/
|
|
34
|
-
export declare function buildRecordAuthorizationInputs(pool: Pool, extraObjects?: readonly ObjectTypeInput[]
|
|
38
|
+
export declare function buildRecordAuthorizationInputs(pool: Pool, extraObjects?: readonly ObjectTypeInput[],
|
|
39
|
+
/** Resolved framework names for the facts adapter (absent ⇒ from the namespace, once). */
|
|
40
|
+
schemas?: IdentitySchemaNames): RecordAuthorizationInputs;
|
|
35
41
|
//# sourceMappingURL=record-authorization.d.ts.map
|
|
@@ -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,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;AAItD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE3D;;;;;;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"}
|
|
@@ -27,9 +27,11 @@ const session_facts_reader_js_1 = require("./session-facts.reader.js");
|
|
|
27
27
|
* A lean `mariadb` pool for the authz grant-reads (built from `APP_DB_*` env via guard's
|
|
28
28
|
* `loadDbConfig`). Kept small — these are pre-gate coarse-grant reads, not the main CRUD pool.
|
|
29
29
|
* Opens the pool at CALL time (never at import) — the caller owns closing it.
|
|
30
|
+
* `schemas` (the names `forRoot` resolved or was given) set the default database and user; absent
|
|
31
|
+
* ⇒ from the env namespace. An explicit `APP_DB_USER` / `APP_DB_NAME` wins (QA P2-5).
|
|
30
32
|
*/
|
|
31
|
-
function createIdentityAuthzPool() {
|
|
32
|
-
const db = (0, guard_1.loadDbConfig)();
|
|
33
|
+
function createIdentityAuthzPool(schemas) {
|
|
34
|
+
const db = schemas === undefined ? (0, guard_1.loadDbConfig)() : (0, guard_1.loadDbConfig)(process.env, schemas);
|
|
33
35
|
const ssl = (0, guard_1.mariadbSslOption)(db.tls);
|
|
34
36
|
return mariadb_1.default.createPool({
|
|
35
37
|
host: db.host,
|
|
@@ -53,11 +55,13 @@ function createIdentityAuthzPool() {
|
|
|
53
55
|
* consumer type is `resource_not_registered`),
|
|
54
56
|
* - `operationManifest` — empty (identity declares no trusted delegated operations).
|
|
55
57
|
*/
|
|
56
|
-
function buildRecordAuthorizationInputs(pool, extraObjects = []
|
|
58
|
+
function buildRecordAuthorizationInputs(pool, extraObjects = [],
|
|
59
|
+
/** Resolved framework names for the facts adapter (absent ⇒ from the namespace, once). */
|
|
60
|
+
schemas) {
|
|
57
61
|
const registry = (0, guard_1.buildIdentityModuleRegistry)([...objects_js_1.identityObjects, ...extraObjects]);
|
|
58
62
|
return {
|
|
59
63
|
pool,
|
|
60
|
-
permissionFacts: new db_permission_facts_adapter_js_1.DbPermissionFactsAdapter(pool, registry),
|
|
64
|
+
permissionFacts: new db_permission_facts_adapter_js_1.DbPermissionFactsAdapter(pool, registry, undefined, schemas),
|
|
61
65
|
sessionFacts: new session_facts_reader_js_1.AlsSessionFactsReader(),
|
|
62
66
|
registry,
|
|
63
67
|
operationManifest: [],
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* the role id are `?`-bound (never interpolated).
|
|
14
14
|
*/
|
|
15
15
|
import type { Queryable } from './permission-version.bumper.js';
|
|
16
|
+
import type { IdentitySchemaNames } from '../../schema.js';
|
|
16
17
|
/** Where a role is bound relative to a set of subtree nodes. */
|
|
17
18
|
export interface RoleBinding {
|
|
18
19
|
readonly inside: boolean;
|
|
@@ -23,7 +24,7 @@ export interface RoleBinding {
|
|
|
23
24
|
* qualifies only if it is bound (`membership_posting → company_membership`) to a node in the
|
|
24
25
|
* subtree. The caller splices `params` where `sql` sits in the surrounding query.
|
|
25
26
|
*/
|
|
26
|
-
export declare function subtreeExists(nodeIds: readonly string[]): {
|
|
27
|
+
export declare function subtreeExists(nodeIds: readonly string[], schemas: IdentitySchemaNames): {
|
|
27
28
|
sql: string;
|
|
28
29
|
params: string[];
|
|
29
30
|
};
|
|
@@ -32,5 +33,5 @@ export declare function subtreeExists(nodeIds: readonly string[]): {
|
|
|
32
33
|
* query. An empty `nodeIds` is treated as "nothing is inside" (every binding is outside) — fail
|
|
33
34
|
* closed, never an `IN ()` syntax error.
|
|
34
35
|
*/
|
|
35
|
-
export declare function readRoleBinding(q: Queryable, roleUuid: string, nodeIds: readonly string[]): Promise<RoleBinding>;
|
|
36
|
+
export declare function readRoleBinding(q: Queryable, roleUuid: string, nodeIds: readonly string[], schemas: IdentitySchemaNames): Promise<RoleBinding>;
|
|
36
37
|
//# sourceMappingURL=role-binding.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role-binding.d.ts","sourceRoot":"","sources":["../../../src/features/authz/role-binding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"role-binding.d.ts","sourceRoot":"","sources":["../../../src/features/authz/role-binding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE3D,gEAAgE;AAChE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,mBAAmB,GAC3B;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAQnC;AASD;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,CAAC,EAAE,SAAS,EACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,WAAW,CAAC,CAwBtB"}
|
|
@@ -2,17 +2,16 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.subtreeExists = subtreeExists;
|
|
4
4
|
exports.readRoleBinding = readRoleBinding;
|
|
5
|
-
const APP = 'shield_app';
|
|
6
5
|
/**
|
|
7
6
|
* The shared EXISTS-subtree fragment for a non-root caller's READS (Task 4.2): a role row `r`
|
|
8
7
|
* qualifies only if it is bound (`membership_posting → company_membership`) to a node in the
|
|
9
8
|
* subtree. The caller splices `params` where `sql` sits in the surrounding query.
|
|
10
9
|
*/
|
|
11
|
-
function subtreeExists(nodeIds) {
|
|
10
|
+
function subtreeExists(nodeIds, schemas) {
|
|
12
11
|
const ph = nodeIds.map(() => '?').join(', ');
|
|
13
12
|
const sql = ` AND EXISTS (` +
|
|
14
|
-
`SELECT 1 FROM \`${
|
|
15
|
-
` JOIN \`${
|
|
13
|
+
`SELECT 1 FROM \`${schemas.app}\`.\`membership_posting\` mp` +
|
|
14
|
+
` JOIN \`${schemas.app}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid` +
|
|
16
15
|
` WHERE mp.mp_role_uuid = r.r_uuid AND cm.cm_company_uuid IN (${ph}))`;
|
|
17
16
|
return { sql, params: [...nodeIds] };
|
|
18
17
|
}
|
|
@@ -29,7 +28,7 @@ function truthy(v) {
|
|
|
29
28
|
* query. An empty `nodeIds` is treated as "nothing is inside" (every binding is outside) — fail
|
|
30
29
|
* closed, never an `IN ()` syntax error.
|
|
31
30
|
*/
|
|
32
|
-
async function readRoleBinding(q, roleUuid, nodeIds) {
|
|
31
|
+
async function readRoleBinding(q, roleUuid, nodeIds, schemas) {
|
|
33
32
|
const ids = nodeIds.length > 0 ? [...nodeIds] : [];
|
|
34
33
|
const inList = ids.length > 0 ? ids.map(() => '?').join(', ') : 'NULL';
|
|
35
34
|
// `x IN (NULL)` is NULL (never true) and `x NOT IN (NULL)` is NULL too, so for an empty list the
|
|
@@ -40,12 +39,12 @@ async function readRoleBinding(q, roleUuid, nodeIds) {
|
|
|
40
39
|
COALESCE(MAX(${outsideExpr}), 0) AS bound_outside
|
|
41
40
|
FROM (
|
|
42
41
|
SELECT cm.cm_company_uuid AS node
|
|
43
|
-
FROM \`${
|
|
44
|
-
JOIN \`${
|
|
42
|
+
FROM \`${schemas.app}\`.\`membership_posting\` mp
|
|
43
|
+
JOIN \`${schemas.app}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid
|
|
45
44
|
WHERE mp.mp_role_uuid = ?
|
|
46
45
|
UNION ALL
|
|
47
46
|
SELECT sa.sa_tenant_id AS node
|
|
48
|
-
FROM \`${
|
|
47
|
+
FROM \`${schemas.app}\`.\`service_account\` sa
|
|
49
48
|
WHERE JSON_CONTAINS(sa.sa_role_ids, JSON_QUOTE(?))
|
|
50
49
|
) b`, [...ids, ...ids, roleUuid, roleUuid]));
|
|
51
50
|
const row = rows[0];
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import type { Principal } from '@astram/core';
|
|
22
22
|
import type { AuditPort } from '@astram/engine';
|
|
23
23
|
import { ScopeClosureService } from '../scope/scope-closure.service.js';
|
|
24
|
+
import { type IdentitySchemaNames } from '../../schema.js';
|
|
24
25
|
/** The read surface used for existence/name lookups (the shared authz pool satisfies it). */
|
|
25
26
|
export interface HierarchyQueryable {
|
|
26
27
|
query(sql: string, params?: readonly unknown[]): Promise<unknown>;
|
|
@@ -49,9 +50,13 @@ export declare class CompanyHierarchyService {
|
|
|
49
50
|
private readonly ceiling;
|
|
50
51
|
private readonly audit?;
|
|
51
52
|
private readonly now;
|
|
53
|
+
/** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
|
|
54
|
+
private readonly schemas;
|
|
52
55
|
constructor(pool: HierarchyQueryable, closure: ScopeClosureService,
|
|
53
56
|
/** Phase 8 #3: required — a company delete must dominate every member (no silent skip). */
|
|
54
|
-
ceiling: MemberDominance, audit?: AuditPort | undefined, now?: () => number
|
|
57
|
+
ceiling: MemberDominance, audit?: AuditPort | undefined, now?: () => number,
|
|
58
|
+
/** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
|
|
59
|
+
schemas?: IdentitySchemaNames);
|
|
55
60
|
/** The actor's own root node (see {@link ownRootOf}). */
|
|
56
61
|
private ownRoot;
|
|
57
62
|
/**
|
|
@@ -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;AAQxF,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;IAkF9B,0FAA0F;YAC5E,kBAAkB;IAmDhC,wFAAwF;YAC1E,sBAAsB;IAgCpC,wFAAwF;IAClF,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGhD"}
|
|
@@ -6,8 +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
|
|
10
|
-
const AUTHZ = 'shield_authz';
|
|
9
|
+
const schema_js_1 = require("../../schema.js");
|
|
11
10
|
/** A JSON-array column (the driver returns an array OR a string) → its string items; else []. */
|
|
12
11
|
function jsonIdArray(v) {
|
|
13
12
|
let arr = v;
|
|
@@ -34,14 +33,18 @@ class CompanyHierarchyService {
|
|
|
34
33
|
ceiling;
|
|
35
34
|
audit;
|
|
36
35
|
now;
|
|
36
|
+
schemas;
|
|
37
37
|
constructor(pool, closure,
|
|
38
38
|
/** Phase 8 #3: required — a company delete must dominate every member (no silent skip). */
|
|
39
|
-
ceiling, audit, now = Date.now
|
|
39
|
+
ceiling, audit, now = Date.now,
|
|
40
|
+
/** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
|
|
41
|
+
schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
|
|
40
42
|
this.pool = pool;
|
|
41
43
|
this.closure = closure;
|
|
42
44
|
this.ceiling = ceiling;
|
|
43
45
|
this.audit = audit;
|
|
44
46
|
this.now = now;
|
|
47
|
+
this.schemas = schemas;
|
|
45
48
|
}
|
|
46
49
|
/** The actor's own root node (see {@link ownRootOf}). */
|
|
47
50
|
ownRoot(actor) {
|
|
@@ -54,7 +57,7 @@ class CompanyHierarchyService {
|
|
|
54
57
|
* stored spelling once and passes that down. Unknown → the input unchanged (later steps 404).
|
|
55
58
|
*/
|
|
56
59
|
async storedUuid(uuid) {
|
|
57
|
-
const rows = (await this.pool.query(`SELECT c_uuid FROM \`${
|
|
60
|
+
const rows = (await this.pool.query(`SELECT c_uuid FROM \`${this.schemas.app}\`.\`companies\` WHERE c_uuid = ? LIMIT 1`, [uuid]));
|
|
58
61
|
return rows[0]?.c_uuid ?? uuid;
|
|
59
62
|
}
|
|
60
63
|
/** Names of the given companies (unknown → absent). */
|
|
@@ -62,7 +65,7 @@ class CompanyHierarchyService {
|
|
|
62
65
|
const unique = [...new Set(ids)];
|
|
63
66
|
if (unique.length === 0)
|
|
64
67
|
return new Map();
|
|
65
|
-
const rows = (await q.query(`SELECT c_uuid, c_name FROM \`${
|
|
68
|
+
const rows = (await q.query(`SELECT c_uuid, c_name FROM \`${this.schemas.app}\`.\`companies\` WHERE c_uuid IN (${unique.map(() => '?').join(', ')})`, unique));
|
|
66
69
|
return new Map(rows.map((r) => [r.c_uuid, r.c_name]));
|
|
67
70
|
}
|
|
68
71
|
async recordAudit(conn, actor, action, attributes, tenantId = actor.tenantId) {
|
|
@@ -109,7 +112,7 @@ class CompanyHierarchyService {
|
|
|
109
112
|
if (newParentUuid !== null && !names.has(newParentUuid)) {
|
|
110
113
|
throw new identity_errors_js_1.IdentityNotFoundError('Parent company not found.');
|
|
111
114
|
}
|
|
112
|
-
const oldParentRows = (await this.pool.query(`SELECT c_parent_uuid FROM \`${
|
|
115
|
+
const oldParentRows = (await this.pool.query(`SELECT c_parent_uuid FROM \`${this.schemas.app}\`.\`companies\` WHERE c_uuid = ? LIMIT 1`, [nodeUuid]));
|
|
113
116
|
const oldParent = oldParentRows[0]?.c_parent_uuid ?? null;
|
|
114
117
|
const oldParentName = oldParent
|
|
115
118
|
? (await this.names(this.pool, [oldParent])).get(oldParent)
|
|
@@ -183,7 +186,7 @@ class CompanyHierarchyService {
|
|
|
183
186
|
const agents = await (0, lock_service_accounts_js_1.lockServiceAccountsWhere)(conn, 'sa_id, sa_tenant_id, sa_label, sa_scope_subtree, sa_role_ids, sa_updated_at_ms', {
|
|
184
187
|
sql: 'sa_tenant_id = ? OR JSON_CONTAINS(sa_scope_subtree, JSON_QUOTE(?))',
|
|
185
188
|
params: [nodeUuid, nodeUuid],
|
|
186
|
-
});
|
|
189
|
+
}, this.schemas);
|
|
187
190
|
const own = agents.filter((a) => a.sa_tenant_id === nodeUuid);
|
|
188
191
|
const others = agents.filter((a) => a.sa_tenant_id !== nodeUuid);
|
|
189
192
|
// Root bypasses inside; a refusal (403, names no agent) rolls the whole delete back.
|
|
@@ -196,8 +199,8 @@ class CompanyHierarchyService {
|
|
|
196
199
|
const touched = new Set();
|
|
197
200
|
const editStamp = (a) => Math.max(nowMs, (a.sa_updated_at_ms == null ? 0 : Number(a.sa_updated_at_ms)) + 1);
|
|
198
201
|
const retire = async (a, reason) => {
|
|
199
|
-
await conn.query(`UPDATE \`${
|
|
200
|
-
const res = (await conn.query(`UPDATE \`${
|
|
202
|
+
await conn.query(`UPDATE \`${this.schemas.app}\`.\`service_account\` SET sa_status = 'disabled', sa_updated_at_ms = ? WHERE sa_id = ?`, [editStamp(a), a.sa_id]);
|
|
203
|
+
const res = (await conn.query(`UPDATE \`${this.schemas.authz}\`.\`service_account_key\`
|
|
201
204
|
SET sak_revoked_at_ms = ?
|
|
202
205
|
WHERE sak_account_id = ? AND sak_revoked_at_ms IS NULL`, [nowMs, a.sa_id]));
|
|
203
206
|
touched.add(a.sa_tenant_id);
|
|
@@ -218,7 +221,7 @@ class CompanyHierarchyService {
|
|
|
218
221
|
await retire(a, 'scope_company_deleted');
|
|
219
222
|
continue;
|
|
220
223
|
}
|
|
221
|
-
await conn.query(`UPDATE \`${
|
|
224
|
+
await conn.query(`UPDATE \`${this.schemas.app}\`.\`service_account\` SET sa_scope_subtree = ?, sa_updated_at_ms = ? WHERE sa_id = ?`, [JSON.stringify(remaining), editStamp(a), a.sa_id]);
|
|
222
225
|
touched.add(a.sa_tenant_id);
|
|
223
226
|
await this.recordAudit(conn, actor, 'service_account.updated', {
|
|
224
227
|
eventType: 'service_account',
|
|
@@ -236,7 +239,7 @@ class CompanyHierarchyService {
|
|
|
236
239
|
async revokeGrantsNaming(conn, actor, nodeUuid, companyName) {
|
|
237
240
|
const grants = (await conn.query(`SELECT ctg_uuid, ctg_owning_tenant_uuid, ctg_grantee_tenant_uuid, ctg_grantee_kind,
|
|
238
241
|
ctg_grantee_uuid, ctg_type_key, ctg_scope, ctg_allowed_actions
|
|
239
|
-
FROM \`${
|
|
242
|
+
FROM \`${this.schemas.authz}\`.\`cross_tenant_grant\`
|
|
240
243
|
WHERE (ctg_owning_tenant_uuid = ? OR ctg_grantee_tenant_uuid = ?)
|
|
241
244
|
AND ctg_status <> 'revoked'
|
|
242
245
|
ORDER BY ctg_uuid
|
|
@@ -247,7 +250,7 @@ class CompanyHierarchyService {
|
|
|
247
250
|
const otherSides = grants.map((g) => g.ctg_owning_tenant_uuid === nodeUuid ? g.ctg_grantee_tenant_uuid : g.ctg_owning_tenant_uuid);
|
|
248
251
|
const names = await this.names(conn, otherSides);
|
|
249
252
|
for (const [i, g] of grants.entries()) {
|
|
250
|
-
await conn.query(`UPDATE \`${
|
|
253
|
+
await conn.query(`UPDATE \`${this.schemas.authz}\`.\`cross_tenant_grant\`
|
|
251
254
|
SET ctg_status = 'revoked', ctg_revoked_at = ?
|
|
252
255
|
WHERE ctg_uuid = ? AND ctg_status <> 'revoked'`, [revokedAt, g.ctg_uuid]);
|
|
253
256
|
const other = otherSides[i];
|
|
@@ -273,12 +276,10 @@ class CompanyHierarchyService {
|
|
|
273
276
|
}
|
|
274
277
|
/** Lock the company, then require dominance over each of its members (see `remove`). */
|
|
275
278
|
async assertMayDeleteMembers(conn, actor, nodeUuid) {
|
|
276
|
-
await conn.query(`SELECT c_uuid FROM \`${
|
|
277
|
-
nodeUuid,
|
|
278
|
-
]);
|
|
279
|
+
await conn.query(`SELECT c_uuid FROM \`${this.schemas.app}\`.\`companies\` WHERE c_uuid = ? FOR UPDATE`, [nodeUuid]);
|
|
279
280
|
if (actor.isRoot === true)
|
|
280
281
|
return;
|
|
281
|
-
const rows = (await conn.query(`SELECT cm_user_uuid AS member FROM \`${
|
|
282
|
+
const rows = (await conn.query(`SELECT cm_user_uuid AS member FROM \`${this.schemas.app}\`.\`company_membership\`
|
|
282
283
|
WHERE cm_company_uuid = ? FOR UPDATE`, [nodeUuid]));
|
|
283
284
|
const members = [...new Set(rows.map((r) => r.member))].sort();
|
|
284
285
|
for (const member of members) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Pool } from 'mariadb';
|
|
2
2
|
import type { ObjectTypeInput } from '@astram/engine';
|
|
3
|
+
import { type IdentitySchemaNames } from '../../schema.js';
|
|
3
4
|
/** One row in the grantee's pending-offers inbox. */
|
|
4
5
|
export interface InboxGrantRow {
|
|
5
6
|
uuid: string;
|
|
@@ -59,13 +60,17 @@ export declare class CrossTenantGrantReadService {
|
|
|
59
60
|
* surface uses, so a future tenantScoped type is picked up the same way it becomes
|
|
60
61
|
* shareable.
|
|
61
62
|
* @param actionsByType per-typeKey action vocabulary for the offerable-types read
|
|
62
|
-
* (decision 1: "the actions per type come from the type's own action set").
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
63
|
+
* (decision 1: "the actions per type come from the type's own action set"). The
|
|
64
|
+
* module passes its registration-based lookup (`effectiveActions` over the same
|
|
65
|
+
* `[...identityObjects, ...extraObjects]`, ADR-0038): CRUD plus a type's declared
|
|
66
|
+
* extras, or exactly its declared list for `crudActions: false` (e.g.
|
|
67
|
+
* `cross_tenant_grant` -> share/accept/revoke) — the single source every
|
|
68
|
+
* action-vocabulary consumer uses, so a per-type set can never drift between callers.
|
|
67
69
|
*/
|
|
68
|
-
|
|
70
|
+
/** The shared FROM/JOIN block, built once from the resolved names. */
|
|
71
|
+
private readonly joins;
|
|
72
|
+
/** (`schemas`: resolved framework names; absent ⇒ resolved from the namespace, once, here.) */
|
|
73
|
+
constructor(pool: Pool, offerableTypeInputs: readonly ObjectTypeInput[], actionsByType: (typeKey: string) => readonly string[], schemas?: IdentitySchemaNames);
|
|
69
74
|
/**
|
|
70
75
|
* Pending offers sitting in the CALLER's tenant inbox: `ctg_grantee_tenant_uuid =
|
|
71
76
|
* tenantId AND ctg_status = 'offered' AND ctg_expires_at_ms > nowMs` (an expired
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cross-tenant-grant-read.service.d.ts","sourceRoot":"","sources":["../../../src/features/cross-tenant/cross-tenant-grant-read.service.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"cross-tenant-grant-read.service.d.ts","sourceRoot":"","sources":["../../../src/features/cross-tenant/cross-tenant-grant-read.service.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,6FAA6F;AAC7F,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAgGD,qBACa,2BAA2B;IAsBpC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAvBhC;;;;;;;;;;;;;;;OAeG;IACH,sEAAsE;IACtE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAE/B,+FAA+F;gBAE5E,IAAI,EAAE,IAAI,EACV,mBAAmB,EAAE,SAAS,eAAe,EAAE,EAC/C,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,SAAS,MAAM,EAAE,EACtE,OAAO,GAAE,mBAA+C;IAK1D;;;;;OAKG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IA0B1E;;;;;OAKG;IACG,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAyBzD;;;;;;;OAOG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAyBjE;;;;;;;;OAQG;IACH,OAAO,CAAC,iBAAiB;IASzB;;;;;;;;;;;;OAYG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAIzC;;;;;;;OAOG;IACG,kBAAkB,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAWxD"}
|
|
@@ -36,8 +36,7 @@ exports.CrossTenantGrantReadService = void 0;
|
|
|
36
36
|
*/
|
|
37
37
|
const common_1 = require("@nestjs/common");
|
|
38
38
|
const engine_1 = require("@astram/engine");
|
|
39
|
-
const
|
|
40
|
-
const APP = 'shield_app';
|
|
39
|
+
const schema_js_1 = require("../../schema.js");
|
|
41
40
|
/**
|
|
42
41
|
* Parse `ctg_allowed_actions` to a `string[]`, accepting BOTH an already-parsed array
|
|
43
42
|
* (production — the mariadb driver auto-parses the JSON column) and a JSON string
|
|
@@ -99,13 +98,14 @@ const SELECT_COLUMNS = `
|
|
|
99
98
|
ctg.ctg_expires_at_ms,
|
|
100
99
|
ctg.ctg_created_at
|
|
101
100
|
`;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
LEFT JOIN \`${
|
|
106
|
-
LEFT JOIN \`${
|
|
107
|
-
LEFT JOIN \`${
|
|
108
|
-
LEFT JOIN \`${
|
|
101
|
+
/** The FROM/JOIN block every read shares, over the resolved framework names. */
|
|
102
|
+
const joinsFor = ({ app, authz }) => `
|
|
103
|
+
FROM \`${authz}\`.\`cross_tenant_grant\` ctg
|
|
104
|
+
LEFT JOIN \`${app}\`.\`companies\` owner_co ON owner_co.c_uuid = ctg.ctg_owning_tenant_uuid
|
|
105
|
+
LEFT JOIN \`${app}\`.\`companies\` grantee_co ON grantee_co.c_uuid = ctg.ctg_grantee_tenant_uuid
|
|
106
|
+
LEFT JOIN \`${app}\`.\`users\` gu ON ctg.ctg_grantee_kind = 'user' AND gu.u_uuid = ctg.ctg_grantee_uuid
|
|
107
|
+
LEFT JOIN \`${app}\`.\`roles\` gr ON ctg.ctg_grantee_kind = 'role' AND gr.r_uuid = ctg.ctg_grantee_uuid
|
|
108
|
+
LEFT JOIN \`${app}\`.\`users\` offerer ON offerer.u_uuid = ctg.ctg_offered_by
|
|
109
109
|
`;
|
|
110
110
|
let CrossTenantGrantReadService = class CrossTenantGrantReadService {
|
|
111
111
|
pool;
|
|
@@ -120,16 +120,21 @@ let CrossTenantGrantReadService = class CrossTenantGrantReadService {
|
|
|
120
120
|
* surface uses, so a future tenantScoped type is picked up the same way it becomes
|
|
121
121
|
* shareable.
|
|
122
122
|
* @param actionsByType per-typeKey action vocabulary for the offerable-types read
|
|
123
|
-
* (decision 1: "the actions per type come from the type's own action set").
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
123
|
+
* (decision 1: "the actions per type come from the type's own action set"). The
|
|
124
|
+
* module passes its registration-based lookup (`effectiveActions` over the same
|
|
125
|
+
* `[...identityObjects, ...extraObjects]`, ADR-0038): CRUD plus a type's declared
|
|
126
|
+
* extras, or exactly its declared list for `crudActions: false` (e.g.
|
|
127
|
+
* `cross_tenant_grant` -> share/accept/revoke) — the single source every
|
|
128
|
+
* action-vocabulary consumer uses, so a per-type set can never drift between callers.
|
|
128
129
|
*/
|
|
129
|
-
|
|
130
|
+
/** The shared FROM/JOIN block, built once from the resolved names. */
|
|
131
|
+
joins;
|
|
132
|
+
/** (`schemas`: resolved framework names; absent ⇒ resolved from the namespace, once, here.) */
|
|
133
|
+
constructor(pool, offerableTypeInputs, actionsByType, schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
|
|
130
134
|
this.pool = pool;
|
|
131
135
|
this.offerableTypeInputs = offerableTypeInputs;
|
|
132
136
|
this.actionsByType = actionsByType;
|
|
137
|
+
this.joins = joinsFor(schemas);
|
|
133
138
|
}
|
|
134
139
|
/**
|
|
135
140
|
* Pending offers sitting in the CALLER's tenant inbox: `ctg_grantee_tenant_uuid =
|
|
@@ -141,7 +146,7 @@ let CrossTenantGrantReadService = class CrossTenantGrantReadService {
|
|
|
141
146
|
if (!tenantId)
|
|
142
147
|
return [];
|
|
143
148
|
const rows = (await this.pool.query(`SELECT ${SELECT_COLUMNS}
|
|
144
|
-
${
|
|
149
|
+
${this.joins}
|
|
145
150
|
WHERE ctg.ctg_grantee_tenant_uuid = ?
|
|
146
151
|
AND ctg.ctg_status = 'offered'
|
|
147
152
|
AND ctg.ctg_expires_at_ms > ?
|
|
@@ -169,7 +174,7 @@ let CrossTenantGrantReadService = class CrossTenantGrantReadService {
|
|
|
169
174
|
if (!tenantId)
|
|
170
175
|
return [];
|
|
171
176
|
const rows = (await this.pool.query(`SELECT ${SELECT_COLUMNS}
|
|
172
|
-
${
|
|
177
|
+
${this.joins}
|
|
173
178
|
WHERE ctg.ctg_owning_tenant_uuid = ?
|
|
174
179
|
ORDER BY ctg.ctg_created_at DESC`, [tenantId]));
|
|
175
180
|
return rows.map((row) => ({
|
|
@@ -198,7 +203,7 @@ let CrossTenantGrantReadService = class CrossTenantGrantReadService {
|
|
|
198
203
|
if (!tenantId)
|
|
199
204
|
return [];
|
|
200
205
|
const rows = (await this.pool.query(`SELECT ${SELECT_COLUMNS}
|
|
201
|
-
${
|
|
206
|
+
${this.joins}
|
|
202
207
|
WHERE ctg.ctg_grantee_tenant_uuid = ?
|
|
203
208
|
AND ctg.ctg_status = 'accepted'
|
|
204
209
|
ORDER BY ctg.ctg_expires_at_ms ASC`, [tenantId]));
|
|
@@ -272,5 +277,5 @@ let CrossTenantGrantReadService = class CrossTenantGrantReadService {
|
|
|
272
277
|
exports.CrossTenantGrantReadService = CrossTenantGrantReadService;
|
|
273
278
|
exports.CrossTenantGrantReadService = CrossTenantGrantReadService = __decorate([
|
|
274
279
|
(0, common_1.Injectable)(),
|
|
275
|
-
__metadata("design:paramtypes", [Object, Array, Function])
|
|
280
|
+
__metadata("design:paramtypes", [Object, Array, Function, Object])
|
|
276
281
|
], CrossTenantGrantReadService);
|