@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.
- package/README.md +1 -1
- package/app/Controllers/AuthController.d.ts.map +1 -1
- package/app/Controllers/AuthController.js +26 -4
- package/app/Middleware/index.d.ts.map +1 -1
- package/app/Middleware/index.js +3 -3
- package/app/Types/controller.d.ts +2 -0
- package/app/Types/controller.d.ts.map +1 -1
- package/app/Types/controller.js +1 -1
- package/package.json +1 -1
- package/routes/api.js +13 -6
- package/src/cli/CLI.d.ts.map +1 -1
- package/src/cli/CLI.js +2 -0
- package/src/cli/commands/AddCommand.js +2 -2
- package/src/cli/commands/BulletproofKeyGenerateCommand.d.ts +10 -0
- package/src/cli/commands/BulletproofKeyGenerateCommand.d.ts.map +1 -0
- package/src/cli/commands/BulletproofKeyGenerateCommand.js +139 -0
- package/src/cli/commands/JwtDevCommand.d.ts.map +1 -1
- package/src/cli/commands/JwtDevCommand.js +51 -32
- package/src/cli/scaffolding/ControllerGenerator.d.ts +1 -1
- package/src/cli/scaffolding/ControllerGenerator.d.ts.map +1 -1
- package/src/cli/scaffolding/ControllerGenerator.js +8 -79
- package/src/config/SecretsManager.d.ts +0 -1
- package/src/config/SecretsManager.d.ts.map +1 -1
- package/src/config/SecretsManager.js +0 -1
- package/src/config/middleware.d.ts +1 -0
- package/src/config/middleware.d.ts.map +1 -1
- package/src/config/middleware.js +3 -0
- package/src/http/error-pages/ErrorPageRenderer.js +7 -1
- package/src/index.d.ts +1 -0
- package/src/index.d.ts.map +1 -1
- package/src/index.js +4 -3
- package/src/middleware/BulletproofAuthMiddleware.d.ts +92 -0
- package/src/middleware/BulletproofAuthMiddleware.d.ts.map +1 -0
- package/src/middleware/BulletproofAuthMiddleware.js +421 -0
- package/src/middleware/CsrfMiddleware.d.ts +0 -1
- package/src/middleware/CsrfMiddleware.d.ts.map +1 -1
- package/src/middleware/CsrfMiddleware.js +8 -1
- package/src/middleware/JwtAuthMiddleware.d.ts.map +1 -1
- package/src/middleware/JwtAuthMiddleware.js +11 -5
- package/src/orm/Database.d.ts.map +1 -1
- package/src/orm/Database.js +48 -39
- package/src/orm/adapters/MySQLProxyAdapter.d.ts.map +1 -1
- package/src/orm/adapters/MySQLProxyAdapter.js +54 -35
- package/src/orm/adapters/PostgreSQLProxyAdapter.d.ts.map +1 -1
- package/src/orm/adapters/PostgreSQLProxyAdapter.js +126 -103
- package/src/orm/adapters/SqlProxyHttpAdapterShared.d.ts +30 -0
- package/src/orm/adapters/SqlProxyHttpAdapterShared.d.ts.map +1 -0
- package/src/orm/adapters/SqlProxyHttpAdapterShared.js +64 -0
- package/src/orm/adapters/SqlServerProxyAdapter.d.ts.map +1 -1
- package/src/orm/adapters/SqlServerProxyAdapter.js +54 -37
- package/src/orm/migrations/MigrationStore.d.ts.map +1 -1
- package/src/orm/migrations/MigrationStore.js +22 -1
- package/src/routes/doc.js +1 -1
- package/src/routes/errorPages.d.ts.map +1 -1
- package/src/routes/errorPages.js +9 -2
- package/src/security/CsrfTokenManager.d.ts.map +1 -1
- package/src/security/CsrfTokenManager.js +57 -23
- package/src/security/JwtManager.d.ts +4 -1
- package/src/security/JwtManager.d.ts.map +1 -1
- package/src/security/JwtManager.js +24 -10
- package/src/security/JwtSessions.d.ts +12 -0
- package/src/security/JwtSessions.d.ts.map +1 -0
- package/src/security/JwtSessions.js +556 -0
- package/src/security/NonceReplay.d.ts +24 -0
- package/src/security/NonceReplay.d.ts.map +1 -0
- package/src/security/NonceReplay.js +42 -0
- package/src/security/TokenRevocation.d.ts.map +1 -1
- package/src/security/TokenRevocation.js +1 -0
- package/src/tools/http/Http.d.ts +5 -0
- package/src/tools/http/Http.d.ts.map +1 -1
- package/src/tools/http/Http.js +25 -9
- package/src/tools/queue/QueueReliabilityOrchestrator.d.ts.map +1 -1
- package/src/tools/queue/QueueReliabilityOrchestrator.js +18 -6
- package/src/validation/Validator.d.ts.map +1 -1
- 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":"
|
|
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
|
-
|
|
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,
|
|
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;
|
|
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"}
|
package/src/routes/errorPages.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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;
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
236
|
-
|
|
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
|
|
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;
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
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"}
|