@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.
Files changed (99) hide show
  1. package/dist/auth/auth.module.d.ts +8 -1
  2. package/dist/auth/auth.module.d.ts.map +1 -1
  3. package/dist/auth/auth.module.js +76 -31
  4. package/dist/authentication/mariadb-api-key-store.d.ts +10 -1
  5. package/dist/authentication/mariadb-api-key-store.d.ts.map +1 -1
  6. package/dist/authentication/mariadb-api-key-store.js +20 -10
  7. package/dist/authentication/mariadb-credential-resolver.d.ts +8 -1
  8. package/dist/authentication/mariadb-credential-resolver.d.ts.map +1 -1
  9. package/dist/authentication/mariadb-credential-resolver.js +15 -5
  10. package/dist/authentication/mariadb-credential-writer.d.ts +6 -1
  11. package/dist/authentication/mariadb-credential-writer.d.ts.map +1 -1
  12. package/dist/authentication/mariadb-credential-writer.js +9 -3
  13. package/dist/authentication/mariadb-email-address-changer.d.ts +10 -1
  14. package/dist/authentication/mariadb-email-address-changer.d.ts.map +1 -1
  15. package/dist/authentication/mariadb-email-address-changer.js +22 -9
  16. package/dist/authentication/mariadb-email-change-token-store.d.ts +8 -1
  17. package/dist/authentication/mariadb-email-change-token-store.d.ts.map +1 -1
  18. package/dist/authentication/mariadb-email-change-token-store.js +18 -9
  19. package/dist/authentication/mariadb-mfa-device-store.d.ts +6 -1
  20. package/dist/authentication/mariadb-mfa-device-store.d.ts.map +1 -1
  21. package/dist/authentication/mariadb-mfa-device-store.js +22 -13
  22. package/dist/authentication/mariadb-profile-resolver.d.ts +6 -1
  23. package/dist/authentication/mariadb-profile-resolver.d.ts.map +1 -1
  24. package/dist/authentication/mariadb-profile-resolver.js +12 -6
  25. package/dist/authentication/mariadb-profile-writer.d.ts +6 -1
  26. package/dist/authentication/mariadb-profile-writer.d.ts.map +1 -1
  27. package/dist/authentication/mariadb-profile-writer.js +11 -4
  28. package/dist/authentication/mariadb-recovery-code-store.d.ts +6 -1
  29. package/dist/authentication/mariadb-recovery-code-store.d.ts.map +1 -1
  30. package/dist/authentication/mariadb-recovery-code-store.js +11 -5
  31. package/dist/authentication/mariadb-reset-token-store.d.ts +6 -1
  32. package/dist/authentication/mariadb-reset-token-store.d.ts.map +1 -1
  33. package/dist/authentication/mariadb-reset-token-store.js +11 -5
  34. package/dist/authentication/mariadb-session-sweeper.d.ts +6 -1
  35. package/dist/authentication/mariadb-session-sweeper.d.ts.map +1 -1
  36. package/dist/authentication/mariadb-session-sweeper.js +11 -5
  37. package/dist/authz/cross-tenant-grant-writer.d.ts.map +1 -1
  38. package/dist/authz/cross-tenant-grant-writer.js +4 -3
  39. package/dist/authz/list-scope-sql.d.ts +10 -2
  40. package/dist/authz/list-scope-sql.d.ts.map +1 -1
  41. package/dist/authz/list-scope-sql.js +4 -4
  42. package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts.map +1 -1
  43. package/dist/authz/mariadb-cross-tenant-grant-adapter.js +4 -3
  44. package/dist/authz/mariadb-list-scope-adapter.d.ts.map +1 -1
  45. package/dist/authz/mariadb-list-scope-adapter.js +4 -3
  46. package/dist/authz/mariadb-policy-store.d.ts.map +1 -1
  47. package/dist/authz/mariadb-policy-store.js +4 -3
  48. package/dist/authz/mariadb-record-rule-adapter.d.ts.map +1 -1
  49. package/dist/authz/mariadb-record-rule-adapter.js +4 -3
  50. package/dist/authz/mariadb-record-share-adapter.d.ts.map +1 -1
  51. package/dist/authz/mariadb-record-share-adapter.js +4 -3
  52. package/dist/authz/record-share-cascade.d.ts.map +1 -1
  53. package/dist/authz/record-share-cascade.js +4 -3
  54. package/dist/authz/record-share-writer.d.ts.map +1 -1
  55. package/dist/authz/record-share-writer.js +4 -3
  56. package/dist/authz/wire-record-authorization.d.ts +8 -0
  57. package/dist/authz/wire-record-authorization.d.ts.map +1 -1
  58. package/dist/authz/wire-record-authorization.js +14 -6
  59. package/dist/boot/boot-sequence.d.ts +10 -0
  60. package/dist/boot/boot-sequence.d.ts.map +1 -1
  61. package/dist/boot/boot-sequence.js +47 -3
  62. package/dist/boot/migration-ledger-check.d.ts +94 -0
  63. package/dist/boot/migration-ledger-check.d.ts.map +1 -0
  64. package/dist/boot/migration-ledger-check.js +280 -0
  65. package/dist/boot/single-layout-probe.d.ts +66 -0
  66. package/dist/boot/single-layout-probe.d.ts.map +1 -0
  67. package/dist/boot/single-layout-probe.js +334 -0
  68. package/dist/config/app-config.d.ts +46 -3
  69. package/dist/config/app-config.d.ts.map +1 -1
  70. package/dist/config/app-config.js +90 -11
  71. package/dist/config/config.module.d.ts +7 -0
  72. package/dist/config/config.module.d.ts.map +1 -1
  73. package/dist/config/config.module.js +8 -1
  74. package/dist/config/framework-schema-names.d.ts +29 -0
  75. package/dist/config/framework-schema-names.d.ts.map +1 -0
  76. package/dist/config/framework-schema-names.js +41 -0
  77. package/dist/identity/permission-facts.adapter.d.ts +7 -1
  78. package/dist/identity/permission-facts.adapter.d.ts.map +1 -1
  79. package/dist/identity/permission-facts.adapter.js +15 -6
  80. package/dist/index.d.ts +5 -2
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.js +18 -2
  83. package/dist/module/shield.module.d.ts.map +1 -1
  84. package/dist/module/shield.module.js +43 -7
  85. package/dist/session/mariadb-session-record-store.d.ts +8 -1
  86. package/dist/session/mariadb-session-record-store.d.ts.map +1 -1
  87. package/dist/session/mariadb-session-record-store.js +23 -13
  88. package/dist/session/session-restriction-store.d.ts +8 -1
  89. package/dist/session/session-restriction-store.d.ts.map +1 -1
  90. package/dist/session/session-restriction-store.js +15 -5
  91. package/migrations/1710000000006-CreateAuthzSchema.cjs +15 -4
  92. package/migrations/1710000000007-ExtendPermissionAndRolePermission.cjs +11 -3
  93. package/migrations/1710000000008-CreateAccessBundleTables.cjs +14 -4
  94. package/migrations/1710000000009-CreateRecordPermissionRule.cjs +15 -4
  95. package/migrations/1710000000014-CreateRecordShare.cjs +15 -4
  96. package/migrations/1710000000015-CreateScopeClosure.cjs +14 -4
  97. package/migrations/1710000000016-AlterCompanyParentFkRestrict.cjs +20 -9
  98. package/migrations/1710000000017-CreateServiceAccountKey.cjs +14 -4
  99. 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":"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"}
