@astram/guard 0.14.0 → 0.16.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/auth.module.d.ts +8 -1
- package/dist/auth/auth.module.d.ts.map +1 -1
- package/dist/auth/auth.module.js +76 -31
- package/dist/authentication/mariadb-api-key-store.d.ts +10 -1
- package/dist/authentication/mariadb-api-key-store.d.ts.map +1 -1
- package/dist/authentication/mariadb-api-key-store.js +20 -10
- package/dist/authentication/mariadb-credential-resolver.d.ts +8 -1
- package/dist/authentication/mariadb-credential-resolver.d.ts.map +1 -1
- package/dist/authentication/mariadb-credential-resolver.js +15 -5
- package/dist/authentication/mariadb-credential-writer.d.ts +6 -1
- package/dist/authentication/mariadb-credential-writer.d.ts.map +1 -1
- package/dist/authentication/mariadb-credential-writer.js +9 -3
- package/dist/authentication/mariadb-email-address-changer.d.ts +10 -1
- package/dist/authentication/mariadb-email-address-changer.d.ts.map +1 -1
- package/dist/authentication/mariadb-email-address-changer.js +22 -9
- package/dist/authentication/mariadb-email-change-token-store.d.ts +8 -1
- package/dist/authentication/mariadb-email-change-token-store.d.ts.map +1 -1
- package/dist/authentication/mariadb-email-change-token-store.js +18 -9
- package/dist/authentication/mariadb-mfa-device-store.d.ts +6 -1
- package/dist/authentication/mariadb-mfa-device-store.d.ts.map +1 -1
- package/dist/authentication/mariadb-mfa-device-store.js +22 -13
- package/dist/authentication/mariadb-profile-resolver.d.ts +6 -1
- package/dist/authentication/mariadb-profile-resolver.d.ts.map +1 -1
- package/dist/authentication/mariadb-profile-resolver.js +12 -6
- package/dist/authentication/mariadb-profile-writer.d.ts +6 -1
- package/dist/authentication/mariadb-profile-writer.d.ts.map +1 -1
- package/dist/authentication/mariadb-profile-writer.js +11 -4
- package/dist/authentication/mariadb-recovery-code-store.d.ts +6 -1
- package/dist/authentication/mariadb-recovery-code-store.d.ts.map +1 -1
- package/dist/authentication/mariadb-recovery-code-store.js +11 -5
- package/dist/authentication/mariadb-reset-token-store.d.ts +6 -1
- package/dist/authentication/mariadb-reset-token-store.d.ts.map +1 -1
- package/dist/authentication/mariadb-reset-token-store.js +11 -5
- package/dist/authentication/mariadb-session-sweeper.d.ts +6 -1
- package/dist/authentication/mariadb-session-sweeper.d.ts.map +1 -1
- package/dist/authentication/mariadb-session-sweeper.js +11 -5
- package/dist/authz/cross-tenant-grant-writer.d.ts.map +1 -1
- package/dist/authz/cross-tenant-grant-writer.js +4 -3
- package/dist/authz/list-scope-sql.d.ts +10 -2
- package/dist/authz/list-scope-sql.d.ts.map +1 -1
- package/dist/authz/list-scope-sql.js +4 -4
- package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts.map +1 -1
- package/dist/authz/mariadb-cross-tenant-grant-adapter.js +4 -3
- package/dist/authz/mariadb-list-scope-adapter.d.ts.map +1 -1
- package/dist/authz/mariadb-list-scope-adapter.js +4 -3
- package/dist/authz/mariadb-policy-store.d.ts.map +1 -1
- package/dist/authz/mariadb-policy-store.js +4 -3
- package/dist/authz/mariadb-record-rule-adapter.d.ts.map +1 -1
- package/dist/authz/mariadb-record-rule-adapter.js +4 -3
- package/dist/authz/mariadb-record-share-adapter.d.ts.map +1 -1
- package/dist/authz/mariadb-record-share-adapter.js +4 -3
- package/dist/authz/record-share-cascade.d.ts.map +1 -1
- package/dist/authz/record-share-cascade.js +4 -3
- package/dist/authz/record-share-writer.d.ts.map +1 -1
- package/dist/authz/record-share-writer.js +4 -3
- package/dist/authz/wire-record-authorization.d.ts +8 -0
- package/dist/authz/wire-record-authorization.d.ts.map +1 -1
- package/dist/authz/wire-record-authorization.js +14 -6
- package/dist/boot/boot-sequence.d.ts +10 -0
- package/dist/boot/boot-sequence.d.ts.map +1 -1
- package/dist/boot/boot-sequence.js +47 -3
- package/dist/boot/migration-ledger-check.d.ts +94 -0
- package/dist/boot/migration-ledger-check.d.ts.map +1 -0
- package/dist/boot/migration-ledger-check.js +280 -0
- package/dist/boot/single-layout-probe.d.ts +66 -0
- package/dist/boot/single-layout-probe.d.ts.map +1 -0
- package/dist/boot/single-layout-probe.js +334 -0
- package/dist/config/app-config.d.ts +46 -3
- package/dist/config/app-config.d.ts.map +1 -1
- package/dist/config/app-config.js +90 -11
- package/dist/config/config.module.d.ts +7 -0
- package/dist/config/config.module.d.ts.map +1 -1
- package/dist/config/config.module.js +8 -1
- package/dist/config/framework-schema-names.d.ts +29 -0
- package/dist/config/framework-schema-names.d.ts.map +1 -0
- package/dist/config/framework-schema-names.js +41 -0
- package/dist/identity/permission-facts.adapter.d.ts +7 -1
- package/dist/identity/permission-facts.adapter.d.ts.map +1 -1
- package/dist/identity/permission-facts.adapter.js +15 -6
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -2
- package/dist/module/shield.module.d.ts.map +1 -1
- package/dist/module/shield.module.js +43 -7
- package/dist/session/mariadb-session-record-store.d.ts +8 -1
- package/dist/session/mariadb-session-record-store.d.ts.map +1 -1
- package/dist/session/mariadb-session-record-store.js +23 -13
- package/dist/session/session-restriction-store.d.ts +8 -1
- package/dist/session/session-restriction-store.d.ts.map +1 -1
- package/dist/session/session-restriction-store.js +15 -5
- package/migrations/1710000000006-CreateAuthzSchema.cjs +15 -4
- package/migrations/1710000000007-ExtendPermissionAndRolePermission.cjs +11 -3
- package/migrations/1710000000008-CreateAccessBundleTables.cjs +14 -4
- package/migrations/1710000000009-CreateRecordPermissionRule.cjs +15 -4
- package/migrations/1710000000014-CreateRecordShare.cjs +15 -4
- package/migrations/1710000000015-CreateScopeClosure.cjs +14 -4
- package/migrations/1710000000016-AlterCompanyParentFkRestrict.cjs +20 -9
- package/migrations/1710000000017-CreateServiceAccountKey.cjs +14 -4
- package/package.json +3 -3
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
* `createIdentityBroker`/`createTotpFactorVerifier`) are pure; the adapters + the
|
|
63
63
|
* node:crypto HMAC live in guard. Core stays zero-dep + unchanged.
|
|
64
64
|
*/
|
|
65
|
-
import { type DynamicModule } from '@nestjs/common';
|
|
65
|
+
import { type DynamicModule, type Provider } from '@nestjs/common';
|
|
66
66
|
import type { CredentialResolver, HmacSha1, FactorVerifier } from '@astram/core';
|
|
67
67
|
import type { SessionFactsProjector } from './ports/session-facts-projector.js';
|
|
68
68
|
import type { ScopeSubtreeResolver } from './ports/scope-subtree-resolver.js';
|
|
@@ -106,6 +106,13 @@ export declare function resolveTotpVerifier(override: FactorVerifier | null | un
|
|
|
106
106
|
* consumer's built CJS dist. Async because of {@link loadCore}.
|
|
107
107
|
*/
|
|
108
108
|
export declare function makeDefaultTotpVerifier(): Promise<FactorVerifier>;
|
|
109
|
+
/** DI token for the single-layout privilege probe hook (a no-op in the four layout). */
|
|
110
|
+
export declare const SHIELD_SINGLE_LAYOUT_PROBE: unique symbol;
|
|
111
|
+
/**
|
|
112
|
+
* Single-database-layout spec §5.F (ADR-0041): the privilege probe runs once at bootstrap on this
|
|
113
|
+
* module's runtime pool, single layout only (four layout: a no-op hook). Never blocks boot.
|
|
114
|
+
*/
|
|
115
|
+
export declare const singleLayoutProbeProvider: Provider;
|
|
109
116
|
/**
|
|
110
117
|
* Options for {@link AuthModule.forRoot} — registration-time collaborators the
|
|
111
118
|
* consumer plugs through {@link ShieldModule.forRoot}.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.module.d.ts","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"auth.module.d.ts","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":"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"}
|
package/dist/auth/auth.module.js
CHANGED
|
@@ -104,7 +104,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
104
104
|
})();
|
|
105
105
|
var AuthModule_1;
|
|
106
106
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
107
|
-
exports.AuthModule = exports.hmacSha1 = void 0;
|
|
107
|
+
exports.AuthModule = exports.singleLayoutProbeProvider = exports.SHIELD_SINGLE_LAYOUT_PROBE = exports.hmacSha1 = void 0;
|
|
108
108
|
exports.resolveTotpVerifier = resolveTotpVerifier;
|
|
109
109
|
exports.makeDefaultTotpVerifier = makeDefaultTotpVerifier;
|
|
110
110
|
exports.resolveDefaultEmailSender = resolveDefaultEmailSender;
|
|
@@ -112,6 +112,9 @@ const common_1 = require("@nestjs/common");
|
|
|
112
112
|
const node_crypto_1 = require("node:crypto");
|
|
113
113
|
const app_config_js_1 = require("../config/app-config.js");
|
|
114
114
|
const config_module_js_1 = require("../config/config.module.js");
|
|
115
|
+
const migration_ledger_check_js_1 = require("../boot/migration-ledger-check.js");
|
|
116
|
+
const single_layout_probe_js_1 = require("../boot/single-layout-probe.js");
|
|
117
|
+
const migrations_dir_js_1 = require("../migrations-dir.js");
|
|
115
118
|
const argon2id_password_hasher_js_1 = require("../authentication/argon2id-password-hasher.js");
|
|
116
119
|
const mariadb_credential_resolver_js_1 = require("../authentication/mariadb-credential-resolver.js");
|
|
117
120
|
const mariadb_profile_resolver_js_1 = require("../authentication/mariadb-profile-resolver.js");
|
|
@@ -251,8 +254,8 @@ function makeCredentialResolverProvider(override) {
|
|
|
251
254
|
provide: tokens_js_1.SHIELD_CREDENTIAL_RESOLVER,
|
|
252
255
|
// #7: the default resolver folds the principal's CONFIRMED MFA devices into a
|
|
253
256
|
// `totp` required factor, so enrolled MFA is enforced at every password login.
|
|
254
|
-
useFactory: (pool, devices) => new mariadb_credential_resolver_js_1.MariaDbCredentialResolver(pool, devices),
|
|
255
|
-
inject: [tokens_js_1.SHIELD_DB_POOL, tokens_js_1.SHIELD_MFA_DEVICE_STORE],
|
|
257
|
+
useFactory: (pool, devices, config) => new mariadb_credential_resolver_js_1.MariaDbCredentialResolver(pool, devices, undefined, (0, app_config_js_1.frameworkSchemasOf)(config)),
|
|
258
|
+
inject: [tokens_js_1.SHIELD_DB_POOL, tokens_js_1.SHIELD_MFA_DEVICE_STORE, config_module_js_1.APP_CONFIG],
|
|
256
259
|
};
|
|
257
260
|
}
|
|
258
261
|
/**
|
|
@@ -272,11 +275,11 @@ const sessionLifetimesProvider = {
|
|
|
272
275
|
*/
|
|
273
276
|
const sessionRecordStoreProvider = {
|
|
274
277
|
provide: index_js_1.SHIELD_SESSION_RECORD_STORE,
|
|
275
|
-
useFactory: (pool, binder) => {
|
|
276
|
-
const durable = new index_js_1.MariaDbSessionRecordStore(pool);
|
|
278
|
+
useFactory: (pool, binder, config) => {
|
|
279
|
+
const durable = new index_js_1.MariaDbSessionRecordStore(pool, (0, app_config_js_1.frameworkSchemasOf)(config));
|
|
277
280
|
return new index_js_1.HotRevokingSessionRecordStore(durable, binder, durable);
|
|
278
281
|
},
|
|
279
|
-
inject: [tokens_js_1.SHIELD_DB_POOL, tokens_js_2.SHIELD_SESSION_TOKEN_BINDER],
|
|
282
|
+
inject: [tokens_js_1.SHIELD_DB_POOL, tokens_js_2.SHIELD_SESSION_TOKEN_BINDER, config_module_js_1.APP_CONFIG],
|
|
280
283
|
};
|
|
281
284
|
/**
|
|
282
285
|
* The Redis permission-version cache (ADR-0035, GAPS #6): gate 5 reads the tenant's version from
|
|
@@ -295,8 +298,8 @@ const permissionVersionCacheProvider = {
|
|
|
295
298
|
*/
|
|
296
299
|
const sessionRestrictionStoreProvider = {
|
|
297
300
|
provide: tokens_js_1.SHIELD_SESSION_RESTRICTIONS,
|
|
298
|
-
useFactory: (pool) => new session_restriction_store_js_1.MariaDbSessionRestrictionStore(pool),
|
|
299
|
-
inject: [tokens_js_1.SHIELD_DB_POOL],
|
|
301
|
+
useFactory: (pool, config) => new session_restriction_store_js_1.MariaDbSessionRestrictionStore(pool, (0, app_config_js_1.frameworkSchemasOf)(config)),
|
|
302
|
+
inject: [tokens_js_1.SHIELD_DB_POOL, config_module_js_1.APP_CONFIG],
|
|
300
303
|
};
|
|
301
304
|
/** Hot session store (Redis adapter, injected with the live client). */
|
|
302
305
|
const hotSessionStoreProvider = {
|
|
@@ -356,11 +359,50 @@ const mfaLimiterProvider = {
|
|
|
356
359
|
*/
|
|
357
360
|
const serviceAuthenticatorProvider = {
|
|
358
361
|
provide: tokens_js_2.SHIELD_SERVICE_AUTHENTICATOR,
|
|
359
|
-
useFactory: (pool) => (0, core_1.createServiceAuthenticator)({
|
|
360
|
-
store: new mariadb_api_key_store_js_1.MariaDbApiKeyStore(pool),
|
|
362
|
+
useFactory: (pool, config) => (0, core_1.createServiceAuthenticator)({
|
|
363
|
+
store: new mariadb_api_key_store_js_1.MariaDbApiKeyStore(pool, (0, app_config_js_1.frameworkSchemasOf)(config)),
|
|
361
364
|
hasher: new argon2id_password_hasher_js_1.Argon2idPasswordHasher(),
|
|
362
365
|
}),
|
|
363
|
-
inject: [tokens_js_1.SHIELD_DB_POOL],
|
|
366
|
+
inject: [tokens_js_1.SHIELD_DB_POOL, config_module_js_1.APP_CONFIG],
|
|
367
|
+
};
|
|
368
|
+
/**
|
|
369
|
+
* configurable-framework-db spec 5.D: at application bootstrap (once, never per request, Shield
|
|
370
|
+
* #12) check that every guard migration is in the `@astram/migrate` ledger, read with ONE SELECT
|
|
371
|
+
* over this module's runtime pool. Lives here, not in `BootModule`, because this is the only
|
|
372
|
+
* guard module holding a pool (the raw pool stays unexported, Phase 8 #5); an enforcement-only
|
|
373
|
+
* app opens no pool and reads no guard table, so it has nothing to check.
|
|
374
|
+
*/
|
|
375
|
+
const migrationLedgerCheckProvider = {
|
|
376
|
+
// Its own token (identity provides another), so a merged module can never shadow one check.
|
|
377
|
+
provide: migration_ledger_check_js_1.SHIELD_GUARD_MIGRATION_LEDGER_CHECK,
|
|
378
|
+
useFactory: (pool, config, registry) => new migration_ledger_check_js_1.FrameworkMigrationLedgerCheck({
|
|
379
|
+
query: (sql) => pool.query(sql),
|
|
380
|
+
// The ledger candidates (G-007 QA P1): `<ns>_app`, or DDL_DB_NAME / APP_DB_NAME /
|
|
381
|
+
// shield_app probed in order in default mode.
|
|
382
|
+
...(0, migration_ledger_check_js_1.resolveMigrationLedgerCheck)(process.env, (0, app_config_js_1.frameworkSchemasOf)(config)),
|
|
383
|
+
migrationsDir: migrations_dir_js_1.guardMigrationsDir,
|
|
384
|
+
nodeEnv: process.env.NODE_ENV,
|
|
385
|
+
logger: new common_1.Logger('Shield'),
|
|
386
|
+
},
|
|
387
|
+
// ShieldModule's per-app registry: one ledger read shared with identity's check.
|
|
388
|
+
registry ?? null),
|
|
389
|
+
inject: [tokens_js_1.SHIELD_DB_POOL, config_module_js_1.APP_CONFIG, { token: migration_ledger_check_js_1.SHIELD_MIGRATION_LEDGER_REGISTRY, optional: true }],
|
|
390
|
+
};
|
|
391
|
+
/** DI token for the single-layout privilege probe hook (a no-op in the four layout). */
|
|
392
|
+
exports.SHIELD_SINGLE_LAYOUT_PROBE = Symbol('SHIELD_SINGLE_LAYOUT_PROBE');
|
|
393
|
+
/**
|
|
394
|
+
* Single-database-layout spec §5.F (ADR-0041): the privilege probe runs once at bootstrap on this
|
|
395
|
+
* module's runtime pool, single layout only (four layout: a no-op hook). Never blocks boot.
|
|
396
|
+
*/
|
|
397
|
+
exports.singleLayoutProbeProvider = {
|
|
398
|
+
provide: exports.SHIELD_SINGLE_LAYOUT_PROBE,
|
|
399
|
+
useFactory: (pool, config) => {
|
|
400
|
+
const schemas = (0, app_config_js_1.frameworkSchemasOf)(config);
|
|
401
|
+
if (schemas.layout !== 'single')
|
|
402
|
+
return { onApplicationBootstrap: () => undefined };
|
|
403
|
+
return new single_layout_probe_js_1.SingleLayoutProbeBoot(async (sql, params) => (await pool.query(sql, params)), schemas.app);
|
|
404
|
+
},
|
|
405
|
+
inject: [tokens_js_1.SHIELD_DB_POOL, config_module_js_1.APP_CONFIG],
|
|
364
406
|
};
|
|
365
407
|
/** Boot-time warnings from the auth composition (dev email logger, disabled email, …). */
|
|
366
408
|
const authBootLogger = new common_1.Logger('ShieldAuth');
|
|
@@ -656,8 +698,8 @@ function makeProfileResolverProvider(override) {
|
|
|
656
698
|
? { provide: tokens_js_1.SHIELD_PROFILE_RESOLVER, useValue: override }
|
|
657
699
|
: {
|
|
658
700
|
provide: tokens_js_1.SHIELD_PROFILE_RESOLVER,
|
|
659
|
-
useFactory: (pool) => new mariadb_profile_resolver_js_1.MariaDbProfileResolver(pool),
|
|
660
|
-
inject: [tokens_js_1.SHIELD_DB_POOL],
|
|
701
|
+
useFactory: (pool, config) => new mariadb_profile_resolver_js_1.MariaDbProfileResolver(pool, undefined, (0, app_config_js_1.frameworkSchemasOf)(config)),
|
|
702
|
+
inject: [tokens_js_1.SHIELD_DB_POOL, config_module_js_1.APP_CONFIG],
|
|
661
703
|
};
|
|
662
704
|
}
|
|
663
705
|
/**
|
|
@@ -671,8 +713,8 @@ function makeCredentialWriterProvider(override) {
|
|
|
671
713
|
? { provide: tokens_js_1.SHIELD_CREDENTIAL_WRITER, useValue: override }
|
|
672
714
|
: {
|
|
673
715
|
provide: tokens_js_1.SHIELD_CREDENTIAL_WRITER,
|
|
674
|
-
useFactory: (pool) => new mariadb_credential_writer_js_1.MariaDbCredentialWriter(pool),
|
|
675
|
-
inject: [tokens_js_1.SHIELD_DB_POOL],
|
|
716
|
+
useFactory: (pool, config) => new mariadb_credential_writer_js_1.MariaDbCredentialWriter(pool, undefined, (0, app_config_js_1.frameworkSchemasOf)(config)),
|
|
717
|
+
inject: [tokens_js_1.SHIELD_DB_POOL, config_module_js_1.APP_CONFIG],
|
|
676
718
|
};
|
|
677
719
|
}
|
|
678
720
|
function makeResetTokenStoreProvider(override) {
|
|
@@ -680,8 +722,8 @@ function makeResetTokenStoreProvider(override) {
|
|
|
680
722
|
? { provide: tokens_js_1.SHIELD_RESET_TOKEN_STORE, useValue: override }
|
|
681
723
|
: {
|
|
682
724
|
provide: tokens_js_1.SHIELD_RESET_TOKEN_STORE,
|
|
683
|
-
useFactory: (pool) => new mariadb_reset_token_store_js_1.MariaDbResetTokenStore(pool),
|
|
684
|
-
inject: [tokens_js_1.SHIELD_DB_POOL],
|
|
725
|
+
useFactory: (pool, config) => new mariadb_reset_token_store_js_1.MariaDbResetTokenStore(pool, undefined, (0, app_config_js_1.frameworkSchemasOf)(config)),
|
|
726
|
+
inject: [tokens_js_1.SHIELD_DB_POOL, config_module_js_1.APP_CONFIG],
|
|
685
727
|
};
|
|
686
728
|
}
|
|
687
729
|
/**
|
|
@@ -690,14 +732,14 @@ function makeResetTokenStoreProvider(override) {
|
|
|
690
732
|
*/
|
|
691
733
|
const emailChangeTokenStoreProvider = {
|
|
692
734
|
provide: tokens_js_1.SHIELD_EMAIL_CHANGE_TOKEN_STORE,
|
|
693
|
-
useFactory: (pool) => new mariadb_email_change_token_store_js_1.MariaDbEmailChangeTokenStore(pool),
|
|
694
|
-
inject: [tokens_js_1.SHIELD_DB_POOL],
|
|
735
|
+
useFactory: (pool, config) => new mariadb_email_change_token_store_js_1.MariaDbEmailChangeTokenStore(pool, undefined, (0, app_config_js_1.frameworkSchemasOf)(config)),
|
|
736
|
+
inject: [tokens_js_1.SHIELD_DB_POOL, config_module_js_1.APP_CONFIG],
|
|
695
737
|
};
|
|
696
738
|
/** The ONE email read/write path for an email change (Phase 12): the framework default. */
|
|
697
739
|
const emailAddressChangerProvider = {
|
|
698
740
|
provide: tokens_js_1.SHIELD_EMAIL_ADDRESS_CHANGER,
|
|
699
|
-
useFactory: (pool) => new mariadb_email_address_changer_js_1.MariaDbEmailAddressChanger(pool),
|
|
700
|
-
inject: [tokens_js_1.SHIELD_DB_POOL],
|
|
741
|
+
useFactory: (pool, config) => new mariadb_email_address_changer_js_1.MariaDbEmailAddressChanger(pool, undefined, (0, app_config_js_1.frameworkSchemasOf)(config)),
|
|
742
|
+
inject: [tokens_js_1.SHIELD_DB_POOL, config_module_js_1.APP_CONFIG],
|
|
701
743
|
};
|
|
702
744
|
/**
|
|
703
745
|
* The per-account email-change REQUEST limiter (Phase 12) — `email-change:pid:<id>`, 3 per 60-min
|
|
@@ -781,8 +823,8 @@ function makeRecoveryCodeStoreProvider(override) {
|
|
|
781
823
|
? { provide: tokens_js_1.SHIELD_RECOVERY_CODE_STORE, useValue: override }
|
|
782
824
|
: {
|
|
783
825
|
provide: tokens_js_1.SHIELD_RECOVERY_CODE_STORE,
|
|
784
|
-
useFactory: (pool, hasher) => new mariadb_recovery_code_store_js_1.MariaDbRecoveryCodeStore(pool, hasher),
|
|
785
|
-
inject: [tokens_js_1.SHIELD_DB_POOL, argon2id_password_hasher_js_1.SHIELD_PASSWORD_HASHER],
|
|
826
|
+
useFactory: (pool, hasher, config) => new mariadb_recovery_code_store_js_1.MariaDbRecoveryCodeStore(pool, hasher, undefined, (0, app_config_js_1.frameworkSchemasOf)(config)),
|
|
827
|
+
inject: [tokens_js_1.SHIELD_DB_POOL, argon2id_password_hasher_js_1.SHIELD_PASSWORD_HASHER, config_module_js_1.APP_CONFIG],
|
|
786
828
|
};
|
|
787
829
|
}
|
|
788
830
|
function makeProfileWriterProvider(override) {
|
|
@@ -790,8 +832,8 @@ function makeProfileWriterProvider(override) {
|
|
|
790
832
|
? { provide: tokens_js_1.SHIELD_PROFILE_WRITER, useValue: override }
|
|
791
833
|
: {
|
|
792
834
|
provide: tokens_js_1.SHIELD_PROFILE_WRITER,
|
|
793
|
-
useFactory: (pool) => new mariadb_profile_writer_js_1.MariaDbProfileWriter(pool),
|
|
794
|
-
inject: [tokens_js_1.SHIELD_DB_POOL],
|
|
835
|
+
useFactory: (pool, config) => new mariadb_profile_writer_js_1.MariaDbProfileWriter(pool, (0, app_config_js_1.frameworkSchemasOf)(config)),
|
|
836
|
+
inject: [tokens_js_1.SHIELD_DB_POOL, config_module_js_1.APP_CONFIG],
|
|
795
837
|
};
|
|
796
838
|
}
|
|
797
839
|
function makeSessionSweeperProvider(override) {
|
|
@@ -799,8 +841,8 @@ function makeSessionSweeperProvider(override) {
|
|
|
799
841
|
? { provide: tokens_js_1.SHIELD_SESSION_SWEEPER, useValue: override }
|
|
800
842
|
: {
|
|
801
843
|
provide: tokens_js_1.SHIELD_SESSION_SWEEPER,
|
|
802
|
-
useFactory: (pool, binder) => new mariadb_session_sweeper_js_1.MariaDbSessionSweeper(pool, binder),
|
|
803
|
-
inject: [tokens_js_1.SHIELD_DB_POOL, tokens_js_2.SHIELD_SESSION_TOKEN_BINDER],
|
|
844
|
+
useFactory: (pool, binder, config) => new mariadb_session_sweeper_js_1.MariaDbSessionSweeper(pool, binder, undefined, (0, app_config_js_1.frameworkSchemasOf)(config)),
|
|
845
|
+
inject: [tokens_js_1.SHIELD_DB_POOL, tokens_js_2.SHIELD_SESSION_TOKEN_BINDER, config_module_js_1.APP_CONFIG],
|
|
804
846
|
};
|
|
805
847
|
}
|
|
806
848
|
function makeMfaDeviceStoreProvider(override) {
|
|
@@ -808,8 +850,8 @@ function makeMfaDeviceStoreProvider(override) {
|
|
|
808
850
|
? { provide: tokens_js_1.SHIELD_MFA_DEVICE_STORE, useValue: override }
|
|
809
851
|
: {
|
|
810
852
|
provide: tokens_js_1.SHIELD_MFA_DEVICE_STORE,
|
|
811
|
-
useFactory: (pool) => new mariadb_mfa_device_store_js_1.MariaDbMfaDeviceStore(pool),
|
|
812
|
-
inject: [tokens_js_1.SHIELD_DB_POOL],
|
|
853
|
+
useFactory: (pool, config) => new mariadb_mfa_device_store_js_1.MariaDbMfaDeviceStore(pool, (0, app_config_js_1.frameworkSchemasOf)(config)),
|
|
854
|
+
inject: [tokens_js_1.SHIELD_DB_POOL, config_module_js_1.APP_CONFIG],
|
|
813
855
|
};
|
|
814
856
|
}
|
|
815
857
|
function makeAuditSinkProvider(override) {
|
|
@@ -947,8 +989,8 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
947
989
|
// keys. The keys are CLOSED-OVER option values (never a guard→identity import).
|
|
948
990
|
{
|
|
949
991
|
provide: tokens_js_1.SHIELD_PERMISSION_FACTS,
|
|
950
|
-
useFactory: (pool) => new permission_facts_adapter_js_1.MariaDbPermissionFactsAdapter(pool, () => identityActionKeys, identityControlPlaneKeys),
|
|
951
|
-
inject: [tokens_js_1.SHIELD_DB_POOL],
|
|
992
|
+
useFactory: (pool, config) => new permission_facts_adapter_js_1.MariaDbPermissionFactsAdapter(pool, () => identityActionKeys, identityControlPlaneKeys, undefined, (0, app_config_js_1.frameworkSchemasOf)(config)),
|
|
993
|
+
inject: [tokens_js_1.SHIELD_DB_POOL, config_module_js_1.APP_CONFIG],
|
|
952
994
|
},
|
|
953
995
|
// The reset-link base URL value the forgot-password handler composes into the
|
|
954
996
|
// emailed link (framework-owned URL composition; EmailSender stays dumb).
|
|
@@ -1002,6 +1044,9 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
1002
1044
|
forgotLimiterProvider,
|
|
1003
1045
|
forgotHintLimiterProvider,
|
|
1004
1046
|
resetLimiterProvider,
|
|
1047
|
+
// spec 5.D: the guard migrations are in the ledger (once at bootstrap; production fails).
|
|
1048
|
+
migrationLedgerCheckProvider,
|
|
1049
|
+
exports.singleLayoutProbeProvider,
|
|
1005
1050
|
],
|
|
1006
1051
|
exports: [
|
|
1007
1052
|
tokens_js_1.SHIELD_AUTHENTICATOR,
|
|
@@ -20,9 +20,18 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import type { Pool } from 'mariadb';
|
|
22
22
|
import type { ApiKeyStore, ApiKeyRecord } from '@astram/core';
|
|
23
|
+
import { type FrameworkSchemaNames } from '../config/framework-schema-names.js';
|
|
23
24
|
export declare class MariaDbApiKeyStore implements ApiKeyStore {
|
|
24
25
|
private readonly pool;
|
|
25
|
-
|
|
26
|
+
/** `<authz>`.`service_account_key`. */
|
|
27
|
+
private readonly keyTable;
|
|
28
|
+
/** The account rows (`<app>`.`service_account`, created by the identity migration `…018`). */
|
|
29
|
+
private readonly accountTable;
|
|
30
|
+
/** `<app>`.`companies` (the account's tenant company). */
|
|
31
|
+
private readonly companyTable;
|
|
32
|
+
constructor(pool: Pool,
|
|
33
|
+
/** Framework database names; absent ⇒ resolved from `SHIELD_DB_NAMESPACE` here, once. */
|
|
34
|
+
schemas?: FrameworkSchemaNames);
|
|
26
35
|
getByAccountId(accountId: string): Promise<ApiKeyRecord | null>;
|
|
27
36
|
/**
|
|
28
37
|
* Persist a NEW key version for an account (issue or rotate). Only the HASH is stored. `version`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mariadb-api-key-store.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-api-key-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"mariadb-api-key-store.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-api-key-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,qCAAqC,CAAC;AA2C7C,qBAAa,kBAAmB,YAAW,WAAW;IASlD,OAAO,CAAC,QAAQ,CAAC,IAAI;IARvB,uCAAuC;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,8FAA8F;IAC9F,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,0DAA0D;IAC1D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;gBAGnB,IAAI,EAAE,IAAI;IAC3B,yFAAyF;IACzF,OAAO,CAAC,EAAE,oBAAoB;IAQ1B,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IA0DrE;;;;OAIG;IACG,UAAU,CAAC,KAAK,EAAE;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;QACvC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;QAC3B,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBjB,iGAAiG;IAC3F,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQxD,+FAA+F;IACzF,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAQ7D"}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MariaDbApiKeyStore = void 0;
|
|
4
|
-
const
|
|
5
|
-
/** The account rows (`service_account`, created by the identity migration `…018`). */
|
|
6
|
-
const APP = 'shield_app';
|
|
4
|
+
const framework_schema_names_js_1 = require("../config/framework-schema-names.js");
|
|
7
5
|
/**
|
|
8
6
|
* Read a JSON-array column into a `string[]`. The mariadb driver returns a JSON/`longtext` column
|
|
9
7
|
* EITHER already-parsed (an array/object — its default for JSON columns) OR as a raw string,
|
|
@@ -28,8 +26,20 @@ function parseJsonArray(v) {
|
|
|
28
26
|
}
|
|
29
27
|
class MariaDbApiKeyStore {
|
|
30
28
|
pool;
|
|
31
|
-
|
|
29
|
+
/** `<authz>`.`service_account_key`. */
|
|
30
|
+
keyTable;
|
|
31
|
+
/** The account rows (`<app>`.`service_account`, created by the identity migration `…018`). */
|
|
32
|
+
accountTable;
|
|
33
|
+
/** `<app>`.`companies` (the account's tenant company). */
|
|
34
|
+
companyTable;
|
|
35
|
+
constructor(pool,
|
|
36
|
+
/** Framework database names; absent ⇒ resolved from `SHIELD_DB_NAMESPACE` here, once. */
|
|
37
|
+
schemas) {
|
|
32
38
|
this.pool = pool;
|
|
39
|
+
const s = (0, framework_schema_names_js_1.schemaNamesAtConstruction)(schemas);
|
|
40
|
+
this.keyTable = (0, framework_schema_names_js_1.qualifiedTableName)(s.authz, 'service_account_key');
|
|
41
|
+
this.accountTable = (0, framework_schema_names_js_1.qualifiedTableName)(s.app, 'service_account');
|
|
42
|
+
this.companyTable = (0, framework_schema_names_js_1.qualifiedTableName)(s.app, 'companies');
|
|
33
43
|
}
|
|
34
44
|
async getByAccountId(accountId) {
|
|
35
45
|
// The CURRENT key = highest version for the account. (Rotation inserts a new version; the old
|
|
@@ -56,9 +66,9 @@ class MariaDbApiKeyStore {
|
|
|
56
66
|
k.sak_secret_hash, k.sak_version, k.sak_expires_at_ms, k.sak_revoked_at_ms,
|
|
57
67
|
sa.sa_scope_subtree, sa.sa_role_ids, sa.sa_status, sa.sa_tenant_id,
|
|
58
68
|
c.c_uuid AS tenant_company_uuid
|
|
59
|
-
FROM
|
|
60
|
-
JOIN
|
|
61
|
-
LEFT JOIN
|
|
69
|
+
FROM ${this.keyTable} k
|
|
70
|
+
JOIN ${this.accountTable} sa ON sa.sa_id = k.sak_account_id
|
|
71
|
+
LEFT JOIN ${this.companyTable} c ON c.c_uuid = sa.sa_tenant_id
|
|
62
72
|
WHERE k.sak_account_id = ?
|
|
63
73
|
ORDER BY k.sak_version DESC
|
|
64
74
|
LIMIT 1`, [accountId]));
|
|
@@ -94,7 +104,7 @@ class MariaDbApiKeyStore {
|
|
|
94
104
|
* the natural key `(account, version)`.
|
|
95
105
|
*/
|
|
96
106
|
async putVersion(input) {
|
|
97
|
-
await this.pool.query(`INSERT INTO
|
|
107
|
+
await this.pool.query(`INSERT INTO ${this.keyTable}
|
|
98
108
|
(sak_account_id, sak_tenant_id, sak_scope_subtree, sak_role_ids,
|
|
99
109
|
sak_secret_hash, sak_version, sak_expires_at_ms, sak_revoked_at_ms, sak_created_at_ms)
|
|
100
110
|
VALUES (?, ?, ?, ?, ?, ?, ?, NULL, ?)`, [
|
|
@@ -110,12 +120,12 @@ class MariaDbApiKeyStore {
|
|
|
110
120
|
}
|
|
111
121
|
/** The current highest version for an account (0 if none) — for computing the next on rotate. */
|
|
112
122
|
async currentVersion(accountId) {
|
|
113
|
-
const rows = (await this.pool.query(`SELECT MAX(sak_version) AS v FROM
|
|
123
|
+
const rows = (await this.pool.query(`SELECT MAX(sak_version) AS v FROM ${this.keyTable} WHERE sak_account_id = ?`, [accountId]));
|
|
114
124
|
return rows[0]?.v != null ? Number(rows[0].v) : 0;
|
|
115
125
|
}
|
|
116
126
|
/** Revoke ALL of an account's keys (immediate fail-closed on the next request). Idempotent. */
|
|
117
127
|
async revoke(accountId, atMs) {
|
|
118
|
-
await this.pool.query(`UPDATE
|
|
128
|
+
await this.pool.query(`UPDATE ${this.keyTable}
|
|
119
129
|
SET sak_revoked_at_ms = ?
|
|
120
130
|
WHERE sak_account_id = ? AND sak_revoked_at_ms IS NULL`, [atMs, accountId]);
|
|
121
131
|
}
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
import type { Pool } from 'mariadb';
|
|
61
61
|
import type { CredentialResolver, ResolvedCredential } from '@astram/core';
|
|
62
62
|
import type { MfaDeviceStore } from '../auth/ports/mfa-device-store.js';
|
|
63
|
+
import { type FrameworkSchemaNames } from '../config/framework-schema-names.js';
|
|
63
64
|
/** DI token for the resolver — re-exported from the shared auth tokens module. */
|
|
64
65
|
export { SHIELD_CREDENTIAL_RESOLVER } from '../auth/tokens.js';
|
|
65
66
|
/**
|
|
@@ -83,6 +84,10 @@ export declare class MariaDbCredentialResolver implements CredentialResolver {
|
|
|
83
84
|
* installed or migrated while the process runs) is enforced without a restart.
|
|
84
85
|
*/
|
|
85
86
|
private usersTableMissingUntil;
|
|
87
|
+
/** `<identity>`.`identity_credential`. */
|
|
88
|
+
private readonly credentialTable;
|
|
89
|
+
/** `<app>`.`users` (the account status JOIN). */
|
|
90
|
+
private readonly usersTable;
|
|
86
91
|
constructor(pool: Pool,
|
|
87
92
|
/**
|
|
88
93
|
* The device store whose CONFIRMED keys become the login's `totp` required factor.
|
|
@@ -91,7 +96,9 @@ export declare class MariaDbCredentialResolver implements CredentialResolver {
|
|
|
91
96
|
*/
|
|
92
97
|
mfaDevices?: (Pick<MfaDeviceStore, "listConfirmedSecrets"> | null) | undefined,
|
|
93
98
|
/** Clock (epoch-ms) for the users-table re-probe window. */
|
|
94
|
-
now?: () => number
|
|
99
|
+
now?: () => number,
|
|
100
|
+
/** Framework database names; absent ⇒ resolved from `SHIELD_DB_NAMESPACE` here, once. */
|
|
101
|
+
schemas?: FrameworkSchemaNames);
|
|
95
102
|
/**
|
|
96
103
|
* Resolve `identityHint` → {@link ResolvedCredential}, or `null` when no such
|
|
97
104
|
* principal exists.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mariadb-credential-resolver.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-credential-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,kBAAkB,EAAkB,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC3F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"mariadb-credential-resolver.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-credential-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,kBAAkB,EAAkB,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC3F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,qCAAqC,CAAC;AAE7C,kFAAkF;AAClF,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AA6D/D;;;;GAIG;AACH,qBAAa,yBAA0B,YAAW,kBAAkB;IAchE,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC5B,4DAA4D;IAC5D,OAAO,CAAC,QAAQ,CAAC,GAAG;IArBtB;;;;OAIG;IACH,OAAO,CAAC,sBAAsB,CAAK;IAEnC,0CAA0C;IAC1C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,iDAAiD;IACjD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAGjB,IAAI,EAAE,IAAI;IAC3B;;;;OAIG;IACc,UAAU,CAAC,GAAE,IAAI,CAAC,cAAc,EAAE,sBAAsB,CAAC,GAAG,IAAI,aAAA;IACjF,4DAA4D;IAC3C,GAAG,GAAE,MAAM,MAAiB;IAC7C,yFAAyF;IACzF,OAAO,CAAC,EAAE,oBAAoB;IAOhC;;;;;;;;;;;;;OAaG;IACG,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAoCvE;;;;;;OAMG;YACW,MAAM;IAyBpB;;;OAGG;YACW,kBAAkB;CAQjC"}
|
|
@@ -60,10 +60,11 @@
|
|
|
60
60
|
*/
|
|
61
61
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
62
62
|
exports.MariaDbCredentialResolver = exports.SHIELD_CREDENTIAL_RESOLVER = void 0;
|
|
63
|
+
const framework_schema_names_js_1 = require("../config/framework-schema-names.js");
|
|
63
64
|
/** DI token for the resolver — re-exported from the shared auth tokens module. */
|
|
64
65
|
var tokens_js_1 = require("../auth/tokens.js");
|
|
65
66
|
Object.defineProperty(exports, "SHIELD_CREDENTIAL_RESOLVER", { enumerable: true, get: function () { return tokens_js_1.SHIELD_CREDENTIAL_RESOLVER; } });
|
|
66
|
-
/** MariaDB `ER_NO_SUCH_TABLE`: the app keeps no
|
|
67
|
+
/** MariaDB `ER_NO_SUCH_TABLE`: the app keeps no `<app>.users` table. */
|
|
67
68
|
const ER_NO_SUCH_TABLE = 1146;
|
|
68
69
|
/** How long the credential-only fallback is used before the users JOIN is tried again. */
|
|
69
70
|
const USERS_TABLE_REPROBE_MS = 60_000;
|
|
@@ -113,6 +114,10 @@ class MariaDbCredentialResolver {
|
|
|
113
114
|
* installed or migrated while the process runs) is enforced without a restart.
|
|
114
115
|
*/
|
|
115
116
|
usersTableMissingUntil = 0;
|
|
117
|
+
/** `<identity>`.`identity_credential`. */
|
|
118
|
+
credentialTable;
|
|
119
|
+
/** `<app>`.`users` (the account status JOIN). */
|
|
120
|
+
usersTable;
|
|
116
121
|
constructor(pool,
|
|
117
122
|
/**
|
|
118
123
|
* The device store whose CONFIRMED keys become the login's `totp` required factor.
|
|
@@ -121,10 +126,15 @@ class MariaDbCredentialResolver {
|
|
|
121
126
|
*/
|
|
122
127
|
mfaDevices,
|
|
123
128
|
/** Clock (epoch-ms) for the users-table re-probe window. */
|
|
124
|
-
now = Date.now
|
|
129
|
+
now = Date.now,
|
|
130
|
+
/** Framework database names; absent ⇒ resolved from `SHIELD_DB_NAMESPACE` here, once. */
|
|
131
|
+
schemas) {
|
|
125
132
|
this.pool = pool;
|
|
126
133
|
this.mfaDevices = mfaDevices;
|
|
127
134
|
this.now = now;
|
|
135
|
+
const s = (0, framework_schema_names_js_1.schemaNamesAtConstruction)(schemas);
|
|
136
|
+
this.credentialTable = (0, framework_schema_names_js_1.qualifiedTableName)(s.identity, 'identity_credential');
|
|
137
|
+
this.usersTable = (0, framework_schema_names_js_1.qualifiedTableName)(s.app, 'users');
|
|
128
138
|
}
|
|
129
139
|
/**
|
|
130
140
|
* Resolve `identityHint` → {@link ResolvedCredential}, or `null` when no such
|
|
@@ -182,8 +192,8 @@ class MariaDbCredentialResolver {
|
|
|
182
192
|
if (this.now() >= this.usersTableMissingUntil) {
|
|
183
193
|
try {
|
|
184
194
|
return (await this.pool.query(`SELECT ic_password_hash, ic_principal_id, ic_tenant_id, ic_roles, u.u_status
|
|
185
|
-
FROM
|
|
186
|
-
LEFT JOIN
|
|
195
|
+
FROM ${this.credentialTable}
|
|
196
|
+
LEFT JOIN ${this.usersTable} u ON u.u_uuid = ic_principal_id
|
|
187
197
|
WHERE ic_identity_hint = ?
|
|
188
198
|
LIMIT 1`, [identityHint]));
|
|
189
199
|
}
|
|
@@ -194,7 +204,7 @@ class MariaDbCredentialResolver {
|
|
|
194
204
|
}
|
|
195
205
|
}
|
|
196
206
|
return (await this.pool.query(`SELECT ic_password_hash, ic_principal_id, ic_tenant_id, ic_roles
|
|
197
|
-
FROM
|
|
207
|
+
FROM ${this.credentialTable}
|
|
198
208
|
WHERE ic_identity_hint = ?
|
|
199
209
|
LIMIT 1`, [identityHint]));
|
|
200
210
|
}
|
|
@@ -6,10 +6,15 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { Pool } from 'mariadb';
|
|
8
8
|
import type { CredentialWriter } from '../auth/ports/credential-writer.js';
|
|
9
|
+
import { type FrameworkSchemaNames } from '../config/framework-schema-names.js';
|
|
9
10
|
export declare class MariaDbCredentialWriter implements CredentialWriter {
|
|
10
11
|
private readonly pool;
|
|
11
12
|
private readonly now;
|
|
12
|
-
|
|
13
|
+
/** `<identity>`.`identity_credential`, resolved at construction. */
|
|
14
|
+
private readonly table;
|
|
15
|
+
constructor(pool: Pool, now?: () => number,
|
|
16
|
+
/** Framework database names; absent ⇒ resolved from `SHIELD_DB_NAMESPACE` here, once. */
|
|
17
|
+
schemas?: FrameworkSchemaNames);
|
|
13
18
|
setPassword(principalId: string, passwordHash: string): Promise<void>;
|
|
14
19
|
getPasswordHash(principalId: string): Promise<string | null>;
|
|
15
20
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mariadb-credential-writer.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-credential-writer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"mariadb-credential-writer.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-credential-writer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,qCAAqC,CAAC;AAM7C,qBAAa,uBAAwB,YAAW,gBAAgB;IAK5D,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,GAAG;IALtB,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAGZ,IAAI,EAAE,IAAI,EACV,GAAG,GAAE,MAAM,MAAiB;IAC7C,yFAAyF;IACzF,OAAO,CAAC,EAAE,oBAAoB;IAQ1B,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOrE,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAOnE"}
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MariaDbCredentialWriter = void 0;
|
|
4
|
+
const framework_schema_names_js_1 = require("../config/framework-schema-names.js");
|
|
4
5
|
class MariaDbCredentialWriter {
|
|
5
6
|
pool;
|
|
6
7
|
now;
|
|
7
|
-
|
|
8
|
+
/** `<identity>`.`identity_credential`, resolved at construction. */
|
|
9
|
+
table;
|
|
10
|
+
constructor(pool, now = Date.now,
|
|
11
|
+
/** Framework database names; absent ⇒ resolved from `SHIELD_DB_NAMESPACE` here, once. */
|
|
12
|
+
schemas) {
|
|
8
13
|
this.pool = pool;
|
|
9
14
|
this.now = now;
|
|
15
|
+
this.table = (0, framework_schema_names_js_1.qualifiedTableName)((0, framework_schema_names_js_1.schemaNamesAtConstruction)(schemas).identity, 'identity_credential');
|
|
10
16
|
}
|
|
11
17
|
async setPassword(principalId, passwordHash) {
|
|
12
|
-
await this.pool.query(
|
|
18
|
+
await this.pool.query(`UPDATE ${this.table} SET ic_password_hash = ?, ic_updated_at_ms = ? WHERE ic_principal_id = ?`, [passwordHash, this.now(), principalId]);
|
|
13
19
|
}
|
|
14
20
|
async getPasswordHash(principalId) {
|
|
15
|
-
const rows = (await this.pool.query(
|
|
21
|
+
const rows = (await this.pool.query(`SELECT ic_password_hash FROM ${this.table} WHERE ic_principal_id = ? LIMIT 1`, [principalId]));
|
|
16
22
|
return rows[0]?.ic_password_hash ?? null;
|
|
17
23
|
}
|
|
18
24
|
}
|
|
@@ -29,10 +29,19 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import type { Pool } from 'mariadb';
|
|
31
31
|
import type { CurrentEmail, EmailAddressChanger, EmailChangeOutcome, EmailChangeRequest } from '../auth/ports/email-address-changer.js';
|
|
32
|
+
import { type FrameworkSchemaNames } from '../config/framework-schema-names.js';
|
|
32
33
|
export declare class MariaDbEmailAddressChanger implements EmailAddressChanger {
|
|
33
34
|
private readonly pool;
|
|
34
35
|
private readonly now;
|
|
35
|
-
|
|
36
|
+
/** `<app>`.`users`. */
|
|
37
|
+
private readonly usersTable;
|
|
38
|
+
/** `<identity>`.`identity_credential`. */
|
|
39
|
+
private readonly credentialTable;
|
|
40
|
+
/** `<identity>`.`email_change_token`. */
|
|
41
|
+
private readonly tokenTable;
|
|
42
|
+
constructor(pool: Pool, now?: () => number,
|
|
43
|
+
/** Framework database names; absent ⇒ resolved from `SHIELD_DB_NAMESPACE` here, once. */
|
|
44
|
+
schemas?: FrameworkSchemaNames);
|
|
36
45
|
readCurrent(principalId: string, candidate: string): Promise<CurrentEmail | null>;
|
|
37
46
|
change(request: EmailChangeRequest): Promise<EmailChangeOutcome>;
|
|
38
47
|
private changeLocked;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mariadb-email-address-changer.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-email-address-changer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,KAAK,EAAE,IAAI,EAAkB,MAAM,SAAS,CAAC;AACpD,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,wCAAwC,CAAC;
|
|
1
|
+
{"version":3,"file":"mariadb-email-address-changer.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-email-address-changer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,KAAK,EAAE,IAAI,EAAkB,MAAM,SAAS,CAAC;AACpD,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,qCAAqC,CAAC;AAqB7C,qBAAa,0BAA2B,YAAW,mBAAmB;IASlE,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,GAAG;IATtB,uBAAuB;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,0CAA0C;IAC1C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,yCAAyC;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAGjB,IAAI,EAAE,IAAI,EACV,GAAG,GAAE,MAAM,MAAiB;IAC7C,yFAAyF;IACzF,OAAO,CAAC,EAAE,oBAAoB;IAQ1B,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAejF,MAAM,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC;YA6BxD,YAAY;CAqE3B"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MariaDbEmailAddressChanger = void 0;
|
|
4
|
+
const framework_schema_names_js_1 = require("../config/framework-schema-names.js");
|
|
4
5
|
/** Seconds to wait for the email-change named lock before giving up (caller → 503). */
|
|
5
6
|
const LOCK_TIMEOUT_S = 10;
|
|
6
7
|
/** MariaDB `ER_DUP_ENTRY`: a unique index (here `uq_users_email` or the hint PK) refused a write. */
|
|
@@ -15,13 +16,25 @@ function isDuplicateKey(err) {
|
|
|
15
16
|
class MariaDbEmailAddressChanger {
|
|
16
17
|
pool;
|
|
17
18
|
now;
|
|
18
|
-
|
|
19
|
+
/** `<app>`.`users`. */
|
|
20
|
+
usersTable;
|
|
21
|
+
/** `<identity>`.`identity_credential`. */
|
|
22
|
+
credentialTable;
|
|
23
|
+
/** `<identity>`.`email_change_token`. */
|
|
24
|
+
tokenTable;
|
|
25
|
+
constructor(pool, now = Date.now,
|
|
26
|
+
/** Framework database names; absent ⇒ resolved from `SHIELD_DB_NAMESPACE` here, once. */
|
|
27
|
+
schemas) {
|
|
19
28
|
this.pool = pool;
|
|
20
29
|
this.now = now;
|
|
30
|
+
const s = (0, framework_schema_names_js_1.schemaNamesAtConstruction)(schemas);
|
|
31
|
+
this.usersTable = (0, framework_schema_names_js_1.qualifiedTableName)(s.app, 'users');
|
|
32
|
+
this.credentialTable = (0, framework_schema_names_js_1.qualifiedTableName)(s.identity, 'identity_credential');
|
|
33
|
+
this.tokenTable = (0, framework_schema_names_js_1.qualifiedTableName)(s.identity, 'email_change_token');
|
|
21
34
|
}
|
|
22
35
|
async readCurrent(principalId, candidate) {
|
|
23
36
|
const rows = (await this.pool.query(`SELECT u_email, u_status, (u_email = ?) AS same_as_candidate
|
|
24
|
-
FROM
|
|
37
|
+
FROM ${this.usersTable} WHERE u_uuid = ? LIMIT 1`, [candidate, principalId]));
|
|
25
38
|
const row = rows[0];
|
|
26
39
|
if (row === undefined)
|
|
27
40
|
return null;
|
|
@@ -66,7 +79,7 @@ class MariaDbEmailAddressChanger {
|
|
|
66
79
|
}
|
|
67
80
|
async changeLocked(conn, request) {
|
|
68
81
|
const { principalId, newEmail } = request;
|
|
69
|
-
const self = (await conn.query(
|
|
82
|
+
const self = (await conn.query(`SELECT u_email, u_status FROM ${this.usersTable} WHERE u_uuid = ? FOR UPDATE`, [principalId]));
|
|
70
83
|
const row = self[0];
|
|
71
84
|
if (row === undefined)
|
|
72
85
|
return { status: 'not_found' };
|
|
@@ -76,24 +89,24 @@ class MariaDbEmailAddressChanger {
|
|
|
76
89
|
if (request.expectedOldEmail !== undefined && oldEmail !== request.expectedOldEmail) {
|
|
77
90
|
return { status: 'stale' };
|
|
78
91
|
}
|
|
79
|
-
const otherUser = (await conn.query(
|
|
92
|
+
const otherUser = (await conn.query(`SELECT u_uuid FROM ${this.usersTable} WHERE u_email = ? AND u_uuid <> ? LIMIT 1 FOR UPDATE`, [newEmail, principalId]));
|
|
80
93
|
if (otherUser.length > 0)
|
|
81
94
|
return { status: 'unavailable' };
|
|
82
|
-
const hintOwners = (await conn.query(
|
|
95
|
+
const hintOwners = (await conn.query(`SELECT ic_principal_id FROM ${this.credentialTable} WHERE ic_identity_hint = ? FOR UPDATE`, [newEmail]));
|
|
83
96
|
if (hintOwners.some((h) => h.ic_principal_id !== principalId))
|
|
84
97
|
return { status: 'unavailable' };
|
|
85
98
|
const selfAlreadyHasNewHint = hintOwners.length > 0;
|
|
86
|
-
await conn.query(
|
|
99
|
+
await conn.query(`SELECT ic_identity_hint FROM ${this.credentialTable} WHERE ic_principal_id = ? FOR UPDATE`, [principalId]);
|
|
87
100
|
// Steps 3–4: a duplicate key here is a row the locked reads could not see (written by a path
|
|
88
101
|
// without the named lock); the index refuses it → `unavailable`, and `change` rolls back.
|
|
89
102
|
let hintMoved = false;
|
|
90
103
|
try {
|
|
91
|
-
await conn.query(
|
|
104
|
+
await conn.query(`UPDATE ${this.usersTable} SET u_email = ? WHERE u_uuid = ?`, [
|
|
92
105
|
newEmail,
|
|
93
106
|
principalId,
|
|
94
107
|
]);
|
|
95
108
|
if (oldEmail !== null && oldEmail !== '' && !selfAlreadyHasNewHint) {
|
|
96
|
-
const moved = (await conn.query(`UPDATE
|
|
109
|
+
const moved = (await conn.query(`UPDATE ${this.credentialTable}
|
|
97
110
|
SET ic_identity_hint = ?, ic_updated_at_ms = ?
|
|
98
111
|
WHERE ic_principal_id = ? AND ic_identity_hint = ?`, [newEmail, this.now(), principalId, oldEmail]));
|
|
99
112
|
hintMoved = moved.affectedRows > 0;
|
|
@@ -104,7 +117,7 @@ class MariaDbEmailAddressChanger {
|
|
|
104
117
|
return { status: 'unavailable' }; // the driver message is dropped
|
|
105
118
|
throw err;
|
|
106
119
|
}
|
|
107
|
-
await conn.query(`UPDATE
|
|
120
|
+
await conn.query(`UPDATE ${this.tokenTable} SET ect_consumed_at_ms = ?
|
|
108
121
|
WHERE ect_principal_id = ? AND ect_consumed_at_ms IS NULL`, [this.now(), principalId]);
|
|
109
122
|
if (request.withinTransaction !== undefined) {
|
|
110
123
|
await request.withinTransaction((sql, params) => conn.query(sql, params ?? []), { oldEmail, hintMoved });
|