@technomoron/api-server-base 1.1.12 → 2.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/api-server-base.cjs +199 -74
- package/dist/cjs/api-server-base.d.ts +70 -29
- package/dist/cjs/auth-api/auth-module.d.ts +96 -0
- package/dist/cjs/auth-api/auth-module.js +1032 -0
- package/dist/cjs/auth-api/compat-auth-storage.d.ts +55 -0
- package/dist/cjs/auth-api/compat-auth-storage.js +116 -0
- package/dist/cjs/auth-api/mem-auth-store.d.ts +66 -0
- package/dist/cjs/auth-api/mem-auth-store.js +135 -0
- package/dist/cjs/{auth-module.d.ts → auth-api/module.d.ts} +7 -7
- package/dist/cjs/{auth-module.cjs → auth-api/module.js} +1 -1
- package/dist/cjs/auth-api/sql-auth-store.d.ts +75 -0
- package/dist/cjs/auth-api/sql-auth-store.js +166 -0
- package/dist/cjs/auth-api/storage.d.ts +36 -0
- package/dist/cjs/{auth-storage.cjs → auth-api/storage.js} +2 -2
- package/dist/cjs/auth-api/types.d.ts +29 -0
- package/dist/cjs/auth-api/types.js +2 -0
- package/dist/cjs/index.cjs +41 -7
- package/dist/cjs/index.d.ts +29 -5
- package/dist/cjs/oauth/base.d.ts +10 -0
- package/dist/cjs/oauth/base.js +6 -0
- package/dist/cjs/oauth/memory.d.ts +16 -0
- package/dist/cjs/oauth/memory.js +99 -0
- package/dist/cjs/oauth/models.d.ts +45 -0
- package/dist/cjs/oauth/models.js +58 -0
- package/dist/cjs/oauth/sequelize.d.ts +68 -0
- package/dist/cjs/oauth/sequelize.js +210 -0
- package/dist/cjs/oauth/types.d.ts +50 -0
- package/dist/cjs/oauth/types.js +3 -0
- package/dist/cjs/passkey/base.d.ts +15 -0
- package/dist/cjs/passkey/base.js +6 -0
- package/dist/cjs/passkey/memory.d.ts +26 -0
- package/dist/cjs/passkey/memory.js +82 -0
- package/dist/cjs/passkey/models.d.ts +25 -0
- package/dist/cjs/passkey/models.js +115 -0
- package/dist/cjs/passkey/sequelize.d.ts +54 -0
- package/dist/cjs/passkey/sequelize.js +211 -0
- package/dist/cjs/passkey/service.d.ts +17 -0
- package/dist/cjs/passkey/service.js +221 -0
- package/dist/cjs/passkey/types.d.ts +75 -0
- package/dist/cjs/passkey/types.js +2 -0
- package/dist/cjs/token/base.d.ts +38 -0
- package/dist/cjs/token/base.js +114 -0
- package/dist/cjs/token/memory.d.ts +19 -0
- package/dist/cjs/token/memory.js +149 -0
- package/dist/cjs/token/sequelize.d.ts +58 -0
- package/dist/cjs/token/sequelize.js +404 -0
- package/dist/cjs/token/types.d.ts +27 -0
- package/dist/cjs/token/types.js +2 -0
- package/dist/cjs/user/base.d.ts +26 -0
- package/dist/cjs/user/base.js +44 -0
- package/dist/cjs/user/memory.d.ts +35 -0
- package/dist/cjs/user/memory.js +173 -0
- package/dist/cjs/user/sequelize.d.ts +41 -0
- package/dist/cjs/user/sequelize.js +182 -0
- package/dist/cjs/user/types.d.ts +11 -0
- package/dist/cjs/user/types.js +2 -0
- package/dist/esm/api-server-base.d.ts +70 -29
- package/dist/esm/api-server-base.js +197 -72
- package/dist/esm/auth-api/auth-module.d.ts +96 -0
- package/dist/esm/auth-api/auth-module.js +1030 -0
- package/dist/esm/auth-api/compat-auth-storage.d.ts +55 -0
- package/dist/esm/auth-api/compat-auth-storage.js +112 -0
- package/dist/esm/auth-api/mem-auth-store.d.ts +66 -0
- package/dist/esm/auth-api/mem-auth-store.js +131 -0
- package/dist/esm/{auth-module.d.ts → auth-api/module.d.ts} +7 -7
- package/dist/esm/{auth-module.js → auth-api/module.js} +1 -1
- package/dist/esm/auth-api/sql-auth-store.d.ts +75 -0
- package/dist/esm/auth-api/sql-auth-store.js +162 -0
- package/dist/esm/auth-api/storage.d.ts +36 -0
- package/dist/esm/{auth-storage.js → auth-api/storage.js} +2 -2
- package/dist/esm/auth-api/types.d.ts +29 -0
- package/dist/esm/auth-api/types.js +1 -0
- package/dist/esm/index.d.ts +29 -5
- package/dist/esm/index.js +19 -2
- package/dist/esm/oauth/base.d.ts +10 -0
- package/dist/esm/oauth/base.js +2 -0
- package/dist/esm/oauth/memory.d.ts +16 -0
- package/dist/esm/oauth/memory.js +92 -0
- package/dist/esm/oauth/models.d.ts +45 -0
- package/dist/esm/oauth/models.js +51 -0
- package/dist/esm/oauth/sequelize.d.ts +68 -0
- package/dist/esm/oauth/sequelize.js +199 -0
- package/dist/esm/oauth/types.d.ts +50 -0
- package/dist/esm/oauth/types.js +2 -0
- package/dist/esm/passkey/base.d.ts +15 -0
- package/dist/esm/passkey/base.js +2 -0
- package/dist/esm/passkey/memory.d.ts +26 -0
- package/dist/esm/passkey/memory.js +78 -0
- package/dist/esm/passkey/models.d.ts +25 -0
- package/dist/esm/passkey/models.js +108 -0
- package/dist/esm/passkey/sequelize.d.ts +54 -0
- package/dist/esm/passkey/sequelize.js +207 -0
- package/dist/esm/passkey/service.d.ts +17 -0
- package/dist/esm/passkey/service.js +217 -0
- package/dist/esm/passkey/types.d.ts +75 -0
- package/dist/esm/passkey/types.js +1 -0
- package/dist/esm/token/base.d.ts +38 -0
- package/dist/esm/token/base.js +107 -0
- package/dist/esm/token/memory.d.ts +19 -0
- package/dist/esm/token/memory.js +145 -0
- package/dist/esm/token/sequelize.d.ts +58 -0
- package/dist/esm/token/sequelize.js +400 -0
- package/dist/esm/token/types.d.ts +27 -0
- package/dist/esm/token/types.js +1 -0
- package/dist/esm/user/base.d.ts +26 -0
- package/dist/esm/user/base.js +37 -0
- package/dist/esm/user/memory.d.ts +35 -0
- package/dist/esm/user/memory.js +169 -0
- package/dist/esm/user/sequelize.d.ts +41 -0
- package/dist/esm/user/sequelize.js +176 -0
- package/dist/esm/user/types.d.ts +11 -0
- package/dist/esm/user/types.js +1 -0
- package/package.json +11 -3
- package/dist/cjs/auth-storage.d.ts +0 -133
- package/dist/esm/auth-storage.d.ts +0 -133
|
@@ -10,13 +10,34 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.ApiServer = exports.ApiError = exports.ApiModule = void 0;
|
|
13
|
+
const node_crypto_1 = require("node:crypto");
|
|
13
14
|
const cookie_parser_1 = __importDefault(require("cookie-parser"));
|
|
14
15
|
const cors_1 = __importDefault(require("cors"));
|
|
15
16
|
const express_1 = __importDefault(require("express"));
|
|
16
|
-
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
17
17
|
const multer_1 = __importDefault(require("multer"));
|
|
18
|
-
const
|
|
19
|
-
const
|
|
18
|
+
const module_js_1 = require("./auth-api/module.js");
|
|
19
|
+
const storage_js_1 = require("./auth-api/storage.js");
|
|
20
|
+
const base_js_1 = require("./token/base.js");
|
|
21
|
+
class JwtHelperStore extends base_js_1.TokenStore {
|
|
22
|
+
async save() {
|
|
23
|
+
throw new Error('Token store is not configured');
|
|
24
|
+
}
|
|
25
|
+
async get() {
|
|
26
|
+
throw new Error('Token store is not configured');
|
|
27
|
+
}
|
|
28
|
+
async delete() {
|
|
29
|
+
throw new Error('Token store is not configured');
|
|
30
|
+
}
|
|
31
|
+
async update() {
|
|
32
|
+
throw new Error('Token store is not configured');
|
|
33
|
+
}
|
|
34
|
+
async list() {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
async close() {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
20
41
|
var api_module_js_1 = require("./api-module.cjs");
|
|
21
42
|
Object.defineProperty(exports, "ApiModule", { enumerable: true, get: function () { return api_module_js_1.ApiModule; } });
|
|
22
43
|
function guess_exception_text(error, defMsg = 'Unknown Error') {
|
|
@@ -330,23 +351,45 @@ function fillConfig(config) {
|
|
|
330
351
|
authApi: config.authApi ?? false,
|
|
331
352
|
devMode: config.devMode ?? false,
|
|
332
353
|
hydrateGetBody: config.hydrateGetBody ?? true,
|
|
333
|
-
validateTokens: config.validateTokens ?? false
|
|
354
|
+
validateTokens: config.validateTokens ?? false,
|
|
355
|
+
apiVersion: config.apiVersion ?? '',
|
|
356
|
+
minClientVersion: config.minClientVersion ?? '',
|
|
357
|
+
tokenStore: config.tokenStore,
|
|
358
|
+
authStores: config.authStores
|
|
334
359
|
};
|
|
335
360
|
}
|
|
336
361
|
class ApiServer {
|
|
337
362
|
constructor(config = {}) {
|
|
338
363
|
this.currReq = null;
|
|
339
364
|
this.apiNotFoundHandler = null;
|
|
365
|
+
this.tokenStoreAdapter = null;
|
|
366
|
+
this.userStoreAdapter = null;
|
|
367
|
+
this.passkeyServiceAdapter = null;
|
|
368
|
+
this.oauthStoreAdapter = null;
|
|
369
|
+
this.canImpersonateAdapter = null;
|
|
340
370
|
this.config = fillConfig(config);
|
|
341
371
|
this.apiBasePath = this.normalizeApiBasePath(this.config.apiBasePath);
|
|
342
|
-
this.
|
|
343
|
-
this.
|
|
372
|
+
this.startedAt = Date.now();
|
|
373
|
+
this.storageAdapter = storage_js_1.nullAuthStorage;
|
|
374
|
+
this.moduleAdapter = module_js_1.nullAuthModule;
|
|
375
|
+
this.jwtHelper = new JwtHelperStore();
|
|
376
|
+
this.tokenStoreAdapter = this.config.tokenStore ?? null;
|
|
377
|
+
if (this.config.authStores) {
|
|
378
|
+
const { userStore, tokenStore, passkeyService, oauthStore, canImpersonate } = this.config.authStores;
|
|
379
|
+
this.userStoreAdapter = userStore;
|
|
380
|
+
this.tokenStoreAdapter = tokenStore;
|
|
381
|
+
this.passkeyServiceAdapter = passkeyService ?? null;
|
|
382
|
+
this.oauthStoreAdapter = oauthStore ?? null;
|
|
383
|
+
this.canImpersonateAdapter = canImpersonate ?? null;
|
|
384
|
+
this.storageAdapter = this;
|
|
385
|
+
}
|
|
344
386
|
this.app = (0, express_1.default)();
|
|
345
387
|
if (config.uploadPath) {
|
|
346
388
|
const upload = (0, multer_1.default)({ dest: config.uploadPath });
|
|
347
389
|
this.app.use(upload.any());
|
|
348
390
|
}
|
|
349
391
|
this.middlewares();
|
|
392
|
+
this.installPingHandler();
|
|
350
393
|
// addSwaggerUi(this.app);
|
|
351
394
|
this.installApiNotFoundHandler();
|
|
352
395
|
}
|
|
@@ -376,72 +419,143 @@ class ApiServer {
|
|
|
376
419
|
getAuthModule() {
|
|
377
420
|
return this.moduleAdapter;
|
|
378
421
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
return {
|
|
385
|
-
success: true,
|
|
386
|
-
token
|
|
387
|
-
};
|
|
422
|
+
setTokenStore(store) {
|
|
423
|
+
this.tokenStoreAdapter = store;
|
|
424
|
+
// If using direct stores, expose self as the auth storage.
|
|
425
|
+
if (this.userStoreAdapter) {
|
|
426
|
+
this.storageAdapter = this;
|
|
388
427
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
428
|
+
return this;
|
|
429
|
+
}
|
|
430
|
+
getTokenStore() {
|
|
431
|
+
return this.tokenStoreAdapter;
|
|
432
|
+
}
|
|
433
|
+
ensureUserStore() {
|
|
434
|
+
if (!this.userStoreAdapter) {
|
|
435
|
+
throw new Error('User store is not configured');
|
|
394
436
|
}
|
|
437
|
+
return this.userStoreAdapter;
|
|
395
438
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const data = jsonwebtoken_1.default.verify(token, secret, options);
|
|
400
|
-
return {
|
|
401
|
-
success: true,
|
|
402
|
-
data
|
|
403
|
-
};
|
|
439
|
+
ensureTokenStore() {
|
|
440
|
+
if (!this.tokenStoreAdapter) {
|
|
441
|
+
throw new Error('Token store is not configured');
|
|
404
442
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
error: 'Token expired'
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
return {
|
|
415
|
-
success: false,
|
|
416
|
-
expired: false,
|
|
417
|
-
error: error instanceof Error ? error.message : String(error)
|
|
418
|
-
};
|
|
419
|
-
}
|
|
443
|
+
return this.tokenStoreAdapter;
|
|
444
|
+
}
|
|
445
|
+
ensurePasskeyService() {
|
|
446
|
+
if (!this.passkeyServiceAdapter) {
|
|
447
|
+
throw new Error('Passkey service is not configured');
|
|
420
448
|
}
|
|
449
|
+
return this.passkeyServiceAdapter;
|
|
421
450
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const data = jsonwebtoken_1.default.decode(token, options);
|
|
426
|
-
// jwt.decode returns null for invalid tokens rather than throwing
|
|
427
|
-
if (data === null) {
|
|
428
|
-
return {
|
|
429
|
-
success: false,
|
|
430
|
-
error: 'Invalid token format'
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
return {
|
|
434
|
-
success: true,
|
|
435
|
-
data
|
|
436
|
-
};
|
|
451
|
+
ensureOAuthStore() {
|
|
452
|
+
if (!this.oauthStoreAdapter) {
|
|
453
|
+
throw new Error('OAuth store is not configured');
|
|
437
454
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
455
|
+
return this.oauthStoreAdapter;
|
|
456
|
+
}
|
|
457
|
+
// AuthStorage-compatible helpers (used by AuthModule)
|
|
458
|
+
async getUser(identifier) {
|
|
459
|
+
return this.userStoreAdapter ? this.userStoreAdapter.findUser(identifier) : null;
|
|
460
|
+
}
|
|
461
|
+
getUserPasswordHash(user) {
|
|
462
|
+
return this.ensureUserStore().getPasswordHash(user) ?? '';
|
|
463
|
+
}
|
|
464
|
+
getUserId(user) {
|
|
465
|
+
return this.ensureUserStore().getUserId(user);
|
|
466
|
+
}
|
|
467
|
+
filterUser(user) {
|
|
468
|
+
return this.ensureUserStore().toPublic(user);
|
|
469
|
+
}
|
|
470
|
+
async verifyPassword(password, hash) {
|
|
471
|
+
return this.ensureUserStore().verifyPassword(password, hash);
|
|
472
|
+
}
|
|
473
|
+
async storeToken(data) {
|
|
474
|
+
if (this.tokenStoreAdapter) {
|
|
475
|
+
return this.tokenStoreAdapter.save(data);
|
|
476
|
+
}
|
|
477
|
+
if (typeof this.storageAdapter.storeToken === 'function') {
|
|
478
|
+
return this.storageAdapter.storeToken(data);
|
|
479
|
+
}
|
|
480
|
+
throw new Error('Token store is not configured');
|
|
481
|
+
}
|
|
482
|
+
async getToken(query, opts) {
|
|
483
|
+
const normalized = {
|
|
484
|
+
...query,
|
|
485
|
+
userId: query.userId !== undefined && query.userId !== null ? String(query.userId) : undefined,
|
|
486
|
+
ruid: query.ruid !== undefined && query.ruid !== null ? String(query.ruid) : undefined
|
|
487
|
+
};
|
|
488
|
+
if (this.tokenStoreAdapter) {
|
|
489
|
+
return this.tokenStoreAdapter.get(normalized, opts);
|
|
490
|
+
}
|
|
491
|
+
if (typeof this.storageAdapter.getToken === 'function') {
|
|
492
|
+
return this.storageAdapter.getToken(normalized, opts);
|
|
493
|
+
}
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
async deleteToken(query) {
|
|
497
|
+
const normalized = {
|
|
498
|
+
...query,
|
|
499
|
+
userId: query.userId !== undefined && query.userId !== null ? String(query.userId) : undefined,
|
|
500
|
+
ruid: query.ruid !== undefined && query.ruid !== null ? String(query.ruid) : undefined
|
|
501
|
+
};
|
|
502
|
+
if (this.tokenStoreAdapter) {
|
|
503
|
+
return this.tokenStoreAdapter.delete(normalized);
|
|
504
|
+
}
|
|
505
|
+
if (typeof this.storageAdapter.deleteToken === 'function') {
|
|
506
|
+
return this.storageAdapter.deleteToken(normalized);
|
|
507
|
+
}
|
|
508
|
+
return 0;
|
|
509
|
+
}
|
|
510
|
+
async createPasskeyChallenge(params) {
|
|
511
|
+
return this.ensurePasskeyService().createChallenge(params);
|
|
512
|
+
}
|
|
513
|
+
async verifyPasskeyResponse(params) {
|
|
514
|
+
return this.ensurePasskeyService().verifyResponse(params);
|
|
515
|
+
}
|
|
516
|
+
async getClient(clientId) {
|
|
517
|
+
return this.oauthStoreAdapter ? this.oauthStoreAdapter.getClient(clientId) : null;
|
|
518
|
+
}
|
|
519
|
+
async verifyClientSecret(client, clientSecret) {
|
|
520
|
+
return this.ensureOAuthStore().verifyClientSecret(client.clientId, clientSecret);
|
|
521
|
+
}
|
|
522
|
+
async createAuthCode(request) {
|
|
523
|
+
const expiresAt = new Date(Date.now() + (request.expiresInSeconds ?? 300) * 1000);
|
|
524
|
+
const code = request.code ?? (0, node_crypto_1.randomUUID)();
|
|
525
|
+
await this.ensureOAuthStore().createAuthCode({ ...request, code, expiresAt });
|
|
526
|
+
return {
|
|
527
|
+
code,
|
|
528
|
+
clientId: request.clientId,
|
|
529
|
+
userId: request.userId,
|
|
530
|
+
redirectUri: request.redirectUri,
|
|
531
|
+
scope: request.scope ?? [],
|
|
532
|
+
codeChallenge: request.codeChallenge,
|
|
533
|
+
codeChallengeMethod: request.codeChallengeMethod,
|
|
534
|
+
expiresAt,
|
|
535
|
+
metadata: request.metadata
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
async consumeAuthCode(code, clientId) {
|
|
539
|
+
const consumed = await this.ensureOAuthStore().consumeAuthCode(code);
|
|
540
|
+
if (!consumed || consumed.clientId !== clientId) {
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
return consumed;
|
|
544
|
+
}
|
|
545
|
+
async canImpersonate(params) {
|
|
546
|
+
if (this.canImpersonateAdapter) {
|
|
547
|
+
return !!(await this.canImpersonateAdapter(params));
|
|
444
548
|
}
|
|
549
|
+
return params.realUserId === params.effectiveUserId;
|
|
550
|
+
}
|
|
551
|
+
jwtSign(payload, secret, expiresInSeconds, options) {
|
|
552
|
+
return (this.tokenStoreAdapter ?? this.jwtHelper).jwtSign(payload, secret, expiresInSeconds, options);
|
|
553
|
+
}
|
|
554
|
+
jwtVerify(token, secret, options) {
|
|
555
|
+
return (this.tokenStoreAdapter ?? this.jwtHelper).jwtVerify(token, secret, options);
|
|
556
|
+
}
|
|
557
|
+
jwtDecode(token, options) {
|
|
558
|
+
return (this.tokenStoreAdapter ?? this.jwtHelper).jwtDecode(token, options);
|
|
445
559
|
}
|
|
446
560
|
async getApiKey(token) {
|
|
447
561
|
void token;
|
|
@@ -459,16 +573,13 @@ class ApiServer {
|
|
|
459
573
|
return this.storageAdapter.verifyPassword(params.password, hash);
|
|
460
574
|
}
|
|
461
575
|
async updateToken(updates) {
|
|
462
|
-
if (
|
|
463
|
-
return
|
|
576
|
+
if (this.tokenStoreAdapter) {
|
|
577
|
+
return this.tokenStoreAdapter.update(updates);
|
|
464
578
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
clientId: updates.clientId,
|
|
470
|
-
scope: updates.scope
|
|
471
|
-
});
|
|
579
|
+
if (typeof this.storageAdapter.updateToken === 'function') {
|
|
580
|
+
return this.storageAdapter.updateToken(updates);
|
|
581
|
+
}
|
|
582
|
+
return false;
|
|
472
583
|
}
|
|
473
584
|
guessExceptionText(error, defMsg = 'Unkown Error') {
|
|
474
585
|
return guess_exception_text(error, defMsg);
|
|
@@ -497,6 +608,20 @@ class ApiServer {
|
|
|
497
608
|
};
|
|
498
609
|
this.app.use((0, cors_1.default)(corsOptions));
|
|
499
610
|
}
|
|
611
|
+
installPingHandler() {
|
|
612
|
+
const path = `${this.apiBasePath}/v1/ping`;
|
|
613
|
+
this.app.get(path, (_req, res) => {
|
|
614
|
+
const payload = {
|
|
615
|
+
status: 'ok',
|
|
616
|
+
apiVersion: this.config.apiVersion ?? '',
|
|
617
|
+
minClientVersion: this.config.minClientVersion ?? '',
|
|
618
|
+
uptimeSec: process.uptime(),
|
|
619
|
+
startedAt: this.startedAt,
|
|
620
|
+
timestamp: new Date().toISOString()
|
|
621
|
+
};
|
|
622
|
+
res.status(200).json({ code: 200, message: 'Success', data: payload });
|
|
623
|
+
});
|
|
624
|
+
}
|
|
500
625
|
normalizeApiBasePath(path) {
|
|
501
626
|
if (!path || typeof path !== 'string') {
|
|
502
627
|
return '/api';
|
|
@@ -668,7 +793,7 @@ class ApiServer {
|
|
|
668
793
|
return this.config.validateTokens || authType === 'strict';
|
|
669
794
|
}
|
|
670
795
|
async assertStoredAccessToken(apiReq, token, tokenData) {
|
|
671
|
-
const userId = this.extractTokenUserId(tokenData);
|
|
796
|
+
const userId = String(this.extractTokenUserId(tokenData));
|
|
672
797
|
const stored = await this.storageAdapter.getToken({
|
|
673
798
|
accessToken: token,
|
|
674
799
|
userId
|
|
@@ -5,48 +5,39 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
import { Application, Request, Response } from 'express';
|
|
8
|
-
import jwt, { JwtPayload, SignOptions, VerifyOptions } from 'jsonwebtoken';
|
|
9
8
|
import { ApiModule } from './api-module.js';
|
|
9
|
+
import { TokenStore, type JwtDecodeResult, type JwtSignResult, type JwtVerifyResult } from './token/base.js';
|
|
10
10
|
import type { ApiAuthClass, ApiKey } from './api-module.js';
|
|
11
|
-
import type { AuthProviderModule } from './auth-module.js';
|
|
12
|
-
import type { AuthStorage, AuthIdentifier
|
|
11
|
+
import type { AuthProviderModule } from './auth-api/module.js';
|
|
12
|
+
import type { AuthStorage, AuthIdentifier } from './auth-api/types.js';
|
|
13
|
+
import type { OAuthStore } from './oauth/base.js';
|
|
14
|
+
import type { AuthCodeData, AuthCodeRequest, OAuthClient } from './oauth/types.js';
|
|
15
|
+
import type { PasskeyService } from './passkey/service.js';
|
|
16
|
+
import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyVerificationParams, PasskeyVerificationResult } from './passkey/types.js';
|
|
17
|
+
import type { Token } from './token/types.js';
|
|
18
|
+
import type { UserStore } from './user/base.js';
|
|
19
|
+
import type { JwtPayload, SignOptions, VerifyOptions } from 'jsonwebtoken';
|
|
13
20
|
export type { Application, Request, Response, NextFunction, Router } from 'express';
|
|
14
21
|
export type { Multer } from 'multer';
|
|
15
22
|
export type { JwtPayload, SignOptions, VerifyOptions } from 'jsonwebtoken';
|
|
16
|
-
export interface
|
|
23
|
+
export interface ExtendedReq extends Request {
|
|
17
24
|
file?: Express.Multer.File;
|
|
18
25
|
files?: Express.Multer.File[] | {
|
|
19
26
|
[fieldname: string]: Express.Multer.File[];
|
|
20
27
|
};
|
|
21
28
|
}
|
|
22
|
-
interface
|
|
23
|
-
success: boolean;
|
|
24
|
-
token?: string;
|
|
25
|
-
error?: string;
|
|
26
|
-
}
|
|
27
|
-
interface JwtVerifyResult<T> {
|
|
28
|
-
success: boolean;
|
|
29
|
-
data?: T;
|
|
30
|
-
expired?: boolean;
|
|
31
|
-
error?: string;
|
|
32
|
-
}
|
|
33
|
-
interface JwtDecodeResult<T> {
|
|
34
|
-
success: boolean;
|
|
35
|
-
data?: T;
|
|
36
|
-
error?: string;
|
|
37
|
-
}
|
|
38
|
-
export interface ApiTokenData extends JwtPayload, AuthTokenMetadata {
|
|
29
|
+
export interface ApiTokenData extends JwtPayload, Partial<Token> {
|
|
39
30
|
uid: unknown;
|
|
40
31
|
iat?: number;
|
|
41
32
|
exp?: number;
|
|
42
33
|
}
|
|
43
34
|
export interface ApiRequest {
|
|
44
35
|
server: any;
|
|
45
|
-
req:
|
|
36
|
+
req: ExtendedReq;
|
|
46
37
|
res: Response;
|
|
47
38
|
tokenData?: ApiTokenData | null;
|
|
48
39
|
token?: string;
|
|
49
|
-
authToken?:
|
|
40
|
+
authToken?: Token | null;
|
|
50
41
|
apiKey?: ApiKey | null;
|
|
51
42
|
clientInfo?: ClientInfo;
|
|
52
43
|
realUid?: AuthIdentifier | null;
|
|
@@ -66,6 +57,16 @@ export interface ClientInfo extends ClientAgentProfile {
|
|
|
66
57
|
ip: string | null;
|
|
67
58
|
ipchain: string[];
|
|
68
59
|
}
|
|
60
|
+
export interface ApiServerAuthStores {
|
|
61
|
+
userStore: UserStore<any, any>;
|
|
62
|
+
tokenStore: TokenStore;
|
|
63
|
+
passkeyService?: PasskeyService;
|
|
64
|
+
oauthStore?: OAuthStore;
|
|
65
|
+
canImpersonate?: (params: {
|
|
66
|
+
realUserId: AuthIdentifier;
|
|
67
|
+
effectiveUserId: AuthIdentifier;
|
|
68
|
+
}) => boolean | Promise<boolean>;
|
|
69
|
+
}
|
|
69
70
|
export { ApiModule } from './api-module.js';
|
|
70
71
|
export type { ApiHandler, ApiAuthType, ApiAuthClass, ApiRoute, ApiKey } from './api-module.js';
|
|
71
72
|
export interface ApiErrorParams {
|
|
@@ -100,15 +101,26 @@ export interface ApiServerConf {
|
|
|
100
101
|
devMode: boolean;
|
|
101
102
|
hydrateGetBody: boolean;
|
|
102
103
|
validateTokens: boolean;
|
|
104
|
+
apiVersion: string;
|
|
105
|
+
minClientVersion: string;
|
|
106
|
+
tokenStore?: TokenStore;
|
|
107
|
+
authStores?: ApiServerAuthStores;
|
|
103
108
|
}
|
|
104
109
|
export declare class ApiServer {
|
|
105
110
|
app: Application;
|
|
106
111
|
currReq: ApiRequest | null;
|
|
107
112
|
readonly config: ApiServerConf;
|
|
113
|
+
readonly startedAt: number;
|
|
108
114
|
private readonly apiBasePath;
|
|
109
115
|
private storageAdapter;
|
|
110
116
|
private moduleAdapter;
|
|
111
117
|
private apiNotFoundHandler;
|
|
118
|
+
private tokenStoreAdapter;
|
|
119
|
+
private userStoreAdapter;
|
|
120
|
+
private passkeyServiceAdapter;
|
|
121
|
+
private oauthStoreAdapter;
|
|
122
|
+
private canImpersonateAdapter;
|
|
123
|
+
private readonly jwtHelper;
|
|
112
124
|
constructor(config?: Partial<ApiServerConf>);
|
|
113
125
|
authStorage<UserRow, SafeUser>(storage: AuthStorage<UserRow, SafeUser>): this;
|
|
114
126
|
/**
|
|
@@ -122,24 +134,53 @@ export declare class ApiServer {
|
|
|
122
134
|
useAuthModule<UserRow>(module: AuthProviderModule<UserRow>): this;
|
|
123
135
|
getAuthStorage(): AuthStorage<any, any>;
|
|
124
136
|
getAuthModule(): AuthProviderModule<any>;
|
|
137
|
+
setTokenStore(store: TokenStore): this;
|
|
138
|
+
getTokenStore(): TokenStore | null;
|
|
139
|
+
private ensureUserStore;
|
|
140
|
+
private ensureTokenStore;
|
|
141
|
+
private ensurePasskeyService;
|
|
142
|
+
private ensureOAuthStore;
|
|
143
|
+
getUser(identifier: AuthIdentifier): Promise<any | null>;
|
|
144
|
+
getUserPasswordHash(user: any): string;
|
|
145
|
+
getUserId(user: any): AuthIdentifier;
|
|
146
|
+
filterUser(user: any): any;
|
|
147
|
+
verifyPassword(password: string, hash: string): Promise<boolean>;
|
|
148
|
+
storeToken(data: Token): Promise<void>;
|
|
149
|
+
getToken(query: Partial<Token> & {
|
|
150
|
+
userId?: AuthIdentifier;
|
|
151
|
+
ruid?: AuthIdentifier;
|
|
152
|
+
}, opts?: {
|
|
153
|
+
includeExpired?: boolean;
|
|
154
|
+
}): Promise<Token | null>;
|
|
155
|
+
deleteToken(query: Partial<Token> & {
|
|
156
|
+
userId?: AuthIdentifier;
|
|
157
|
+
ruid?: AuthIdentifier;
|
|
158
|
+
}): Promise<number>;
|
|
159
|
+
createPasskeyChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
|
|
160
|
+
verifyPasskeyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
|
|
161
|
+
getClient(clientId: string): Promise<OAuthClient | null>;
|
|
162
|
+
verifyClientSecret(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
|
|
163
|
+
createAuthCode(request: AuthCodeRequest): Promise<AuthCodeData>;
|
|
164
|
+
consumeAuthCode(code: string, clientId: string): Promise<AuthCodeData | null>;
|
|
165
|
+
canImpersonate(params: {
|
|
166
|
+
realUserId: AuthIdentifier;
|
|
167
|
+
effectiveUserId: AuthIdentifier;
|
|
168
|
+
}): Promise<boolean>;
|
|
125
169
|
jwtSign(payload: any, secret: string, expiresInSeconds: number, options?: SignOptions): JwtSignResult;
|
|
126
170
|
jwtVerify<T>(token: string, secret: string, options?: VerifyOptions): JwtVerifyResult<T>;
|
|
127
|
-
jwtDecode<T>(token: string, options?:
|
|
171
|
+
jwtDecode<T>(token: string, options?: import('jsonwebtoken').DecodeOptions): JwtDecodeResult<T>;
|
|
128
172
|
getApiKey<T = ApiKey>(token: string): Promise<T | null>;
|
|
129
173
|
authenticateUser(params: {
|
|
130
174
|
login: string;
|
|
131
175
|
password: string;
|
|
132
176
|
}): Promise<boolean>;
|
|
133
|
-
updateToken(updates: {
|
|
134
|
-
accessToken: string;
|
|
177
|
+
updateToken(updates: Partial<Token> & {
|
|
135
178
|
refreshToken: string;
|
|
136
|
-
expires?: Date;
|
|
137
|
-
clientId?: string;
|
|
138
|
-
scope?: string[];
|
|
139
179
|
}): Promise<boolean>;
|
|
140
180
|
guessExceptionText(error: any, defMsg?: string): string;
|
|
141
181
|
protected authorize(apiReq: ApiRequest, requiredClass: ApiAuthClass): Promise<void>;
|
|
142
182
|
private middlewares;
|
|
183
|
+
private installPingHandler;
|
|
143
184
|
private normalizeApiBasePath;
|
|
144
185
|
private installApiNotFoundHandler;
|
|
145
186
|
private ensureApiNotFoundOrdering;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type ApiRequest, type ApiRoute, type ApiServer } from '../api-server-base.js';
|
|
2
|
+
import { BaseAuthModule, type AuthProviderModule } from './module.js';
|
|
3
|
+
import type { AuthIdentifier, AuthStorage } from './types.js';
|
|
4
|
+
import type { OAuthCallbackParams, OAuthCallbackResult, OAuthStartParams, OAuthStartResult } from '../oauth/types.js';
|
|
5
|
+
import type { TokenPair, Token } from '../token/types.js';
|
|
6
|
+
interface CanImpersonateContext<UserEntity> {
|
|
7
|
+
apiReq: ApiRequest;
|
|
8
|
+
realUser: UserEntity;
|
|
9
|
+
realUserId: AuthIdentifier;
|
|
10
|
+
targetUser: UserEntity;
|
|
11
|
+
effectiveUserId: AuthIdentifier;
|
|
12
|
+
}
|
|
13
|
+
interface AuthModuleOptions<UserEntity> {
|
|
14
|
+
namespace?: string;
|
|
15
|
+
defaultDomain?: string;
|
|
16
|
+
canImpersonate?: (context: CanImpersonateContext<UserEntity>) => Promise<boolean> | boolean;
|
|
17
|
+
}
|
|
18
|
+
type TokenMetadata = Partial<Token> & {
|
|
19
|
+
sessionCookie?: boolean;
|
|
20
|
+
};
|
|
21
|
+
interface TokenIssueOptions extends TokenMetadata {
|
|
22
|
+
expires?: Date;
|
|
23
|
+
sessionCookie?: boolean;
|
|
24
|
+
}
|
|
25
|
+
interface NormalizedTokenMetadata extends TokenMetadata {
|
|
26
|
+
domain: string;
|
|
27
|
+
fingerprint: string;
|
|
28
|
+
label: string;
|
|
29
|
+
browser: string;
|
|
30
|
+
device: string;
|
|
31
|
+
ip: string;
|
|
32
|
+
os: string;
|
|
33
|
+
}
|
|
34
|
+
type TokenClaims = TokenMetadata & {
|
|
35
|
+
uid: string;
|
|
36
|
+
exp?: number;
|
|
37
|
+
iat?: number;
|
|
38
|
+
};
|
|
39
|
+
type AuthCapableServer<PublicUser> = ApiServer & {
|
|
40
|
+
initiateOAuth?: (params: OAuthStartParams) => Promise<OAuthStartResult>;
|
|
41
|
+
completeOAuth?: (params: OAuthCallbackParams) => Promise<OAuthCallbackResult<PublicUser>>;
|
|
42
|
+
};
|
|
43
|
+
export default class AuthModule<UserEntity, PublicUser> extends BaseAuthModule<UserEntity> implements AuthProviderModule<UserEntity> {
|
|
44
|
+
static defaultNamespace: string;
|
|
45
|
+
server: AuthCapableServer<PublicUser>;
|
|
46
|
+
private readonly defaultDomain?;
|
|
47
|
+
private readonly canImpersonateHook?;
|
|
48
|
+
constructor(options?: AuthModuleOptions<UserEntity>);
|
|
49
|
+
protected get storage(): AuthStorage<UserEntity, PublicUser>;
|
|
50
|
+
protected canImpersonate(apiReq: ApiRequest, realUser: UserEntity, targetUser: UserEntity): Promise<boolean>;
|
|
51
|
+
protected ensureImpersonationAllowed(apiReq: ApiRequest, realUser: UserEntity, targetUser: UserEntity): Promise<void>;
|
|
52
|
+
protected buildTokenPayload(user: UserEntity, metadata?: TokenMetadata): TokenClaims;
|
|
53
|
+
protected buildTokenMetadata(metadata?: TokenMetadata): NormalizedTokenMetadata;
|
|
54
|
+
protected enrichTokenMetadata(apiReq: ApiRequest, metadata?: TokenMetadata): TokenMetadata;
|
|
55
|
+
private sessionRefreshTtlSeconds;
|
|
56
|
+
private normalizeRefreshTtlSeconds;
|
|
57
|
+
private resolveSessionPreferences;
|
|
58
|
+
private mergeSessionPreferences;
|
|
59
|
+
private sessionPrefsFromRecord;
|
|
60
|
+
private cookieOptions;
|
|
61
|
+
private setJwtCookies;
|
|
62
|
+
issueTokens(apiReq: ApiRequest, user: UserEntity, metadata?: TokenIssueOptions): Promise<TokenPair>;
|
|
63
|
+
private assertAuthReady;
|
|
64
|
+
private parseLoginBody;
|
|
65
|
+
private parseImpersonationRequest;
|
|
66
|
+
private resolveImpersonationIdentifier;
|
|
67
|
+
private buildImpersonationMetadata;
|
|
68
|
+
private getUserOrThrow;
|
|
69
|
+
private getRealUserIdentifier;
|
|
70
|
+
private resolveActorContext;
|
|
71
|
+
private extractRefreshToken;
|
|
72
|
+
private normalizeScope;
|
|
73
|
+
private postLogin;
|
|
74
|
+
private postRefresh;
|
|
75
|
+
private postLogout;
|
|
76
|
+
private postWhoAmI;
|
|
77
|
+
private postPasskeyChallenge;
|
|
78
|
+
private postPasskeyVerify;
|
|
79
|
+
private postImpersonation;
|
|
80
|
+
private deleteImpersonation;
|
|
81
|
+
private getUserFromPasskey;
|
|
82
|
+
private postOAuthStart;
|
|
83
|
+
private postOAuthCallback;
|
|
84
|
+
private postOAuthAuthorize;
|
|
85
|
+
private postOAuthToken;
|
|
86
|
+
private handleAuthorizationCodeGrant;
|
|
87
|
+
private handleRefreshTokenGrant;
|
|
88
|
+
private clearOAuthCookies;
|
|
89
|
+
private buildTokenResponse;
|
|
90
|
+
private resolveScope;
|
|
91
|
+
private resolveClientAuthentication;
|
|
92
|
+
private assertRedirectUriAllowed;
|
|
93
|
+
private resolveUserForOAuth;
|
|
94
|
+
defineRoutes(): ApiRoute[];
|
|
95
|
+
}
|
|
96
|
+
export {};
|