@technomoron/api-server-base 2.0.0-beta.12 → 2.0.0-beta.15
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-module.d.ts +3 -2
- package/dist/cjs/api-server-base.cjs +125 -18
- package/dist/cjs/api-server-base.d.ts +21 -15
- package/dist/cjs/auth-api/auth-module.js +10 -10
- package/dist/cjs/auth-api/module.d.ts +1 -1
- package/dist/cjs/index.cjs +1 -11
- package/dist/cjs/index.d.ts +0 -5
- package/dist/cjs/passkey/service.js +11 -11
- package/dist/cjs/token/base.d.ts +2 -1
- package/dist/esm/api-module.d.ts +3 -2
- package/dist/esm/api-server-base.d.ts +21 -15
- package/dist/esm/api-server-base.js +125 -18
- package/dist/esm/auth-api/auth-module.js +10 -10
- package/dist/esm/auth-api/module.d.ts +1 -1
- package/dist/esm/index.d.ts +0 -5
- package/dist/esm/index.js +0 -5
- package/dist/esm/passkey/service.js +11 -11
- package/dist/esm/token/base.d.ts +2 -1
- package/docs/swagger/openapi.json +2010 -0
- package/package.json +65 -18
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { Application, Request, Response, type ErrorRequestHandler, type RequestHandler } from 'express';
|
|
8
8
|
import { ApiModule } from './api-module.js';
|
|
9
|
-
import { TokenStore, type JwtDecodeResult, type JwtSignResult, type JwtVerifyResult } from './token/base.js';
|
|
9
|
+
import { TokenStore, type JwtDecodeResult, type JwtSignPayload, type JwtSignResult, type JwtVerifyResult } from './token/base.js';
|
|
10
10
|
import type { ApiAuthClass, ApiAuthType, ApiKey } from './api-module.js';
|
|
11
11
|
import type { AuthProviderModule } from './auth-api/module.js';
|
|
12
12
|
import type { AuthAdapter, AuthIdentifier } from './auth-api/types.js';
|
|
@@ -32,7 +32,7 @@ export interface ApiTokenData extends JwtPayload, Partial<Token> {
|
|
|
32
32
|
exp?: number;
|
|
33
33
|
}
|
|
34
34
|
export interface ApiRequest {
|
|
35
|
-
server:
|
|
35
|
+
server: ApiServer;
|
|
36
36
|
req: ExtendedReq;
|
|
37
37
|
res: Response;
|
|
38
38
|
tokenData?: ApiTokenData | null;
|
|
@@ -64,7 +64,7 @@ export interface ClientInfo extends ClientAgentProfile {
|
|
|
64
64
|
ipchain: string[];
|
|
65
65
|
}
|
|
66
66
|
export interface ApiServerAuthStores {
|
|
67
|
-
userStore: UserStore<
|
|
67
|
+
userStore: UserStore<unknown, unknown>;
|
|
68
68
|
tokenStore: TokenStore;
|
|
69
69
|
passkeyService?: PasskeyService;
|
|
70
70
|
oauthStore?: OAuthStore;
|
|
@@ -77,13 +77,13 @@ export { ApiModule } from './api-module.js';
|
|
|
77
77
|
export type { ApiHandler, ApiAuthType, ApiAuthClass, ApiRoute, ApiKey } from './api-module.js';
|
|
78
78
|
export interface ApiErrorParams {
|
|
79
79
|
code?: number;
|
|
80
|
-
message?:
|
|
81
|
-
data?:
|
|
80
|
+
message?: unknown;
|
|
81
|
+
data?: unknown;
|
|
82
82
|
errors?: Record<string, string>;
|
|
83
83
|
}
|
|
84
84
|
export declare class ApiError extends Error {
|
|
85
85
|
code: number;
|
|
86
|
-
data:
|
|
86
|
+
data: unknown;
|
|
87
87
|
errors: Record<string, string>;
|
|
88
88
|
constructor({ code, message, data, errors }: ApiErrorParams);
|
|
89
89
|
}
|
|
@@ -95,6 +95,8 @@ export interface ApiServerConf {
|
|
|
95
95
|
origins: string[];
|
|
96
96
|
debug: boolean;
|
|
97
97
|
apiBasePath: string;
|
|
98
|
+
swaggerEnabled?: boolean;
|
|
99
|
+
swaggerPath?: string;
|
|
98
100
|
accessSecret: string;
|
|
99
101
|
refreshSecret: string;
|
|
100
102
|
cookieDomain: string;
|
|
@@ -121,6 +123,7 @@ export declare class ApiServer {
|
|
|
121
123
|
private readonly apiBasePath;
|
|
122
124
|
private storageAdapter;
|
|
123
125
|
private moduleAdapter;
|
|
126
|
+
private serverAuthAdapter;
|
|
124
127
|
private apiNotFoundHandler;
|
|
125
128
|
private tokenStoreAdapter;
|
|
126
129
|
private userStoreAdapter;
|
|
@@ -139,8 +142,8 @@ export declare class ApiServer {
|
|
|
139
142
|
* @deprecated Use {@link ApiServer.authModule} instead.
|
|
140
143
|
*/
|
|
141
144
|
useAuthModule<UserRow>(module: AuthProviderModule<UserRow>): this;
|
|
142
|
-
getAuthStorage(): AuthAdapter<
|
|
143
|
-
getAuthModule(): AuthProviderModule<
|
|
145
|
+
getAuthStorage<UserRow = unknown, SafeUser = unknown>(): AuthAdapter<UserRow, SafeUser>;
|
|
146
|
+
getAuthModule<UserRow = unknown>(): AuthProviderModule<UserRow>;
|
|
144
147
|
setTokenStore(store: TokenStore): this;
|
|
145
148
|
getTokenStore(): TokenStore | null;
|
|
146
149
|
private ensureUserStore;
|
|
@@ -149,10 +152,11 @@ export declare class ApiServer {
|
|
|
149
152
|
listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
150
153
|
deletePasskeyCredential(credentialId: Buffer | string): Promise<boolean>;
|
|
151
154
|
private ensureOAuthStore;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
private getServerAuthAdapter;
|
|
156
|
+
getUser(identifier: AuthIdentifier): Promise<unknown | null>;
|
|
157
|
+
getUserPasswordHash(user: unknown): string;
|
|
158
|
+
getUserId(user: unknown): AuthIdentifier;
|
|
159
|
+
filterUser(user: unknown): unknown;
|
|
156
160
|
verifyPassword(password: string, hash: string): Promise<boolean>;
|
|
157
161
|
storeToken(data: Token): Promise<void>;
|
|
158
162
|
getToken(query: Partial<Token> & {
|
|
@@ -175,7 +179,7 @@ export declare class ApiServer {
|
|
|
175
179
|
realUserId: AuthIdentifier;
|
|
176
180
|
effectiveUserId: AuthIdentifier;
|
|
177
181
|
}): Promise<boolean>;
|
|
178
|
-
jwtSign(payload:
|
|
182
|
+
jwtSign(payload: JwtSignPayload, secret: string, expiresInSeconds: number, options?: SignOptions): JwtSignResult;
|
|
179
183
|
jwtVerify<T>(token: string, secret: string, options?: VerifyOptions): JwtVerifyResult<T>;
|
|
180
184
|
jwtDecode<T>(token: string, options?: import('jsonwebtoken').DecodeOptions): JwtDecodeResult<T>;
|
|
181
185
|
getApiKey<T = ApiKey>(token: string): Promise<T | null>;
|
|
@@ -186,10 +190,12 @@ export declare class ApiServer {
|
|
|
186
190
|
updateToken(updates: Partial<Token> & {
|
|
187
191
|
refreshToken: string;
|
|
188
192
|
}): Promise<boolean>;
|
|
189
|
-
guessExceptionText(error:
|
|
193
|
+
guessExceptionText(error: unknown, defMsg?: string): string;
|
|
190
194
|
protected authorize(apiReq: ApiRequest, requiredClass: ApiAuthClass): Promise<void>;
|
|
191
195
|
private middlewares;
|
|
192
196
|
private installPingHandler;
|
|
197
|
+
private loadSwaggerSpec;
|
|
198
|
+
private installSwaggerHandler;
|
|
193
199
|
private normalizeApiBasePath;
|
|
194
200
|
private installApiNotFoundHandler;
|
|
195
201
|
private ensureApiNotFoundOrdering;
|
|
@@ -216,7 +222,7 @@ export declare class ApiServer {
|
|
|
216
222
|
}): RequestHandler;
|
|
217
223
|
expressErrorHandler(): ErrorRequestHandler;
|
|
218
224
|
private handle_request;
|
|
219
|
-
api<T extends ApiModule<
|
|
225
|
+
api<T extends ApiModule<unknown>>(module: T): this;
|
|
220
226
|
dumpRequest(apiReq: ApiRequest): void;
|
|
221
227
|
private formatDebugValue;
|
|
222
228
|
dumpResponse(apiReq: ApiRequest, payload: unknown, status: number): void;
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
import { randomUUID } from 'node:crypto';
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
10
|
+
import path from 'node:path';
|
|
8
11
|
import cookieParser from 'cookie-parser';
|
|
9
12
|
import cors from 'cors';
|
|
10
13
|
import express from 'express';
|
|
@@ -39,11 +42,14 @@ function guess_exception_text(error, defMsg = 'Unknown Error') {
|
|
|
39
42
|
msg.push(error);
|
|
40
43
|
}
|
|
41
44
|
else if (error && typeof error === 'object') {
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
const errorDetails = error;
|
|
46
|
+
if (typeof errorDetails.message === 'string' && errorDetails.message.trim() !== '') {
|
|
47
|
+
msg.push(errorDetails.message);
|
|
44
48
|
}
|
|
45
|
-
if (
|
|
46
|
-
|
|
49
|
+
if (errorDetails.parent &&
|
|
50
|
+
typeof errorDetails.parent.message === 'string' &&
|
|
51
|
+
errorDetails.parent.message.trim() !== '') {
|
|
52
|
+
msg.push(errorDetails.parent.message);
|
|
47
53
|
}
|
|
48
54
|
}
|
|
49
55
|
return msg.length > 0 ? msg.join(' / ') : defMsg;
|
|
@@ -332,6 +338,8 @@ function fillConfig(config) {
|
|
|
332
338
|
origins: config.origins ?? [],
|
|
333
339
|
debug: config.debug ?? false,
|
|
334
340
|
apiBasePath: config.apiBasePath ?? '/api',
|
|
341
|
+
swaggerEnabled: config.swaggerEnabled ?? false,
|
|
342
|
+
swaggerPath: config.swaggerPath ?? '',
|
|
335
343
|
accessSecret: config.accessSecret ?? '',
|
|
336
344
|
refreshSecret: config.refreshSecret ?? '',
|
|
337
345
|
cookieDomain: config.cookieDomain ?? '.somewhere-over-the-rainbow.com',
|
|
@@ -354,6 +362,7 @@ function fillConfig(config) {
|
|
|
354
362
|
export class ApiServer {
|
|
355
363
|
constructor(config = {}) {
|
|
356
364
|
this.currReq = null;
|
|
365
|
+
this.serverAuthAdapter = null;
|
|
357
366
|
this.apiNotFoundHandler = null;
|
|
358
367
|
this.tokenStoreAdapter = null;
|
|
359
368
|
this.userStoreAdapter = null;
|
|
@@ -374,7 +383,7 @@ export class ApiServer {
|
|
|
374
383
|
this.passkeyServiceAdapter = passkeyService ?? null;
|
|
375
384
|
this.oauthStoreAdapter = oauthStore ?? null;
|
|
376
385
|
this.canImpersonateAdapter = canImpersonate ?? null;
|
|
377
|
-
this.storageAdapter = this;
|
|
386
|
+
this.storageAdapter = this.getServerAuthAdapter();
|
|
378
387
|
}
|
|
379
388
|
this.app = express();
|
|
380
389
|
if (config.uploadPath) {
|
|
@@ -383,6 +392,7 @@ export class ApiServer {
|
|
|
383
392
|
}
|
|
384
393
|
this.middlewares();
|
|
385
394
|
this.installPingHandler();
|
|
395
|
+
this.installSwaggerHandler();
|
|
386
396
|
// addSwaggerUi(this.app);
|
|
387
397
|
this.installApiNotFoundHandler();
|
|
388
398
|
}
|
|
@@ -414,9 +424,9 @@ export class ApiServer {
|
|
|
414
424
|
}
|
|
415
425
|
setTokenStore(store) {
|
|
416
426
|
this.tokenStoreAdapter = store;
|
|
417
|
-
// If using direct stores, expose
|
|
427
|
+
// If using direct stores, expose the server-backed auth adapter.
|
|
418
428
|
if (this.userStoreAdapter) {
|
|
419
|
-
this.storageAdapter = this;
|
|
429
|
+
this.storageAdapter = this.getServerAuthAdapter();
|
|
420
430
|
}
|
|
421
431
|
return this;
|
|
422
432
|
}
|
|
@@ -453,6 +463,33 @@ export class ApiServer {
|
|
|
453
463
|
}
|
|
454
464
|
return this.oauthStoreAdapter;
|
|
455
465
|
}
|
|
466
|
+
getServerAuthAdapter() {
|
|
467
|
+
if (this.serverAuthAdapter) {
|
|
468
|
+
return this.serverAuthAdapter;
|
|
469
|
+
}
|
|
470
|
+
const server = this;
|
|
471
|
+
this.serverAuthAdapter = {
|
|
472
|
+
getUser: (identifier) => server.getUser(identifier),
|
|
473
|
+
getUserPasswordHash: (user) => server.getUserPasswordHash(user),
|
|
474
|
+
getUserId: (user) => server.getUserId(user),
|
|
475
|
+
filterUser: (user) => server.filterUser(user),
|
|
476
|
+
verifyPassword: (password, hash) => server.verifyPassword(password, hash),
|
|
477
|
+
storeToken: (data) => server.storeToken(data),
|
|
478
|
+
getToken: (query, opts) => server.getToken(query, opts),
|
|
479
|
+
deleteToken: (query) => server.deleteToken(query),
|
|
480
|
+
updateToken: (updates) => server.updateToken(updates),
|
|
481
|
+
createPasskeyChallenge: (params) => server.createPasskeyChallenge(params),
|
|
482
|
+
verifyPasskeyResponse: (params) => server.verifyPasskeyResponse(params),
|
|
483
|
+
listUserCredentials: (userId) => server.listUserCredentials(userId),
|
|
484
|
+
deletePasskeyCredential: (credentialId) => server.deletePasskeyCredential(credentialId),
|
|
485
|
+
getClient: (clientId) => server.getClient(clientId),
|
|
486
|
+
verifyClientSecret: (client, clientSecret) => server.verifyClientSecret(client, clientSecret),
|
|
487
|
+
createAuthCode: (request) => server.createAuthCode(request),
|
|
488
|
+
consumeAuthCode: (code, clientId) => server.consumeAuthCode(code, clientId),
|
|
489
|
+
canImpersonate: (params) => server.canImpersonate(params)
|
|
490
|
+
};
|
|
491
|
+
return this.serverAuthAdapter;
|
|
492
|
+
}
|
|
456
493
|
// AuthAdapter-compatible helpers (used by AuthModule)
|
|
457
494
|
async getUser(identifier) {
|
|
458
495
|
return this.userStoreAdapter ? this.userStoreAdapter.findUser(identifier) : null;
|
|
@@ -473,8 +510,9 @@ export class ApiServer {
|
|
|
473
510
|
if (this.tokenStoreAdapter) {
|
|
474
511
|
return this.tokenStoreAdapter.save(data);
|
|
475
512
|
}
|
|
476
|
-
|
|
477
|
-
|
|
513
|
+
const storage = this.storageAdapter;
|
|
514
|
+
if (typeof storage.storeToken === 'function') {
|
|
515
|
+
return storage.storeToken(data);
|
|
478
516
|
}
|
|
479
517
|
throw new Error('Token store is not configured');
|
|
480
518
|
}
|
|
@@ -487,8 +525,9 @@ export class ApiServer {
|
|
|
487
525
|
if (this.tokenStoreAdapter) {
|
|
488
526
|
return this.tokenStoreAdapter.get(normalized, opts);
|
|
489
527
|
}
|
|
490
|
-
|
|
491
|
-
|
|
528
|
+
const storage = this.storageAdapter;
|
|
529
|
+
if (typeof storage.getToken === 'function') {
|
|
530
|
+
return storage.getToken(normalized, opts);
|
|
492
531
|
}
|
|
493
532
|
return null;
|
|
494
533
|
}
|
|
@@ -501,8 +540,9 @@ export class ApiServer {
|
|
|
501
540
|
if (this.tokenStoreAdapter) {
|
|
502
541
|
return this.tokenStoreAdapter.delete(normalized);
|
|
503
542
|
}
|
|
504
|
-
|
|
505
|
-
|
|
543
|
+
const storage = this.storageAdapter;
|
|
544
|
+
if (typeof storage.deleteToken === 'function') {
|
|
545
|
+
return storage.deleteToken(normalized);
|
|
506
546
|
}
|
|
507
547
|
return 0;
|
|
508
548
|
}
|
|
@@ -575,8 +615,9 @@ export class ApiServer {
|
|
|
575
615
|
if (this.tokenStoreAdapter) {
|
|
576
616
|
return this.tokenStoreAdapter.update(updates);
|
|
577
617
|
}
|
|
578
|
-
|
|
579
|
-
|
|
618
|
+
const storage = this.storageAdapter;
|
|
619
|
+
if (typeof storage.updateToken === 'function') {
|
|
620
|
+
return storage.updateToken(updates);
|
|
580
621
|
}
|
|
581
622
|
return false;
|
|
582
623
|
}
|
|
@@ -622,6 +663,58 @@ export class ApiServer {
|
|
|
622
663
|
res.status(200).json({ success: true, code: 200, message: 'Success', data: payload, errors: {} });
|
|
623
664
|
});
|
|
624
665
|
}
|
|
666
|
+
loadSwaggerSpec() {
|
|
667
|
+
const candidates = [path.resolve(process.cwd(), 'docs/swagger/openapi.json')];
|
|
668
|
+
if (typeof __dirname === 'string') {
|
|
669
|
+
candidates.push(path.resolve(__dirname, '../../docs/swagger/openapi.json'));
|
|
670
|
+
}
|
|
671
|
+
try {
|
|
672
|
+
const require = createRequire(path.join(process.cwd(), 'package.json'));
|
|
673
|
+
const entry = require.resolve('@technomoron/api-server-base');
|
|
674
|
+
const packageRoot = path.resolve(path.dirname(entry), '..', '..');
|
|
675
|
+
candidates.push(path.join(packageRoot, 'docs/swagger/openapi.json'));
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
// Ignore resolution failures; fall back to any existing candidate.
|
|
679
|
+
}
|
|
680
|
+
for (const candidate of candidates) {
|
|
681
|
+
if (!fs.existsSync(candidate)) {
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
try {
|
|
685
|
+
const raw = fs.readFileSync(candidate, 'utf8');
|
|
686
|
+
return JSON.parse(raw);
|
|
687
|
+
}
|
|
688
|
+
catch {
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
installSwaggerHandler() {
|
|
695
|
+
const rawPath = typeof this.config.swaggerPath === 'string' ? this.config.swaggerPath.trim() : '';
|
|
696
|
+
const enabled = Boolean(this.config.swaggerEnabled) || rawPath.length > 0;
|
|
697
|
+
if (!enabled) {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
const base = this.apiBasePath === '/' ? '' : this.apiBasePath;
|
|
701
|
+
const resolved = rawPath.length > 0 ? rawPath : `${base}/swagger`;
|
|
702
|
+
const path = resolved.startsWith('/') ? resolved : `/${resolved}`;
|
|
703
|
+
const spec = this.loadSwaggerSpec();
|
|
704
|
+
this.app.get(path, (_req, res) => {
|
|
705
|
+
if (!spec) {
|
|
706
|
+
res.status(500).json({
|
|
707
|
+
success: false,
|
|
708
|
+
code: 500,
|
|
709
|
+
message: 'Swagger spec is unavailable',
|
|
710
|
+
data: null,
|
|
711
|
+
errors: {}
|
|
712
|
+
});
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
res.status(200).json(spec);
|
|
716
|
+
});
|
|
717
|
+
}
|
|
625
718
|
normalizeApiBasePath(path) {
|
|
626
719
|
if (!path || typeof path !== 'string') {
|
|
627
720
|
return '/api';
|
|
@@ -657,7 +750,7 @@ export class ApiServer {
|
|
|
657
750
|
if (!this.apiNotFoundHandler) {
|
|
658
751
|
return;
|
|
659
752
|
}
|
|
660
|
-
const stack = this.app
|
|
753
|
+
const stack = this.app._router?.stack;
|
|
661
754
|
if (!stack) {
|
|
662
755
|
return;
|
|
663
756
|
}
|
|
@@ -1150,7 +1243,8 @@ export class ApiServer {
|
|
|
1150
1243
|
api(module) {
|
|
1151
1244
|
const router = express.Router();
|
|
1152
1245
|
module.server = this;
|
|
1153
|
-
|
|
1246
|
+
const moduleType = module.moduleType;
|
|
1247
|
+
if (moduleType === 'auth') {
|
|
1154
1248
|
this.authModule(module);
|
|
1155
1249
|
}
|
|
1156
1250
|
module.checkConfig();
|
|
@@ -1160,7 +1254,20 @@ export class ApiServer {
|
|
|
1160
1254
|
module.mountpath = mountPath;
|
|
1161
1255
|
module.defineRoutes().forEach((r) => {
|
|
1162
1256
|
const handler = this.handle_request(r.handler, r.auth);
|
|
1163
|
-
|
|
1257
|
+
switch (r.method) {
|
|
1258
|
+
case 'get':
|
|
1259
|
+
router.get(r.path, handler);
|
|
1260
|
+
break;
|
|
1261
|
+
case 'post':
|
|
1262
|
+
router.post(r.path, handler);
|
|
1263
|
+
break;
|
|
1264
|
+
case 'put':
|
|
1265
|
+
router.put(r.path, handler);
|
|
1266
|
+
break;
|
|
1267
|
+
case 'delete':
|
|
1268
|
+
router.delete(r.path, handler);
|
|
1269
|
+
break;
|
|
1270
|
+
}
|
|
1164
1271
|
if (this.config.debug) {
|
|
1165
1272
|
console.log(`Adding ${mountPath}${r.path} (${r.method.toUpperCase()})`);
|
|
1166
1273
|
}
|
|
@@ -1050,26 +1050,26 @@ class AuthModule extends BaseAuthModule {
|
|
|
1050
1050
|
throw new ApiError({ code: 401, message: 'Authorization requires user authentication' });
|
|
1051
1051
|
}
|
|
1052
1052
|
hasPasskeyService() {
|
|
1053
|
-
const
|
|
1054
|
-
if (
|
|
1053
|
+
const storageHints = this.storage;
|
|
1054
|
+
if (storageHints.passkeyService || storageHints.passkeyStore) {
|
|
1055
1055
|
return true;
|
|
1056
1056
|
}
|
|
1057
|
-
if (
|
|
1057
|
+
if (storageHints.adapter?.passkeyService || storageHints.adapter?.passkeyStore) {
|
|
1058
1058
|
return true;
|
|
1059
1059
|
}
|
|
1060
|
-
const
|
|
1061
|
-
return !!
|
|
1060
|
+
const serverHints = this.server;
|
|
1061
|
+
return !!serverHints.passkeyServiceAdapter;
|
|
1062
1062
|
}
|
|
1063
1063
|
hasOAuthStore() {
|
|
1064
|
-
const
|
|
1065
|
-
if (
|
|
1064
|
+
const storageHints = this.storage;
|
|
1065
|
+
if (storageHints.oauthStore) {
|
|
1066
1066
|
return true;
|
|
1067
1067
|
}
|
|
1068
|
-
if (
|
|
1068
|
+
if (storageHints.adapter?.oauthStore) {
|
|
1069
1069
|
return true;
|
|
1070
1070
|
}
|
|
1071
|
-
const
|
|
1072
|
-
return !!
|
|
1071
|
+
const serverHints = this.server;
|
|
1072
|
+
return !!serverHints.oauthStoreAdapter;
|
|
1073
1073
|
}
|
|
1074
1074
|
storageImplements(key) {
|
|
1075
1075
|
const candidate = this.storage[key];
|
|
@@ -8,7 +8,7 @@ export interface AuthProviderModule<UserRow = unknown> {
|
|
|
8
8
|
expires?: Date;
|
|
9
9
|
}): Promise<TokenPair>;
|
|
10
10
|
}
|
|
11
|
-
export declare abstract class BaseAuthModule<UserRow = unknown> extends ApiModule
|
|
11
|
+
export declare abstract class BaseAuthModule<UserRow = unknown> extends ApiModule implements AuthProviderModule<UserRow> {
|
|
12
12
|
readonly moduleType: "auth";
|
|
13
13
|
protected constructor(opts: {
|
|
14
14
|
namespace: string;
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -11,22 +11,17 @@ export { nullAuthAdapter, BaseAuthAdapter } from './auth-api/storage.js';
|
|
|
11
11
|
export { nullAuthModule, BaseAuthModule } from './auth-api/module.js';
|
|
12
12
|
export { CompositeAuthAdapter } from './auth-api/compat-auth-storage.js';
|
|
13
13
|
export { MemAuthStore } from './auth-api/mem-auth-store.js';
|
|
14
|
-
export { SqlAuthStore } from './auth-api/sql-auth-store.js';
|
|
15
14
|
export { default as AuthModule } from './auth-api/auth-module.js';
|
|
16
15
|
export type { OAuthStartParams, OAuthStartResult, OAuthCallbackParams, OAuthCallbackResult } from './oauth/types.js';
|
|
17
16
|
export type { BcryptHasherOptions, CreateUserInput, UpdateUserInput, PublicUserMapper } from './user/types.js';
|
|
18
17
|
export { UserStore } from './user/base.js';
|
|
19
18
|
export { MemoryUserStore } from './user/memory.js';
|
|
20
|
-
export { SequelizeUserStore } from './user/sequelize.js';
|
|
21
19
|
export type { MemoryUserAttributes, MemoryUserStoreOptions } from './user/memory.js';
|
|
22
20
|
export { TokenStore } from './token/base.js';
|
|
23
21
|
export { MemoryTokenStore } from './token/memory.js';
|
|
24
|
-
export { SequelizeTokenStore } from './token/sequelize.js';
|
|
25
22
|
export { PasskeyService } from './passkey/service.js';
|
|
26
23
|
export { PasskeyStore } from './passkey/base.js';
|
|
27
24
|
export { MemoryPasskeyStore } from './passkey/memory.js';
|
|
28
|
-
export { SequelizePasskeyStore } from './passkey/sequelize.js';
|
|
29
25
|
export type { PasskeyServiceConfig, PasskeyChallengeRecord, PasskeyUserDescriptor, StoredPasskeyCredential, PasskeyChallenge, PasskeyChallengeParams, PasskeyVerificationParams, PasskeyVerificationResult } from './passkey/types.js';
|
|
30
26
|
export { OAuthStore } from './oauth/base.js';
|
|
31
27
|
export { MemoryOAuthStore } from './oauth/memory.js';
|
|
32
|
-
export { SequelizeOAuthStore } from './oauth/sequelize.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -5,18 +5,13 @@ export { nullAuthAdapter, BaseAuthAdapter } from './auth-api/storage.js';
|
|
|
5
5
|
export { nullAuthModule, BaseAuthModule } from './auth-api/module.js';
|
|
6
6
|
export { CompositeAuthAdapter } from './auth-api/compat-auth-storage.js';
|
|
7
7
|
export { MemAuthStore } from './auth-api/mem-auth-store.js';
|
|
8
|
-
export { SqlAuthStore } from './auth-api/sql-auth-store.js';
|
|
9
8
|
export { default as AuthModule } from './auth-api/auth-module.js';
|
|
10
9
|
export { UserStore } from './user/base.js';
|
|
11
10
|
export { MemoryUserStore } from './user/memory.js';
|
|
12
|
-
export { SequelizeUserStore } from './user/sequelize.js';
|
|
13
11
|
export { TokenStore } from './token/base.js';
|
|
14
12
|
export { MemoryTokenStore } from './token/memory.js';
|
|
15
|
-
export { SequelizeTokenStore } from './token/sequelize.js';
|
|
16
13
|
export { PasskeyService } from './passkey/service.js';
|
|
17
14
|
export { PasskeyStore } from './passkey/base.js';
|
|
18
15
|
export { MemoryPasskeyStore } from './passkey/memory.js';
|
|
19
|
-
export { SequelizePasskeyStore } from './passkey/sequelize.js';
|
|
20
16
|
export { OAuthStore } from './oauth/base.js';
|
|
21
17
|
export { MemoryOAuthStore } from './oauth/memory.js';
|
|
22
|
-
export { SequelizeOAuthStore } from './oauth/sequelize.js';
|
|
@@ -60,11 +60,13 @@ function toBufferOrNull(value) {
|
|
|
60
60
|
}
|
|
61
61
|
async function spkiToCosePublicKey(spki) {
|
|
62
62
|
try {
|
|
63
|
-
const subtle = globalThis.crypto?.subtle ??
|
|
63
|
+
const subtle = (globalThis.crypto?.subtle ??
|
|
64
|
+
(await import('crypto')).webcrypto?.subtle);
|
|
64
65
|
if (!subtle) {
|
|
65
66
|
return null;
|
|
66
67
|
}
|
|
67
|
-
const
|
|
68
|
+
const spkiView = new Uint8Array(spki);
|
|
69
|
+
const key = await subtle.importKey('spki', spkiView, { name: 'ECDSA', namedCurve: 'P-256' }, true, ['verify']);
|
|
68
70
|
const raw = Buffer.from(await subtle.exportKey('raw', key));
|
|
69
71
|
if (raw.length !== 65 || raw[0] !== 0x04) {
|
|
70
72
|
return null;
|
|
@@ -212,15 +214,16 @@ export class PasskeyService {
|
|
|
212
214
|
return { verified: false };
|
|
213
215
|
}
|
|
214
216
|
const registrationInfo = result.registrationInfo;
|
|
215
|
-
const attestationResponse = params.response
|
|
217
|
+
const attestationResponse = params.response.response;
|
|
216
218
|
const credentialIdPrimary = toBufferOrNull(registrationInfo.credentialID);
|
|
217
|
-
const credentialIdFallback = toBufferOrNull(params.response
|
|
219
|
+
const credentialIdFallback = toBufferOrNull(params.response.id);
|
|
218
220
|
const credentialId = credentialIdPrimary && credentialIdPrimary.length > 0 ? credentialIdPrimary : credentialIdFallback;
|
|
219
221
|
const publicKeyPrimary = toBufferOrNull(registrationInfo.credentialPublicKey);
|
|
220
222
|
let publicKeyFallback = toBufferOrNull(attestationResponse?.publicKey);
|
|
221
223
|
if ((!publicKeyPrimary || publicKeyPrimary.length === 0) && attestationResponse?.attestationObject) {
|
|
222
224
|
try {
|
|
223
|
-
const
|
|
225
|
+
const attestationObject = String(attestationResponse.attestationObject);
|
|
226
|
+
const attObj = decodeAttestationObject(isoBase64URL.toBuffer(attestationObject));
|
|
224
227
|
const parsedAuth = parseAuthenticatorData(attObj.get('authData'));
|
|
225
228
|
publicKeyFallback = toBufferOrNull(parsedAuth.credentialPublicKey) ?? publicKeyFallback;
|
|
226
229
|
}
|
|
@@ -282,13 +285,10 @@ export class PasskeyService {
|
|
|
282
285
|
}
|
|
283
286
|
const user = await this.requireUser({ userId: credential.userId, login: record.login });
|
|
284
287
|
const storedAuthData = {
|
|
285
|
-
id: credential.credentialId,
|
|
286
|
-
publicKey: toBuffer(credential.publicKey),
|
|
287
|
-
credentialPublicKey: toBuffer(credential.publicKey),
|
|
288
|
+
id: toBase64Url(credential.credentialId),
|
|
289
|
+
publicKey: new Uint8Array(toBuffer(credential.publicKey)),
|
|
288
290
|
counter: credential.counter,
|
|
289
|
-
transports: credential.transports ?? undefined
|
|
290
|
-
credentialBackedUp: credential.backedUp,
|
|
291
|
-
credentialDeviceType: credential.deviceType
|
|
291
|
+
transports: credential.transports ?? undefined
|
|
292
292
|
// simplewebauthn accepts either Uint8Array or Buffer; ensure Buffer
|
|
293
293
|
// see https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/server/src/authentication/verifyAuthenticationResponse.ts
|
|
294
294
|
};
|
package/dist/esm/token/base.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export interface JwtSignResult {
|
|
|
5
5
|
token?: string;
|
|
6
6
|
error?: string;
|
|
7
7
|
}
|
|
8
|
+
export type JwtSignPayload = string | Buffer | Record<string, unknown>;
|
|
8
9
|
export interface JwtVerifyResult<T> {
|
|
9
10
|
success: boolean;
|
|
10
11
|
data?: T;
|
|
@@ -32,7 +33,7 @@ export declare abstract class TokenStore {
|
|
|
32
33
|
}): Promise<Token[]>;
|
|
33
34
|
abstract close(): Promise<void>;
|
|
34
35
|
normalizeToken(token: Partial<Token>): Token;
|
|
35
|
-
jwtSign(payload:
|
|
36
|
+
jwtSign(payload: JwtSignPayload, secret: string, expiresInSeconds: number, options?: SignOptions): JwtSignResult;
|
|
36
37
|
jwtVerify<T>(token: string, secret: string, options?: VerifyOptions): JwtVerifyResult<T>;
|
|
37
38
|
jwtDecode<T>(token: string, options?: DecodeOptions): JwtDecodeResult<T>;
|
|
38
39
|
}
|