1
+ {"version":3,"file":"auth.module.d.ts","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":"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"}
@@ -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
- constructor(pool: Pool);
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;AA+C9D,qBAAa,kBAAmB,YAAW,WAAW;IACxC,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAEjC,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
+ {"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 AUTHZ = 'shield_authz';
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
- constructor(pool) {
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 \`${AUTHZ}\`.\`service_account_key\` k
60
- JOIN \`${APP}\`.\`service_account\` sa ON sa.sa_id = k.sak_account_id
61
- LEFT JOIN \`${APP}\`.\`companies\` c ON c.c_uuid = sa.sa_tenant_id
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 \`${AUTHZ}\`.\`service_account_key\`
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 \`${AUTHZ}\`.\`service_account_key\` WHERE sak_account_id = ?`, [accountId]));
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 \`${AUTHZ}\`.\`service_account_key\`
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;AAExE,kFAAkF;AAClF,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AA6D/D;;;;GAIG;AACH,qBAAa,yBAA0B,YAAW,kBAAkB;IAShE,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC5B,4DAA4D;IAC5D,OAAO,CAAC,QAAQ,CAAC,GAAG;IAhBtB;;;;OAIG;IACH,OAAO,CAAC,sBAAsB,CAAK;gBAGhB,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;IAG/C;;;;;;;;;;;;;OAaG;IACG,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAoCvE;;;;;;OAMG;YACW,MAAM;IAyBpB;;;OAGG;YACW,kBAAkB;CAQjC"}
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 `shield_app.users` table. */
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 shield_identity.identity_credential
186
- LEFT JOIN shield_app.users u ON u.u_uuid = ic_principal_id
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 shield_identity.identity_credential
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
- constructor(pool: Pool, now?: () => number);
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;AAM3E,qBAAa,uBAAwB,YAAW,gBAAgB;IAE5D,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,GAAG;gBADH,IAAI,EAAE,IAAI,EACV,GAAG,GAAE,MAAM,MAAiB;IAGzC,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
+ {"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
- constructor(pool, now = Date.now) {
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('UPDATE `shield_identity`.`identity_credential` SET ic_password_hash = ?, ic_updated_at_ms = ? WHERE ic_principal_id = ?', [passwordHash, this.now(), principalId]);
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('SELECT ic_password_hash FROM `shield_identity`.`identity_credential` WHERE ic_principal_id = ? LIMIT 1', [principalId]));
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
- constructor(pool: Pool, now?: () => number);
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;AAqBhD,qBAAa,0BAA2B,YAAW,mBAAmB;IAElE,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,GAAG;gBADH,IAAI,EAAE,IAAI,EACV,GAAG,GAAE,MAAM,MAAiB;IAGzC,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
+ {"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
- constructor(pool, now = Date.now) {
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 \`shield_app\`.\`users\` WHERE u_uuid = ? LIMIT 1`, [candidate, principalId]));
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('SELECT u_email, u_status FROM `shield_app`.`users` WHERE u_uuid = ? FOR UPDATE', [principalId]));
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('SELECT u_uuid FROM `shield_app`.`users` WHERE u_email = ? AND u_uuid <> ? LIMIT 1 FOR UPDATE', [newEmail, principalId]));
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('SELECT ic_principal_id FROM `shield_identity`.`identity_credential` WHERE ic_identity_hint = ? FOR UPDATE', [newEmail]));
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('SELECT ic_identity_hint FROM `shield_identity`.`identity_credential` WHERE ic_principal_id = ? FOR UPDATE', [principalId]);
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('UPDATE `shield_app`.`users` SET u_email = ? WHERE u_uuid = ?', [
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 \`shield_identity\`.\`identity_credential\`
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 \`shield_identity\`.\`email_change_token\` SET ect_consumed_at_ms = ?
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 });