@technomoron/api-server-base 2.0.0-beta.14 → 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.
@@ -1,5 +1,6 @@
1
1
  import type { ApiRequest } from './api-server-base.js';
2
- export type ApiHandler = (apiReq: ApiRequest) => Promise<[number] | [number, any] | [number, any, string]>;
2
+ export type ApiHandlerResult<Data = unknown> = [number] | [number, Data] | [number, Data, string];
3
+ export type ApiHandler<Data = unknown> = (apiReq: ApiRequest) => Promise<ApiHandlerResult<Data>>;
3
4
  export type ApiAuthType = 'none' | 'maybe' | 'yes' | 'strict' | 'apikey';
4
5
  export type ApiAuthClass = 'any' | 'admin';
5
6
  export interface ApiKey {
@@ -14,7 +15,7 @@ export type ApiRoute = {
14
15
  req: ApiAuthClass;
15
16
  };
16
17
  };
17
- export declare class ApiModule<T> {
18
+ export declare class ApiModule<T = unknown> {
18
19
  server: T;
19
20
  namespace: string;
20
21
  mountpath: string;
@@ -49,11 +49,14 @@ function guess_exception_text(error, defMsg = 'Unknown Error') {
49
49
  msg.push(error);
50
50
  }
51
51
  else if (error && typeof error === 'object') {
52
- if (typeof error.message === 'string' && error.message.trim() !== '') {
53
- msg.push(error.message);
52
+ const errorDetails = error;
53
+ if (typeof errorDetails.message === 'string' && errorDetails.message.trim() !== '') {
54
+ msg.push(errorDetails.message);
54
55
  }
55
- if (error.parent && typeof error.parent.message === 'string' && error.parent.message.trim() !== '') {
56
- msg.push(error.parent.message);
56
+ if (errorDetails.parent &&
57
+ typeof errorDetails.parent.message === 'string' &&
58
+ errorDetails.parent.message.trim() !== '') {
59
+ msg.push(errorDetails.parent.message);
57
60
  }
58
61
  }
59
62
  return msg.length > 0 ? msg.join(' / ') : defMsg;
@@ -367,6 +370,7 @@ function fillConfig(config) {
367
370
  class ApiServer {
368
371
  constructor(config = {}) {
369
372
  this.currReq = null;
373
+ this.serverAuthAdapter = null;
370
374
  this.apiNotFoundHandler = null;
371
375
  this.tokenStoreAdapter = null;
372
376
  this.userStoreAdapter = null;
@@ -387,7 +391,7 @@ class ApiServer {
387
391
  this.passkeyServiceAdapter = passkeyService ?? null;
388
392
  this.oauthStoreAdapter = oauthStore ?? null;
389
393
  this.canImpersonateAdapter = canImpersonate ?? null;
390
- this.storageAdapter = this;
394
+ this.storageAdapter = this.getServerAuthAdapter();
391
395
  }
392
396
  this.app = (0, express_1.default)();
393
397
  if (config.uploadPath) {
@@ -428,9 +432,9 @@ class ApiServer {
428
432
  }
429
433
  setTokenStore(store) {
430
434
  this.tokenStoreAdapter = store;
431
- // If using direct stores, expose self as the auth storage.
435
+ // If using direct stores, expose the server-backed auth adapter.
432
436
  if (this.userStoreAdapter) {
433
- this.storageAdapter = this;
437
+ this.storageAdapter = this.getServerAuthAdapter();
434
438
  }
435
439
  return this;
436
440
  }
@@ -467,6 +471,33 @@ class ApiServer {
467
471
  }
468
472
  return this.oauthStoreAdapter;
469
473
  }
474
+ getServerAuthAdapter() {
475
+ if (this.serverAuthAdapter) {
476
+ return this.serverAuthAdapter;
477
+ }
478
+ const server = this;
479
+ this.serverAuthAdapter = {
480
+ getUser: (identifier) => server.getUser(identifier),
481
+ getUserPasswordHash: (user) => server.getUserPasswordHash(user),
482
+ getUserId: (user) => server.getUserId(user),
483
+ filterUser: (user) => server.filterUser(user),
484
+ verifyPassword: (password, hash) => server.verifyPassword(password, hash),
485
+ storeToken: (data) => server.storeToken(data),
486
+ getToken: (query, opts) => server.getToken(query, opts),
487
+ deleteToken: (query) => server.deleteToken(query),
488
+ updateToken: (updates) => server.updateToken(updates),
489
+ createPasskeyChallenge: (params) => server.createPasskeyChallenge(params),
490
+ verifyPasskeyResponse: (params) => server.verifyPasskeyResponse(params),
491
+ listUserCredentials: (userId) => server.listUserCredentials(userId),
492
+ deletePasskeyCredential: (credentialId) => server.deletePasskeyCredential(credentialId),
493
+ getClient: (clientId) => server.getClient(clientId),
494
+ verifyClientSecret: (client, clientSecret) => server.verifyClientSecret(client, clientSecret),
495
+ createAuthCode: (request) => server.createAuthCode(request),
496
+ consumeAuthCode: (code, clientId) => server.consumeAuthCode(code, clientId),
497
+ canImpersonate: (params) => server.canImpersonate(params)
498
+ };
499
+ return this.serverAuthAdapter;
500
+ }
470
501
  // AuthAdapter-compatible helpers (used by AuthModule)
471
502
  async getUser(identifier) {
472
503
  return this.userStoreAdapter ? this.userStoreAdapter.findUser(identifier) : null;
@@ -487,8 +518,9 @@ class ApiServer {
487
518
  if (this.tokenStoreAdapter) {
488
519
  return this.tokenStoreAdapter.save(data);
489
520
  }
490
- if (typeof this.storageAdapter.storeToken === 'function') {
491
- return this.storageAdapter.storeToken(data);
521
+ const storage = this.storageAdapter;
522
+ if (typeof storage.storeToken === 'function') {
523
+ return storage.storeToken(data);
492
524
  }
493
525
  throw new Error('Token store is not configured');
494
526
  }
@@ -501,8 +533,9 @@ class ApiServer {
501
533
  if (this.tokenStoreAdapter) {
502
534
  return this.tokenStoreAdapter.get(normalized, opts);
503
535
  }
504
- if (typeof this.storageAdapter.getToken === 'function') {
505
- return this.storageAdapter.getToken(normalized, opts);
536
+ const storage = this.storageAdapter;
537
+ if (typeof storage.getToken === 'function') {
538
+ return storage.getToken(normalized, opts);
506
539
  }
507
540
  return null;
508
541
  }
@@ -515,8 +548,9 @@ class ApiServer {
515
548
  if (this.tokenStoreAdapter) {
516
549
  return this.tokenStoreAdapter.delete(normalized);
517
550
  }
518
- if (typeof this.storageAdapter.deleteToken === 'function') {
519
- return this.storageAdapter.deleteToken(normalized);
551
+ const storage = this.storageAdapter;
552
+ if (typeof storage.deleteToken === 'function') {
553
+ return storage.deleteToken(normalized);
520
554
  }
521
555
  return 0;
522
556
  }
@@ -589,8 +623,9 @@ class ApiServer {
589
623
  if (this.tokenStoreAdapter) {
590
624
  return this.tokenStoreAdapter.update(updates);
591
625
  }
592
- if (typeof this.storageAdapter.updateToken === 'function') {
593
- return this.storageAdapter.updateToken(updates);
626
+ const storage = this.storageAdapter;
627
+ if (typeof storage.updateToken === 'function') {
628
+ return storage.updateToken(updates);
594
629
  }
595
630
  return false;
596
631
  }
@@ -723,7 +758,7 @@ class ApiServer {
723
758
  if (!this.apiNotFoundHandler) {
724
759
  return;
725
760
  }
726
- const stack = this.app?._router?.stack;
761
+ const stack = this.app._router?.stack;
727
762
  if (!stack) {
728
763
  return;
729
764
  }
@@ -1216,7 +1251,8 @@ class ApiServer {
1216
1251
  api(module) {
1217
1252
  const router = express_1.default.Router();
1218
1253
  module.server = this;
1219
- if (module?.moduleType === 'auth') {
1254
+ const moduleType = module.moduleType;
1255
+ if (moduleType === 'auth') {
1220
1256
  this.authModule(module);
1221
1257
  }
1222
1258
  module.checkConfig();
@@ -1226,7 +1262,20 @@ class ApiServer {
1226
1262
  module.mountpath = mountPath;
1227
1263
  module.defineRoutes().forEach((r) => {
1228
1264
  const handler = this.handle_request(r.handler, r.auth);
1229
- router[r.method](r.path, handler);
1265
+ switch (r.method) {
1266
+ case 'get':
1267
+ router.get(r.path, handler);
1268
+ break;
1269
+ case 'post':
1270
+ router.post(r.path, handler);
1271
+ break;
1272
+ case 'put':
1273
+ router.put(r.path, handler);
1274
+ break;
1275
+ case 'delete':
1276
+ router.delete(r.path, handler);
1277
+ break;
1278
+ }
1230
1279
  if (this.config.debug) {
1231
1280
  console.log(`Adding ${mountPath}${r.path} (${r.method.toUpperCase()})`);
1232
1281
  }
@@ -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: any;
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<any, any>;
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?: any;
81
- data?: any;
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: any;
86
+ data: unknown;
87
87
  errors: Record<string, string>;
88
88
  constructor({ code, message, data, errors }: ApiErrorParams);
89
89
  }
@@ -123,6 +123,7 @@ export declare class ApiServer {
123
123
  private readonly apiBasePath;
124
124
  private storageAdapter;
125
125
  private moduleAdapter;
126
+ private serverAuthAdapter;
126
127
  private apiNotFoundHandler;
127
128
  private tokenStoreAdapter;
128
129
  private userStoreAdapter;
@@ -141,8 +142,8 @@ export declare class ApiServer {
141
142
  * @deprecated Use {@link ApiServer.authModule} instead.
142
143
  */
143
144
  useAuthModule<UserRow>(module: AuthProviderModule<UserRow>): this;
144
- getAuthStorage(): AuthAdapter<any, any>;
145
- getAuthModule(): AuthProviderModule<any>;
145
+ getAuthStorage<UserRow = unknown, SafeUser = unknown>(): AuthAdapter<UserRow, SafeUser>;
146
+ getAuthModule<UserRow = unknown>(): AuthProviderModule<UserRow>;
146
147
  setTokenStore(store: TokenStore): this;
147
148
  getTokenStore(): TokenStore | null;
148
149
  private ensureUserStore;
@@ -151,10 +152,11 @@ export declare class ApiServer {
151
152
  listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
152
153
  deletePasskeyCredential(credentialId: Buffer | string): Promise<boolean>;
153
154
  private ensureOAuthStore;
154
- getUser(identifier: AuthIdentifier): Promise<any | null>;
155
- getUserPasswordHash(user: any): string;
156
- getUserId(user: any): AuthIdentifier;
157
- filterUser(user: any): any;
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;
158
160
  verifyPassword(password: string, hash: string): Promise<boolean>;
159
161
  storeToken(data: Token): Promise<void>;
160
162
  getToken(query: Partial<Token> & {
@@ -177,7 +179,7 @@ export declare class ApiServer {
177
179
  realUserId: AuthIdentifier;
178
180
  effectiveUserId: AuthIdentifier;
179
181
  }): Promise<boolean>;
180
- jwtSign(payload: any, secret: string, expiresInSeconds: number, options?: SignOptions): JwtSignResult;
182
+ jwtSign(payload: JwtSignPayload, secret: string, expiresInSeconds: number, options?: SignOptions): JwtSignResult;
181
183
  jwtVerify<T>(token: string, secret: string, options?: VerifyOptions): JwtVerifyResult<T>;
182
184
  jwtDecode<T>(token: string, options?: import('jsonwebtoken').DecodeOptions): JwtDecodeResult<T>;
183
185
  getApiKey<T = ApiKey>(token: string): Promise<T | null>;
@@ -188,7 +190,7 @@ export declare class ApiServer {
188
190
  updateToken(updates: Partial<Token> & {
189
191
  refreshToken: string;
190
192
  }): Promise<boolean>;
191
- guessExceptionText(error: any, defMsg?: string): string;
193
+ guessExceptionText(error: unknown, defMsg?: string): string;
192
194
  protected authorize(apiReq: ApiRequest, requiredClass: ApiAuthClass): Promise<void>;
193
195
  private middlewares;
194
196
  private installPingHandler;
@@ -220,7 +222,7 @@ export declare class ApiServer {
220
222
  }): RequestHandler;
221
223
  expressErrorHandler(): ErrorRequestHandler;
222
224
  private handle_request;
223
- api<T extends ApiModule<any>>(module: T): this;
225
+ api<T extends ApiModule<unknown>>(module: T): this;
224
226
  dumpRequest(apiReq: ApiRequest): void;
225
227
  private formatDebugValue;
226
228
  dumpResponse(apiReq: ApiRequest, payload: unknown, status: number): void;
@@ -1052,26 +1052,26 @@ class AuthModule extends module_js_1.BaseAuthModule {
1052
1052
  throw new api_server_base_js_1.ApiError({ code: 401, message: 'Authorization requires user authentication' });
1053
1053
  }
1054
1054
  hasPasskeyService() {
1055
- const storageAny = this.storage;
1056
- if (storageAny.passkeyService || storageAny.passkeyStore) {
1055
+ const storageHints = this.storage;
1056
+ if (storageHints.passkeyService || storageHints.passkeyStore) {
1057
1057
  return true;
1058
1058
  }
1059
- if (storageAny.adapter?.passkeyService || storageAny.adapter?.passkeyStore) {
1059
+ if (storageHints.adapter?.passkeyService || storageHints.adapter?.passkeyStore) {
1060
1060
  return true;
1061
1061
  }
1062
- const serverAny = this.server;
1063
- return !!serverAny.passkeyServiceAdapter;
1062
+ const serverHints = this.server;
1063
+ return !!serverHints.passkeyServiceAdapter;
1064
1064
  }
1065
1065
  hasOAuthStore() {
1066
- const storageAny = this.storage;
1067
- if (storageAny.oauthStore) {
1066
+ const storageHints = this.storage;
1067
+ if (storageHints.oauthStore) {
1068
1068
  return true;
1069
1069
  }
1070
- if (storageAny.adapter?.oauthStore) {
1070
+ if (storageHints.adapter?.oauthStore) {
1071
1071
  return true;
1072
1072
  }
1073
- const serverAny = this.server;
1074
- return !!serverAny.oauthStoreAdapter;
1073
+ const serverHints = this.server;
1074
+ return !!serverHints.oauthStoreAdapter;
1075
1075
  }
1076
1076
  storageImplements(key) {
1077
1077
  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<any> implements AuthProviderModule<UserRow> {
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;
@@ -96,11 +96,13 @@ function toBufferOrNull(value) {
96
96
  }
97
97
  async function spkiToCosePublicKey(spki) {
98
98
  try {
99
- const subtle = globalThis.crypto?.subtle ?? (await Promise.resolve().then(() => __importStar(require('crypto')))).webcrypto?.subtle;
99
+ const subtle = (globalThis.crypto?.subtle ??
100
+ (await Promise.resolve().then(() => __importStar(require('crypto')))).webcrypto?.subtle);
100
101
  if (!subtle) {
101
102
  return null;
102
103
  }
103
- const key = await subtle.importKey('spki', spki, { name: 'ECDSA', namedCurve: 'P-256' }, true, ['verify']);
104
+ const spkiView = new Uint8Array(spki);
105
+ const key = await subtle.importKey('spki', spkiView, { name: 'ECDSA', namedCurve: 'P-256' }, true, ['verify']);
104
106
  const raw = Buffer.from(await subtle.exportKey('raw', key));
105
107
  if (raw.length !== 65 || raw[0] !== 0x04) {
106
108
  return null;
@@ -248,15 +250,16 @@ class PasskeyService {
248
250
  return { verified: false };
249
251
  }
250
252
  const registrationInfo = result.registrationInfo;
251
- const attestationResponse = params.response?.response;
253
+ const attestationResponse = params.response.response;
252
254
  const credentialIdPrimary = toBufferOrNull(registrationInfo.credentialID);
253
- const credentialIdFallback = toBufferOrNull(params.response?.id);
255
+ const credentialIdFallback = toBufferOrNull(params.response.id);
254
256
  const credentialId = credentialIdPrimary && credentialIdPrimary.length > 0 ? credentialIdPrimary : credentialIdFallback;
255
257
  const publicKeyPrimary = toBufferOrNull(registrationInfo.credentialPublicKey);
256
258
  let publicKeyFallback = toBufferOrNull(attestationResponse?.publicKey);
257
259
  if ((!publicKeyPrimary || publicKeyPrimary.length === 0) && attestationResponse?.attestationObject) {
258
260
  try {
259
- const attObj = (0, helpers_1.decodeAttestationObject)(helpers_1.isoBase64URL.toBuffer(attestationResponse.attestationObject));
261
+ const attestationObject = String(attestationResponse.attestationObject);
262
+ const attObj = (0, helpers_1.decodeAttestationObject)(helpers_1.isoBase64URL.toBuffer(attestationObject));
260
263
  const parsedAuth = (0, helpers_1.parseAuthenticatorData)(attObj.get('authData'));
261
264
  publicKeyFallback = toBufferOrNull(parsedAuth.credentialPublicKey) ?? publicKeyFallback;
262
265
  }
@@ -318,13 +321,10 @@ class PasskeyService {
318
321
  }
319
322
  const user = await this.requireUser({ userId: credential.userId, login: record.login });
320
323
  const storedAuthData = {
321
- id: credential.credentialId,
322
- publicKey: toBuffer(credential.publicKey),
323
- credentialPublicKey: toBuffer(credential.publicKey),
324
+ id: toBase64Url(credential.credentialId),
325
+ publicKey: new Uint8Array(toBuffer(credential.publicKey)),
324
326
  counter: credential.counter,
325
- transports: credential.transports ?? undefined,
326
- credentialBackedUp: credential.backedUp,
327
- credentialDeviceType: credential.deviceType
327
+ transports: credential.transports ?? undefined
328
328
  // simplewebauthn accepts either Uint8Array or Buffer; ensure Buffer
329
329
  // see https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/server/src/authentication/verifyAuthenticationResponse.ts
330
330
  };
@@ -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: any, secret: string, expiresInSeconds: number, options?: SignOptions): JwtSignResult;
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
  }
@@ -1,5 +1,6 @@
1
1
  import type { ApiRequest } from './api-server-base.js';
2
- export type ApiHandler = (apiReq: ApiRequest) => Promise<[number] | [number, any] | [number, any, string]>;
2
+ export type ApiHandlerResult<Data = unknown> = [number] | [number, Data] | [number, Data, string];
3
+ export type ApiHandler<Data = unknown> = (apiReq: ApiRequest) => Promise<ApiHandlerResult<Data>>;
3
4
  export type ApiAuthType = 'none' | 'maybe' | 'yes' | 'strict' | 'apikey';
4
5
  export type ApiAuthClass = 'any' | 'admin';
5
6
  export interface ApiKey {
@@ -14,7 +15,7 @@ export type ApiRoute = {
14
15
  req: ApiAuthClass;
15
16
  };
16
17
  };
17
- export declare class ApiModule<T> {
18
+ export declare class ApiModule<T = unknown> {
18
19
  server: T;
19
20
  namespace: string;
20
21
  mountpath: string;
@@ -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: any;
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<any, any>;
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?: any;
81
- data?: any;
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: any;
86
+ data: unknown;
87
87
  errors: Record<string, string>;
88
88
  constructor({ code, message, data, errors }: ApiErrorParams);
89
89
  }
@@ -123,6 +123,7 @@ export declare class ApiServer {
123
123
  private readonly apiBasePath;
124
124
  private storageAdapter;
125
125
  private moduleAdapter;
126
+ private serverAuthAdapter;
126
127
  private apiNotFoundHandler;
127
128
  private tokenStoreAdapter;
128
129
  private userStoreAdapter;
@@ -141,8 +142,8 @@ export declare class ApiServer {
141
142
  * @deprecated Use {@link ApiServer.authModule} instead.
142
143
  */
143
144
  useAuthModule<UserRow>(module: AuthProviderModule<UserRow>): this;
144
- getAuthStorage(): AuthAdapter<any, any>;
145
- getAuthModule(): AuthProviderModule<any>;
145
+ getAuthStorage<UserRow = unknown, SafeUser = unknown>(): AuthAdapter<UserRow, SafeUser>;
146
+ getAuthModule<UserRow = unknown>(): AuthProviderModule<UserRow>;
146
147
  setTokenStore(store: TokenStore): this;
147
148
  getTokenStore(): TokenStore | null;
148
149
  private ensureUserStore;
@@ -151,10 +152,11 @@ export declare class ApiServer {
151
152
  listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
152
153
  deletePasskeyCredential(credentialId: Buffer | string): Promise<boolean>;
153
154
  private ensureOAuthStore;
154
- getUser(identifier: AuthIdentifier): Promise<any | null>;
155
- getUserPasswordHash(user: any): string;
156
- getUserId(user: any): AuthIdentifier;
157
- filterUser(user: any): any;
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;
158
160
  verifyPassword(password: string, hash: string): Promise<boolean>;
159
161
  storeToken(data: Token): Promise<void>;
160
162
  getToken(query: Partial<Token> & {
@@ -177,7 +179,7 @@ export declare class ApiServer {
177
179
  realUserId: AuthIdentifier;
178
180
  effectiveUserId: AuthIdentifier;
179
181
  }): Promise<boolean>;
180
- jwtSign(payload: any, secret: string, expiresInSeconds: number, options?: SignOptions): JwtSignResult;
182
+ jwtSign(payload: JwtSignPayload, secret: string, expiresInSeconds: number, options?: SignOptions): JwtSignResult;
181
183
  jwtVerify<T>(token: string, secret: string, options?: VerifyOptions): JwtVerifyResult<T>;
182
184
  jwtDecode<T>(token: string, options?: import('jsonwebtoken').DecodeOptions): JwtDecodeResult<T>;
183
185
  getApiKey<T = ApiKey>(token: string): Promise<T | null>;
@@ -188,7 +190,7 @@ export declare class ApiServer {
188
190
  updateToken(updates: Partial<Token> & {
189
191
  refreshToken: string;
190
192
  }): Promise<boolean>;
191
- guessExceptionText(error: any, defMsg?: string): string;
193
+ guessExceptionText(error: unknown, defMsg?: string): string;
192
194
  protected authorize(apiReq: ApiRequest, requiredClass: ApiAuthClass): Promise<void>;
193
195
  private middlewares;
194
196
  private installPingHandler;
@@ -220,7 +222,7 @@ export declare class ApiServer {
220
222
  }): RequestHandler;
221
223
  expressErrorHandler(): ErrorRequestHandler;
222
224
  private handle_request;
223
- api<T extends ApiModule<any>>(module: T): this;
225
+ api<T extends ApiModule<unknown>>(module: T): this;
224
226
  dumpRequest(apiReq: ApiRequest): void;
225
227
  private formatDebugValue;
226
228
  dumpResponse(apiReq: ApiRequest, payload: unknown, status: number): void;
@@ -42,11 +42,14 @@ function guess_exception_text(error, defMsg = 'Unknown Error') {
42
42
  msg.push(error);
43
43
  }
44
44
  else if (error && typeof error === 'object') {
45
- if (typeof error.message === 'string' && error.message.trim() !== '') {
46
- msg.push(error.message);
45
+ const errorDetails = error;
46
+ if (typeof errorDetails.message === 'string' && errorDetails.message.trim() !== '') {
47
+ msg.push(errorDetails.message);
47
48
  }
48
- if (error.parent && typeof error.parent.message === 'string' && error.parent.message.trim() !== '') {
49
- msg.push(error.parent.message);
49
+ if (errorDetails.parent &&
50
+ typeof errorDetails.parent.message === 'string' &&
51
+ errorDetails.parent.message.trim() !== '') {
52
+ msg.push(errorDetails.parent.message);
50
53
  }
51
54
  }
52
55
  return msg.length > 0 ? msg.join(' / ') : defMsg;
@@ -359,6 +362,7 @@ function fillConfig(config) {
359
362
  export class ApiServer {
360
363
  constructor(config = {}) {
361
364
  this.currReq = null;
365
+ this.serverAuthAdapter = null;
362
366
  this.apiNotFoundHandler = null;
363
367
  this.tokenStoreAdapter = null;
364
368
  this.userStoreAdapter = null;
@@ -379,7 +383,7 @@ export class ApiServer {
379
383
  this.passkeyServiceAdapter = passkeyService ?? null;
380
384
  this.oauthStoreAdapter = oauthStore ?? null;
381
385
  this.canImpersonateAdapter = canImpersonate ?? null;
382
- this.storageAdapter = this;
386
+ this.storageAdapter = this.getServerAuthAdapter();
383
387
  }
384
388
  this.app = express();
385
389
  if (config.uploadPath) {
@@ -420,9 +424,9 @@ export class ApiServer {
420
424
  }
421
425
  setTokenStore(store) {
422
426
  this.tokenStoreAdapter = store;
423
- // If using direct stores, expose self as the auth storage.
427
+ // If using direct stores, expose the server-backed auth adapter.
424
428
  if (this.userStoreAdapter) {
425
- this.storageAdapter = this;
429
+ this.storageAdapter = this.getServerAuthAdapter();
426
430
  }
427
431
  return this;
428
432
  }
@@ -459,6 +463,33 @@ export class ApiServer {
459
463
  }
460
464
  return this.oauthStoreAdapter;
461
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
+ }
462
493
  // AuthAdapter-compatible helpers (used by AuthModule)
463
494
  async getUser(identifier) {
464
495
  return this.userStoreAdapter ? this.userStoreAdapter.findUser(identifier) : null;
@@ -479,8 +510,9 @@ export class ApiServer {
479
510
  if (this.tokenStoreAdapter) {
480
511
  return this.tokenStoreAdapter.save(data);
481
512
  }
482
- if (typeof this.storageAdapter.storeToken === 'function') {
483
- return this.storageAdapter.storeToken(data);
513
+ const storage = this.storageAdapter;
514
+ if (typeof storage.storeToken === 'function') {
515
+ return storage.storeToken(data);
484
516
  }
485
517
  throw new Error('Token store is not configured');
486
518
  }
@@ -493,8 +525,9 @@ export class ApiServer {
493
525
  if (this.tokenStoreAdapter) {
494
526
  return this.tokenStoreAdapter.get(normalized, opts);
495
527
  }
496
- if (typeof this.storageAdapter.getToken === 'function') {
497
- return this.storageAdapter.getToken(normalized, opts);
528
+ const storage = this.storageAdapter;
529
+ if (typeof storage.getToken === 'function') {
530
+ return storage.getToken(normalized, opts);
498
531
  }
499
532
  return null;
500
533
  }
@@ -507,8 +540,9 @@ export class ApiServer {
507
540
  if (this.tokenStoreAdapter) {
508
541
  return this.tokenStoreAdapter.delete(normalized);
509
542
  }
510
- if (typeof this.storageAdapter.deleteToken === 'function') {
511
- return this.storageAdapter.deleteToken(normalized);
543
+ const storage = this.storageAdapter;
544
+ if (typeof storage.deleteToken === 'function') {
545
+ return storage.deleteToken(normalized);
512
546
  }
513
547
  return 0;
514
548
  }
@@ -581,8 +615,9 @@ export class ApiServer {
581
615
  if (this.tokenStoreAdapter) {
582
616
  return this.tokenStoreAdapter.update(updates);
583
617
  }
584
- if (typeof this.storageAdapter.updateToken === 'function') {
585
- return this.storageAdapter.updateToken(updates);
618
+ const storage = this.storageAdapter;
619
+ if (typeof storage.updateToken === 'function') {
620
+ return storage.updateToken(updates);
586
621
  }
587
622
  return false;
588
623
  }
@@ -715,7 +750,7 @@ export class ApiServer {
715
750
  if (!this.apiNotFoundHandler) {
716
751
  return;
717
752
  }
718
- const stack = this.app?._router?.stack;
753
+ const stack = this.app._router?.stack;
719
754
  if (!stack) {
720
755
  return;
721
756
  }
@@ -1208,7 +1243,8 @@ export class ApiServer {
1208
1243
  api(module) {
1209
1244
  const router = express.Router();
1210
1245
  module.server = this;
1211
- if (module?.moduleType === 'auth') {
1246
+ const moduleType = module.moduleType;
1247
+ if (moduleType === 'auth') {
1212
1248
  this.authModule(module);
1213
1249
  }
1214
1250
  module.checkConfig();
@@ -1218,7 +1254,20 @@ export class ApiServer {
1218
1254
  module.mountpath = mountPath;
1219
1255
  module.defineRoutes().forEach((r) => {
1220
1256
  const handler = this.handle_request(r.handler, r.auth);
1221
- router[r.method](r.path, handler);
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
+ }
1222
1271
  if (this.config.debug) {
1223
1272
  console.log(`Adding ${mountPath}${r.path} (${r.method.toUpperCase()})`);
1224
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 storageAny = this.storage;
1054
- if (storageAny.passkeyService || storageAny.passkeyStore) {
1053
+ const storageHints = this.storage;
1054
+ if (storageHints.passkeyService || storageHints.passkeyStore) {
1055
1055
  return true;
1056
1056
  }
1057
- if (storageAny.adapter?.passkeyService || storageAny.adapter?.passkeyStore) {
1057
+ if (storageHints.adapter?.passkeyService || storageHints.adapter?.passkeyStore) {
1058
1058
  return true;
1059
1059
  }
1060
- const serverAny = this.server;
1061
- return !!serverAny.passkeyServiceAdapter;
1060
+ const serverHints = this.server;
1061
+ return !!serverHints.passkeyServiceAdapter;
1062
1062
  }
1063
1063
  hasOAuthStore() {
1064
- const storageAny = this.storage;
1065
- if (storageAny.oauthStore) {
1064
+ const storageHints = this.storage;
1065
+ if (storageHints.oauthStore) {
1066
1066
  return true;
1067
1067
  }
1068
- if (storageAny.adapter?.oauthStore) {
1068
+ if (storageHints.adapter?.oauthStore) {
1069
1069
  return true;
1070
1070
  }
1071
- const serverAny = this.server;
1072
- return !!serverAny.oauthStoreAdapter;
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<any> implements AuthProviderModule<UserRow> {
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;
@@ -60,11 +60,13 @@ function toBufferOrNull(value) {
60
60
  }
61
61
  async function spkiToCosePublicKey(spki) {
62
62
  try {
63
- const subtle = globalThis.crypto?.subtle ?? (await import('crypto')).webcrypto?.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 key = await subtle.importKey('spki', spki, { name: 'ECDSA', namedCurve: 'P-256' }, true, ['verify']);
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?.response;
217
+ const attestationResponse = params.response.response;
216
218
  const credentialIdPrimary = toBufferOrNull(registrationInfo.credentialID);
217
- const credentialIdFallback = toBufferOrNull(params.response?.id);
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 attObj = decodeAttestationObject(isoBase64URL.toBuffer(attestationResponse.attestationObject));
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
  };
@@ -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: any, secret: string, expiresInSeconds: number, options?: SignOptions): JwtSignResult;
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@technomoron/api-server-base",
3
- "version": "2.0.0-beta.14",
3
+ "version": "2.0.0-beta.15",
4
4
  "description": "Api Server Skeleton / Base Class",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.cjs",
@@ -59,11 +59,12 @@
59
59
  "test:functional": "vitest run tests/functional",
60
60
  "test:watch": "vitest --watch",
61
61
  "prepublishOnly": "node scripts/run-builds.cjs",
62
- "lint": "eslint --ext .js,.ts,.vue ./",
63
- "lintfix": "eslint --fix --ext .js,.ts,.vue ./",
64
- "format": "eslint --fix --ext .js,.ts,.vue ./ && prettier --write \"**/*.{js,jsx,ts,tsx,vue,json,css,scss,md}\"",
65
- "cleanbuild": "rm -rf ./dist/ && eslint --fix --ext .js,.ts,.vue ./ && prettier --log-level warn --write \"**/*.{js,jsx,ts,tsx,vue,json,css,scss,md}\" && node scripts/run-builds.cjs",
66
- "pretty": "prettier --write \"**/*.{js,jsx,ts,tsx,vue,json,css,scss,md}\""
62
+ "lint": "eslint --no-error-on-unmatched-pattern --ext .js,.cjs,.mjs,.ts,.mts,.tsx,.vue,.json ./",
63
+ "lintfix": "eslint --fix --no-error-on-unmatched-pattern --ext .js,.cjs,.mjs,.ts,.mts,.tsx,.vue,.json ./",
64
+ "format": "npm run lintfix && npm run pretty",
65
+ "cleanbuild": "rm -rf ./dist/ && npm run format && npm run build",
66
+ "pretty": "prettier --write \"**/*.{js,jsx,cjs,mjs,ts,tsx,mts,vue,json,md}\"",
67
+ "lintconfig": "node lintconfig.cjs"
67
68
  },
68
69
  "dependencies": {
69
70
  "@simplewebauthn/server": "^13.2.2",
@@ -85,11 +86,10 @@
85
86
  "@types/supertest": "^6.0.3",
86
87
  "@typescript-eslint/eslint-plugin": "^8.50.1",
87
88
  "@typescript-eslint/parser": "^8.50.1",
88
- "@vue/eslint-config-prettier": "10.2.0",
89
- "@vue/eslint-config-typescript": "^14.6.0",
90
89
  "eslint": "^9.39.2",
90
+ "eslint-config-prettier": "^10.1.8",
91
91
  "eslint-plugin-import": "^2.32.0",
92
- "eslint-plugin-vue": "^10.6.2",
92
+ "jsonc-eslint-parser": "^2.4.1",
93
93
  "mysql2": "^3.16.0",
94
94
  "pg": "^8.16.3",
95
95
  "prettier": "^3.7.4",
@@ -129,5 +129,6 @@
129
129
  "dist/",
130
130
  "docs/swagger/openapi.json",
131
131
  "package.json"
132
- ]
132
+ ],
133
+ "packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
133
134
  }