@zintrust/core 0.1.48 → 0.1.49

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 (75) hide show
  1. package/README.md +1 -1
  2. package/app/Controllers/AuthController.d.ts.map +1 -1
  3. package/app/Controllers/AuthController.js +26 -4
  4. package/app/Middleware/index.d.ts.map +1 -1
  5. package/app/Middleware/index.js +3 -3
  6. package/app/Types/controller.d.ts +2 -0
  7. package/app/Types/controller.d.ts.map +1 -1
  8. package/app/Types/controller.js +1 -1
  9. package/package.json +1 -1
  10. package/routes/api.js +13 -6
  11. package/src/cli/CLI.d.ts.map +1 -1
  12. package/src/cli/CLI.js +2 -0
  13. package/src/cli/commands/AddCommand.js +2 -2
  14. package/src/cli/commands/BulletproofKeyGenerateCommand.d.ts +10 -0
  15. package/src/cli/commands/BulletproofKeyGenerateCommand.d.ts.map +1 -0
  16. package/src/cli/commands/BulletproofKeyGenerateCommand.js +139 -0
  17. package/src/cli/commands/JwtDevCommand.d.ts.map +1 -1
  18. package/src/cli/commands/JwtDevCommand.js +51 -32
  19. package/src/cli/scaffolding/ControllerGenerator.d.ts +1 -1
  20. package/src/cli/scaffolding/ControllerGenerator.d.ts.map +1 -1
  21. package/src/cli/scaffolding/ControllerGenerator.js +8 -79
  22. package/src/config/SecretsManager.d.ts +0 -1
  23. package/src/config/SecretsManager.d.ts.map +1 -1
  24. package/src/config/SecretsManager.js +0 -1
  25. package/src/config/middleware.d.ts +1 -0
  26. package/src/config/middleware.d.ts.map +1 -1
  27. package/src/config/middleware.js +3 -0
  28. package/src/http/error-pages/ErrorPageRenderer.js +7 -1
  29. package/src/index.d.ts +1 -0
  30. package/src/index.d.ts.map +1 -1
  31. package/src/index.js +4 -3
  32. package/src/middleware/BulletproofAuthMiddleware.d.ts +92 -0
  33. package/src/middleware/BulletproofAuthMiddleware.d.ts.map +1 -0
  34. package/src/middleware/BulletproofAuthMiddleware.js +421 -0
  35. package/src/middleware/CsrfMiddleware.d.ts +0 -1
  36. package/src/middleware/CsrfMiddleware.d.ts.map +1 -1
  37. package/src/middleware/CsrfMiddleware.js +8 -1
  38. package/src/middleware/JwtAuthMiddleware.d.ts.map +1 -1
  39. package/src/middleware/JwtAuthMiddleware.js +11 -5
  40. package/src/orm/Database.d.ts.map +1 -1
  41. package/src/orm/Database.js +48 -39
  42. package/src/orm/adapters/MySQLProxyAdapter.d.ts.map +1 -1
  43. package/src/orm/adapters/MySQLProxyAdapter.js +54 -35
  44. package/src/orm/adapters/PostgreSQLProxyAdapter.d.ts.map +1 -1
  45. package/src/orm/adapters/PostgreSQLProxyAdapter.js +126 -103
  46. package/src/orm/adapters/SqlProxyHttpAdapterShared.d.ts +30 -0
  47. package/src/orm/adapters/SqlProxyHttpAdapterShared.d.ts.map +1 -0
  48. package/src/orm/adapters/SqlProxyHttpAdapterShared.js +64 -0
  49. package/src/orm/adapters/SqlServerProxyAdapter.d.ts.map +1 -1
  50. package/src/orm/adapters/SqlServerProxyAdapter.js +54 -37
  51. package/src/orm/migrations/MigrationStore.d.ts.map +1 -1
  52. package/src/orm/migrations/MigrationStore.js +22 -1
  53. package/src/routes/doc.js +1 -1
  54. package/src/routes/errorPages.d.ts.map +1 -1
  55. package/src/routes/errorPages.js +9 -2
  56. package/src/security/CsrfTokenManager.d.ts.map +1 -1
  57. package/src/security/CsrfTokenManager.js +57 -23
  58. package/src/security/JwtManager.d.ts +4 -1
  59. package/src/security/JwtManager.d.ts.map +1 -1
  60. package/src/security/JwtManager.js +24 -10
  61. package/src/security/JwtSessions.d.ts +12 -0
  62. package/src/security/JwtSessions.d.ts.map +1 -0
  63. package/src/security/JwtSessions.js +556 -0
  64. package/src/security/NonceReplay.d.ts +24 -0
  65. package/src/security/NonceReplay.d.ts.map +1 -0
  66. package/src/security/NonceReplay.js +42 -0
  67. package/src/security/TokenRevocation.d.ts.map +1 -1
  68. package/src/security/TokenRevocation.js +1 -0
  69. package/src/tools/http/Http.d.ts +5 -0
  70. package/src/tools/http/Http.d.ts.map +1 -1
  71. package/src/tools/http/Http.js +25 -9
  72. package/src/tools/queue/QueueReliabilityOrchestrator.d.ts.map +1 -1
  73. package/src/tools/queue/QueueReliabilityOrchestrator.js +18 -6
  74. package/src/validation/Validator.d.ts.map +1 -1
  75. package/src/validation/Validator.js +4 -2
