@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
|
@@ -9,8 +9,11 @@ var IdentityFeatureModule_1;
|
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.IdentityFeatureModule = void 0;
|
|
11
11
|
exports.assertRequestPathUsesIdentityAuthorization = assertRequestPathUsesIdentityAuthorization;
|
|
12
|
+
exports.assertFrameworkSchemasAgree = assertFrameworkSchemasAgree;
|
|
13
|
+
exports.assertFrameworkDatabaseAgrees = assertFrameworkDatabaseAgrees;
|
|
12
14
|
const common_1 = require("@nestjs/common");
|
|
13
15
|
const core_1 = require("@nestjs/core");
|
|
16
|
+
const core_2 = require("@astram/core");
|
|
14
17
|
const engine_1 = require("@astram/engine");
|
|
15
18
|
const guard_1 = require("@astram/guard");
|
|
16
19
|
const objects_js_1 = require("../objects.js");
|
|
@@ -22,6 +25,7 @@ const role_service_js_1 = require("./role/role.service.js");
|
|
|
22
25
|
const role_controller_js_1 = require("./role/role.controller.js");
|
|
23
26
|
const user_service_js_1 = require("./user/user.service.js");
|
|
24
27
|
const email_address_changer_token_js_1 = require("./user/email-address-changer.token.js");
|
|
28
|
+
const framework_schemas_token_js_1 = require("./framework-schemas.token.js");
|
|
25
29
|
const user_controller_js_1 = require("./user/user.controller.js");
|
|
26
30
|
const membership_controller_js_1 = require("./membership/membership.controller.js");
|
|
27
31
|
const membership_service_js_1 = require("./membership/membership.service.js");
|
|
@@ -34,6 +38,7 @@ const permission_version_bumper_js_1 = require("./authz/permission-version.bumpe
|
|
|
34
38
|
const record_authorization_js_1 = require("./authz/record-authorization.js");
|
|
35
39
|
const db_permission_facts_adapter_js_1 = require("./authz/db-permission-facts.adapter.js");
|
|
36
40
|
const session_revocation_check_js_1 = require("./user/session-revocation-check.js");
|
|
41
|
+
const migration_ledger_check_js_1 = require("./migration-ledger-check.js");
|
|
37
42
|
const access_bundle_store_js_1 = require("./authz/access-bundle-store.js");
|
|
38
43
|
const grant_admin_service_js_1 = require("./authz/grant-admin.service.js");
|
|
39
44
|
const grant_admin_controller_js_1 = require("./authz/grant-admin.controller.js");
|
|
@@ -56,6 +61,8 @@ const audit_controller_js_1 = require("./audit/audit.controller.js");
|
|
|
56
61
|
const cross_tenant_grant_read_service_js_1 = require("./cross-tenant/cross-tenant-grant-read.service.js");
|
|
57
62
|
const cross_tenant_grant_controller_js_1 = require("./cross-tenant/cross-tenant-grant.controller.js");
|
|
58
63
|
const grant_authorize_js_1 = require("./cross-tenant/grant-authorize.js");
|
|
64
|
+
const files_service_js_1 = require("./files/files.service.js");
|
|
65
|
+
const files_controller_js_1 = require("./files/files.controller.js");
|
|
59
66
|
/** DI token of the provider that links the guard's Redis version cache to the bumper (ADR-0035). */
|
|
60
67
|
const IDENTITY_VERSION_PUBLISHER_LINK = Symbol('IDENTITY_VERSION_PUBLISHER_LINK');
|
|
61
68
|
/** DI token of the boot check that warns when a user disable cannot revoke sessions. */
|
|
@@ -89,9 +96,59 @@ function assertRequestPathUsesIdentityAuthorization(authority) {
|
|
|
89
96
|
}
|
|
90
97
|
return true;
|
|
91
98
|
}
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
99
|
+
/** DI token of the boot check that identity and guard use the same framework database names. */
|
|
100
|
+
const IDENTITY_FRAMEWORK_SCHEMAS_CHECK = Symbol('IDENTITY_FRAMEWORK_SCHEMAS_CHECK');
|
|
101
|
+
/**
|
|
102
|
+
* The boot check behind {@link IDENTITY_FRAMEWORK_SCHEMAS_CHECK} (configurable-framework-db,
|
|
103
|
+
* ADR-0037; reviewer R2): the names `IdentityFeatureModule.forRoot` resolved — and handed to the
|
|
104
|
+
* engine, the audit sinks, the reader and every identity store — must equal guard's
|
|
105
|
+
* `FRAMEWORK_SCHEMAS`, or engine/guard/identity would split across databases. A guard that
|
|
106
|
+
* provides no `FRAMEWORK_SCHEMAS` (older than this seam) may only run on the default names.
|
|
107
|
+
* Throws (fail closed) with the namespaces and names only.
|
|
108
|
+
*/
|
|
109
|
+
function assertFrameworkSchemasAgree(identity, guard) {
|
|
110
|
+
const names = (s) => `namespace=${s.namespace} app=${s.app} identity=${s.identity} authz=${s.authz} audit=${s.audit}`;
|
|
111
|
+
if (guard === undefined || guard === null) {
|
|
112
|
+
if (identity.isDefault)
|
|
113
|
+
return true;
|
|
114
|
+
throw new Error(`IdentityFeatureModule: resolved ${names(identity)}, but @astram/guard provides no ` +
|
|
115
|
+
'FRAMEWORK_SCHEMAS; upgrade @astram/guard so every framework module uses the same names.');
|
|
116
|
+
}
|
|
117
|
+
const same = identity.app === guard.app &&
|
|
118
|
+
identity.identity === guard.identity &&
|
|
119
|
+
identity.authz === guard.authz &&
|
|
120
|
+
identity.audit === guard.audit;
|
|
121
|
+
if (!same) {
|
|
122
|
+
throw new Error(`IdentityFeatureModule: framework database names disagree — identity ${names(identity)}; ` +
|
|
123
|
+
`guard ${names(guard)}. Pass the same names (or none) to ShieldModule and ` +
|
|
124
|
+
'IdentityFeatureModule.');
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
/** DI token of the boot check that guard's runtime database equals identity's (QA P2-5). */
|
|
129
|
+
const IDENTITY_FRAMEWORK_DATABASE_CHECK = Symbol('IDENTITY_FRAMEWORK_DATABASE_CHECK');
|
|
130
|
+
/**
|
|
131
|
+
* The boot check behind {@link IDENTITY_FRAMEWORK_DATABASE_CHECK} (G-007 QA P2-5): the runtime
|
|
132
|
+
* database identity's engine and authz pool connect to (derived from `forRoot({ schemas })`)
|
|
133
|
+
* must equal guard's `APP_CONFIG.db.database`, or unqualified queries of guard and identity would
|
|
134
|
+
* land in different databases. No guard `APP_CONFIG` visible ⇒ nothing to compare. Throws (fail
|
|
135
|
+
* closed) with database names only.
|
|
136
|
+
*/
|
|
137
|
+
function assertFrameworkDatabaseAgrees(identityDatabase, guardConfig) {
|
|
138
|
+
const guardDatabase = guardConfig?.db?.database;
|
|
139
|
+
if (guardDatabase === undefined || guardDatabase === identityDatabase)
|
|
140
|
+
return true;
|
|
141
|
+
throw new Error(`IdentityFeatureModule: runtime database ${identityDatabase} differs from @astram/guard's ` +
|
|
142
|
+
`${guardDatabase}. Pass the same names (or none) to ShieldModule and ` +
|
|
143
|
+
'IdentityFeatureModule, and one APP_DB_NAME.');
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Map guard's DbConfig → the engine's EngineDbConfig (adds the pool cap the engine needs). The
|
|
147
|
+
* database and default user follow `schemas` (the names `forRoot` resolved or was given); an
|
|
148
|
+
* explicit `APP_DB_USER` / `APP_DB_NAME` still wins (QA P2-5).
|
|
149
|
+
*/
|
|
150
|
+
function engineDbFromEnv(schemas) {
|
|
151
|
+
const db = (0, guard_1.loadDbConfig)(process.env, schemas);
|
|
95
152
|
const ssl = (0, guard_1.mariadbSslOption)(db.tls);
|
|
96
153
|
return {
|
|
97
154
|
host: db.host,
|
|
@@ -132,22 +189,34 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
132
189
|
* cross-tenant / policy authority, grant ceiling, guided rule-builder schema and owner-share
|
|
133
190
|
* resolver. Without them a consumer type is unregistered everywhere (denied), not policy-
|
|
134
191
|
* authorable and not shareable ("type '…' is not registered — cannot share").
|
|
192
|
+
* @param schemas the resolved framework database names (configurable-framework-db, ADR-0037).
|
|
193
|
+
* Absent ⇒ resolved ONCE here from `SHIELD_DB_NAMESPACE` (unset ⇒ `shield_*`; invalid ⇒
|
|
194
|
+
* throws). The one set is handed to the engine (registrations + its audit sink), the seven
|
|
195
|
+
* audit sinks, the audit reader and every identity store; a boot check refuses names that
|
|
196
|
+
* differ from guard's `FRAMEWORK_SCHEMAS`. Never read per request.
|
|
135
197
|
*/
|
|
136
|
-
static forRoot({ rootIds, extraObjects = [], } = {}) {
|
|
198
|
+
static forRoot({ rootIds, extraObjects = [], schemas: givenSchemas, } = {}) {
|
|
199
|
+
// The framework database names, resolved ONCE (boot) for every SQL path built below.
|
|
200
|
+
// Caller-supplied names are checked before any SQL is built (fail closed, Shield #1).
|
|
201
|
+
const schemas = givenSchemas !== undefined
|
|
202
|
+
? (0, core_2.assertFrameworkSchemaNames)(givenSchemas, 'IdentityFeatureModule.forRoot schemas')
|
|
203
|
+
: (0, core_2.resolveFrameworkSchemas)(process.env);
|
|
137
204
|
// ONE shared shield_authz pool for the whole grant-admin stack (ADR-0011): the
|
|
138
205
|
// permission-version bumper, the AccessBundleStore, the GrantAdminService, and the two
|
|
139
206
|
// reconcile jobs all run over this single pool (don't open several). Opened once at
|
|
140
207
|
// forRoot() (not import time) — mirrors the pre-existing bump-pool pattern. Since Phase 9 #7
|
|
141
208
|
// it also backs the request path's decision authority (provided to ShieldModule below).
|
|
142
|
-
const authzPool = (0, record_authorization_js_1.createIdentityAuthzPool)();
|
|
209
|
+
const authzPool = (0, record_authorization_js_1.createIdentityAuthzPool)(schemas);
|
|
210
|
+
// QA P2-5: the engine and the authz pool connect to the database derived from `schemas`.
|
|
211
|
+
const engineDb = engineDbFromEnv(schemas);
|
|
143
212
|
// The ONE record authorization (Phase 9 #7, closes KI #55/#57): built here from this module's
|
|
144
213
|
// own `extraObjects`, provided to ShieldModule under SHIELD_RECORD_AUTHORIZATION, and reused for
|
|
145
214
|
// every internal authority below. Its registry is the ONE key universe: the grant ceiling sees
|
|
146
215
|
// every key the request path grants (review M5 / Phase 8 #4), and a consumer type is
|
|
147
216
|
// registered for the cross-tenant writer and the policy author too.
|
|
148
|
-
const recordAuthorization = (0, record_authorization_js_1.buildRecordAuthorizationInputs)(authzPool, extraObjects);
|
|
217
|
+
const recordAuthorization = (0, record_authorization_js_1.buildRecordAuthorizationInputs)(authzPool, extraObjects, schemas);
|
|
149
218
|
const ceilingRegistry = recordAuthorization.registry;
|
|
150
|
-
const bumper = new permission_version_bumper_js_1.PermissionVersionBumper();
|
|
219
|
+
const bumper = new permission_version_bumper_js_1.PermissionVersionBumper(undefined, schemas);
|
|
151
220
|
const postingVersionBump = {
|
|
152
221
|
bumpTenant: (tenantUuid) => bumper.bumpForTenant(tenantUuid, authzPool),
|
|
153
222
|
// A user disabled/re-enabled: every membership tenant (autocommit pool → published at once).
|
|
@@ -155,16 +224,12 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
155
224
|
// A role delete (Phase 8 #2): its holder tenants, read before the delete cascades postings.
|
|
156
225
|
tenantsForRole: (roleUuid) => bumper.tenantsForRole(roleUuid, authzPool),
|
|
157
226
|
};
|
|
158
|
-
// The core ModuleRegistry the catalog reconcile job needs, built from the identity
|
|
159
|
-
// object registrations only (guard's bridge reflects decorated classes → registry): it
|
|
160
|
-
// catalogs the identity permissions. Authorities use the shared `recordAuthorization.registry`.
|
|
161
|
-
const identityRegistry = (0, guard_1.buildIdentityModuleRegistry)([...objects_js_1.identityObjects]);
|
|
162
227
|
// The grant-admin collaborators built at forRoot() over the shared pool (mirrors the
|
|
163
228
|
// postingVersionBump closure pattern above). GrantCeiling (ADR-0029) enforces the
|
|
164
229
|
// self-escalation ceiling: a non-root grantor may only grant a key they themselves hold —
|
|
165
230
|
// resolved over the SAME authz pool + registry the object-type gate uses (one source of
|
|
166
231
|
// truth for "what a principal holds"). ALWAYS wired here, so production always enforces.
|
|
167
|
-
const accessBundleStore = new access_bundle_store_js_1.AccessBundleStore(authzPool);
|
|
232
|
+
const accessBundleStore = new access_bundle_store_js_1.AccessBundleStore(authzPool, schemas);
|
|
168
233
|
// (`ceilingRegistry` is the shared record-authorization registry built at the top of forRoot.)
|
|
169
234
|
// `rootIds` marks platform roots as never dominated.
|
|
170
235
|
// rootIds is passed EXPLICITLY (review F5): `null` states "not configured". The dominance
|
|
@@ -175,30 +240,33 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
175
240
|
'ShieldModule.forRoot({ rootIds }). Platform administrators are still protected via ' +
|
|
176
241
|
'users.u_is_platform_superuser.');
|
|
177
242
|
}
|
|
178
|
-
const grantCeiling = new grant_ceiling_js_1.GrantCeiling(authzPool, ceilingRegistry, rootIds ?? null);
|
|
243
|
+
const grantCeiling = new grant_ceiling_js_1.GrantCeiling(authzPool, ceilingRegistry, rootIds ?? null, undefined, schemas);
|
|
244
|
+
// Every identity audit sink writes `<audit>.audit_log` of the resolved names (Shield #6: the
|
|
245
|
+
// audit DB of the same namespace).
|
|
246
|
+
const auditSinkConfig = { schema: schemas.audit };
|
|
179
247
|
// Grant administration is audited on the grant's own transaction (D6, fail-closed).
|
|
180
|
-
const grantAdminAuditSink = new engine_1.MariaDbAuditSink(
|
|
181
|
-
const grantAdminService = new grant_admin_service_js_1.GrantAdminService(authzPool, bumper, accessBundleStore, grantCeiling, grantAdminAuditSink);
|
|
248
|
+
const grantAdminAuditSink = new engine_1.MariaDbAuditSink(auditSinkConfig);
|
|
249
|
+
const grantAdminService = new grant_admin_service_js_1.GrantAdminService(authzPool, bumper, accessBundleStore, grantCeiling, grantAdminAuditSink, schemas);
|
|
182
250
|
// Scope closure (Task 1.2 of the scoped-hierarchy program): the reconciler + node
|
|
183
251
|
// move/delete maintenance, over the SAME shared authzPool (it reaches BOTH shield_app —
|
|
184
252
|
// companies — and shield_authz — scope_closure). Reuses the SAME `bumper` instance so a
|
|
185
253
|
// moveNode's session-invalidation joins the move's transaction. reconcileClosure() is
|
|
186
254
|
// kicked post-boot by the CatalogBootstrapProvider (mirrors the catalog reconcile).
|
|
187
|
-
const scopeClosureService = new scope_closure_service_js_1.ScopeClosureService(authzPool, bumper);
|
|
255
|
+
const scopeClosureService = new scope_closure_service_js_1.ScopeClosureService(authzPool, bumper, schemas);
|
|
188
256
|
// Company hierarchy (security hardening #2): the ONLY path that moves/deletes/attaches a company
|
|
189
257
|
// node — through the closure service above — audited on the move/delete transaction.
|
|
190
258
|
const companyHierarchyService = new company_hierarchy_service_js_1.CompanyHierarchyService(authzPool, scopeClosureService, grantCeiling, // Phase 8 #3: a company delete must dominate every member
|
|
191
|
-
new engine_1.MariaDbAuditSink(
|
|
259
|
+
new engine_1.MariaDbAuditSink(auditSinkConfig), undefined, schemas);
|
|
192
260
|
// Audit viewer (Phase 5): the AuditReader + LabelResolver over the SAME shared authzPool
|
|
193
261
|
// (the reader queries shield_audit + the resolver/actor-lookup query shield_app, both
|
|
194
262
|
// reachable by that pool). `auditNameToId` is the actor-name -> uuid reverse lookup for the
|
|
195
263
|
// actor filter (astram3's exact closure). A mariadb Pool.query(sql, params) structurally
|
|
196
264
|
// satisfies the reader/resolver's `AuditQueryable` — pass authzPool directly. Built at
|
|
197
265
|
// forRoot() (not via Nest DI) because it needs the hand-built pool, then provided useValue.
|
|
198
|
-
const auditReader = new mariadb_audit_reader_js_1.MariaDbAuditReader(authzPool);
|
|
199
|
-
const auditLabels = new directory_label_resolver_js_1.DirectoryLabelResolver(authzPool);
|
|
266
|
+
const auditReader = new mariadb_audit_reader_js_1.MariaDbAuditReader(authzPool, { schema: schemas.audit });
|
|
267
|
+
const auditLabels = new directory_label_resolver_js_1.DirectoryLabelResolver(authzPool, schemas);
|
|
200
268
|
const auditNameToId = async (name) => {
|
|
201
|
-
const rows = (await authzPool.query(
|
|
269
|
+
const rows = (await authzPool.query(`SELECT u_uuid AS id FROM \`${schemas.app}\`.\`users\` WHERE u_username = ? LIMIT 1`, [name]));
|
|
202
270
|
return rows[0]?.id ?? null;
|
|
203
271
|
};
|
|
204
272
|
const auditReadService = new audit_read_service_js_1.AuditReadService(auditReader, auditLabels, auditNameToId);
|
|
@@ -222,7 +290,7 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
222
290
|
// consumer passes to ShieldModule.forRoot (root-correctness for control-plane grants).
|
|
223
291
|
const authorizeCrossTenantGrant = (0, grant_authorize_js_1.makeAuthorizeCrossTenantGrant)({
|
|
224
292
|
authority: crossTenantAuthority,
|
|
225
|
-
readRoleIds: (principalId, tenantId) => (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(authzPool, principalId, tenantId),
|
|
293
|
+
readRoleIds: (principalId, tenantId) => (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(authzPool, principalId, tenantId, schemas),
|
|
226
294
|
isRoot: (principalId) => (rootIds ?? []).includes(principalId),
|
|
227
295
|
});
|
|
228
296
|
// Audit bridge: map guard's `GrantAuditEvent` → the engine's `MariaDbAuditSink` (the SAME
|
|
@@ -237,7 +305,7 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
237
305
|
// never happen for a cross-tenant grant) is de-duped to ONE row.
|
|
238
306
|
// BEST-EFFORT: a throw here must NEVER fail the lifecycle write (the writer already swallows a
|
|
239
307
|
// sink throw; we also guard here so a mapping/DB error is inert).
|
|
240
|
-
const grantAuditSink = new engine_1.MariaDbAuditSink(
|
|
308
|
+
const grantAuditSink = new engine_1.MariaDbAuditSink(auditSinkConfig);
|
|
241
309
|
const grantAudit = {
|
|
242
310
|
record: async (event) => {
|
|
243
311
|
try {
|
|
@@ -268,11 +336,20 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
268
336
|
db: authzPool,
|
|
269
337
|
authorize: authorizeCrossTenantGrant,
|
|
270
338
|
audit: grantAudit,
|
|
339
|
+
schema: schemas.authz,
|
|
271
340
|
});
|
|
272
341
|
// The read side (inbox/mine/incoming/offerable-types) over the same pool + the merged object
|
|
273
342
|
// set (offerable = tenantScoped types only, the consumer's included, KI #57) + the per-type
|
|
274
343
|
// action vocabulary.
|
|
275
|
-
|
|
344
|
+
// The ONE registration-based action lookup (ADR-0038): typeKey → the type's declared
|
|
345
|
+
// vocabulary (`effectiveActions`, validated) over identity + the consumer's extraObjects.
|
|
346
|
+
// `undefined` for an unregistered type. Shared by every action-vocabulary consumer here.
|
|
347
|
+
const actionsByTypeKey = new Map([...objects_js_1.identityObjects, ...extraObjects].map((input) => {
|
|
348
|
+
const reg = typeof input === 'function' ? (0, engine_1.reflectObjectType)(input) : input;
|
|
349
|
+
return [reg.typeKey, (0, engine_1.effectiveActions)(reg)];
|
|
350
|
+
}));
|
|
351
|
+
const actionsByType = (typeKey) => actionsByTypeKey.get(typeKey);
|
|
352
|
+
const crossTenantGrantReadService = new cross_tenant_grant_read_service_js_1.CrossTenantGrantReadService(authzPool, [...objects_js_1.identityObjects, ...extraObjects], (typeKey) => actionsByType(typeKey) ?? [], schemas);
|
|
276
353
|
// ── GUIDED RULE-BUILDER (list-scoping engine Phase 5, §9.1) ──────────────────────────────
|
|
277
354
|
// The policy-authoring surface over the guard `MariaDbPolicyStore` + core `makeAuthorPolicy`.
|
|
278
355
|
// Reuses the SAME shared registry + `crossTenantAuthority` (a DecisionAuthority over the
|
|
@@ -280,7 +357,7 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
280
357
|
// core AuditSink is one-arg (`record(event)`); the engine `MariaDbAuditSink` is two-arg
|
|
281
358
|
// (`record(event, query)`) — so bind the query to the shared authzPool (mirrors the
|
|
282
359
|
// `grantAudit` bridge above). Best-effort audit (a sink throw must not fail the author).
|
|
283
|
-
const policyAuditSink = new engine_1.MariaDbAuditSink(
|
|
360
|
+
const policyAuditSink = new engine_1.MariaDbAuditSink(auditSinkConfig);
|
|
284
361
|
const policyAudit = {
|
|
285
362
|
record: async (event) => {
|
|
286
363
|
try {
|
|
@@ -291,7 +368,7 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
291
368
|
}
|
|
292
369
|
},
|
|
293
370
|
};
|
|
294
|
-
const policyStore = new guard_1.MariaDbPolicyStore(authzPool);
|
|
371
|
+
const policyStore = new guard_1.MariaDbPolicyStore(authzPool, { schema: schemas.authz });
|
|
295
372
|
// The guided-builder SCHEMA source — the authorable field/action metadata, reflected from the
|
|
296
373
|
// registered identity object types (engine `reflectObjectType` yields fields WITH their
|
|
297
374
|
// `type`, which the core registry descriptor drops). Only tenantScoped, owned DATA-plane types
|
|
@@ -304,7 +381,7 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
304
381
|
return {
|
|
305
382
|
typeKey: reg.typeKey,
|
|
306
383
|
label: reg.typeKey,
|
|
307
|
-
actions: (
|
|
384
|
+
actions: actionsByType(reg.typeKey) ?? [],
|
|
308
385
|
fields: reg.fields.map((f) => ({ key: f.key, type: f.type })),
|
|
309
386
|
};
|
|
310
387
|
});
|
|
@@ -329,7 +406,9 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
329
406
|
// and the writer rejects "type 'note' is not registered — cannot share." (Live-acceptance fix.)
|
|
330
407
|
const shareTypeInfoByKey = new Map();
|
|
331
408
|
for (const input of [...objects_js_1.identityObjects, ...extraObjects]) {
|
|
332
|
-
|
|
409
|
+
// R1 / KI #1 (spec v2 §5.B): resolve `FRAMEWORK_SCHEMA` tokens to the resolved names before
|
|
410
|
+
// the schema reaches `RecordShareWriter` SQL; an unresolvable token throws (fail closed).
|
|
411
|
+
const reg = (0, engine_1.resolveObjectTypeSchema)(typeof input === 'function' ? (0, engine_1.reflectObjectType)(input) : input, schemas);
|
|
333
412
|
const owner = (0, engine_1.ownerColumn)(reg);
|
|
334
413
|
const pk = reg.primaryKey.kind === 'uuid' ? (0, engine_1.uuidPkColumn)(reg.primaryKey) : null;
|
|
335
414
|
// Only OWNED types with a uuid PK + an owner column are shareable; others are omitted (the
|
|
@@ -349,10 +428,11 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
349
428
|
const shareTypeResolver = {
|
|
350
429
|
resolve: (typeKey) => shareTypeInfoByKey.get(typeKey),
|
|
351
430
|
};
|
|
352
|
-
const shareAuditSink = new engine_1.MariaDbAuditSink(
|
|
431
|
+
const shareAuditSink = new engine_1.MariaDbAuditSink(auditSinkConfig);
|
|
353
432
|
const recordShareWriter = new guard_1.RecordShareWriter({
|
|
354
433
|
db: authzPool,
|
|
355
434
|
types: shareTypeResolver,
|
|
435
|
+
schema: schemas.authz,
|
|
356
436
|
audit: {
|
|
357
437
|
record: async (event) => {
|
|
358
438
|
try {
|
|
@@ -373,18 +453,18 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
373
453
|
},
|
|
374
454
|
},
|
|
375
455
|
});
|
|
376
|
-
const shareService = new share_service_js_1.ShareService(recordShareWriter, auditLabels);
|
|
456
|
+
const shareService = new share_service_js_1.ShareService(recordShareWriter, auditLabels, actionsByType);
|
|
377
457
|
// ── SERVICE ACCOUNTS + API KEYS (ADR-0033, Phase 2) ──────────────────────────────────────
|
|
378
458
|
// The machine-identity admin surface: create/scope service accounts + issue/rotate/revoke
|
|
379
459
|
// their `ask_…` API keys. Self-wired here over the SAME shared authzPool (which reaches BOTH
|
|
380
460
|
// shield_app.service_account — the accounts — AND shield_authz.service_account_key — the hashed
|
|
381
461
|
// keys). The guard's MariaDbApiKeyStore + Argon2idPasswordHasher back the credential lifecycle;
|
|
382
462
|
// the service enforces own-tenant + the ADR-0029 ceiling (scope ⊆ mine, roles ⊆ mine).
|
|
383
|
-
const serviceAccountStore = new service_account_store_js_1.ServiceAccountStore(authzPool);
|
|
384
|
-
const serviceAccountKeyStore = new guard_1.MariaDbApiKeyStore(authzPool);
|
|
463
|
+
const serviceAccountStore = new service_account_store_js_1.ServiceAccountStore(authzPool, schemas);
|
|
464
|
+
const serviceAccountKeyStore = new guard_1.MariaDbApiKeyStore(authzPool, schemas);
|
|
385
465
|
const serviceAccountHasher = new guard_1.Argon2idPasswordHasher();
|
|
386
466
|
// Best-effort audit → the SAME shield_audit sink pattern (a sink throw never fails the op).
|
|
387
|
-
const serviceAccountAuditSink = new engine_1.MariaDbAuditSink(
|
|
467
|
+
const serviceAccountAuditSink = new engine_1.MariaDbAuditSink(auditSinkConfig);
|
|
388
468
|
const serviceAccountAudit = {
|
|
389
469
|
record: async (event) => {
|
|
390
470
|
try {
|
|
@@ -406,18 +486,25 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
406
486
|
// shield_app.users lookup the audit actor filter uses (inverted). Unknown → null (view falls
|
|
407
487
|
// back to no name, never an id).
|
|
408
488
|
const resolveActorName = async (id) => {
|
|
409
|
-
const rows = (await authzPool.query(
|
|
489
|
+
const rows = (await authzPool.query(`SELECT u_username AS name FROM \`${schemas.app}\`.\`users\` WHERE u_uuid = ? LIMIT 1`, [id]));
|
|
410
490
|
return rows[0]?.name ?? null;
|
|
411
491
|
};
|
|
412
492
|
const serviceAccountService = new service_account_service_js_1.ServiceAccountService(serviceAccountStore, serviceAccountKeyStore, serviceAccountHasher, serviceAccountAudit, resolveActorName,
|
|
413
493
|
// The ceiling's roles: in-effect postings (the same source as gate 5 and GrantCeiling).
|
|
414
|
-
(principalId, tenantId) => (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(authzPool, principalId, tenantId));
|
|
494
|
+
(principalId, tenantId) => (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(authzPool, principalId, tenantId, schemas));
|
|
415
495
|
// NOTE: the machine AUTH path (the `ServiceAuthenticator` behind `SHIELD_SERVICE_AUTHENTICATOR`)
|
|
416
496
|
// is provided by the guard's own AuthModule (under `auth: true`), NOT here — a token provided in
|
|
417
497
|
// this later, sibling feature module is invisible to the APP_GUARD in ShieldModule (that DI
|
|
418
498
|
// boundary made an identity-provided authenticator resolve to `undefined` → `ask_…` bearers
|
|
419
499
|
// 401'd). This module owns only the ACCOUNT ADMIN surface (store + service + controller); the
|
|
420
500
|
// guard owns authentication. (ADR-0033.)
|
|
501
|
+
// Stored files (file-storage SPEC §5 I-K): `file_access` events go to the SAME shield_audit
|
|
502
|
+
// sink over the shared authzPool. Not best-effort here: FilesService decides (a read event
|
|
503
|
+
// that cannot be written withholds the bytes).
|
|
504
|
+
const filesAuditSink = new engine_1.MariaDbAuditSink(auditSinkConfig);
|
|
505
|
+
const filesAudit = {
|
|
506
|
+
record: (event) => filesAuditSink.record(event, (sql, params) => authzPool.query(sql, params)),
|
|
507
|
+
};
|
|
421
508
|
return {
|
|
422
509
|
module: IdentityFeatureModule_1,
|
|
423
510
|
imports: [
|
|
@@ -429,10 +516,13 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
429
516
|
// snapshot is empty and no bundles reconcile into shield_authz.access_bundle.
|
|
430
517
|
engine_1.EngineModule.forFeature([], { accessBundles: index_js_1.identityAccessBundles }),
|
|
431
518
|
// The engine's runtime CRUD pipe + the identity object registrations.
|
|
519
|
+
// R2: the SAME resolved names, so the engine's registrations (FRAMEWORK_SCHEMA tokens) and
|
|
520
|
+
// its audit sink cannot land in another namespace than identity's own SQL.
|
|
432
521
|
engine_1.EngineModule.forRoot({
|
|
433
|
-
db:
|
|
522
|
+
db: engineDb,
|
|
434
523
|
registrations: [...objects_js_1.identityObjects],
|
|
435
524
|
listScope,
|
|
525
|
+
schemas,
|
|
436
526
|
}),
|
|
437
527
|
// NestJS DiscoveryService/MetadataScanner — the boot bootstrap provider reflects the
|
|
438
528
|
// live controllers through this to build the OperationInventory.
|
|
@@ -469,6 +559,9 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
469
559
|
// Service accounts (ADR-0033) — `service-accounts/*` collides with nothing; its static
|
|
470
560
|
// sub-paths (/keys, /keys/rotate, /keys/revoke) sit under `:id` and are method-distinct.
|
|
471
561
|
service_account_controller_js_1.ServiceAccountController,
|
|
562
|
+
// Stored-file download (file-storage SPEC §3) — `files/:uuid` collides with nothing.
|
|
563
|
+
// Gated on `stored_file:read`; answers 501 when no object store is bound. Appended last.
|
|
564
|
+
files_controller_js_1.FilesController,
|
|
472
565
|
],
|
|
473
566
|
providers: [
|
|
474
567
|
company_service_js_1.CompanyService,
|
|
@@ -483,8 +576,11 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
483
576
|
// (collision refusal + login-hint follow in one transaction), over the shared authz pool.
|
|
484
577
|
{
|
|
485
578
|
provide: email_address_changer_token_js_1.IDENTITY_EMAIL_ADDRESS_CHANGER,
|
|
486
|
-
useValue: new guard_1.MariaDbEmailAddressChanger(authzPool),
|
|
579
|
+
useValue: new guard_1.MariaDbEmailAddressChanger(authzPool, Date.now, schemas),
|
|
487
580
|
},
|
|
581
|
+
// The same resolved names for identity services Nest constructs (UserService's
|
|
582
|
+
// email-change audit sink), never a second env read.
|
|
583
|
+
{ provide: framework_schemas_token_js_1.IDENTITY_FRAMEWORK_SCHEMAS, useValue: schemas },
|
|
488
584
|
// ADR-0035 (GAPS #6): attach the guard's Redis permission-version cache to the ONE shared
|
|
489
585
|
// bumper, so every committed bump (postings, grants, hierarchy) is published to Redis,
|
|
490
586
|
// where gate 5 reads it. Provided by the auth surface under `auth: true` (through the
|
|
@@ -506,6 +602,27 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
506
602
|
useFactory: assertRequestPathUsesIdentityAuthorization,
|
|
507
603
|
inject: [{ token: guard_1.SHIELD_DECISION_AUTHORITY, optional: true }],
|
|
508
604
|
},
|
|
605
|
+
// configurable-framework-db (R2): fail the boot unless guard's FRAMEWORK_SCHEMAS equal the
|
|
606
|
+
// names this module resolved (and gave the engine, the sinks and every store).
|
|
607
|
+
{
|
|
608
|
+
provide: IDENTITY_FRAMEWORK_SCHEMAS_CHECK,
|
|
609
|
+
useFactory: (guardSchemas) => assertFrameworkSchemasAgree(schemas, guardSchemas),
|
|
610
|
+
inject: [{ token: guard_1.FRAMEWORK_SCHEMAS, optional: true }],
|
|
611
|
+
},
|
|
612
|
+
// QA P2-5: and guard's runtime database must be the one identity connects to.
|
|
613
|
+
{
|
|
614
|
+
provide: IDENTITY_FRAMEWORK_DATABASE_CHECK,
|
|
615
|
+
useFactory: (guardConfig) => assertFrameworkDatabaseAgrees(engineDb.database, guardConfig),
|
|
616
|
+
inject: [{ token: guard_1.APP_CONFIG, optional: true }],
|
|
617
|
+
},
|
|
618
|
+
// configurable-framework-db spec 5.D: the identity migrations are in the ledger, checked
|
|
619
|
+
// once at application bootstrap over the shared runtime pool (production fails, dev warns).
|
|
620
|
+
// Guard + identity ids; one ledger read shared with AuthModule's check (guard's registry).
|
|
621
|
+
{
|
|
622
|
+
provide: migration_ledger_check_js_1.IDENTITY_MIGRATION_LEDGER_CHECK,
|
|
623
|
+
useFactory: (registry) => (0, migration_ledger_check_js_1.identityMigrationLedgerCheck)(authzPool, schemas, process.env, registry ?? null),
|
|
624
|
+
inject: [{ token: guard_1.SHIELD_MIGRATION_LEDGER_REGISTRY, optional: true }],
|
|
625
|
+
},
|
|
509
626
|
{
|
|
510
627
|
provide: IDENTITY_VERSION_PUBLISHER_LINK,
|
|
511
628
|
useFactory: (cache) => {
|
|
@@ -541,16 +658,18 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
541
658
|
// enforcement guard can authenticate `ask_…` bearers out-of-the-box. Both useValue.
|
|
542
659
|
{ provide: service_account_service_js_1.ServiceAccountService, useValue: serviceAccountService },
|
|
543
660
|
// The mutating half of auto-catalog: a single CatalogReconcileJob (post-boot pass +
|
|
544
|
-
// cron), over the shared pool + the identity
|
|
661
|
+
// cron), over the shared pool + the ONE record-authorization registry (identity objects +
|
|
662
|
+
// `extraObjects`, KI #70): the catalog holds exactly the keys decide() and the grant
|
|
663
|
+
// ceiling know, so a consumer type's keys are grantable and its rows are not orphaned.
|
|
545
664
|
{
|
|
546
665
|
provide: catalog_reconcile_job_js_1.CatalogReconcileJob,
|
|
547
|
-
useValue: new catalog_reconcile_job_js_1.CatalogReconcileJob(authzPool,
|
|
666
|
+
useValue: new catalog_reconcile_job_js_1.CatalogReconcileJob(authzPool, recordAuthorization.registry, schemas),
|
|
548
667
|
},
|
|
549
668
|
// The bundle half: reconciles the merged AccessBundleSpec[] (ENGINE_ALL_ACCESS_BUNDLES,
|
|
550
669
|
// reflected by the engine from identityAccessBundles) into shield_authz.access_bundle.
|
|
551
670
|
{
|
|
552
671
|
provide: access_bundle_reconcile_job_js_1.AccessBundleReconcileJob,
|
|
553
|
-
useFactory: (bundles) => new access_bundle_reconcile_job_js_1.AccessBundleReconcileJob(authzPool, bundles),
|
|
672
|
+
useFactory: (bundles) => new access_bundle_reconcile_job_js_1.AccessBundleReconcileJob(authzPool, bundles, schemas),
|
|
554
673
|
inject: [engine_1.ENGINE_ALL_ACCESS_BUNDLES],
|
|
555
674
|
},
|
|
556
675
|
// Scope closure reconciler + node move/delete maintenance (Task 1.2), built at forRoot()
|
|
@@ -560,10 +679,18 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
|
|
|
560
679
|
// Boot: read-only verifyGatable (fail-closed). Post-boot: kicks the jobs once (catalog
|
|
561
680
|
// THEN bundles, THEN the scope-closure reconcile) + schedules them (DISABLE_ALL_CRONS-
|
|
562
681
|
// gated). Injects DiscoveryService/MetadataScanner/Reflector (from DiscoveryModule
|
|
563
|
-
// above), the jobs, the ScopeClosureService, and
|
|
682
|
+
// above), the jobs, the ScopeClosureService, ENGINE_ALL_REGISTRATIONS and the record
|
|
683
|
+
// authorization (whose registry is the verify key universe, KI #70).
|
|
564
684
|
catalog_bootstrap_provider_js_1.CatalogBootstrapProvider,
|
|
685
|
+
// Stored files (file-storage Phase 5): the request-principal RecordsService + the guard's
|
|
686
|
+
// object store (`null`/absent ⇒ every method answers 501) + the file_access audit bridge.
|
|
687
|
+
{
|
|
688
|
+
provide: files_service_js_1.FilesService,
|
|
689
|
+
useFactory: (records, store) => new files_service_js_1.FilesService(records, store ?? null, filesAudit),
|
|
690
|
+
inject: [engine_1.RecordsService, { token: guard_1.SHIELD_OBJECT_STORE, optional: true }],
|
|
691
|
+
},
|
|
565
692
|
],
|
|
566
|
-
exports: [company_service_js_1.CompanyService, role_service_js_1.RoleService, user_service_js_1.UserService],
|
|
693
|
+
exports: [company_service_js_1.CompanyService, role_service_js_1.RoleService, user_service_js_1.UserService, files_service_js_1.FilesService],
|
|
567
694
|
};
|
|
568
695
|
}
|
|
569
696
|
};
|
|
@@ -30,9 +30,15 @@
|
|
|
30
30
|
* a switch. All SQL values are `?`-bound (never interpolated).
|
|
31
31
|
*/
|
|
32
32
|
import type { Pool } from 'mariadb';
|
|
33
|
+
import { type IdentitySchemaNames } from '../../schema.js';
|
|
33
34
|
export declare class DbMembershipChecker {
|
|
34
35
|
private readonly pool;
|
|
35
|
-
|
|
36
|
+
private readonly schemas;
|
|
37
|
+
/**
|
|
38
|
+
* @param schemas resolved framework names; the membership table lives in `schemas.app`.
|
|
39
|
+
* Absent ⇒ resolved from the namespace, once, here.
|
|
40
|
+
*/
|
|
41
|
+
constructor(pool: Pool, schemas?: IdentitySchemaNames);
|
|
36
42
|
/**
|
|
37
43
|
* Does `principalId` hold an ACTIVE membership at `nodeId`? A single existence read —
|
|
38
44
|
* `true` iff a row with `cm_is_active = 1 AND cm_status = 'active'` exists. Throws on a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"membership-checker.d.ts","sourceRoot":"","sources":["../../../src/features/membership/membership-checker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"membership-checker.d.ts","sourceRoot":"","sources":["../../../src/features/membership/membership-checker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF,qBAAa,mBAAmB;IAM5B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAN1B;;;OAGG;gBAEgB,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,mBAA+C;IAG3E;;;;OAIG;IACG,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAWjF"}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DbMembershipChecker = void 0;
|
|
4
|
-
|
|
5
|
-
const APP = 'shield_app';
|
|
4
|
+
const schema_js_1 = require("../../schema.js");
|
|
6
5
|
class DbMembershipChecker {
|
|
7
6
|
pool;
|
|
8
|
-
|
|
7
|
+
schemas;
|
|
8
|
+
/**
|
|
9
|
+
* @param schemas resolved framework names; the membership table lives in `schemas.app`.
|
|
10
|
+
* Absent ⇒ resolved from the namespace, once, here.
|
|
11
|
+
*/
|
|
12
|
+
constructor(pool, schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
|
|
9
13
|
this.pool = pool;
|
|
14
|
+
this.schemas = schemas;
|
|
10
15
|
}
|
|
11
16
|
/**
|
|
12
17
|
* Does `principalId` hold an ACTIVE membership at `nodeId`? A single existence read —
|
|
@@ -15,7 +20,7 @@ class DbMembershipChecker {
|
|
|
15
20
|
*/
|
|
16
21
|
async hasActiveMembership(principalId, nodeId) {
|
|
17
22
|
const rows = (await this.pool.query(`SELECT 1 AS ok
|
|
18
|
-
FROM \`${
|
|
23
|
+
FROM \`${this.schemas.app}\`.\`company_membership\`
|
|
19
24
|
WHERE cm_user_uuid = ? AND cm_company_uuid = ?
|
|
20
25
|
AND cm_is_active = 1 AND cm_status = 'active'
|
|
21
26
|
LIMIT 1`, [principalId, nodeId]));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FrameworkSchemas } from '@astram/core';
|
|
2
|
+
import { FrameworkMigrationLedgerCheck, type MigrationLedgerRegistry } from '@astram/guard';
|
|
3
|
+
/** DI token of identity's ledger check (guard's `AuthModule` uses its own token). */
|
|
4
|
+
export declare const IDENTITY_MIGRATION_LEDGER_CHECK: unique symbol;
|
|
5
|
+
/**
|
|
6
|
+
* configurable-framework-db spec 5.D: the boot hook that checks every guard AND identity
|
|
7
|
+
* migration is in the `@astram/migrate` ledger (identity reads guard's authz tables; guard never
|
|
8
|
+
* imports identity, ADR-0003). Runs once at application bootstrap, never per request (Shield
|
|
9
|
+
* #12). With guard's per-app `registry`, it shares one ledger read with `AuthModule`'s check.
|
|
10
|
+
* Outside `NODE_ENV` development/test a missing id, ledger table or unreadable ledger fails boot.
|
|
11
|
+
*/
|
|
12
|
+
export declare function identityMigrationLedgerCheck(pool: {
|
|
13
|
+
query(sql: string): Promise<unknown>;
|
|
14
|
+
}, schemas: FrameworkSchemas, env?: NodeJS.ProcessEnv, registry?: MigrationLedgerRegistry | null): FrameworkMigrationLedgerCheck;
|
|
15
|
+
//# sourceMappingURL=migration-ledger-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration-ledger-check.d.ts","sourceRoot":"","sources":["../../src/features/migration-ledger-check.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EACL,6BAA6B,EAG7B,KAAK,uBAAuB,EAC7B,MAAM,eAAe,CAAC;AAGvB,qFAAqF;AACrF,eAAO,MAAM,+BAA+B,eAA4C,CAAC;AAEzF;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE;IAAE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAC9C,OAAO,EAAE,gBAAgB,EACzB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,QAAQ,GAAE,uBAAuB,GAAG,IAAW,GAC9C,6BAA6B,CAY/B"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IDENTITY_MIGRATION_LEDGER_CHECK = void 0;
|
|
4
|
+
exports.identityMigrationLedgerCheck = identityMigrationLedgerCheck;
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const guard_1 = require("@astram/guard");
|
|
7
|
+
const migrations_dir_js_1 = require("../migrations-dir.js");
|
|
8
|
+
/** DI token of identity's ledger check (guard's `AuthModule` uses its own token). */
|
|
9
|
+
exports.IDENTITY_MIGRATION_LEDGER_CHECK = Symbol('IDENTITY_MIGRATION_LEDGER_CHECK');
|
|
10
|
+
/**
|
|
11
|
+
* configurable-framework-db spec 5.D: the boot hook that checks every guard AND identity
|
|
12
|
+
* migration is in the `@astram/migrate` ledger (identity reads guard's authz tables; guard never
|
|
13
|
+
* imports identity, ADR-0003). Runs once at application bootstrap, never per request (Shield
|
|
14
|
+
* #12). With guard's per-app `registry`, it shares one ledger read with `AuthModule`'s check.
|
|
15
|
+
* Outside `NODE_ENV` development/test a missing id, ledger table or unreadable ledger fails boot.
|
|
16
|
+
*/
|
|
17
|
+
function identityMigrationLedgerCheck(pool, schemas, env = process.env, registry = null) {
|
|
18
|
+
return new guard_1.FrameworkMigrationLedgerCheck({
|
|
19
|
+
query: (sql) => pool.query(sql),
|
|
20
|
+
// Same candidates as guard's check (G-007 QA P1), so the registry shares one probe + read.
|
|
21
|
+
...(0, guard_1.resolveMigrationLedgerCheck)(env, schemas),
|
|
22
|
+
migrationsDir: [guard_1.guardMigrationsDir, migrations_dir_js_1.identityMigrationsDir],
|
|
23
|
+
nodeEnv: env.NODE_ENV,
|
|
24
|
+
logger: new common_1.Logger('IdentityFeatureModule'),
|
|
25
|
+
}, registry);
|
|
26
|
+
}
|
|
@@ -91,7 +91,8 @@ export interface PolicyServiceDeps {
|
|
|
91
91
|
/**
|
|
92
92
|
* The authorable-type field/action metadata (built in the module from the reflected object
|
|
93
93
|
* types). Drives `schema()` — the guided builder's field + operator dropdowns. A type absent
|
|
94
|
-
* here is NOT authorable via the builder (schema → NotFound)
|
|
94
|
+
* here is NOT authorable via the builder (schema → NotFound), and `author` rejects any non-`'*'`
|
|
95
|
+
* action for it (declared-action check, fail closed — SPEC v2 §5G).
|
|
95
96
|
*/
|
|
96
97
|
readonly schemaSource?: readonly TypeSchemaSource[];
|
|
97
98
|
readonly now?: () => number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy.service.d.ts","sourceRoot":"","sources":["../../../src/features/policy/policy.service.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"policy.service.d.ts","sourceRoot":"","sources":["../../../src/features/policy/policy.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -106,6 +106,21 @@ class PolicyService {
|
|
|
106
106
|
* reasons to typed errors; a transient throw PROPAGATES (→ 503).
|
|
107
107
|
*/
|
|
108
108
|
async author(actor, policy) {
|
|
109
|
+
// Declared-action check (SPEC v2 §5G): before core `makeAuthorPolicy`, so an undeclared
|
|
110
|
+
// action saves nothing and audits nothing. `'*'` stays allowed. Fail closed: with no schema
|
|
111
|
+
// source (or no entry for the type) every non-'*' action is undeclared.
|
|
112
|
+
// SPEC v3 §11 rule 6: a record rule on `create` is rejected even when `create` is declared.
|
|
113
|
+
// Record rules are evaluated per record (core gate 6) and the create gate is type level, so
|
|
114
|
+
// such a rule would never take effect. `'*'` stays allowed (it applies to the other actions).
|
|
115
|
+
if (policy.action === 'create') {
|
|
116
|
+
throw new InvalidPolicyError("Record rules are evaluated per record and never apply at create; a rule on action 'create' would never take effect. Use the object-type create grant instead.");
|
|
117
|
+
}
|
|
118
|
+
if (policy.action !== '*') {
|
|
119
|
+
const declared = this.schemaByType.get(policy.typeKey)?.actions;
|
|
120
|
+
if (declared === undefined || !declared.includes(policy.action)) {
|
|
121
|
+
throw new InvalidPolicyError(`Action '${policy.action}' is not declared on type '${policy.typeKey}'.`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
109
124
|
const mutation = {
|
|
110
125
|
kind: 'author_policy',
|
|
111
126
|
admin: actor,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ScopeEdge } from '@astram/guard';
|
|
2
|
+
import { type IdentitySchemaNames } from '../../schema.js';
|
|
2
3
|
/**
|
|
3
4
|
* The minimal query surface a transaction connection must expose. A `mariadb`
|
|
4
5
|
* `PoolConnection` satisfies it structurally; kept framework-agnostic (mirrors `Queryable`
|
|
@@ -30,7 +31,13 @@ export interface ScopeVersionBumper {
|
|
|
30
31
|
export declare class ScopeClosureService {
|
|
31
32
|
private readonly pool;
|
|
32
33
|
private readonly bumper;
|
|
33
|
-
|
|
34
|
+
private readonly schemas;
|
|
35
|
+
/**
|
|
36
|
+
* @param schemas resolved framework names: the node tree (`companies`) lives in `schemas.app`,
|
|
37
|
+
* the closure table in `schemas.authz` (ADR-0011 / Task 1.1). Absent ⇒ resolved from the
|
|
38
|
+
* namespace, once, here.
|
|
39
|
+
*/
|
|
40
|
+
constructor(pool: ScopePool, bumper: ScopeVersionBumper, schemas?: IdentitySchemaNames);
|
|
34
41
|
/**
|
|
35
42
|
* Rebuild `scope_closure` from the current `companies.c_parent_uuid` edges. Idempotent
|
|
36
43
|
* (delete-then-insert in a tx → a second run over the same tree writes the identical set).
|