@astram/identity 0.11.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/cross-tenant-grant.object.d.ts.map +1 -1
- package/dist/cross-tenant-grant.object.js +3 -2
- 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-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 +5 -1
- 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 +18 -14
- 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 +134 -33
- 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/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/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/record-share.object.d.ts.map +1 -1
- package/dist/record-share.object.js +3 -2
- package/dist/schema.d.ts +15 -2
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +17 -2
- 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 +14 -4
- package/migrations/1710000000099-SeedDevIdentity.cjs +14 -6
- package/migrations/1710000000100-SeedDevTenantBAndRoot.cjs +14 -6
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cross-tenant-grant.object.d.ts","sourceRoot":"","sources":["../src/cross-tenant-grant.object.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cross-tenant-grant.object.d.ts","sourceRoot":"","sources":["../src/cross-tenant-grant.object.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBASa,gBAAgB;IAE3B,IAAI,EAAG,MAAM,CAAC;IAGd,gBAAgB,EAAG,MAAM,CAAC;IAG1B,iBAAiB,EAAG,MAAM,CAAC;CAC5B"}
|
|
@@ -12,8 +12,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.CrossTenantGrant = void 0;
|
|
13
13
|
const engine_1 = require("@astram/engine");
|
|
14
14
|
const guard_1 = require("@astram/guard");
|
|
15
|
-
/** The
|
|
16
|
-
|
|
15
|
+
/** The authz-database token `cross_tenant_grant` lives in (migration 1710000000011); the engine
|
|
16
|
+
* resolves it at registration (ADR-0037). */
|
|
17
|
+
const SCHEMA = engine_1.FRAMEWORK_SCHEMA.authz;
|
|
17
18
|
/**
|
|
18
19
|
* `cross_tenant_grant` — an AUTHORITY-ONLY @ObjectType, never CRUD'd through the
|
|
19
20
|
* RecordEngine pipe. Its sole purpose is to give `cross_tenant_grant` an entry in the
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LabelResolver, LabelKind } from '@astram/core';
|
|
2
2
|
import type { AuditQueryable } from './mariadb-audit-reader.js';
|
|
3
|
+
import { type IdentitySchemaNames } from '../../schema.js';
|
|
3
4
|
/**
|
|
4
5
|
* The id->human-label chokepoint (ADR-0012, design §2.2). A deleted/unknown
|
|
5
6
|
* entity resolves to a FALLBACK label (never the id); a directory OUTAGE throws
|
|
@@ -7,7 +8,9 @@ import type { AuditQueryable } from './mariadb-audit-reader.js';
|
|
|
7
8
|
*/
|
|
8
9
|
export declare class DirectoryLabelResolver implements LabelResolver {
|
|
9
10
|
private readonly db;
|
|
10
|
-
|
|
11
|
+
private readonly schemas;
|
|
12
|
+
/** @param schemas resolved framework names (absent ⇒ resolved from the namespace, once). */
|
|
13
|
+
constructor(db: AuditQueryable, schemas?: IdentitySchemaNames);
|
|
11
14
|
resolve(kind: LabelKind, id: string, _tenantId?: string): Promise<string>;
|
|
12
15
|
private fallback;
|
|
13
16
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directory-label-resolver.d.ts","sourceRoot":"","sources":["../../../src/features/audit/directory-label-resolver.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"directory-label-resolver.d.ts","sourceRoot":"","sources":["../../../src/features/audit/directory-label-resolver.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF;;;;GAIG;AACH,qBAAa,sBAAuB,YAAW,aAAa;IAGxD,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAH1B,4FAA4F;gBAEzE,EAAE,EAAE,cAAc,EAClB,OAAO,GAAE,mBAA+C;IAGrE,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA8B/E,OAAO,CAAC,QAAQ;CAMjB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DirectoryLabelResolver = void 0;
|
|
4
|
-
const
|
|
4
|
+
const schema_js_1 = require("../../schema.js");
|
|
5
5
|
/**
|
|
6
6
|
* The id->human-label chokepoint (ADR-0012, design §2.2). A deleted/unknown
|
|
7
7
|
* entity resolves to a FALLBACK label (never the id); a directory OUTAGE throws
|
|
@@ -9,23 +9,26 @@ const APP = 'shield_app';
|
|
|
9
9
|
*/
|
|
10
10
|
class DirectoryLabelResolver {
|
|
11
11
|
db;
|
|
12
|
-
|
|
12
|
+
schemas;
|
|
13
|
+
/** @param schemas resolved framework names (absent ⇒ resolved from the namespace, once). */
|
|
14
|
+
constructor(db, schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
|
|
13
15
|
this.db = db;
|
|
16
|
+
this.schemas = schemas;
|
|
14
17
|
}
|
|
15
18
|
async resolve(kind, id, _tenantId) {
|
|
16
19
|
if (!id)
|
|
17
20
|
return this.fallback(kind);
|
|
18
21
|
switch (kind) {
|
|
19
22
|
case 'principal': {
|
|
20
|
-
const rows = (await this.db.query(`SELECT u_username AS label FROM \`${
|
|
23
|
+
const rows = (await this.db.query(`SELECT u_username AS label FROM \`${this.schemas.app}\`.\`users\` WHERE u_uuid = ? LIMIT 1`, [id]));
|
|
21
24
|
return rows[0]?.label ?? this.fallback(kind);
|
|
22
25
|
}
|
|
23
26
|
case 'tenant': {
|
|
24
|
-
const rows = (await this.db.query(`SELECT c_name AS label FROM \`${
|
|
27
|
+
const rows = (await this.db.query(`SELECT c_name AS label FROM \`${this.schemas.app}\`.\`companies\` WHERE c_uuid = ? LIMIT 1`, [id]));
|
|
25
28
|
return rows[0]?.label ?? this.fallback(kind);
|
|
26
29
|
}
|
|
27
30
|
case 'role': {
|
|
28
|
-
const rows = (await this.db.query(`SELECT r_name AS label FROM \`${
|
|
31
|
+
const rows = (await this.db.query(`SELECT r_name AS label FROM \`${this.schemas.app}\`.\`roles\` WHERE r_uuid = ? LIMIT 1`, [id]));
|
|
29
32
|
return rows[0]?.label ?? this.fallback(kind);
|
|
30
33
|
}
|
|
31
34
|
default:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AuditReader, type AuditEvent } from '@astram/core';
|
|
2
2
|
export interface AuditQueryable {
|
|
3
3
|
query(sql: string, params?: readonly unknown[]): Promise<unknown>;
|
|
4
4
|
}
|
|
@@ -11,6 +11,10 @@ export interface AuditQueryable {
|
|
|
11
11
|
export declare class MariaDbAuditReader implements AuditReader {
|
|
12
12
|
private readonly db;
|
|
13
13
|
private readonly schema;
|
|
14
|
+
/**
|
|
15
|
+
* @param opts.schema the audit database. Absent ⇒ the audit name resolved from
|
|
16
|
+
* `SHIELD_DB_NAMESPACE` here, once at construction (unset ⇒ the default audit database).
|
|
17
|
+
*/
|
|
14
18
|
constructor(db: AuditQueryable, opts?: {
|
|
15
19
|
schema?: string;
|
|
16
20
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mariadb-audit-reader.d.ts","sourceRoot":"","sources":["../../../src/features/audit/mariadb-audit-reader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"mariadb-audit-reader.d.ts","sourceRoot":"","sources":["../../../src/features/audit/mariadb-audit-reader.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAeD;;;;;GAKG;AACH,qBAAa,kBAAmB,YAAW,WAAW;IACpD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAiB;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC;;;OAGG;gBACS,EAAE,EAAE,cAAc,EAAE,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO;IAQxD,IAAI,CAAC,KAAK,EAAE;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;QAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC;IAgClC;;;;;;OAMG;IACG,KAAK,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAY1F;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAuBxB,OAAO,CAAC,OAAO;CA+BhB"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MariaDbAuditReader = void 0;
|
|
4
|
-
|
|
4
|
+
// mariadb-audit-reader.ts
|
|
5
|
+
const core_1 = require("@astram/core");
|
|
5
6
|
const ALL_TENANTS = '*';
|
|
6
7
|
/**
|
|
7
8
|
* The astram3 AuditReader over `shield_audit.audit_log` (design §2.2). Newest-first,
|
|
@@ -12,9 +13,16 @@ const ALL_TENANTS = '*';
|
|
|
12
13
|
class MariaDbAuditReader {
|
|
13
14
|
db;
|
|
14
15
|
schema;
|
|
16
|
+
/**
|
|
17
|
+
* @param opts.schema the audit database. Absent ⇒ the audit name resolved from
|
|
18
|
+
* `SHIELD_DB_NAMESPACE` here, once at construction (unset ⇒ the default audit database).
|
|
19
|
+
*/
|
|
15
20
|
constructor(db, opts = {}) {
|
|
16
21
|
this.db = db;
|
|
17
|
-
this.schema =
|
|
22
|
+
this.schema =
|
|
23
|
+
opts.schema !== undefined
|
|
24
|
+
? (0, core_1.assertSchemaName)(opts.schema, 'MariaDbAuditReader opts.schema')
|
|
25
|
+
: (0, core_1.resolveFrameworkSchemas)(process.env).audit;
|
|
18
26
|
}
|
|
19
27
|
async read(query) {
|
|
20
28
|
const { where, params } = this.buildScopeClause(query);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Pool } from 'mariadb';
|
|
2
2
|
import type { AccessBundleSpec } from '@astram/engine';
|
|
3
|
+
import { type IdentitySchemaNames } from '../../schema.js';
|
|
3
4
|
/** The outcome of one reconcile pass. */
|
|
4
5
|
export interface AccessBundleReconcileReport {
|
|
5
6
|
readonly added: number;
|
|
@@ -24,9 +25,11 @@ export interface AccessBundleReconcileReport {
|
|
|
24
25
|
export declare class AccessBundleReconcileJob {
|
|
25
26
|
private readonly pool;
|
|
26
27
|
private readonly bundles;
|
|
28
|
+
private readonly schemas;
|
|
27
29
|
private readonly store;
|
|
28
30
|
private running;
|
|
29
|
-
|
|
31
|
+
/** @param schemas resolved framework names (absent ⇒ resolved from the namespace, once). */
|
|
32
|
+
constructor(pool: Pool, bundles: readonly AccessBundleSpec[], schemas?: IdentitySchemaNames);
|
|
30
33
|
/** Idempotent single pass. Guarded so overlapping ticks don't double-run. */
|
|
31
34
|
runOnce(_nowIso: string): Promise<AccessBundleReconcileReport>;
|
|
32
35
|
/** The set of permission keys that are ACTIVE in the live catalog. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-bundle-reconcile.job.d.ts","sourceRoot":"","sources":["../../../src/features/authz/access-bundle-reconcile.job.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"access-bundle-reconcile.job.d.ts","sourceRoot":"","sources":["../../../src/features/authz/access-bundle-reconcile.job.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF,yCAAyC;AACzC,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,wBAAwB;IAMjC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAP1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,OAAO,CAAS;IAExB,4FAA4F;gBAEzE,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,SAAS,gBAAgB,EAAE,EACpC,OAAO,GAAE,mBAA+C;IAK3E,6EAA6E;IACvE,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAqDpE,sEAAsE;YACxD,wBAAwB;CAMvC"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AccessBundleReconcileJob = void 0;
|
|
4
4
|
const access_bundle_store_js_1 = require("./access-bundle-store.js");
|
|
5
|
-
const
|
|
5
|
+
const schema_js_1 = require("../../schema.js");
|
|
6
6
|
/**
|
|
7
7
|
* Reconciles the developer-declared `AccessBundleSpec[]` (from
|
|
8
8
|
* `ENGINE_ALL_ACCESS_BUNDLES`) into `shield_authz.access_bundle` (+ `_permission`),
|
|
@@ -20,12 +20,15 @@ const AUTHZ = 'shield_authz';
|
|
|
20
20
|
class AccessBundleReconcileJob {
|
|
21
21
|
pool;
|
|
22
22
|
bundles;
|
|
23
|
+
schemas;
|
|
23
24
|
store;
|
|
24
25
|
running = false;
|
|
25
|
-
|
|
26
|
+
/** @param schemas resolved framework names (absent ⇒ resolved from the namespace, once). */
|
|
27
|
+
constructor(pool, bundles, schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
|
|
26
28
|
this.pool = pool;
|
|
27
29
|
this.bundles = bundles;
|
|
28
|
-
this.
|
|
30
|
+
this.schemas = schemas;
|
|
31
|
+
this.store = new access_bundle_store_js_1.AccessBundleStore(pool, schemas);
|
|
29
32
|
}
|
|
30
33
|
/** Idempotent single pass. Guarded so overlapping ticks don't double-run. */
|
|
31
34
|
async runOnce(_nowIso) {
|
|
@@ -77,7 +80,7 @@ class AccessBundleReconcileJob {
|
|
|
77
80
|
}
|
|
78
81
|
/** The set of permission keys that are ACTIVE in the live catalog. */
|
|
79
82
|
async loadActivePermissionKeys() {
|
|
80
|
-
const rows = (await this.pool.query(`SELECT perm_key FROM \`${
|
|
83
|
+
const rows = (await this.pool.query(`SELECT perm_key FROM \`${this.schemas.authz}\`.\`permission\` WHERE perm_status = 'active'`));
|
|
81
84
|
return new Set(rows.map((r) => r.perm_key));
|
|
82
85
|
}
|
|
83
86
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Pool } from 'mariadb';
|
|
2
2
|
import type { AccessBundleSpec } from '@astram/engine';
|
|
3
|
+
import { type IdentitySchemaNames } from '../../schema.js';
|
|
3
4
|
/** A row from `shield_authz.access_bundle`, resolved to camelCase. */
|
|
4
5
|
export interface AccessBundleRow {
|
|
5
6
|
readonly uuid: string;
|
|
@@ -18,7 +19,9 @@ export interface AccessBundleRow {
|
|
|
18
19
|
*/
|
|
19
20
|
export declare class AccessBundleStore {
|
|
20
21
|
private readonly pool;
|
|
21
|
-
|
|
22
|
+
private readonly schemas;
|
|
23
|
+
/** @param schemas resolved framework names (absent ⇒ resolved from the namespace, once). */
|
|
24
|
+
constructor(pool: Pool, schemas?: IdentitySchemaNames);
|
|
22
25
|
/** All bundles (active + orphaned), for the reconciler's declared-vs-present diff. */
|
|
23
26
|
listBundles(): Promise<AccessBundleRow[]>;
|
|
24
27
|
/** Resolve a bundle's UUID by its business key. `null` when absent. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-bundle-store.d.ts","sourceRoot":"","sources":["../../../src/features/authz/access-bundle-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"access-bundle-store.d.ts","sourceRoot":"","sources":["../../../src/features/authz/access-bundle-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;CACxC;AAED;;;;;;GAMG;AACH,qBAAa,iBAAiB;IAG1B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAH1B,4FAA4F;gBAEzE,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,mBAA+C;IAG3E,sFAAsF;IAChF,WAAW,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAsB/C,uEAAuE;IACjE,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAS5E;;;;;;OAMG;IACG,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAuBnF;;;OAGG;IACG,yBAAyB,IAAI,OAAO,CACxC,KAAK,CAAC;QACJ,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,EAAE,CAAC;KAC1B,CAAC,CACH;IA4CD;;;;;;OAMG;IACG,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,GAAG,WAAW,CAAC;IAuCzF;;;OAGG;IACG,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BvF;;;;OAIG;IACG,YAAY,CAAC,kBAAkB,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAUhG"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AccessBundleStore = void 0;
|
|
4
|
-
const
|
|
4
|
+
const schema_js_1 = require("../../schema.js");
|
|
5
5
|
/**
|
|
6
6
|
* Raw-SQL (parameterized, NO ORM) CRUD over `shield_authz.access_bundle` +
|
|
7
7
|
* `access_bundle_permission`, mirroring `mariadb-catalog-store.ts`.
|
|
@@ -11,13 +11,16 @@ const AUTHZ = 'shield_authz';
|
|
|
11
11
|
*/
|
|
12
12
|
class AccessBundleStore {
|
|
13
13
|
pool;
|
|
14
|
-
|
|
14
|
+
schemas;
|
|
15
|
+
/** @param schemas resolved framework names (absent ⇒ resolved from the namespace, once). */
|
|
16
|
+
constructor(pool, schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
|
|
15
17
|
this.pool = pool;
|
|
18
|
+
this.schemas = schemas;
|
|
16
19
|
}
|
|
17
20
|
/** All bundles (active + orphaned), for the reconciler's declared-vs-present diff. */
|
|
18
21
|
async listBundles() {
|
|
19
22
|
const rows = (await this.pool.query(`SELECT ab_uuid, ab_key, ab_module, ab_label, ab_description, ab_status
|
|
20
|
-
FROM \`${
|
|
23
|
+
FROM \`${this.schemas.authz}\`.\`access_bundle\``));
|
|
21
24
|
return rows.map((r) => ({
|
|
22
25
|
uuid: r.ab_uuid,
|
|
23
26
|
key: r.ab_key,
|
|
@@ -29,7 +32,7 @@ class AccessBundleStore {
|
|
|
29
32
|
}
|
|
30
33
|
/** Resolve a bundle's UUID by its business key. `null` when absent. */
|
|
31
34
|
async resolveBundleUuid(module, key) {
|
|
32
|
-
const rows = (await this.pool.query(`SELECT ab_uuid FROM \`${
|
|
35
|
+
const rows = (await this.pool.query(`SELECT ab_uuid FROM \`${this.schemas.authz}\`.\`access_bundle\`
|
|
33
36
|
WHERE ab_module = ? AND ab_key = ? LIMIT 1`, [module, key]));
|
|
34
37
|
return rows[0]?.ab_uuid ?? null;
|
|
35
38
|
}
|
|
@@ -42,14 +45,14 @@ class AccessBundleStore {
|
|
|
42
45
|
*/
|
|
43
46
|
async resolveActiveBundleKeys(bundleKey) {
|
|
44
47
|
const rows = (await this.pool.query(`SELECT abp.abp_perm_key AS perm_key
|
|
45
|
-
FROM \`${
|
|
46
|
-
JOIN \`${
|
|
48
|
+
FROM \`${this.schemas.authz}\`.\`access_bundle\` ab
|
|
49
|
+
JOIN \`${this.schemas.authz}\`.\`access_bundle_permission\` abp
|
|
47
50
|
ON abp.abp_bundle_uuid = ab.ab_uuid
|
|
48
51
|
WHERE ab.ab_key = ? AND ab.ab_status = 'active'
|
|
49
52
|
ORDER BY abp.abp_perm_key`, [bundleKey]));
|
|
50
53
|
if (rows.length === 0) {
|
|
51
54
|
// Distinguish "no such active bundle" from "active bundle with zero keys".
|
|
52
|
-
const exists = (await this.pool.query(`SELECT 1 FROM \`${
|
|
55
|
+
const exists = (await this.pool.query(`SELECT 1 FROM \`${this.schemas.authz}\`.\`access_bundle\`
|
|
53
56
|
WHERE ab_key = ? AND ab_status = 'active' LIMIT 1`, [bundleKey]));
|
|
54
57
|
if (exists.length === 0)
|
|
55
58
|
return null;
|
|
@@ -64,8 +67,8 @@ class AccessBundleStore {
|
|
|
64
67
|
async listActiveBundlesWithKeys() {
|
|
65
68
|
const rows = (await this.pool.query(`SELECT ab.ab_key AS bundle_key, ab.ab_module AS module, ab.ab_label AS label,
|
|
66
69
|
ab.ab_description AS description, abp.abp_perm_key AS perm_key
|
|
67
|
-
FROM \`${
|
|
68
|
-
LEFT JOIN \`${
|
|
70
|
+
FROM \`${this.schemas.authz}\`.\`access_bundle\` ab
|
|
71
|
+
LEFT JOIN \`${this.schemas.authz}\`.\`access_bundle_permission\` abp
|
|
69
72
|
ON abp.abp_bundle_uuid = ab.ab_uuid
|
|
70
73
|
WHERE ab.ab_status = 'active'
|
|
71
74
|
ORDER BY ab.ab_module, ab.ab_key, abp.abp_perm_key`));
|
|
@@ -97,7 +100,7 @@ class AccessBundleStore {
|
|
|
97
100
|
async upsertBundle(spec) {
|
|
98
101
|
const description = spec.description ?? null;
|
|
99
102
|
const existing = (await this.pool.query(`SELECT ab_uuid, ab_label, ab_description, ab_status
|
|
100
|
-
FROM \`${
|
|
103
|
+
FROM \`${this.schemas.authz}\`.\`access_bundle\`
|
|
101
104
|
WHERE ab_module = ? AND ab_key = ? LIMIT 1`, [spec.module, spec.key]));
|
|
102
105
|
const cur = existing[0];
|
|
103
106
|
if (cur) {
|
|
@@ -106,12 +109,12 @@ class AccessBundleStore {
|
|
|
106
109
|
cur.ab_status === 'active';
|
|
107
110
|
if (same)
|
|
108
111
|
return 'unchanged';
|
|
109
|
-
await this.pool.query(`UPDATE \`${
|
|
112
|
+
await this.pool.query(`UPDATE \`${this.schemas.authz}\`.\`access_bundle\`
|
|
110
113
|
SET ab_label = ?, ab_description = ?, ab_status = 'active'
|
|
111
114
|
WHERE ab_uuid = ?`, [spec.label, description, cur.ab_uuid]);
|
|
112
115
|
return 'updated';
|
|
113
116
|
}
|
|
114
|
-
await this.pool.query(`INSERT INTO \`${
|
|
117
|
+
await this.pool.query(`INSERT INTO \`${this.schemas.authz}\`.\`access_bundle\`
|
|
115
118
|
(ab_uuid, ab_key, ab_module, ab_label, ab_description, ab_status)
|
|
116
119
|
VALUES (UUID(), ?, ?, ?, ?, 'active')`, [spec.key, spec.module, spec.label, description]);
|
|
117
120
|
return 'inserted';
|
|
@@ -121,7 +124,7 @@ class AccessBundleStore {
|
|
|
121
124
|
* insert the missing ones. Idempotent — a re-sync with the same set is a no-op.
|
|
122
125
|
*/
|
|
123
126
|
async syncBundlePermissions(bundleUuid, keys) {
|
|
124
|
-
const existing = (await this.pool.query(`SELECT abp_perm_key FROM \`${
|
|
127
|
+
const existing = (await this.pool.query(`SELECT abp_perm_key FROM \`${this.schemas.authz}\`.\`access_bundle_permission\`
|
|
125
128
|
WHERE abp_bundle_uuid = ?`, [bundleUuid]));
|
|
126
129
|
const have = new Set(existing.map((r) => r.abp_perm_key));
|
|
127
130
|
const want = new Set(keys);
|
|
@@ -129,11 +132,11 @@ class AccessBundleStore {
|
|
|
129
132
|
const toInsert = [...want].filter((k) => !have.has(k));
|
|
130
133
|
if (toDelete.length > 0) {
|
|
131
134
|
const ph = toDelete.map(() => '?').join(', ');
|
|
132
|
-
await this.pool.query(`DELETE FROM \`${
|
|
135
|
+
await this.pool.query(`DELETE FROM \`${this.schemas.authz}\`.\`access_bundle_permission\`
|
|
133
136
|
WHERE abp_bundle_uuid = ? AND abp_perm_key IN (${ph})`, [bundleUuid, ...toDelete]);
|
|
134
137
|
}
|
|
135
138
|
for (const key of toInsert) {
|
|
136
|
-
await this.pool.query(`INSERT INTO \`${
|
|
139
|
+
await this.pool.query(`INSERT INTO \`${this.schemas.authz}\`.\`access_bundle_permission\`
|
|
137
140
|
(abp_uuid, abp_bundle_uuid, abp_perm_key)
|
|
138
141
|
VALUES (UUID(), ?, ?)`, [bundleUuid, key]);
|
|
139
142
|
}
|
|
@@ -145,7 +148,7 @@ class AccessBundleStore {
|
|
|
145
148
|
*/
|
|
146
149
|
async markOrphaned(modKeysNotDeclared) {
|
|
147
150
|
for (const [module, key] of modKeysNotDeclared) {
|
|
148
|
-
await this.pool.query(`UPDATE \`${
|
|
151
|
+
await this.pool.query(`UPDATE \`${this.schemas.authz}\`.\`access_bundle\`
|
|
149
152
|
SET ab_status = 'orphaned'
|
|
150
153
|
WHERE ab_module = ? AND ab_key = ?`, [module, key]);
|
|
151
154
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Pool } from 'mariadb';
|
|
2
2
|
import { type ReconcileReport } from '@astram/guard';
|
|
3
3
|
import type { ModuleRegistry } from '@astram/core';
|
|
4
|
+
import type { IdentitySchemaNames } from '../../schema.js';
|
|
4
5
|
/**
|
|
5
6
|
* Single-runner wrapper around the framework CatalogReconciler.reconcile (the mutating
|
|
6
7
|
* half). Called once shortly after boot and on a cron. Holds the latest report for
|
|
@@ -15,7 +16,8 @@ export declare class CatalogReconcileJob {
|
|
|
15
16
|
private readonly store;
|
|
16
17
|
private latest;
|
|
17
18
|
private running;
|
|
18
|
-
|
|
19
|
+
/** @param schemas resolved framework names for the catalog store (absent ⇒ from the namespace). */
|
|
20
|
+
constructor(pool: Pool, registry: ModuleRegistry, schemas?: IdentitySchemaNames);
|
|
19
21
|
/** Idempotent single pass. Guarded so overlapping ticks don't double-run. */
|
|
20
22
|
runOnce(nowIso: string): Promise<ReconcileReport | null>;
|
|
21
23
|
getLatest(): ReconcileReport | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog-reconcile.job.d.ts","sourceRoot":"","sources":["../../../src/features/authz/catalog-reconcile.job.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"catalog-reconcile.job.d.ts","sourceRoot":"","sources":["../../../src/features/authz/catalog-reconcile.job.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE3D;;;;;;;GAOG;AACH,qBAAa,mBAAmB;IAS5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAR3B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA2B;IACtD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,OAAO,CAAS;IAExB,mGAAmG;gBAEjG,IAAI,EAAE,IAAI,EACO,QAAQ,EAAE,cAAc,EACzC,OAAO,CAAC,EAAE,mBAAmB;IAK/B,6EAA6E;IACvE,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAiB9D,SAAS,IAAI,eAAe,GAAG,IAAI;CAGpC"}
|
|
@@ -17,9 +17,10 @@ class CatalogReconcileJob {
|
|
|
17
17
|
store;
|
|
18
18
|
latest = null;
|
|
19
19
|
running = false;
|
|
20
|
-
|
|
20
|
+
/** @param schemas resolved framework names for the catalog store (absent ⇒ from the namespace). */
|
|
21
|
+
constructor(pool, registry, schemas) {
|
|
21
22
|
this.registry = registry;
|
|
22
|
-
this.store = new mariadb_catalog_store_js_1.MariaDbCatalogStore(pool);
|
|
23
|
+
this.store = new mariadb_catalog_store_js_1.MariaDbCatalogStore(pool, schemas);
|
|
23
24
|
}
|
|
24
25
|
/** Idempotent single pass. Guarded so overlapping ticks don't double-run. */
|
|
25
26
|
async runOnce(nowIso) {
|
|
@@ -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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"}
|