@@ -1 +1 @@
1
- {"version":3,"file":"MigrationStore.d.ts","sourceRoot":"","sources":["../../../../src/orm/migrations/MigrationStore.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAI/C,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AA8ChG,eAAO,MAAM,cAAc;oBACH,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;8BASzC,SAAS,UACN,cAAc,YACZ,MAAM,GACd,OAAO,CAAC,MAAM,CAAC;sBAiBZ,SAAS,SACN,cAAc,WACZ,MAAM,GACd,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;sBAyBlC,SAAS,UACL;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,cAAc,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAC9E,OAAO,CAAC,IAAI,CAAC;mBAuCV,SAAS,UACL;QACN,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,cAAc,CAAC;QACtB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,qBAAqB,CAAC;QAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,GACA,OAAO,CAAC,IAAI,CAAC;kCAiBV,SAAS,UACL;QAAE,KAAK,EAAE,cAAc,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GACnE,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;8BAyB5C,SAAS,UACL;QAAE,KAAK,EAAE,cAAc,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GACjD,OAAO,CAAC,MAAM,EAAE,CAAC;qBAgBd,SAAS,UACL;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,cAAc,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAC/D,OAAO,CAAC,IAAI,CAAC;EAShB,CAAC"}
1
+ {"version":3,"file":"MigrationStore.d.ts","sourceRoot":"","sources":["../../../../src/orm/migrations/MigrationStore.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAI/C,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AA0DhG,eAAO,MAAM,cAAc;oBACH,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;8BAkBzC,SAAS,UACN,cAAc,YACZ,MAAM,GACd,OAAO,CAAC,MAAM,CAAC;sBAiBZ,SAAS,SACN,cAAc,WACZ,MAAM,GACd,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;sBAyBlC,SAAS,UACL;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,cAAc,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAC9E,OAAO,CAAC,IAAI,CAAC;mBAuCV,SAAS,UACL;QACN,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,cAAc,CAAC;QACtB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,qBAAqB,CAAC;QAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,GACA,OAAO,CAAC,IAAI,CAAC;kCAiBV,SAAS,UACL;QAAE,KAAK,EAAE,cAAc,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GACnE,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;8BAyB5C,SAAS,UACL;QAAE,KAAK,EAAE,cAAc,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GACjD,OAAO,CAAC,MAAM,EAAE,CAAC;qBAgBd,SAAS,UACL;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,cAAc,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAC/D,OAAO,CAAC,IAAI,CAAC;EAShB,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { Env } from '../../config/env.js';
1
2
  import { ErrorFactory } from '../../exceptions/ZintrustError.js';
2
3
  import { QueryBuilder } from '../QueryBuilder.js';
3
4
  function nowIso() {
@@ -21,7 +22,19 @@ const hasMigrationsTableSupport = (adapter) => {
21
22
  };
22
23
  const requireMigrationsTableSupport = (adapter) => {
23
24
  if (!hasMigrationsTableSupport(adapter)) {
24
- throw ErrorFactory.createCliError('Migrations tracking is not supported for this database adapter yet.');
25
+ const isSqlProxyEnabled = Env.getBool('USE_POSTGRES_PROXY', false) ||
26
+ Env.getBool('USE_MYSQL_PROXY', false) ||
27
+ Env.getBool('USE_SQLSERVER_PROXY', false) ||
28
+ Env.get('POSTGRES_PROXY_URL', '').trim() !== '' ||
29
+ Env.get('MYSQL_PROXY_URL', '').trim() !== '' ||
30
+ Env.get('SQLSERVER_PROXY_URL', '').trim() !== '';
31
+ const hint = isSqlProxyEnabled
32
+ ? 'If you are using SQL proxy adapters, ensure the proxy stack is running (e.g. `zin cp up` or `docker compose -f docker-compose.proxy.yml up -d`).'
33
+ : undefined;
34
+ let message = 'Migrations tracking is not supported for this database adapter yet.';
35
+ if (hint)
36
+ message = `${message} ${hint}`;
37
+ throw ErrorFactory.createCliError(message);
25
38
  }
26
39
  return async () => {
27
40
  await adapter.ensureMigrationsTable();
@@ -32,6 +45,14 @@ export const MigrationStore = Object.freeze({
32
45
  assertDbSupportsMigrations(db);
33
46
  const adapter = db.getAdapterInstance(false);
34
47
  const ensure = requireMigrationsTableSupport(adapter);
48
+ // getAdapterInstance(false) returns a raw adapter without going through Database.query()
49
+ // which auto-connects; ensure we're connected before creating the migrations table.
50
+ if (typeof db.connect === 'function') {
51
+ await db.connect();
52
+ }
53
+ else if (typeof adapter.connect === 'function') {
54
+ await adapter.connect();
55
+ }
35
56
  await ensure();
36
57
  },
37
58
  async getLastCompletedBatch(db, scope = 'global', service = '') {
package/src/routes/doc.js CHANGED
@@ -16,7 +16,7 @@ export { MIME_TYPES_MAP } from './common.js';
16
16
  */
17
17
  // Backward-compatible re-exports
18
18
  export { findPackageRoot, findPackageRootAsync, getFrameworkPublicRoots, getFrameworkPublicRootsAsync, getPublicRoot, getPublicRootAsync, } from './publicRoot.js';
19
- const PUBLIC_ROOT_CACHE_TTL_MS = 3000000000; //50 minutes, effectively caching for the duration of typical dev sessions
19
+ const PUBLIC_ROOT_CACHE_TTL_MS = 3000000000; // 50 minutes, can be adjusted as needed
20
20
  let cachedPublicRoot = null;
21
21
  const getCachedPublicRootAsync = async () => {
22
22
  const now = Date.now();
@@ -1 +1 @@
1
- {"version":3,"file":"errorPages.d.ts","sourceRoot":"","sources":["../../../src/routes/errorPages.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAInD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AA8JhD,eAAO,MAAM,mBAAmB,GAAI,SAAS,MAAM,EAAE,UAAU,SAAS,KAAG,OAkD1E,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,SAAS,MAAM,EACf,UAAU,SAAS,KAClB,OAAO,CAAC,OAAO,CA4CjB,CAAC;AAmBF,eAAO,MAAM,oBAAoB,GAAI,UAAU,SAAS,KAAG,OAE1D,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAU,UAAU,SAAS,KAAG,OAAO,CAAC,OAAO,CAKpF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAI,QAAQ,OAAO,KAAG,IAO1D,CAAC;;uCAP+C,OAAO,KAAG,IAAI;mCAjIlB,MAAM,YAAY,SAAS,KAAG,OAAO;;AA0IlF,wBAAiE"}
1
+ {"version":3,"file":"errorPages.d.ts","sourceRoot":"","sources":["../../../src/routes/errorPages.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAInD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAuKhD,eAAO,MAAM,mBAAmB,GAAI,SAAS,MAAM,EAAE,UAAU,SAAS,KAAG,OAkD1E,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,SAAS,MAAM,EACf,UAAU,SAAS,KAClB,OAAO,CAAC,OAAO,CA4CjB,CAAC;AAmBF,eAAO,MAAM,oBAAoB,GAAI,UAAU,SAAS,KAAG,OAE1D,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAU,UAAU,SAAS,KAAG,OAAO,CAAC,OAAO,CAKpF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAI,QAAQ,OAAO,KAAG,IAO1D,CAAC;;uCAP+C,OAAO,KAAG,IAAI;mCAjIlB,MAAM,YAAY,SAAS,KAAG,OAAO;;AA0IlF,wBAAiE"}
@@ -10,10 +10,17 @@ import { Router } from './Router.js';
10
10
  import { ErrorFactory } from '../exceptions/ZintrustError.js';
11
11
  import * as fs from '../node-singletons/fs.js';
12
12
  import * as path from '../node-singletons/path.js';
13
+ let cachedCandidateRoots = null;
13
14
  const getCandidatePublicRoots = () => {
14
- const roots = [path.join(process.cwd(), 'public'), ...getFrameworkPublicRoots()];
15
+ const cwd = process.cwd();
16
+ if (cachedCandidateRoots !== null && cachedCandidateRoots.cwd === cwd) {
17
+ return cachedCandidateRoots.roots;
18
+ }
19
+ const roots = [path.join(cwd, 'public'), ...getFrameworkPublicRoots()];
15
20
  const unique = new Set(roots.map((root) => root.trim()).filter((root) => root !== ''));
16
- return [...unique];
21
+ const resolved = [...unique];
22
+ cachedCandidateRoots = { cwd, roots: resolved };
23
+ return resolved;
17
24
  };
18
25
  const pathExistsAsync = async (candidate) => {
19
26
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"CsrfTokenManager.d.ts","sourceRoot":"","sources":["../../../src/security/CsrfTokenManager.ts"],"names":[],"mappings":"AACA;;;GAGG;AAQH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAC/D,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxD,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,iBAAiB,CAAC;CAC9D;AAED,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE/C,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAkSF;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,oBAE7B,CAAC"}
1
+ {"version":3,"file":"CsrfTokenManager.d.ts","sourceRoot":"","sources":["../../../src/security/CsrfTokenManager.ts"],"names":[],"mappings":"AACA;;;GAGG;AAQH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAC/D,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxD,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,iBAAiB,CAAC;CAC9D;AAED,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE/C,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAyVF;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,oBAE7B,CAAC"}
@@ -121,12 +121,54 @@ const createRedisClientFactory = (options) => {
121
121
  return redisClient;
122
122
  };
123
123
  };
124
+ const createRedisPrefixVersioner = (keyPrefix, getRedisClient) => {
125
+ const versionKey = `${keyPrefix}__v`;
126
+ let cachedVersion = null;
127
+ const getVersion = async () => {
128
+ if (cachedVersion !== null)
129
+ return cachedVersion;
130
+ const client = getRedisClient();
131
+ const raw = await client.get(versionKey);
132
+ const v = (raw ?? '1').trim();
133
+ cachedVersion = v === '' ? '1' : v;
134
+ return cachedVersion;
135
+ };
136
+ const getEffectivePrefix = async () => {
137
+ const v = await getVersion();
138
+ return `${keyPrefix}${v}:`;
139
+ };
140
+ const bumpPrefixVersion = async () => {
141
+ const client = getRedisClient();
142
+ // INCR creates the key if it does not exist.
143
+ const next = await client.incr(versionKey);
144
+ cachedVersion = String(next);
145
+ };
146
+ return {
147
+ getEffectivePrefix,
148
+ bumpPrefixVersion,
149
+ };
150
+ };
151
+ const scanRedisKeys = async (client, match) => {
152
+ const keys = [];
153
+ const stream = client.scanStream({ match, count: 200 });
154
+ return new Promise((resolve, reject) => {
155
+ stream.on('data', (resultKeys) => {
156
+ if (Array.isArray(resultKeys) && resultKeys.length) {
157
+ keys.push(...resultKeys);
158
+ }
159
+ });
160
+ stream.on('end', () => resolve(keys));
161
+ stream.on('error', (err) => reject(err));
162
+ });
163
+ };
124
164
  const createRedisTokenOperations = (keyPrefix, tokenTtl, getRedisClient) => {
125
- const buildKey = (sessionId) => `${keyPrefix}${sessionId}`;
165
+ const { getEffectivePrefix, bumpPrefixVersion } = createRedisPrefixVersioner(keyPrefix, getRedisClient);
166
+ const buildKey = (prefix, sessionId) => `${prefix}${sessionId}`;
126
167
  const fetchTokenData = async (sessionId) => {
127
168
  try {
128
169
  const client = getRedisClient();
129
- const payload = await client.get(buildKey(sessionId));
170
+ const prefix = await getEffectivePrefix();
171
+ const payload = await client.get(buildKey(prefix, sessionId));
130
172
  if (payload === null || payload === '')
131
173
  return null;
132
174
  const parsed = JSON.parse(payload);
@@ -140,13 +182,14 @@ const createRedisTokenOperations = (keyPrefix, tokenTtl, getRedisClient) => {
140
182
  const saveTokenData = async (data) => {
141
183
  try {
142
184
  const client = getRedisClient();
185
+ const prefix = await getEffectivePrefix();
143
186
  const stored = {
144
187
  token: data.token,
145
188
  sessionId: data.sessionId,
146
189
  createdAt: data.createdAt.getTime(),
147
190
  expiresAt: data.expiresAt.getTime(),
148
191
  };
149
- await client.set(buildKey(data.sessionId), JSON.stringify(stored), 'PX', tokenTtl);
192
+ await client.set(buildKey(prefix, data.sessionId), JSON.stringify(stored), 'PX', tokenTtl);
150
193
  }
151
194
  catch (error) {
152
195
  Logger.error('CSRF Redis save failed', error);
@@ -155,37 +198,30 @@ const createRedisTokenOperations = (keyPrefix, tokenTtl, getRedisClient) => {
155
198
  const deleteToken = async (sessionId) => {
156
199
  try {
157
200
  const client = getRedisClient();
158
- await client.del(buildKey(sessionId));
201
+ const prefix = await getEffectivePrefix();
202
+ await client.del(buildKey(prefix, sessionId));
159
203
  }
160
204
  catch (error) {
161
205
  Logger.error('CSRF Redis delete failed', error);
162
206
  }
163
207
  };
164
- const scanKeys = async () => {
208
+ const scanKeys = async (effectivePrefix) => {
165
209
  const client = getRedisClient();
166
- const keys = [];
167
- const stream = client.scanStream({ match: `${keyPrefix}*`, count: 200 });
168
- return new Promise((resolve, reject) => {
169
- stream.on('data', (resultKeys) => {
170
- if (Array.isArray(resultKeys) && resultKeys.length) {
171
- keys.push(...resultKeys);
172
- }
173
- });
174
- stream.on('end', () => resolve(keys));
175
- stream.on('error', (err) => reject(err));
176
- });
210
+ return scanRedisKeys(client, `${effectivePrefix}*`);
177
211
  };
178
212
  return {
179
213
  fetchTokenData,
180
214
  saveTokenData,
181
215
  deleteToken,
182
216
  scanKeys,
217
+ getEffectivePrefix,
218
+ bumpPrefixVersion,
183
219
  };
184
220
  };
185
221
  const createRedisManager = (tokenLength, tokenTtl, options) => {
186
222
  const keyPrefix = options?.keyPrefix ?? RedisKeys.getCsrfPrefix();
187
223
  const getRedisClient = createRedisClientFactory(options);
188
- const { fetchTokenData, saveTokenData, deleteToken, scanKeys } = createRedisTokenOperations(keyPrefix, tokenTtl, getRedisClient);
224
+ const { fetchTokenData, saveTokenData, deleteToken, scanKeys, getEffectivePrefix, bumpPrefixVersion, } = createRedisTokenOperations(keyPrefix, tokenTtl, getRedisClient);
189
225
  return {
190
226
  async generateToken(sessionId) {
191
227
  const token = randomBytes(tokenLength).toString('hex');
@@ -232,11 +268,8 @@ const createRedisManager = (tokenLength, tokenTtl, options) => {
232
268
  },
233
269
  async clear() {
234
270
  try {
235
- const keys = await scanKeys();
236
- if (keys.length === 0)
237
- return;
238
- const client = getRedisClient();
239
- await client.del(...keys);
271
+ // Logical clear: bump the version so future operations use a new prefix.
272
+ await bumpPrefixVersion();
240
273
  }
241
274
  catch (error) {
242
275
  Logger.error('CSRF Redis clear failed', error);
@@ -244,7 +277,8 @@ const createRedisManager = (tokenLength, tokenTtl, options) => {
244
277
  },
245
278
  async getTokenCount() {
246
279
  try {
247
- const keys = await scanKeys();
280
+ const prefix = await getEffectivePrefix();
281
+ const keys = await scanKeys(prefix);
248
282
  return keys.length;
249
283
  }
250
284
  catch (error) {
@@ -3,6 +3,7 @@
3
3
  * JSON Web Token generation, verification, and claims management
4
4
  * Uses native Node.js crypto module (zero external dependencies)
5
5
  */
6
+ import type { AuthorizationHeader } from './JwtSessions';
6
7
  export type JwtAlgorithm = 'HS256' | 'HS512' | 'RS256';
7
8
  export interface JwtPayload {
8
9
  sub?: string;
@@ -33,7 +34,9 @@ export interface IJwtManager {
33
34
  }
34
35
  export interface JwtManagerType {
35
36
  create(): IJwtManager;
36
- signAccessToken: (payload: JwtPayload, expiresIn?: number) => string;
37
+ signAccessToken: (payload: JwtPayload, expiresIn?: number) => Promise<string>;
38
+ logout: (authHeader: AuthorizationHeader) => Promise<void>;
39
+ logoutAll: (sub: string) => Promise<void>;
37
40
  }
38
41
  /**
39
42
  * JwtManager namespace - sealed for immutability
@@ -1 +1 @@
1
- {"version":3,"file":"JwtManager.d.ts","sourceRoot":"","sources":["../../../src/security/JwtManager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAEvD,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACxD,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACxD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IAC5D,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC;IAClC,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,aAAa,IAAI,MAAM,CAAC;CACzB;AAQD,MAAM,WAAW,cAAc;IAC7B,MAAM,IAAI,WAAW,CAAC;IACtB,eAAe,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;CACtE;AA6ED;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,cAGvB,CAAC"}
1
+ {"version":3,"file":"JwtManager.d.ts","sourceRoot":"","sources":["../../../src/security/JwtManager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAGjE,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAEvD,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACxD,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACxD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IAC5D,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC;IAClC,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,aAAa,IAAI,MAAM,CAAC;CACzB;AAQD,MAAM,WAAW,cAAc;IAC7B,MAAM,IAAI,WAAW,CAAC;IACtB,eAAe,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9E,MAAM,EAAE,CAAC,UAAU,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAyFD;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,cAKvB,CAAC"}
@@ -6,6 +6,7 @@
6
6
  import { securityConfig } from '../config/index.js';
7
7
  import { ErrorFactory } from '../exceptions/ZintrustError.js';
8
8
  import { createHmac, createSign, createVerify, randomBytes } from '../node-singletons/crypto.js';
9
+ import { JwtSessions } from './JwtSessions.js';
9
10
  const createJwt = () => {
10
11
  const algorithm = securityConfig.jwt.algorithm;
11
12
  const secret = securityConfig.jwt.secret;
@@ -15,27 +16,38 @@ const createJwt = () => {
15
16
  }
16
17
  return jwt;
17
18
  };
18
- const signAccessToken = (payload, expiresIn) => {
19
+ const logout = async (authHeader) => {
20
+ await JwtSessions.logout(authHeader);
21
+ };
22
+ const logoutAll = async (sub) => {
23
+ await JwtSessions.logoutAll(sub);
24
+ };
25
+ const signAccessToken = async (payload, expiresIn) => {
19
26
  const algorithm = securityConfig.jwt.algorithm;
20
27
  const jwt = createJwt();
28
+ let token;
21
29
  // JwtManager currently supports HMAC secrets directly for HS algorithms.
22
30
  // For other algorithms, verify will still reject mismatched tokens.
23
31
  if (algorithm !== 'HS256' && algorithm !== 'HS512') {
24
- return jwt.sign(payload, {
32
+ token = jwt.sign(payload, {
33
+ algorithm,
34
+ issuer: securityConfig.jwt.issuer,
35
+ audience: securityConfig.jwt.audience,
36
+ jwtId: jwt.generateJwtId(),
37
+ });
38
+ }
39
+ else {
40
+ token = jwt.sign(payload, {
25
41
  algorithm,
42
+ expiresIn: expiresIn ?? securityConfig.jwt.expiresIn,
26
43
  issuer: securityConfig.jwt.issuer,
27
44
  audience: securityConfig.jwt.audience,
45
+ subject: typeof payload.sub === 'string' ? payload.sub : undefined,
28
46
  jwtId: jwt.generateJwtId(),
29
47
  });
30
48
  }
31
- return jwt.sign(payload, {
32
- algorithm,
33
- expiresIn: expiresIn ?? securityConfig.jwt.expiresIn,
34
- issuer: securityConfig.jwt.issuer,
35
- audience: securityConfig.jwt.audience,
36
- subject: typeof payload.sub === 'string' ? payload.sub : undefined,
37
- jwtId: jwt.generateJwtId(),
38
- });
49
+ await JwtSessions.register(token);
50
+ return token;
39
51
  };
40
52
  /**
41
53
  * Create a new JWT manager instance
@@ -77,6 +89,8 @@ const create = () => {
77
89
  export const JwtManager = Object.freeze({
78
90
  create,
79
91
  signAccessToken,
92
+ logout,
93
+ logoutAll,
80
94
  });
81
95
  /**
82
96
  * Sign JWT token
@@ -0,0 +1,12 @@
1
+ export type JwtSessionsDriverName = 'database' | 'memory' | 'redis' | 'kv' | 'kv-remote';
2
+ export type AuthorizationHeader = string | string[] | undefined;
3
+ export declare const JwtSessions: Readonly<{
4
+ register(token: string): Promise<void>;
5
+ isActive(token: string): Promise<boolean>;
6
+ logout(header: AuthorizationHeader): Promise<string | null>;
7
+ logoutAll(sub: string): Promise<void>;
8
+ getDriver(): JwtSessionsDriverName;
9
+ _resetForTests(): void;
10
+ }>;
11
+ export default JwtSessions;
12
+ //# sourceMappingURL=JwtSessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JwtSessions.d.ts","sourceRoot":"","sources":["../../../src/security/JwtSessions.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,IAAI,GAAG,WAAW,CAAC;AAEzF,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;AAqnBhE,eAAO,MAAM,WAAW;oBACA,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;oBAKtB,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;mBAM1B,mBAAmB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;mBAU5C,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;iBAQ9B,qBAAqB;sBAIhB,IAAI;EAItB,CAAC;AAEH,eAAe,WAAW,CAAC"}