@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
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { Pool } from 'mariadb';
|
|
15
15
|
import type { PermissionFactsPort, CoarseGrants, ModuleRegistry } from '@astram/core';
|
|
16
|
+
import { type IdentitySchemaNames } from '../../schema.js';
|
|
16
17
|
/**
|
|
17
18
|
* Expand raw grant strings (exact or wildcard) to the set of EXACT registered keys.
|
|
18
19
|
* Bounded to `allKeys` — a wildcard can never yield a key that isn't registered.
|
|
@@ -31,10 +32,11 @@ export declare function expandGrantKeys(rawKeys: string[], allKeys: string[]): s
|
|
|
31
32
|
* window read. Making THIS function kind-aware gives all three callers correct grants uniformly
|
|
32
33
|
* (one definition of "a principal's roles"), preserving the single `decide()` path.
|
|
33
34
|
*
|
|
35
|
+
* @param schemas the resolved framework names (the tables live in `schemas.app`).
|
|
34
36
|
* @param now injectable clock (epoch-ms), defaults to `Date.now` — bound as a `?`
|
|
35
37
|
* integer against the BIGINT `mp_effective_*_ms` columns (timezone-immune).
|
|
36
38
|
*/
|
|
37
|
-
export declare function readRoleIdsForTenant(pool: Pool, principalId: string, tenantId: string, now?: () => number): Promise<string[]>;
|
|
39
|
+
export declare function readRoleIdsForTenant(pool: Pool, principalId: string, tenantId: string, schemas: IdentitySchemaNames, now?: () => number): Promise<string[]>;
|
|
38
40
|
/**
|
|
39
41
|
* The next instant (epoch-ms, strictly after `now`) at which this USER's in-effect roles in
|
|
40
42
|
* `tenantId` change by themselves — the earliest future `mp_effective_from_ms` (a posting starts)
|
|
@@ -42,7 +44,7 @@ export declare function readRoleIdsForTenant(pool: Pool, principalId: string, te
|
|
|
42
44
|
* these moments, so cached facts must expire there (ADR-0035). `null` when there is none, and for
|
|
43
45
|
* a service account (its roles have no windows). A query failure propagates (fail closed).
|
|
44
46
|
*/
|
|
45
|
-
export declare function readNextPostingBoundaryMs(pool: Pool, principalId: string, tenantId: string, now?: () => number): Promise<number | null>;
|
|
47
|
+
export declare function readNextPostingBoundaryMs(pool: Pool, principalId: string, tenantId: string, schemas: IdentitySchemaNames, now?: () => number): Promise<number | null>;
|
|
46
48
|
export declare class DbPermissionFactsAdapter implements PermissionFactsPort {
|
|
47
49
|
private readonly pool;
|
|
48
50
|
private readonly registry;
|
|
@@ -52,13 +54,17 @@ export declare class DbPermissionFactsAdapter implements PermissionFactsPort {
|
|
|
52
54
|
* Injectable so a test can fix the clock relative to a seeded expired/active posting.
|
|
53
55
|
*/
|
|
54
56
|
private readonly now;
|
|
57
|
+
/** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
|
|
58
|
+
private readonly schemas;
|
|
55
59
|
constructor(pool: Pool, registry: ModuleRegistry,
|
|
56
60
|
/**
|
|
57
61
|
* Injectable clock (epoch-ms) for the posting effective-window compare. Defaults to
|
|
58
62
|
* `Date.now`. Bound as a `?` integer against the BIGINT `_ms` columns — timezone-immune.
|
|
59
63
|
* Injectable so a test can fix the clock relative to a seeded expired/active posting.
|
|
60
64
|
*/
|
|
61
|
-
now?: () => number
|
|
65
|
+
now?: () => number,
|
|
66
|
+
/** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
|
|
67
|
+
schemas?: IdentitySchemaNames);
|
|
62
68
|
currentPermissionsVersion(_principalId: string, tenantId: string): Promise<number>;
|
|
63
69
|
/**
|
|
64
70
|
* The coarse grants + (ADR-0035) the LIVE role ids they were derived from + the time bound of
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db-permission-facts.adapter.d.ts","sourceRoot":"","sources":["../../../src/features/authz/db-permission-facts.adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"db-permission-facts.adapter.d.ts","sourceRoot":"","sources":["../../../src/features/authz/db-permission-facts.adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEtF,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAIpF;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAe9E;AA2CD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,mBAAmB,EAC5B,GAAG,GAAE,MAAM,MAAiB,GAC3B,OAAO,CAAC,MAAM,EAAE,CAAC,CAqBnB;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,mBAAmB,EAC5B,GAAG,GAAE,MAAM,MAAiB,GAC3B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkBxB;AAED,qBAAa,wBAAyB,YAAW,mBAAmB;IAEhE,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,mFAAmF;IACnF,OAAO,CAAC,QAAQ,CAAC,OAAO;gBATP,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,cAAc;IACzC;;;;OAIG;IACc,GAAG,GAAE,MAAM,MAAiB;IAC7C,mFAAmF;IAClE,OAAO,GAAE,mBAA+C;IAGrE,yBAAyB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAaxF;;;;;;;OAOG;IACG,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;YA6BtE,WAAW;IASzB;;;OAGG;YACW,WAAW;YAIX,aAAa;CAS5B"}
|
|
@@ -5,6 +5,7 @@ exports.expandGrantKeys = expandGrantKeys;
|
|
|
5
5
|
exports.readRoleIdsForTenant = readRoleIdsForTenant;
|
|
6
6
|
exports.readNextPostingBoundaryMs = readNextPostingBoundaryMs;
|
|
7
7
|
const core_1 = require("@astram/core");
|
|
8
|
+
const schema_js_1 = require("../../schema.js");
|
|
8
9
|
const MODULE_PREFIX = 'identity';
|
|
9
10
|
/**
|
|
10
11
|
* Expand raw grant strings (exact or wildcard) to the set of EXACT registered keys.
|
|
@@ -58,8 +59,8 @@ function parseRoleIdsJson(v) {
|
|
|
58
59
|
* membership path). A DISABLED account returns `[]` (in-scope but no roles → deny everything —
|
|
59
60
|
* defense-in-depth alongside the key's own revoke check).
|
|
60
61
|
*/
|
|
61
|
-
async function readServiceAccountRoleIds(pool, principalId, tenantId) {
|
|
62
|
-
const rows = (await pool.query(`SELECT sa_role_ids, sa_status FROM
|
|
62
|
+
async function readServiceAccountRoleIds(pool, principalId, tenantId, schemas) {
|
|
63
|
+
const rows = (await pool.query(`SELECT sa_role_ids, sa_status FROM \`${schemas.app}\`.\`service_account\`
|
|
63
64
|
WHERE sa_id = ? AND sa_tenant_id = ? LIMIT 1`, [principalId, tenantId]));
|
|
64
65
|
const row = rows[0];
|
|
65
66
|
if (row === undefined)
|
|
@@ -78,12 +79,13 @@ async function readServiceAccountRoleIds(pool, principalId, tenantId) {
|
|
|
78
79
|
* window read. Making THIS function kind-aware gives all three callers correct grants uniformly
|
|
79
80
|
* (one definition of "a principal's roles"), preserving the single `decide()` path.
|
|
80
81
|
*
|
|
82
|
+
* @param schemas the resolved framework names (the tables live in `schemas.app`).
|
|
81
83
|
* @param now injectable clock (epoch-ms), defaults to `Date.now` — bound as a `?`
|
|
82
84
|
* integer against the BIGINT `mp_effective_*_ms` columns (timezone-immune).
|
|
83
85
|
*/
|
|
84
|
-
async function readRoleIdsForTenant(pool, principalId, tenantId, now = Date.now) {
|
|
86
|
+
async function readRoleIdsForTenant(pool, principalId, tenantId, schemas, now = Date.now) {
|
|
85
87
|
// Service-account path (ADR-0033): its roles live on the account row, not memberships.
|
|
86
|
-
const saRoles = await readServiceAccountRoleIds(pool, principalId, tenantId);
|
|
88
|
+
const saRoles = await readServiceAccountRoleIds(pool, principalId, tenantId, schemas);
|
|
87
89
|
if (saRoles !== null)
|
|
88
90
|
return saRoles;
|
|
89
91
|
// Human path: roles from the active membership's in-effect postings — of an ACTIVE account only
|
|
@@ -92,9 +94,9 @@ async function readRoleIdsForTenant(pool, principalId, tenantId, now = Date.now)
|
|
|
92
94
|
// has a users row).
|
|
93
95
|
const nowMs = now();
|
|
94
96
|
const rows = (await pool.query(`SELECT DISTINCT mp.mp_role_uuid AS role_uuid
|
|
95
|
-
FROM
|
|
96
|
-
JOIN
|
|
97
|
-
JOIN
|
|
97
|
+
FROM \`${schemas.app}\`.\`company_membership\` cm
|
|
98
|
+
JOIN \`${schemas.app}\`.\`users\` u ON u.u_uuid = cm.cm_user_uuid AND u.u_status = 'active'
|
|
99
|
+
JOIN \`${schemas.app}\`.\`membership_posting\` mp ON mp.mp_membership_uuid = cm.cm_uuid
|
|
98
100
|
WHERE cm.cm_user_uuid = ? AND cm.cm_company_uuid = ? AND cm.cm_is_active = 1
|
|
99
101
|
AND (mp.mp_effective_from_ms IS NULL OR mp.mp_effective_from_ms <= ?)
|
|
100
102
|
AND (mp.mp_effective_until_ms IS NULL OR mp.mp_effective_until_ms > ?)`, [principalId, tenantId, nowMs, nowMs]));
|
|
@@ -107,7 +109,7 @@ async function readRoleIdsForTenant(pool, principalId, tenantId, now = Date.now)
|
|
|
107
109
|
* these moments, so cached facts must expire there (ADR-0035). `null` when there is none, and for
|
|
108
110
|
* a service account (its roles have no windows). A query failure propagates (fail closed).
|
|
109
111
|
*/
|
|
110
|
-
async function readNextPostingBoundaryMs(pool, principalId, tenantId, now = Date.now) {
|
|
112
|
+
async function readNextPostingBoundaryMs(pool, principalId, tenantId, schemas, now = Date.now) {
|
|
111
113
|
const nowMs = now();
|
|
112
114
|
const rows = (await pool.query(`SELECT MIN(CASE
|
|
113
115
|
WHEN mp.mp_effective_from_ms IS NOT NULL AND mp.mp_effective_from_ms > ?
|
|
@@ -115,8 +117,8 @@ async function readNextPostingBoundaryMs(pool, principalId, tenantId, now = Date
|
|
|
115
117
|
WHEN mp.mp_effective_until_ms IS NOT NULL AND mp.mp_effective_until_ms > ?
|
|
116
118
|
THEN mp.mp_effective_until_ms
|
|
117
119
|
END) AS next_boundary_ms
|
|
118
|
-
FROM
|
|
119
|
-
JOIN
|
|
120
|
+
FROM \`${schemas.app}\`.\`company_membership\` cm
|
|
121
|
+
JOIN \`${schemas.app}\`.\`membership_posting\` mp ON mp.mp_membership_uuid = cm.cm_uuid
|
|
120
122
|
WHERE cm.cm_user_uuid = ? AND cm.cm_company_uuid = ? AND cm.cm_is_active = 1`, [nowMs, nowMs, principalId, tenantId]));
|
|
121
123
|
const raw = rows[0]?.next_boundary_ms;
|
|
122
124
|
if (raw === null || raw === undefined)
|
|
@@ -128,20 +130,24 @@ class DbPermissionFactsAdapter {
|
|
|
128
130
|
pool;
|
|
129
131
|
registry;
|
|
130
132
|
now;
|
|
133
|
+
schemas;
|
|
131
134
|
constructor(pool, registry,
|
|
132
135
|
/**
|
|
133
136
|
* Injectable clock (epoch-ms) for the posting effective-window compare. Defaults to
|
|
134
137
|
* `Date.now`. Bound as a `?` integer against the BIGINT `_ms` columns — timezone-immune.
|
|
135
138
|
* Injectable so a test can fix the clock relative to a seeded expired/active posting.
|
|
136
139
|
*/
|
|
137
|
-
now = Date.now
|
|
140
|
+
now = Date.now,
|
|
141
|
+
/** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
|
|
142
|
+
schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
|
|
138
143
|
this.pool = pool;
|
|
139
144
|
this.registry = registry;
|
|
140
145
|
this.now = now;
|
|
146
|
+
this.schemas = schemas;
|
|
141
147
|
}
|
|
142
148
|
async currentPermissionsVersion(_principalId, tenantId) {
|
|
143
149
|
try {
|
|
144
|
-
const rows = (await this.pool.query(`SELECT apv_version FROM
|
|
150
|
+
const rows = (await this.pool.query(`SELECT apv_version FROM \`${this.schemas.authz}\`.\`authz_permission_version\`
|
|
145
151
|
WHERE apv_tenant_uuid = ? LIMIT 1`, [tenantId]));
|
|
146
152
|
return rows.length > 0 ? Number(rows[0]?.apv_version) : 0;
|
|
147
153
|
}
|
|
@@ -166,7 +172,7 @@ class DbPermissionFactsAdapter {
|
|
|
166
172
|
version = await this.readVersion(tenantId);
|
|
167
173
|
roleIds = await this.readRoleIds(principalId, tenantId);
|
|
168
174
|
rawKeys = roleIds.length === 0 ? [] : await this.readGrantKeys(roleIds);
|
|
169
|
-
validUntilMs = await readNextPostingBoundaryMs(this.pool, principalId, tenantId, this.now);
|
|
175
|
+
validUntilMs = await readNextPostingBoundaryMs(this.pool, principalId, tenantId, this.schemas, this.now);
|
|
170
176
|
}
|
|
171
177
|
catch (cause) {
|
|
172
178
|
if (cause instanceof core_1.AuthorizationUnavailableError)
|
|
@@ -182,7 +188,7 @@ class DbPermissionFactsAdapter {
|
|
|
182
188
|
};
|
|
183
189
|
}
|
|
184
190
|
async readVersion(tenantId) {
|
|
185
|
-
const rows = (await this.pool.query(`SELECT apv_version FROM
|
|
191
|
+
const rows = (await this.pool.query(`SELECT apv_version FROM \`${this.schemas.authz}\`.\`authz_permission_version\`
|
|
186
192
|
WHERE apv_tenant_uuid = ? LIMIT 1`, [tenantId]));
|
|
187
193
|
return rows.length > 0 ? Number(rows[0]?.apv_version) : 0;
|
|
188
194
|
}
|
|
@@ -191,11 +197,11 @@ class DbPermissionFactsAdapter {
|
|
|
191
197
|
* tenant — delegates to the shared {@link readRoleIdsForTenant} (single source).
|
|
192
198
|
*/
|
|
193
199
|
async readRoleIds(principalId, tenantId) {
|
|
194
|
-
return readRoleIdsForTenant(this.pool, principalId, tenantId, this.now);
|
|
200
|
+
return readRoleIdsForTenant(this.pool, principalId, tenantId, this.schemas, this.now);
|
|
195
201
|
}
|
|
196
202
|
async readGrantKeys(roleIds) {
|
|
197
203
|
const placeholders = roleIds.map(() => '?').join(', ');
|
|
198
|
-
const rows = (await this.pool.query(`SELECT rp_perm_key FROM
|
|
204
|
+
const rows = (await this.pool.query(`SELECT rp_perm_key FROM \`${this.schemas.authz}\`.\`role_permission\`
|
|
199
205
|
WHERE rp_role_uuid IN (${placeholders})`, roleIds));
|
|
200
206
|
return rows.map((r) => r.rp_perm_key);
|
|
201
207
|
}
|
|
@@ -4,6 +4,7 @@ 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';
|
|
7
|
+
import { type IdentitySchemaNames } from '../../schema.js';
|
|
7
8
|
/**
|
|
8
9
|
* The caller's reachable subtree, threaded from `req.principal` (Task 4.2). It scopes the
|
|
9
10
|
* grant-admin reads so a caller sees ONLY roles bound within their subtree — the SAME cached
|
|
@@ -54,6 +55,7 @@ export declare class GrantAdminService {
|
|
|
54
55
|
private readonly bumper;
|
|
55
56
|
private readonly ceiling?;
|
|
56
57
|
private readonly audit?;
|
|
58
|
+
private readonly schemas;
|
|
57
59
|
private readonly bundles;
|
|
58
60
|
/**
|
|
59
61
|
* @param ceiling The ADR-0029 self-escalation ceiling ("only grant what you hold") plus the
|
|
@@ -63,8 +65,9 @@ export declare class GrantAdminService {
|
|
|
63
65
|
* enforces. Root bypasses regardless.
|
|
64
66
|
* @param audit The grant-administration audit sink (D6): written on the grant's own transaction
|
|
65
67
|
* connection, fail-closed. The identity module ALWAYS wires the engine `MariaDbAuditSink`.
|
|
68
|
+
* @param schemas Resolved framework names (absent ⇒ resolved from the namespace, once, here).
|
|
66
69
|
*/
|
|
67
|
-
constructor(pool: Pool, bumper: PermissionVersionBumper, bundleStore?: AccessBundleStore, ceiling?: GrantCeiling | undefined, audit?: AuditPort | undefined);
|
|
70
|
+
constructor(pool: Pool, bumper: PermissionVersionBumper, bundleStore?: AccessBundleStore, ceiling?: GrantCeiling | undefined, audit?: AuditPort | undefined, schemas?: IdentitySchemaNames);
|
|
68
71
|
/** All grantable exact keys (from the catalog) + the computed wildcard set. */
|
|
69
72
|
listCatalog(): Promise<{
|
|
70
73
|
catalog: CatalogRow[];
|
|
@@ -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,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EAAE,YAAY,EAAa,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"grant-admin.service.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-admin.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,8 +8,7 @@ const grant_ceiling_js_1 = require("./grant-ceiling.js");
|
|
|
8
8
|
const role_binding_js_1 = require("./role-binding.js");
|
|
9
9
|
const identity_errors_js_1 = require("../shared/identity-errors.js");
|
|
10
10
|
const lock_service_accounts_js_1 = require("../service-account/lock-service-accounts.js");
|
|
11
|
-
const
|
|
12
|
-
const APP = 'shield_app';
|
|
11
|
+
const schema_js_1 = require("../../schema.js");
|
|
13
12
|
/** The grant-admin actions that REMOVE authority from a role's holders (Phase 8 #2). */
|
|
14
13
|
const REVOKE_ACTIONS = new Set([
|
|
15
14
|
'role.permission_revoked',
|
|
@@ -42,6 +41,7 @@ class GrantAdminService {
|
|
|
42
41
|
bumper;
|
|
43
42
|
ceiling;
|
|
44
43
|
audit;
|
|
44
|
+
schemas;
|
|
45
45
|
bundles;
|
|
46
46
|
/**
|
|
47
47
|
* @param ceiling The ADR-0029 self-escalation ceiling ("only grant what you hold") plus the
|
|
@@ -51,20 +51,22 @@ class GrantAdminService {
|
|
|
51
51
|
* enforces. Root bypasses regardless.
|
|
52
52
|
* @param audit The grant-administration audit sink (D6): written on the grant's own transaction
|
|
53
53
|
* connection, fail-closed. The identity module ALWAYS wires the engine `MariaDbAuditSink`.
|
|
54
|
+
* @param schemas Resolved framework names (absent ⇒ resolved from the namespace, once, here).
|
|
54
55
|
*/
|
|
55
|
-
constructor(pool, bumper, bundleStore, ceiling, audit) {
|
|
56
|
+
constructor(pool, bumper, bundleStore, ceiling, audit, schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
|
|
56
57
|
this.pool = pool;
|
|
57
58
|
this.bumper = bumper;
|
|
58
59
|
this.ceiling = ceiling;
|
|
59
60
|
this.audit = audit;
|
|
60
|
-
this.
|
|
61
|
+
this.schemas = schemas;
|
|
62
|
+
this.bundles = bundleStore ?? new access_bundle_store_js_1.AccessBundleStore(pool, schemas);
|
|
61
63
|
}
|
|
62
64
|
/** All grantable exact keys (from the catalog) + the computed wildcard set. */
|
|
63
65
|
async listCatalog() {
|
|
64
66
|
let rows;
|
|
65
67
|
try {
|
|
66
68
|
rows = (await this.pool.query(`SELECT perm_key, perm_label, perm_module, perm_type_key, perm_action
|
|
67
|
-
FROM \`${
|
|
69
|
+
FROM \`${this.schemas.authz}\`.\`permission\`
|
|
68
70
|
WHERE perm_status = 'active'
|
|
69
71
|
ORDER BY perm_type_key, perm_action`));
|
|
70
72
|
}
|
|
@@ -98,15 +100,15 @@ class GrantAdminService {
|
|
|
98
100
|
const ph = subtreeScope.nodeIds.map(() => '?').join(', ');
|
|
99
101
|
// Empty subtree → no placeholders → the check can't match → fail-closed empty.
|
|
100
102
|
const bound = subtreeScope.nodeIds.length
|
|
101
|
-
? (await this.pool.query(`SELECT 1 FROM \`${
|
|
102
|
-
JOIN \`${
|
|
103
|
+
? (await this.pool.query(`SELECT 1 FROM \`${this.schemas.app}\`.\`membership_posting\` mp
|
|
104
|
+
JOIN \`${this.schemas.app}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid
|
|
103
105
|
WHERE mp.mp_role_uuid = ? AND cm.cm_company_uuid IN (${ph})
|
|
104
106
|
LIMIT 1`, [roleUuid, ...subtreeScope.nodeIds]))
|
|
105
107
|
: [];
|
|
106
108
|
if (bound.length === 0)
|
|
107
109
|
return { grants: [] };
|
|
108
110
|
}
|
|
109
|
-
const rows = (await this.pool.query(`SELECT rp_perm_key FROM \`${
|
|
111
|
+
const rows = (await this.pool.query(`SELECT rp_perm_key FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid = ? ORDER BY rp_perm_key`, [roleUuid]));
|
|
110
112
|
return { grants: rows.map((r) => r.rp_perm_key) };
|
|
111
113
|
}
|
|
112
114
|
catch (cause) {
|
|
@@ -147,11 +149,9 @@ class GrantAdminService {
|
|
|
147
149
|
// Lock the role row FIRST — the posting path locks the same row before it inserts — then
|
|
148
150
|
// re-run the binding rule on THIS connection, so a binding committed by a writer we waited
|
|
149
151
|
// for is seen, and a role that became shared in the meantime is refused (rolled back).
|
|
150
|
-
await conn.query(`SELECT r_uuid FROM \`${
|
|
151
|
-
roleUuid,
|
|
152
|
-
]);
|
|
152
|
+
await conn.query(`SELECT r_uuid FROM \`${this.schemas.app}\`.\`roles\` WHERE r_uuid = ? FOR UPDATE`, [roleUuid]);
|
|
153
153
|
if (this.ceiling && actor.isRoot !== true) {
|
|
154
|
-
grant_ceiling_js_1.GrantCeiling.checkAdministrable(await (0, role_binding_js_1.readRoleBinding)(conn, roleUuid, (0, grant_ceiling_js_1.subtreeOf)(actor)));
|
|
154
|
+
grant_ceiling_js_1.GrantCeiling.checkAdministrable(await (0, role_binding_js_1.readRoleBinding)(conn, roleUuid, (0, grant_ceiling_js_1.subtreeOf)(actor), this.schemas));
|
|
155
155
|
// Phase 8 #2: a revocation strips authority from every holder of the role, so the actor
|
|
156
156
|
// must dominate each of them — checked under the role lock (no holder can appear unseen).
|
|
157
157
|
// Phase 9 #9: that includes the service accounts holding the role, judged as a role
|
|
@@ -196,12 +196,12 @@ class GrantAdminService {
|
|
|
196
196
|
if (!this.audit)
|
|
197
197
|
return;
|
|
198
198
|
const query = (sql, params) => conn.query(sql, params);
|
|
199
|
-
const roleRows = (await query(`SELECT r_name FROM \`${
|
|
199
|
+
const roleRows = (await query(`SELECT r_name FROM \`${this.schemas.app}\`.\`roles\` WHERE r_uuid = ? LIMIT 1`, [roleUuid]));
|
|
200
200
|
const tenants = [...new Set(bumpedTenants)];
|
|
201
201
|
const nameOf = new Map();
|
|
202
202
|
if (tenants.length > 0) {
|
|
203
203
|
const ph = tenants.map(() => '?').join(', ');
|
|
204
|
-
const rows = (await query(`SELECT c_uuid, c_name FROM \`${
|
|
204
|
+
const rows = (await query(`SELECT c_uuid, c_name FROM \`${this.schemas.app}\`.\`companies\` WHERE c_uuid IN (${ph})`, tenants));
|
|
205
205
|
for (const r of rows)
|
|
206
206
|
nameOf.set(r.c_uuid, r.c_name);
|
|
207
207
|
}
|
|
@@ -248,11 +248,11 @@ class GrantAdminService {
|
|
|
248
248
|
const actorUuid = grantor.id;
|
|
249
249
|
await this.writeGrants('role.permission_granted', roleUuid, [key], grantor, async (conn, k) => {
|
|
250
250
|
// Idempotent insert — UNIQUE (rp_role_uuid, rp_perm_key); a duplicate is a no-op.
|
|
251
|
-
await conn.query(`INSERT INTO \`${
|
|
251
|
+
await conn.query(`INSERT INTO \`${this.schemas.authz}\`.\`role_permission\`
|
|
252
252
|
(rp_uuid, rp_role_uuid, rp_perm_key, rp_granted_at, rp_granted_by)
|
|
253
253
|
SELECT UUID(), ?, ?, UTC_TIMESTAMP(), ?
|
|
254
254
|
WHERE NOT EXISTS (
|
|
255
|
-
SELECT 1 FROM \`${
|
|
255
|
+
SELECT 1 FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?
|
|
256
256
|
)`, [roleUuid, k, actorUuid ?? null, roleUuid, k]);
|
|
257
257
|
});
|
|
258
258
|
}
|
|
@@ -267,7 +267,7 @@ class GrantAdminService {
|
|
|
267
267
|
roleUuid = await this.storedRoleUuid(roleUuid);
|
|
268
268
|
await this.assertMayChangeGrants(roleUuid, [key], actor);
|
|
269
269
|
await this.writeGrants('role.permission_revoked', roleUuid, [key], actor, async (conn, k) => {
|
|
270
|
-
await conn.query(`DELETE FROM \`${
|
|
270
|
+
await conn.query(`DELETE FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?`, [roleUuid, k]);
|
|
271
271
|
});
|
|
272
272
|
}
|
|
273
273
|
/**
|
|
@@ -301,16 +301,14 @@ class GrantAdminService {
|
|
|
301
301
|
if (this.ceiling && actor.isRoot !== true) {
|
|
302
302
|
await this.ceiling.assertMayStripRoleFromServiceAccounts(actor, roleUuid, accounts.map((a) => a.sa_tenant_id), 'delete');
|
|
303
303
|
}
|
|
304
|
-
const keys = (await conn.query(`SELECT rp_perm_key FROM \`${
|
|
304
|
+
const keys = (await conn.query(`SELECT rp_perm_key FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid = ? ORDER BY rp_perm_key`, [roleUuid])).map((r) => r.rp_perm_key);
|
|
305
305
|
const bumped = await this.bumper.bumpForRole(roleUuid, conn);
|
|
306
|
-
await conn.query(`DELETE FROM \`${
|
|
307
|
-
roleUuid,
|
|
308
|
-
]);
|
|
306
|
+
await conn.query(`DELETE FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid = ?`, [roleUuid]);
|
|
309
307
|
const nowMs = Date.now();
|
|
310
308
|
for (const a of accounts) {
|
|
311
309
|
const remaining = jsonIdArray(a.sa_role_ids).filter((id) => id !== roleUuid);
|
|
312
310
|
const previous = a.sa_updated_at_ms === null ? 0 : Number(a.sa_updated_at_ms);
|
|
313
|
-
await conn.query(`UPDATE \`${
|
|
311
|
+
await conn.query(`UPDATE \`${this.schemas.app}\`.\`service_account\` SET sa_role_ids = ?, sa_updated_at_ms = ? WHERE sa_id = ?`, [JSON.stringify(remaining), Math.max(nowMs, previous + 1), a.sa_id]);
|
|
314
312
|
}
|
|
315
313
|
await this.auditGrantChange(conn, 'role.permission_revoked', roleUuid, keys, actor, bumped, {
|
|
316
314
|
roleDeleted: true,
|
|
@@ -349,14 +347,11 @@ class GrantAdminService {
|
|
|
349
347
|
async storedRoleUuid(roleUuid) {
|
|
350
348
|
if (typeof roleUuid !== 'string' || roleUuid.length === 0)
|
|
351
349
|
return roleUuid;
|
|
352
|
-
const rows = (await this.pool.query(`SELECT r_uuid FROM \`${
|
|
350
|
+
const rows = (await this.pool.query(`SELECT r_uuid FROM \`${this.schemas.app}\`.\`roles\` WHERE r_uuid = ? LIMIT 1`, [roleUuid]));
|
|
353
351
|
return rows[0]?.r_uuid ?? roleUuid;
|
|
354
352
|
}
|
|
355
353
|
lockAccountsHoldingRole(conn, roleUuid) {
|
|
356
|
-
return (0, lock_service_accounts_js_1.lockServiceAccountsWhere)(conn, 'sa_id, sa_tenant_id, sa_role_ids, sa_updated_at_ms', {
|
|
357
|
-
sql: 'JSON_CONTAINS(sa_role_ids, JSON_QUOTE(?))',
|
|
358
|
-
params: [roleUuid],
|
|
359
|
-
});
|
|
354
|
+
return (0, lock_service_accounts_js_1.lockServiceAccountsWhere)(conn, 'sa_id, sa_tenant_id, sa_role_ids, sa_updated_at_ms', { sql: 'JSON_CONTAINS(sa_role_ids, JSON_QUOTE(?))', params: [roleUuid] }, this.schemas);
|
|
360
355
|
}
|
|
361
356
|
// ── Access bundles ────────────────────────────────────────────────────────
|
|
362
357
|
/** All active bundles + their resolved atomic keys, for the Access UI. */
|
|
@@ -385,11 +380,11 @@ class GrantAdminService {
|
|
|
385
380
|
const actorUuid = grantor.id;
|
|
386
381
|
await this.writeGrants('role.bundle_granted', roleUuid, keys, grantor, async (conn, key) => {
|
|
387
382
|
// Idempotent — UNIQUE (rp_role_uuid, rp_perm_key); a duplicate is a no-op.
|
|
388
|
-
await conn.query(`INSERT INTO \`${
|
|
383
|
+
await conn.query(`INSERT INTO \`${this.schemas.authz}\`.\`role_permission\`
|
|
389
384
|
(rp_uuid, rp_role_uuid, rp_perm_key, rp_granted_at, rp_granted_by)
|
|
390
385
|
SELECT UUID(), ?, ?, UTC_TIMESTAMP(), ?
|
|
391
386
|
WHERE NOT EXISTS (
|
|
392
|
-
SELECT 1 FROM \`${
|
|
387
|
+
SELECT 1 FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?
|
|
393
388
|
)`, [roleUuid, key, actorUuid ?? null, roleUuid, key]);
|
|
394
389
|
}, { bundle: bundleKey });
|
|
395
390
|
}
|
|
@@ -405,7 +400,7 @@ class GrantAdminService {
|
|
|
405
400
|
const keys = await this.resolveBundleKeysOrThrow(bundleKey);
|
|
406
401
|
await this.assertMayChangeGrants(roleUuid, [...keys], actor);
|
|
407
402
|
await this.writeGrants('role.bundle_revoked', roleUuid, keys, actor, async (conn, key) => {
|
|
408
|
-
await conn.query(`DELETE FROM \`${
|
|
403
|
+
await conn.query(`DELETE FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?`, [roleUuid, key]);
|
|
409
404
|
}, { bundle: bundleKey });
|
|
410
405
|
}
|
|
411
406
|
/** Resolve a bundle key → its atomic keys; throw BadKeyError when unknown/orphaned. */
|
|
@@ -451,14 +446,14 @@ class GrantAdminService {
|
|
|
451
446
|
// identical on BOTH the count and page queries so they can never disagree.
|
|
452
447
|
const scope = subtreeScope.isRoot
|
|
453
448
|
? { sql: '', params: [] }
|
|
454
|
-
: (0, role_binding_js_1.subtreeExists)(subtreeScope.nodeIds);
|
|
449
|
+
: (0, role_binding_js_1.subtreeExists)(subtreeScope.nodeIds, this.schemas);
|
|
455
450
|
// Distinct granted roles (a role appears iff it has ≥1 role_permission row), filtered
|
|
456
451
|
// by name search. Bind the LIKE term as a param (wildcards escaped).
|
|
457
452
|
const likeClause = search ? `AND r.r_name LIKE ?` : '';
|
|
458
453
|
const likeArgs = search ? [`%${search.replace(/[%_\\]/g, '\\$&')}%`] : [];
|
|
459
454
|
const [{ total = 0 } = {}] = (await this.pool.query(`SELECT COUNT(DISTINCT rp.rp_role_uuid) AS total
|
|
460
|
-
FROM \`${
|
|
461
|
-
JOIN
|
|
455
|
+
FROM \`${this.schemas.authz}\`.\`role_permission\` rp
|
|
456
|
+
JOIN \`${this.schemas.app}\`.\`roles\` r ON r.r_uuid = rp.rp_role_uuid
|
|
462
457
|
WHERE 1 = 1${scope.sql} ${likeClause}`, [...scope.params, ...likeArgs]));
|
|
463
458
|
// The page of role uuids + name + latest-grant metadata. name/grantedAt sort in SQL;
|
|
464
459
|
// permCount falls back to a stable name sort here (re-sorted after aggregation).
|
|
@@ -466,8 +461,8 @@ class GrantAdminService {
|
|
|
466
461
|
const pageRoles = (await this.pool.query(`SELECT rp.rp_role_uuid AS role_uuid,
|
|
467
462
|
r.r_name AS role_name,
|
|
468
463
|
MAX(rp.rp_granted_at) AS latest_at
|
|
469
|
-
FROM \`${
|
|
470
|
-
JOIN
|
|
464
|
+
FROM \`${this.schemas.authz}\`.\`role_permission\` rp
|
|
465
|
+
JOIN \`${this.schemas.app}\`.\`roles\` r ON r.r_uuid = rp.rp_role_uuid
|
|
471
466
|
WHERE 1 = 1${scope.sql} ${likeClause}
|
|
472
467
|
GROUP BY rp.rp_role_uuid, r.r_name
|
|
473
468
|
ORDER BY ${orderCol} ${dir}, r.r_name ASC
|
|
@@ -482,8 +477,8 @@ class GrantAdminService {
|
|
|
482
477
|
rp.rp_perm_key AS perm_key,
|
|
483
478
|
rp.rp_granted_at AS granted_at,
|
|
484
479
|
u.u_username AS granted_by_name
|
|
485
|
-
FROM \`${
|
|
486
|
-
LEFT JOIN
|
|
480
|
+
FROM \`${this.schemas.authz}\`.\`role_permission\` rp
|
|
481
|
+
LEFT JOIN \`${this.schemas.app}\`.\`users\` u ON u.u_uuid = rp.rp_granted_by
|
|
487
482
|
WHERE rp.rp_role_uuid IN (${ph})`, roleUuids));
|
|
488
483
|
const byRole = new Map();
|
|
489
484
|
for (const row of grantRows) {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import type { Pool } from 'mariadb';
|
|
16
16
|
import type { ModuleRegistry, Principal } from '@astram/core';
|
|
17
17
|
import { type RoleBinding } from './role-binding.js';
|
|
18
|
+
import { type IdentitySchemaNames } from '../../schema.js';
|
|
18
19
|
/** The grantor exceeds their own authority — cannot grant a key they do not hold (→ 403). */
|
|
19
20
|
export declare class GrantCeilingError extends Error {
|
|
20
21
|
constructor(key: string, message?: string);
|
|
@@ -43,6 +44,8 @@ export declare class GrantCeiling {
|
|
|
43
44
|
private readonly registry;
|
|
44
45
|
/** Injectable clock (epoch-ms) for the posting effective-window read; defaults to Date.now. */
|
|
45
46
|
private readonly now;
|
|
47
|
+
/** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
|
|
48
|
+
private readonly schemas;
|
|
46
49
|
private readonly rootIds;
|
|
47
50
|
constructor(pool: Pool, registry: ModuleRegistry,
|
|
48
51
|
/**
|
|
@@ -53,7 +56,9 @@ export declare class GrantCeiling {
|
|
|
53
56
|
*/
|
|
54
57
|
rootIds: readonly string[] | null,
|
|
55
58
|
/** Injectable clock (epoch-ms) for the posting effective-window read; defaults to Date.now. */
|
|
56
|
-
now?: () => number
|
|
59
|
+
now?: () => number,
|
|
60
|
+
/** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
|
|
61
|
+
schemas?: IdentitySchemaNames);
|
|
57
62
|
/** The D1 administer rule over a binding read: used inside and nowhere outside (404 / 403). */
|
|
58
63
|
static checkAdministrable(b: RoleBinding): void;
|
|
59
64
|
/** The #11 assign rule over a binding read: never a role also used outside (403). */
|
|
@@ -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;AAI9D,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"grant-ceiling.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-ceiling.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,8 +9,7 @@ const db_permission_facts_adapter_js_1 = require("./db-permission-facts.adapter.
|
|
|
9
9
|
const role_binding_js_1 = require("./role-binding.js");
|
|
10
10
|
const identity_errors_js_1 = require("../shared/identity-errors.js");
|
|
11
11
|
const service_account_service_js_1 = require("../service-account/service-account.service.js");
|
|
12
|
-
const
|
|
13
|
-
const APP = 'shield_app';
|
|
12
|
+
const schema_js_1 = require("../../schema.js");
|
|
14
13
|
/** The grantor exceeds their own authority — cannot grant a key they do not hold (→ 403). */
|
|
15
14
|
class GrantCeilingError extends Error {
|
|
16
15
|
constructor(key, message) {
|
|
@@ -54,6 +53,7 @@ class GrantCeiling {
|
|
|
54
53
|
pool;
|
|
55
54
|
registry;
|
|
56
55
|
now;
|
|
56
|
+
schemas;
|
|
57
57
|
rootIds;
|
|
58
58
|
constructor(pool, registry,
|
|
59
59
|
/**
|
|
@@ -64,10 +64,13 @@ class GrantCeiling {
|
|
|
64
64
|
*/
|
|
65
65
|
rootIds,
|
|
66
66
|
/** Injectable clock (epoch-ms) for the posting effective-window read; defaults to Date.now. */
|
|
67
|
-
now = Date.now
|
|
67
|
+
now = Date.now,
|
|
68
|
+
/** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
|
|
69
|
+
schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
|
|
68
70
|
this.pool = pool;
|
|
69
71
|
this.registry = registry;
|
|
70
72
|
this.now = now;
|
|
73
|
+
this.schemas = schemas;
|
|
71
74
|
if (rootIds !== null && !Array.isArray(rootIds)) {
|
|
72
75
|
throw new TypeError('GrantCeiling: rootIds is required (the root allowlist, or null when none is configured)');
|
|
73
76
|
}
|
|
@@ -96,7 +99,7 @@ class GrantCeiling {
|
|
|
96
99
|
if (roleIds.length === 0)
|
|
97
100
|
return [];
|
|
98
101
|
const ph = roleIds.map(() => '?').join(', ');
|
|
99
|
-
const rows = (await this.pool.query(`SELECT rp_perm_key FROM \`${
|
|
102
|
+
const rows = (await this.pool.query(`SELECT rp_perm_key FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid IN (${ph})`, [...roleIds]));
|
|
100
103
|
return rows.map((r) => r.rp_perm_key);
|
|
101
104
|
}
|
|
102
105
|
/** Run a store read, mapping any non-domain failure to AuthorizationUnavailableError (503). */
|
|
@@ -118,11 +121,11 @@ class GrantCeiling {
|
|
|
118
121
|
*/
|
|
119
122
|
async effectiveKeys(grantor) {
|
|
120
123
|
try {
|
|
121
|
-
const roleIds = await (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(this.pool, grantor.id, grantor.tenantId, this.now);
|
|
124
|
+
const roleIds = await (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(this.pool, grantor.id, grantor.tenantId, this.schemas, this.now);
|
|
122
125
|
if (roleIds.length === 0)
|
|
123
126
|
return new Set();
|
|
124
127
|
const ph = roleIds.map(() => '?').join(', ');
|
|
125
|
-
const rows = (await this.pool.query(`SELECT rp_perm_key FROM \`${
|
|
128
|
+
const rows = (await this.pool.query(`SELECT rp_perm_key FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid IN (${ph})`, roleIds));
|
|
126
129
|
const raw = rows.map((r) => r.rp_perm_key);
|
|
127
130
|
const registered = this.registry.listActions().map((a) => a.permissionKey);
|
|
128
131
|
return new Set((0, db_permission_facts_adapter_js_1.expandGrantKeys)(raw, registered));
|
|
@@ -198,13 +201,13 @@ class GrantCeiling {
|
|
|
198
201
|
}
|
|
199
202
|
const subtree = new Set(subtreeOf(actor));
|
|
200
203
|
const { companies, roleIds, superuser, storedId } = await this.facts('target memberships', async () => {
|
|
201
|
-
const su = (await this.pool.query(`SELECT u_uuid, u_is_platform_superuser AS su FROM \`${
|
|
204
|
+
const su = (await this.pool.query(`SELECT u_uuid, u_is_platform_superuser AS su FROM \`${this.schemas.app}\`.\`users\` WHERE u_uuid = ? LIMIT 1`, [targetUserId]));
|
|
202
205
|
const memberships = (await this.pool.query(`SELECT DISTINCT cm_company_uuid AS company
|
|
203
|
-
FROM \`${
|
|
206
|
+
FROM \`${this.schemas.app}\`.\`company_membership\`
|
|
204
207
|
WHERE cm_user_uuid = ?`, [targetUserId]));
|
|
205
208
|
const postings = (await this.pool.query(`SELECT DISTINCT mp.mp_role_uuid AS role_uuid
|
|
206
|
-
FROM \`${
|
|
207
|
-
JOIN \`${
|
|
209
|
+
FROM \`${this.schemas.app}\`.\`membership_posting\` mp
|
|
210
|
+
JOIN \`${this.schemas.app}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid
|
|
208
211
|
WHERE cm.cm_user_uuid = ?`, [targetUserId]));
|
|
209
212
|
return {
|
|
210
213
|
companies: memberships.map((m) => m.company),
|
|
@@ -253,8 +256,8 @@ class GrantCeiling {
|
|
|
253
256
|
return;
|
|
254
257
|
const holders = await this.facts('role holders', async () => {
|
|
255
258
|
const rows = (await this.pool.query(`SELECT DISTINCT cm.cm_user_uuid AS holder
|
|
256
|
-
FROM \`${
|
|
257
|
-
JOIN \`${
|
|
259
|
+
FROM \`${this.schemas.app}\`.\`membership_posting\` mp
|
|
260
|
+
JOIN \`${this.schemas.app}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid
|
|
258
261
|
WHERE mp.mp_role_uuid = ?`, [roleUuid]));
|
|
259
262
|
return rows.map((r) => r.holder);
|
|
260
263
|
});
|
|
@@ -298,7 +301,7 @@ class GrantCeiling {
|
|
|
298
301
|
const nodes = (0, service_account_service_js_1.ceilingOf)(admin, []);
|
|
299
302
|
if (accountTenantIds.some((t) => !(0, service_account_service_js_1.ceilingAllowsNode)(nodes, t)))
|
|
300
303
|
throw refuse();
|
|
301
|
-
const held = await this.facts('held roles', () => (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(this.pool, actor.id, actor.tenantId, this.now));
|
|
304
|
+
const held = await this.facts('held roles', () => (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(this.pool, actor.id, actor.tenantId, this.schemas, this.now));
|
|
302
305
|
if (!(0, service_account_service_js_1.ceilingAllowsRole)((0, service_account_service_js_1.ceilingOf)(admin, held), roleUuid))
|
|
303
306
|
throw refuse();
|
|
304
307
|
}
|
|
@@ -328,7 +331,7 @@ class GrantCeiling {
|
|
|
328
331
|
throw refuse();
|
|
329
332
|
if (accounts.every((a) => a.roleIds.length === 0))
|
|
330
333
|
return;
|
|
331
|
-
const held = await this.facts('held roles', () => (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(this.pool, actor.id, actor.tenantId, this.now));
|
|
334
|
+
const held = await this.facts('held roles', () => (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(this.pool, actor.id, actor.tenantId, this.schemas, this.now));
|
|
332
335
|
const ceiling = (0, service_account_service_js_1.ceilingOf)(admin, held);
|
|
333
336
|
if (accounts.some((a) => a.roleIds.some((r) => !(0, service_account_service_js_1.ceilingAllowsRole)(ceiling, r)))) {
|
|
334
337
|
throw refuse();
|
|
@@ -350,7 +353,7 @@ class GrantCeiling {
|
|
|
350
353
|
return;
|
|
351
354
|
if (companyUuid === null)
|
|
352
355
|
throw new identity_errors_js_1.IdentityBadRequestError(message);
|
|
353
|
-
const rows = await this.facts('company org units', async () => (await this.pool.query(`SELECT ${unitCol} AS unit FROM \`${
|
|
356
|
+
const rows = await this.facts('company org units', async () => (await this.pool.query(`SELECT ${unitCol} AS unit FROM \`${this.schemas.app}\`.\`${table}\`
|
|
354
357
|
WHERE ${companyCol} = ? AND ${unitCol} IN (${wanted.map(() => '?').join(', ')})`, [companyUuid, ...wanted])));
|
|
355
358
|
const linked = new Set(rows.map((r) => r.unit));
|
|
356
359
|
if (wanted.some((u) => !linked.has(u)))
|
|
@@ -361,7 +364,7 @@ class GrantCeiling {
|
|
|
361
364
|
}
|
|
362
365
|
/** Where `roleUuid` is bound relative to the actor's subtree (postings + service accounts). */
|
|
363
366
|
async roleBinding(actor, roleUuid) {
|
|
364
|
-
return this.facts('role binding', () => (0, role_binding_js_1.readRoleBinding)(this.pool, roleUuid, subtreeOf(actor)));
|
|
367
|
+
return this.facts('role binding', () => (0, role_binding_js_1.readRoleBinding)(this.pool, roleUuid, subtreeOf(actor), this.schemas));
|
|
365
368
|
}
|
|
366
369
|
/**
|
|
367
370
|
* Assert the actor may ADMINISTER the role — edit its grants, rename or delete it (#1, D1
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Pool } from 'mariadb';
|
|
2
2
|
import type { CatalogStore, CatalogPermissionRow } from '@astram/guard';
|
|
3
|
+
import { type IdentitySchemaNames } from '../../schema.js';
|
|
3
4
|
/**
|
|
4
5
|
* CatalogStore impl over shield_authz.permission (raw parameterized SQL, mirroring
|
|
5
6
|
* permission-facts.adapter). The reconciler (framework) calls this; guard does no DB
|
|
@@ -7,7 +8,9 @@ import type { CatalogStore, CatalogPermissionRow } from '@astram/guard';
|
|
|
7
8
|
*/
|
|
8
9
|
export declare class MariaDbCatalogStore implements CatalogStore {
|
|
9
10
|
private readonly pool;
|
|
10
|
-
|
|
11
|
+
private readonly schemas;
|
|
12
|
+
/** @param schemas resolved framework names (absent ⇒ resolved from the namespace, once). */
|
|
13
|
+
constructor(pool: Pool, schemas?: IdentitySchemaNames);
|
|
11
14
|
listPermissions(): Promise<CatalogPermissionRow[]>;
|
|
12
15
|
upsertPermission(row: Omit<CatalogPermissionRow, 'status'>): Promise<boolean>;
|
|
13
16
|
markOrphaned(keys: readonly string[]): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mariadb-catalog-store.d.ts","sourceRoot":"","sources":["../../../src/features/authz/mariadb-catalog-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"mariadb-catalog-store.d.ts","sourceRoot":"","sources":["../../../src/features/authz/mariadb-catalog-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF;;;;GAIG;AACH,qBAAa,mBAAoB,YAAW,YAAY;IAGpD,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAH1B,4FAA4F;gBAEzE,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,mBAA+C;IAGrE,eAAe,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAwBlD,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAmB7E,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IASpD,UAAU,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAQzD"}
|