@astram/identity 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth-facts-keys.d.ts +16 -0
- package/dist/auth-facts-keys.d.ts.map +1 -1
- package/dist/auth-facts-keys.js +26 -10
- package/dist/cross-tenant-grant.object.d.ts +2 -2
- package/dist/cross-tenant-grant.object.d.ts.map +1 -1
- package/dist/cross-tenant-grant.object.js +8 -4
- package/dist/features/audit/directory-label-resolver.d.ts +4 -1
- package/dist/features/audit/directory-label-resolver.d.ts.map +1 -1
- package/dist/features/audit/directory-label-resolver.js +8 -5
- package/dist/features/audit/mariadb-audit-reader.d.ts +5 -1
- package/dist/features/audit/mariadb-audit-reader.d.ts.map +1 -1
- package/dist/features/audit/mariadb-audit-reader.js +10 -2
- package/dist/features/authz/access-bundle-reconcile.job.d.ts +4 -1
- package/dist/features/authz/access-bundle-reconcile.job.d.ts.map +1 -1
- package/dist/features/authz/access-bundle-reconcile.job.js +7 -4
- package/dist/features/authz/access-bundle-store.d.ts +4 -1
- package/dist/features/authz/access-bundle-store.d.ts.map +1 -1
- package/dist/features/authz/access-bundle-store.js +19 -16
- package/dist/features/authz/catalog-bootstrap.provider.d.ts +4 -1
- package/dist/features/authz/catalog-bootstrap.provider.d.ts.map +1 -1
- package/dist/features/authz/catalog-bootstrap.provider.js +37 -6
- package/dist/features/authz/catalog-reconcile.job.d.ts +3 -1
- package/dist/features/authz/catalog-reconcile.job.d.ts.map +1 -1
- package/dist/features/authz/catalog-reconcile.job.js +3 -2
- package/dist/features/authz/db-permission-facts.adapter.d.ts +9 -3
- package/dist/features/authz/db-permission-facts.adapter.d.ts.map +1 -1
- package/dist/features/authz/db-permission-facts.adapter.js +22 -16
- package/dist/features/authz/grant-admin.service.d.ts +4 -1
- package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
- package/dist/features/authz/grant-admin.service.js +32 -37
- package/dist/features/authz/grant-ceiling.d.ts +6 -1
- package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
- package/dist/features/authz/grant-ceiling.js +19 -16
- package/dist/features/authz/mariadb-catalog-store.d.ts +4 -1
- package/dist/features/authz/mariadb-catalog-store.d.ts.map +1 -1
- package/dist/features/authz/mariadb-catalog-store.js +10 -7
- package/dist/features/authz/permission-version.bumper.d.ts +6 -1
- package/dist/features/authz/permission-version.bumper.d.ts.map +1 -1
- package/dist/features/authz/permission-version.bumper.js +13 -11
- package/dist/features/authz/record-authorization.d.ts +8 -2
- package/dist/features/authz/record-authorization.d.ts.map +1 -1
- package/dist/features/authz/record-authorization.js +8 -4
- package/dist/features/authz/role-binding.d.ts +3 -2
- package/dist/features/authz/role-binding.d.ts.map +1 -1
- package/dist/features/authz/role-binding.js +7 -8
- package/dist/features/company/company-hierarchy.service.d.ts +6 -1
- package/dist/features/company/company-hierarchy.service.d.ts.map +1 -1
- package/dist/features/company/company-hierarchy.service.js +17 -16
- package/dist/features/cross-tenant/cross-tenant-grant-read.service.d.ts +11 -6
- package/dist/features/cross-tenant/cross-tenant-grant-read.service.d.ts.map +1 -1
- package/dist/features/cross-tenant/cross-tenant-grant-read.service.js +24 -19
- package/dist/features/files/files.controller.d.ts +27 -0
- package/dist/features/files/files.controller.d.ts.map +1 -0
- package/dist/features/files/files.controller.js +94 -0
- package/dist/features/files/files.service.d.ts +87 -0
- package/dist/features/files/files.service.d.ts.map +1 -0
- package/dist/features/files/files.service.js +311 -0
- package/dist/features/framework-schemas.token.d.ts +8 -0
- package/dist/features/framework-schemas.token.d.ts.map +1 -0
- package/dist/features/framework-schemas.token.js +10 -0
- package/dist/features/identity-feature.module.d.ts +29 -1
- package/dist/features/identity-feature.module.d.ts.map +1 -1
- package/dist/features/identity-feature.module.js +168 -41
- package/dist/features/membership/membership-checker.d.ts +7 -1
- package/dist/features/membership/membership-checker.d.ts.map +1 -1
- package/dist/features/membership/membership-checker.js +9 -4
- package/dist/features/migration-ledger-check.d.ts +15 -0
- package/dist/features/migration-ledger-check.d.ts.map +1 -0
- package/dist/features/migration-ledger-check.js +26 -0
- package/dist/features/policy/policy.service.d.ts +2 -1
- package/dist/features/policy/policy.service.d.ts.map +1 -1
- package/dist/features/policy/policy.service.js +15 -0
- package/dist/features/scope/scope-closure.service.d.ts +8 -1
- package/dist/features/scope/scope-closure.service.d.ts.map +1 -1
- package/dist/features/scope/scope-closure.service.js +22 -19
- package/dist/features/scope/scope-subtree.resolver.d.ts +7 -1
- package/dist/features/scope/scope-subtree.resolver.d.ts.map +1 -1
- package/dist/features/scope/scope-subtree.resolver.js +9 -4
- package/dist/features/service-account/lock-service-accounts.d.ts +3 -1
- package/dist/features/service-account/lock-service-accounts.d.ts.map +1 -1
- package/dist/features/service-account/lock-service-accounts.js +4 -4
- package/dist/features/service-account/no-expiry-key-report.d.ts +9 -3
- package/dist/features/service-account/no-expiry-key-report.d.ts.map +1 -1
- package/dist/features/service-account/no-expiry-key-report.js +17 -10
- package/dist/features/service-account/service-account.store.d.ts +8 -1
- package/dist/features/service-account/service-account.store.d.ts.map +1 -1
- package/dist/features/service-account/service-account.store.js +19 -14
- package/dist/features/share/share.service.d.ts +19 -2
- package/dist/features/share/share.service.d.ts.map +1 -1
- package/dist/features/share/share.service.js +36 -2
- package/dist/features/user/user.service.d.ts +7 -3
- package/dist/features/user/user.service.d.ts.map +1 -1
- package/dist/features/user/user.service.js +12 -4
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/objects.d.ts.map +1 -1
- package/dist/objects.js +2 -0
- package/dist/record-share.object.d.ts +1 -1
- package/dist/record-share.object.d.ts.map +1 -1
- package/dist/record-share.object.js +7 -3
- package/dist/schema.d.ts +15 -2
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +17 -2
- package/dist/service-account.object.d.ts +3 -2
- package/dist/service-account.object.d.ts.map +1 -1
- package/dist/service-account.object.js +4 -2
- package/dist/stored-file.object.d.ts +35 -0
- package/dist/stored-file.object.d.ts.map +1 -0
- package/dist/stored-file.object.js +94 -0
- package/migrations/1710000000001-CreateIdentityOrgModel.cjs +14 -4
- package/migrations/1710000000002-CreateMembershipAndPostings.cjs +14 -4
- package/migrations/1710000000003-CreateIdentityCredential.cjs +14 -4
- package/migrations/1710000000004-CreateSessionTables.cjs +14 -4
- package/migrations/1710000000005-CreateMfaAndTokenSurface.cjs +14 -4
- package/migrations/1710000000010-CreateAuditLog.cjs +14 -4
- package/migrations/1710000000011-CreateCrossTenantGrant.cjs +15 -4
- package/migrations/1710000000012-SeedCrossTenantGrantPerms.cjs +15 -4
- package/migrations/1710000000013-CreateSharedResource.cjs +14 -4
- package/migrations/1710000000018-CreateServiceAccount.cjs +13 -4
- package/migrations/1710000000019-AddSessionMustReenroll.cjs +14 -4
- package/migrations/1710000000020-CreateEmailChangeToken.cjs +15 -5
- package/migrations/1710000000021-AddUsersEmailUniqueIndex.cjs +24 -13
- package/migrations/1710000000022-CreateStoredFile.cjs +53 -0
- package/migrations/1710000000099-SeedDevIdentity.cjs +14 -6
- package/migrations/1710000000100-SeedDevTenantBAndRoot.cjs +14 -6
- package/package.json +8 -4
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
import type { ObjectTypeInput } from '@astram/engine';
|
|
2
|
+
/** The auth-facts key universe for identity's objects plus a consumer's `extraObjects`. */
|
|
3
|
+
export interface IdentityAuthKeys {
|
|
4
|
+
/** Every action key: pass as `ShieldModule.forRoot({ identityActionKeys })`. */
|
|
5
|
+
readonly actionKeys: string[];
|
|
6
|
+
/** The control-plane subset: pass as `ShieldModule.forRoot({ identityControlPlaneKeys })`. */
|
|
7
|
+
readonly controlPlaneKeys: string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Build the auth-facts key universe from identity's objects plus `extraObjects` (pass the same
|
|
11
|
+
* array as `IdentityFeatureModule.forRoot({ extraObjects })`). Same registry bridge as the record
|
|
12
|
+
* authorization, so the keys match decide() and the permission catalog exactly.
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildIdentityAuthKeys(extraObjects?: readonly ObjectTypeInput[]): IdentityAuthKeys;
|
|
15
|
+
/** Identity's own action keys (no consumer types; see {@link buildIdentityAuthKeys}). */
|
|
1
16
|
export declare const identityAuthActionKeys: readonly string[];
|
|
17
|
+
/** Identity's own control-plane keys (no consumer types; see {@link buildIdentityAuthKeys}). */
|
|
2
18
|
export declare const identityControlPlaneKeys: readonly string[];
|
|
3
19
|
//# sourceMappingURL=auth-facts-keys.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-facts-keys.d.ts","sourceRoot":"","sources":["../src/auth-facts-keys.ts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,
|
|
1
|
+
{"version":3,"file":"auth-facts-keys.d.ts","sourceRoot":"","sources":["../src/auth-facts-keys.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGtD,2FAA2F;AAC3F,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAC9B,8FAA8F;IAC9F,QAAQ,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC;CACrC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,YAAY,GAAE,SAAS,eAAe,EAAO,GAC5C,gBAAgB,CAQlB;AAID,yFAAyF;AACzF,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAA4B,CAAC;AAEjF,gGAAgG;AAChG,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAAkC,CAAC"}
|
package/dist/auth-facts-keys.js
CHANGED
|
@@ -1,24 +1,40 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.identityControlPlaneKeys = exports.identityAuthActionKeys = void 0;
|
|
4
|
+
exports.buildIdentityAuthKeys = buildIdentityAuthKeys;
|
|
4
5
|
/**
|
|
5
|
-
* The framework identity permission-key universe, projected
|
|
6
|
+
* The framework identity permission-key universe, projected from `identityObjects` via
|
|
6
7
|
* `@astram/guard`'s registry bridge. Exported for the consumer to pass into
|
|
7
8
|
* `ShieldModule.forRoot({ auth: true, identityActionKeys, identityControlPlaneKeys })` — so the
|
|
8
9
|
* auth-facts adapter (in guard) can expand `identity:*` grants + union root's control-plane keys
|
|
9
10
|
* WITHOUT guard importing this package (the sanctioned graph is identity → guard).
|
|
10
11
|
*
|
|
12
|
+
* A consumer that registers its own object types passes the SAME list it gives
|
|
13
|
+
* `IdentityFeatureModule.forRoot({ extraObjects })` to {@link buildIdentityAuthKeys} (KI #70), so
|
|
14
|
+
* `/auth/facts` covers the same key universe decide() and the permission catalog use.
|
|
15
|
+
*
|
|
11
16
|
* ⚠ NO MODULE-INIT CYCLE: `identityObjects` is imported from the leaf `./objects.js` (NOT from
|
|
12
17
|
* `./index.js`, which re-exports THIS file). So the array is fully initialized before the
|
|
13
|
-
* `
|
|
18
|
+
* `buildIdentityAuthKeys()` call below runs at module-eval time.
|
|
14
19
|
*/
|
|
15
20
|
const guard_1 = require("@astram/guard");
|
|
16
21
|
const objects_js_1 = require("./objects.js");
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Build the auth-facts key universe from identity's objects plus `extraObjects` (pass the same
|
|
24
|
+
* array as `IdentityFeatureModule.forRoot({ extraObjects })`). Same registry bridge as the record
|
|
25
|
+
* authorization, so the keys match decide() and the permission catalog exactly.
|
|
26
|
+
*/
|
|
27
|
+
function buildIdentityAuthKeys(extraObjects = []) {
|
|
28
|
+
const actions = (0, guard_1.buildIdentityModuleRegistry)([...objects_js_1.identityObjects, ...extraObjects]).listActions();
|
|
29
|
+
return {
|
|
30
|
+
actionKeys: actions.map((a) => a.permissionKey),
|
|
31
|
+
controlPlaneKeys: actions
|
|
32
|
+
.filter((a) => a.plane === 'control')
|
|
33
|
+
.map((a) => a.permissionKey),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const identityOnly = buildIdentityAuthKeys();
|
|
37
|
+
/** Identity's own action keys (no consumer types; see {@link buildIdentityAuthKeys}). */
|
|
38
|
+
exports.identityAuthActionKeys = identityOnly.actionKeys;
|
|
39
|
+
/** Identity's own control-plane keys (no consumer types; see {@link buildIdentityAuthKeys}). */
|
|
40
|
+
exports.identityControlPlaneKeys = identityOnly.controlPlaneKeys;
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Mirrors `membership-posting.object.ts`'s shape (system, no trusted columns needed
|
|
15
15
|
* beyond the PK) — the engine's `validateAccess` requires `table` + at least one
|
|
16
|
-
* `@Field`; the PK satisfies that. Action vocabulary is NOT the CRUD
|
|
17
|
-
*
|
|
16
|
+
* `@Field`; the PK satisfies that. Action vocabulary is NOT the CRUD verbs: it opts out
|
|
17
|
+
* (`crudActions: false`, ADR-0038) and declares exactly `@astram/guard`'s
|
|
18
18
|
* `CROSS_TENANT_GRANT_ACTIONS` (`share`/`accept`/`revoke`).
|
|
19
19
|
*
|
|
20
20
|
* Ported from astram3's frozen
|
|
@@ -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"}
|
|
@@ -11,8 +11,10 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.CrossTenantGrant = void 0;
|
|
13
13
|
const engine_1 = require("@astram/engine");
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const guard_1 = require("@astram/guard");
|
|
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;
|
|
16
18
|
/**
|
|
17
19
|
* `cross_tenant_grant` — an AUTHORITY-ONLY @ObjectType, never CRUD'd through the
|
|
18
20
|
* RecordEngine pipe. Its sole purpose is to give `cross_tenant_grant` an entry in the
|
|
@@ -28,8 +30,8 @@ const SCHEMA = 'shield_authz';
|
|
|
28
30
|
*
|
|
29
31
|
* Mirrors `membership-posting.object.ts`'s shape (system, no trusted columns needed
|
|
30
32
|
* beyond the PK) — the engine's `validateAccess` requires `table` + at least one
|
|
31
|
-
* `@Field`; the PK satisfies that. Action vocabulary is NOT the CRUD
|
|
32
|
-
*
|
|
33
|
+
* `@Field`; the PK satisfies that. Action vocabulary is NOT the CRUD verbs: it opts out
|
|
34
|
+
* (`crudActions: false`, ADR-0038) and declares exactly `@astram/guard`'s
|
|
33
35
|
* `CROSS_TENANT_GRANT_ACTIONS` (`share`/`accept`/`revoke`).
|
|
34
36
|
*
|
|
35
37
|
* Ported from astram3's frozen
|
|
@@ -59,6 +61,8 @@ exports.CrossTenantGrant = CrossTenantGrant = __decorate([
|
|
|
59
61
|
table: 'cross_tenant_grant',
|
|
60
62
|
schema: SCHEMA,
|
|
61
63
|
access: { plane: 'control', system: true },
|
|
64
|
+
crudActions: false,
|
|
65
|
+
actions: guard_1.CROSS_TENANT_GRANT_ACTIONS,
|
|
62
66
|
}),
|
|
63
67
|
(0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'ctg_uuid' })
|
|
64
68
|
], CrossTenantGrant);
|
|
@@ -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,5 +1,6 @@
|
|
|
1
1
|
import { type OnApplicationBootstrap } from '@nestjs/common';
|
|
2
2
|
import { DiscoveryService, MetadataScanner, Reflector } from '@nestjs/core';
|
|
3
|
+
import { type RecordAuthorizationInputs } from '@astram/guard';
|
|
3
4
|
import type { ObjectType } from '@astram/engine';
|
|
4
5
|
import { CatalogReconcileJob } from './catalog-reconcile.job.js';
|
|
5
6
|
import { AccessBundleReconcileJob } from './access-bundle-reconcile.job.js';
|
|
@@ -23,8 +24,10 @@ export declare class CatalogBootstrapProvider implements OnApplicationBootstrap
|
|
|
23
24
|
private readonly accessBundleJob;
|
|
24
25
|
private readonly scopeClosure;
|
|
25
26
|
private readonly registrations;
|
|
27
|
+
private readonly recordAuthorization;
|
|
28
|
+
private readonly logger;
|
|
26
29
|
private timer;
|
|
27
|
-
constructor(discovery: DiscoveryService, scanner: MetadataScanner, reflector: Reflector, job: CatalogReconcileJob, accessBundleJob: AccessBundleReconcileJob, scopeClosure: ScopeClosureService, registrations: readonly ObjectType[]);
|
|
30
|
+
constructor(discovery: DiscoveryService, scanner: MetadataScanner, reflector: Reflector, job: CatalogReconcileJob, accessBundleJob: AccessBundleReconcileJob, scopeClosure: ScopeClosureService, registrations: readonly ObjectType[], recordAuthorization: RecordAuthorizationInputs);
|
|
28
31
|
onApplicationBootstrap(): Promise<void>;
|
|
29
32
|
/** Rebuild the scope closure; a failure is logged loudly (never silently swallowed) but does
|
|
30
33
|
* not abort boot or a cron tick — the reconcile is idempotent and retried next interval. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog-bootstrap.provider.d.ts","sourceRoot":"","sources":["../../../src/features/authz/catalog-bootstrap.provider.ts"],"names":[],"mappings":"AAIA,OAAO,
|
|
1
|
+
{"version":3,"file":"catalog-bootstrap.provider.d.ts","sourceRoot":"","sources":["../../../src/features/authz/catalog-bootstrap.provider.ts"],"names":[],"mappings":"AAIA,OAAO,EAA8B,KAAK,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACzF,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EAKL,KAAK,yBAAyB,EAC/B,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAKxE;;;;;;;;;;GAUG;AACH,qBACa,wBAAyB,YAAW,sBAAsB;IAKnE,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAE7B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAE9B,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IAbtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6C;IACpE,OAAO,CAAC,KAAK,CAA+B;gBAGzB,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,SAAS,EACpB,GAAG,EAAE,mBAAmB,EACxB,eAAe,EAAE,wBAAwB,EACzC,YAAY,EAAE,mBAAmB,EAEjC,aAAa,EAAE,SAAS,UAAU,EAAE,EAEpC,mBAAmB,EAAE,yBAAyB;IAG3D,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAyE7C;iGAC6F;YAC/E,qBAAqB;CAOpC"}
|
|
@@ -11,6 +11,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
11
11
|
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
12
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
13
|
};
|
|
14
|
+
var CatalogBootstrapProvider_1;
|
|
14
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
16
|
exports.CatalogBootstrapProvider = void 0;
|
|
16
17
|
// Ported from astram3 `server/src/features/authz/catalog-bootstrap.provider.ts`.
|
|
@@ -37,7 +38,7 @@ const RECONCILE_INTERVAL_MS = 60 * 60 * 1000; // hourly; honors DISABLE_ALL_CRON
|
|
|
37
38
|
* health controller injects — so verify, the post-boot pass, and the endpoint all share
|
|
38
39
|
* one job. This provider owns only the boot verify + the schedule.
|
|
39
40
|
*/
|
|
40
|
-
let CatalogBootstrapProvider = class CatalogBootstrapProvider {
|
|
41
|
+
let CatalogBootstrapProvider = CatalogBootstrapProvider_1 = class CatalogBootstrapProvider {
|
|
41
42
|
discovery;
|
|
42
43
|
scanner;
|
|
43
44
|
reflector;
|
|
@@ -45,8 +46,10 @@ let CatalogBootstrapProvider = class CatalogBootstrapProvider {
|
|
|
45
46
|
accessBundleJob;
|
|
46
47
|
scopeClosure;
|
|
47
48
|
registrations;
|
|
49
|
+
recordAuthorization;
|
|
50
|
+
logger = new common_1.Logger(CatalogBootstrapProvider_1.name);
|
|
48
51
|
timer = null;
|
|
49
|
-
constructor(discovery, scanner, reflector, job, accessBundleJob, scopeClosure, registrations) {
|
|
52
|
+
constructor(discovery, scanner, reflector, job, accessBundleJob, scopeClosure, registrations, recordAuthorization) {
|
|
50
53
|
this.discovery = discovery;
|
|
51
54
|
this.scanner = scanner;
|
|
52
55
|
this.reflector = reflector;
|
|
@@ -54,11 +57,38 @@ let CatalogBootstrapProvider = class CatalogBootstrapProvider {
|
|
|
54
57
|
this.accessBundleJob = accessBundleJob;
|
|
55
58
|
this.scopeClosure = scopeClosure;
|
|
56
59
|
this.registrations = registrations;
|
|
60
|
+
this.recordAuthorization = recordAuthorization;
|
|
57
61
|
}
|
|
58
62
|
async onApplicationBootstrap() {
|
|
59
|
-
|
|
63
|
+
// ONE key universe (KI #70): verify against the registry decide(), the grant ceiling and the
|
|
64
|
+
// catalog reconcile job all use (identity objects + the consumer's `extraObjects`).
|
|
65
|
+
const registry = this.recordAuthorization.registry;
|
|
60
66
|
const inventoryScanner = new guard_1.OperationInventoryScanner(this.discovery, this.scanner, this.reflector);
|
|
61
|
-
const
|
|
67
|
+
const scanned = inventoryScanner.scan(MODULE_KEY);
|
|
68
|
+
// A type registered with `EngineModule.forFeature` but not passed as `extraObjects` is
|
|
69
|
+
// unknown to decide() (`resource_not_registered`), so every gated access to it is denied
|
|
70
|
+
// and its keys are not catalogued. Before KI #70 verify ran over the engine registrations,
|
|
71
|
+
// so such routes booted; keep them booting (no new boot failure for existing consumers) but
|
|
72
|
+
// say loudly that they can never be allowed.
|
|
73
|
+
const engineOnly = this.registrations.filter((r) => registry.resolveObjectType(r.typeKey) === undefined);
|
|
74
|
+
let inventory = scanned;
|
|
75
|
+
if (engineOnly.length > 0) {
|
|
76
|
+
const engineOnlyKeys = new Set((0, guard_1.buildIdentityModuleRegistry)(engineOnly)
|
|
77
|
+
.listActions()
|
|
78
|
+
.map((a) => a.permissionKey));
|
|
79
|
+
const denied = scanned.operations.filter((op) => op.permissionKey !== undefined && engineOnlyKeys.has(op.permissionKey));
|
|
80
|
+
this.logger.warn(`object type(s) ${engineOnly.map((r) => r.typeKey).join(', ')} are registered with ` +
|
|
81
|
+
'EngineModule.forFeature but not in IdentityFeatureModule.forRoot({ extraObjects }): ' +
|
|
82
|
+
'decide() denies every gated access to them and their keys are not catalogued' +
|
|
83
|
+
(denied.length > 0
|
|
84
|
+
? `; always-denied routes: ${denied.map((op) => op.operationId).join(', ')}`
|
|
85
|
+
: '') +
|
|
86
|
+
'. Add them to extraObjects.');
|
|
87
|
+
inventory = {
|
|
88
|
+
...scanned,
|
|
89
|
+
operations: scanned.operations.filter((op) => !denied.includes(op)),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
62
92
|
// Fail-closed (ADR-0002): a route that can't be gated aborts boot.
|
|
63
93
|
new guard_1.CatalogReconciler().verifyGatable(inventory, registry);
|
|
64
94
|
// Mutating reconcile runs AFTER boot (not blocking, not fail-closed).
|
|
@@ -97,13 +127,14 @@ let CatalogBootstrapProvider = class CatalogBootstrapProvider {
|
|
|
97
127
|
}
|
|
98
128
|
};
|
|
99
129
|
exports.CatalogBootstrapProvider = CatalogBootstrapProvider;
|
|
100
|
-
exports.CatalogBootstrapProvider = CatalogBootstrapProvider = __decorate([
|
|
130
|
+
exports.CatalogBootstrapProvider = CatalogBootstrapProvider = CatalogBootstrapProvider_1 = __decorate([
|
|
101
131
|
(0, common_1.Injectable)(),
|
|
102
132
|
__param(6, (0, common_1.Inject)(engine_1.ENGINE_ALL_REGISTRATIONS)),
|
|
133
|
+
__param(7, (0, common_1.Inject)(guard_1.SHIELD_RECORD_AUTHORIZATION)),
|
|
103
134
|
__metadata("design:paramtypes", [core_1.DiscoveryService,
|
|
104
135
|
core_1.MetadataScanner,
|
|
105
136
|
core_1.Reflector,
|
|
106
137
|
catalog_reconcile_job_js_1.CatalogReconcileJob,
|
|
107
138
|
access_bundle_reconcile_job_js_1.AccessBundleReconcileJob,
|
|
108
|
-
scope_closure_service_js_1.ScopeClosureService, Array])
|
|
139
|
+
scope_closure_service_js_1.ScopeClosureService, Array, Object])
|
|
109
140
|
], CatalogBootstrapProvider);
|
|
@@ -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) {
|