@technomoron/api-server-base 2.0.0-beta.11 → 2.0.0-beta.14

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.
@@ -11,6 +11,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.ApiServer = exports.ApiError = exports.ApiModule = void 0;
13
13
  const node_crypto_1 = require("node:crypto");
14
+ const node_fs_1 = __importDefault(require("node:fs"));
15
+ const node_module_1 = require("node:module");
16
+ const node_path_1 = __importDefault(require("node:path"));
14
17
  const cookie_parser_1 = __importDefault(require("cookie-parser"));
15
18
  const cors_1 = __importDefault(require("cors"));
16
19
  const express_1 = __importDefault(require("express"));
@@ -340,6 +343,8 @@ function fillConfig(config) {
340
343
  origins: config.origins ?? [],
341
344
  debug: config.debug ?? false,
342
345
  apiBasePath: config.apiBasePath ?? '/api',
346
+ swaggerEnabled: config.swaggerEnabled ?? false,
347
+ swaggerPath: config.swaggerPath ?? '',
343
348
  accessSecret: config.accessSecret ?? '',
344
349
  refreshSecret: config.refreshSecret ?? '',
345
350
  cookieDomain: config.cookieDomain ?? '.somewhere-over-the-rainbow.com',
@@ -391,6 +396,7 @@ class ApiServer {
391
396
  }
392
397
  this.middlewares();
393
398
  this.installPingHandler();
399
+ this.installSwaggerHandler();
394
400
  // addSwaggerUi(this.app);
395
401
  this.installApiNotFoundHandler();
396
402
  }
@@ -630,6 +636,58 @@ class ApiServer {
630
636
  res.status(200).json({ success: true, code: 200, message: 'Success', data: payload, errors: {} });
631
637
  });
632
638
  }
639
+ loadSwaggerSpec() {
640
+ const candidates = [node_path_1.default.resolve(process.cwd(), 'docs/swagger/openapi.json')];
641
+ if (typeof __dirname === 'string') {
642
+ candidates.push(node_path_1.default.resolve(__dirname, '../../docs/swagger/openapi.json'));
643
+ }
644
+ try {
645
+ const require = (0, node_module_1.createRequire)(node_path_1.default.join(process.cwd(), 'package.json'));
646
+ const entry = require.resolve('@technomoron/api-server-base');
647
+ const packageRoot = node_path_1.default.resolve(node_path_1.default.dirname(entry), '..', '..');
648
+ candidates.push(node_path_1.default.join(packageRoot, 'docs/swagger/openapi.json'));
649
+ }
650
+ catch {
651
+ // Ignore resolution failures; fall back to any existing candidate.
652
+ }
653
+ for (const candidate of candidates) {
654
+ if (!node_fs_1.default.existsSync(candidate)) {
655
+ continue;
656
+ }
657
+ try {
658
+ const raw = node_fs_1.default.readFileSync(candidate, 'utf8');
659
+ return JSON.parse(raw);
660
+ }
661
+ catch {
662
+ return null;
663
+ }
664
+ }
665
+ return null;
666
+ }
667
+ installSwaggerHandler() {
668
+ const rawPath = typeof this.config.swaggerPath === 'string' ? this.config.swaggerPath.trim() : '';
669
+ const enabled = Boolean(this.config.swaggerEnabled) || rawPath.length > 0;
670
+ if (!enabled) {
671
+ return;
672
+ }
673
+ const base = this.apiBasePath === '/' ? '' : this.apiBasePath;
674
+ const resolved = rawPath.length > 0 ? rawPath : `${base}/swagger`;
675
+ const path = resolved.startsWith('/') ? resolved : `/${resolved}`;
676
+ const spec = this.loadSwaggerSpec();
677
+ this.app.get(path, (_req, res) => {
678
+ if (!spec) {
679
+ res.status(500).json({
680
+ success: false,
681
+ code: 500,
682
+ message: 'Swagger spec is unavailable',
683
+ data: null,
684
+ errors: {}
685
+ });
686
+ return;
687
+ }
688
+ res.status(200).json(spec);
689
+ });
690
+ }
633
691
  normalizeApiBasePath(path) {
634
692
  if (!path || typeof path !== 'string') {
635
693
  return '/api';
@@ -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;
@@ -190,6 +192,8 @@ export declare class ApiServer {
190
192
  protected authorize(apiReq: ApiRequest, requiredClass: ApiAuthClass): Promise<void>;
191
193
  private middlewares;
192
194
  private installPingHandler;
195
+ private loadSwaggerSpec;
196
+ private installSwaggerHandler;
193
197
  private normalizeApiBasePath;
194
198
  private installApiNotFoundHandler;
195
199
  private ensureApiNotFoundOrdering;
@@ -600,15 +600,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
600
600
  const params = {
601
601
  action,
602
602
  login: toStringOrNull(body.login) ?? undefined,
603
- userId: isAuthIdentifier(body.userId) ? body.userId : undefined,
604
- userAgent: toStringOrNull(body.userAgent) ?? undefined,
605
- domain: toStringOrNull(body.domain) ?? undefined,
606
- fingerprint: toStringOrNull(body.fingerprint) ?? undefined,
607
- label: toStringOrNull(body.label) ?? undefined,
608
- browser: toStringOrNull(body.browser) ?? undefined,
609
- device: toStringOrNull(body.device) ?? undefined,
610
- ip: toStringOrNull(body.ip) ?? undefined,
611
- os: toStringOrNull(body.os) ?? undefined
603
+ userId: isAuthIdentifier(body.userId) ? body.userId : undefined
612
604
  };
613
605
  const challenge = await this.storage.createPasskeyChallenge(params);
614
606
  return [200, challenge];
@@ -624,18 +616,25 @@ class AuthModule extends module_js_1.BaseAuthModule {
624
616
  if (!expectedChallenge || typeof response !== 'object' || response === null) {
625
617
  throw new api_server_base_js_1.ApiError({ code: 400, message: 'Malformed passkey verification payload' });
626
618
  }
627
- const params = {
628
- expectedChallenge,
629
- response: response,
630
- login: toStringOrNull(body.login) ?? undefined,
631
- userId: isAuthIdentifier(body.userId) ? body.userId : undefined,
619
+ const rawMetadata = {
632
620
  domain: toStringOrNull(body.domain) ?? undefined,
633
621
  fingerprint: toStringOrNull(body.fingerprint) ?? undefined,
634
622
  label: toStringOrNull(body.label) ?? undefined,
635
623
  browser: toStringOrNull(body.browser) ?? undefined,
636
624
  device: toStringOrNull(body.device) ?? undefined,
637
625
  ip: toStringOrNull(body.ip) ?? undefined,
638
- os: toStringOrNull(body.os) ?? undefined,
626
+ os: toStringOrNull(body.os) ?? undefined
627
+ };
628
+ const clientInfo = apiReq.getClientInfo();
629
+ const userAgent = toStringOrNull(body.userAgent) ?? (clientInfo.ua ? clientInfo.ua : null);
630
+ const requestMetadata = this.enrichTokenMetadata(apiReq, rawMetadata);
631
+ const params = {
632
+ expectedChallenge,
633
+ response: response,
634
+ login: toStringOrNull(body.login) ?? undefined,
635
+ userId: isAuthIdentifier(body.userId) ? body.userId : undefined,
636
+ userAgent: userAgent ?? undefined,
637
+ ...requestMetadata,
639
638
  ...sessionPrefs
640
639
  };
641
640
  const result = await this.storage.verifyPasskeyResponse(params);
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.SequelizeOAuthStore = exports.MemoryOAuthStore = exports.OAuthStore = exports.SequelizePasskeyStore = exports.MemoryPasskeyStore = exports.PasskeyStore = exports.PasskeyService = exports.SequelizeTokenStore = exports.MemoryTokenStore = exports.TokenStore = exports.SequelizeUserStore = exports.MemoryUserStore = exports.UserStore = exports.AuthModule = exports.SqlAuthStore = exports.MemAuthStore = exports.CompositeAuthAdapter = exports.BaseAuthModule = exports.nullAuthModule = exports.BaseAuthAdapter = exports.nullAuthAdapter = exports.ApiModule = exports.ApiError = exports.ApiServer = void 0;
6
+ exports.MemoryOAuthStore = exports.OAuthStore = exports.MemoryPasskeyStore = exports.PasskeyStore = exports.PasskeyService = exports.MemoryTokenStore = exports.TokenStore = exports.MemoryUserStore = exports.UserStore = exports.AuthModule = exports.MemAuthStore = exports.CompositeAuthAdapter = exports.BaseAuthModule = exports.nullAuthModule = exports.BaseAuthAdapter = exports.nullAuthAdapter = exports.ApiModule = exports.ApiError = exports.ApiServer = void 0;
7
7
  var api_server_base_js_1 = require("./api-server-base.cjs");
8
8
  Object.defineProperty(exports, "ApiServer", { enumerable: true, get: function () { return __importDefault(api_server_base_js_1).default; } });
9
9
  var api_server_base_js_2 = require("./api-server-base.cjs");
@@ -20,33 +20,23 @@ var compat_auth_storage_js_1 = require("./auth-api/compat-auth-storage.js");
20
20
  Object.defineProperty(exports, "CompositeAuthAdapter", { enumerable: true, get: function () { return compat_auth_storage_js_1.CompositeAuthAdapter; } });
21
21
  var mem_auth_store_js_1 = require("./auth-api/mem-auth-store.js");
22
22
  Object.defineProperty(exports, "MemAuthStore", { enumerable: true, get: function () { return mem_auth_store_js_1.MemAuthStore; } });
23
- var sql_auth_store_js_1 = require("./auth-api/sql-auth-store.js");
24
- Object.defineProperty(exports, "SqlAuthStore", { enumerable: true, get: function () { return sql_auth_store_js_1.SqlAuthStore; } });
25
23
  var auth_module_js_1 = require("./auth-api/auth-module.js");
26
24
  Object.defineProperty(exports, "AuthModule", { enumerable: true, get: function () { return __importDefault(auth_module_js_1).default; } });
27
25
  var base_js_1 = require("./user/base.js");
28
26
  Object.defineProperty(exports, "UserStore", { enumerable: true, get: function () { return base_js_1.UserStore; } });
29
27
  var memory_js_1 = require("./user/memory.js");
30
28
  Object.defineProperty(exports, "MemoryUserStore", { enumerable: true, get: function () { return memory_js_1.MemoryUserStore; } });
31
- var sequelize_js_1 = require("./user/sequelize.js");
32
- Object.defineProperty(exports, "SequelizeUserStore", { enumerable: true, get: function () { return sequelize_js_1.SequelizeUserStore; } });
33
29
  var base_js_2 = require("./token/base.js");
34
30
  Object.defineProperty(exports, "TokenStore", { enumerable: true, get: function () { return base_js_2.TokenStore; } });
35
31
  var memory_js_2 = require("./token/memory.js");
36
32
  Object.defineProperty(exports, "MemoryTokenStore", { enumerable: true, get: function () { return memory_js_2.MemoryTokenStore; } });
37
- var sequelize_js_2 = require("./token/sequelize.js");
38
- Object.defineProperty(exports, "SequelizeTokenStore", { enumerable: true, get: function () { return sequelize_js_2.SequelizeTokenStore; } });
39
33
  var service_js_1 = require("./passkey/service.js");
40
34
  Object.defineProperty(exports, "PasskeyService", { enumerable: true, get: function () { return service_js_1.PasskeyService; } });
41
35
  var base_js_3 = require("./passkey/base.js");
42
36
  Object.defineProperty(exports, "PasskeyStore", { enumerable: true, get: function () { return base_js_3.PasskeyStore; } });
43
37
  var memory_js_3 = require("./passkey/memory.js");
44
38
  Object.defineProperty(exports, "MemoryPasskeyStore", { enumerable: true, get: function () { return memory_js_3.MemoryPasskeyStore; } });
45
- var sequelize_js_3 = require("./passkey/sequelize.js");
46
- Object.defineProperty(exports, "SequelizePasskeyStore", { enumerable: true, get: function () { return sequelize_js_3.SequelizePasskeyStore; } });
47
39
  var base_js_4 = require("./oauth/base.js");
48
40
  Object.defineProperty(exports, "OAuthStore", { enumerable: true, get: function () { return base_js_4.OAuthStore; } });
49
41
  var memory_js_4 = require("./oauth/memory.js");
50
42
  Object.defineProperty(exports, "MemoryOAuthStore", { enumerable: true, get: function () { return memory_js_4.MemoryOAuthStore; } });
51
- var sequelize_js_4 = require("./oauth/sequelize.js");
52
- Object.defineProperty(exports, "SequelizeOAuthStore", { enumerable: true, get: function () { return sequelize_js_4.SequelizeOAuthStore; } });
@@ -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';
@@ -62,9 +62,11 @@ class MemoryPasskeyStore extends base_js_1.PasskeyStore {
62
62
  }
63
63
  async saveChallenge(record) {
64
64
  this.challenges.set(record.challenge, {
65
- ...record,
65
+ challenge: record.challenge,
66
+ action: record.action,
66
67
  userId: record.userId !== undefined ? normalizeUserId(record.userId) : undefined,
67
- metadata: record.metadata ? { ...record.metadata } : {}
68
+ login: record.login ?? undefined,
69
+ expiresAt: record.expiresAt
68
70
  });
69
71
  }
70
72
  async consumeChallenge(challenge) {
@@ -73,7 +75,7 @@ class MemoryPasskeyStore extends base_js_1.PasskeyStore {
73
75
  return null;
74
76
  }
75
77
  this.challenges.delete(challenge);
76
- return { ...record, metadata: record.metadata ? { ...record.metadata } : {} };
78
+ return { ...record };
77
79
  }
78
80
  async cleanupChallenges(now) {
79
81
  for (const [challenge, record] of this.challenges.entries()) {
@@ -1,5 +1,4 @@
1
1
  import { Model, type InferAttributes, type InferCreationAttributes, type ModelStatic, type Sequelize } from 'sequelize';
2
- import type { PasskeyChallengeMetadata } from './service.js';
3
2
  export declare class PasskeyCredentialModel extends Model<InferAttributes<PasskeyCredentialModel>, InferCreationAttributes<PasskeyCredentialModel>> {
4
3
  credentialId: Buffer;
5
4
  userId: number;
@@ -8,6 +7,13 @@ export declare class PasskeyCredentialModel extends Model<InferAttributes<Passke
8
7
  transports: string[] | null;
9
8
  backedUp: boolean;
10
9
  deviceType: string;
10
+ label: string | null;
11
+ createdDomain: string | null;
12
+ createdUserAgent: string | null;
13
+ createdBrowser: string | null;
14
+ createdOs: string | null;
15
+ createdDevice: string | null;
16
+ createdIp: string | null;
11
17
  createdAt?: Date;
12
18
  updatedAt?: Date;
13
19
  }
@@ -16,7 +22,6 @@ export declare class PasskeyChallengeModel extends Model<InferAttributes<Passkey
16
22
  action: 'register' | 'authenticate';
17
23
  userId: number | null;
18
24
  login: string | null;
19
- metadata: PasskeyChallengeMetadata | null;
20
25
  expiresAt: Date;
21
26
  createdAt?: Date;
22
27
  updatedAt?: Date;
@@ -67,6 +67,40 @@ function initPasskeyCredentialModel(sequelize) {
67
67
  type: sequelize_1.DataTypes.STRING(32),
68
68
  allowNull: false,
69
69
  defaultValue: 'multiDevice'
70
+ },
71
+ label: {
72
+ type: sequelize_1.DataTypes.STRING(120),
73
+ allowNull: true
74
+ },
75
+ createdDomain: {
76
+ field: 'created_domain',
77
+ type: sequelize_1.DataTypes.STRING(255),
78
+ allowNull: true
79
+ },
80
+ createdUserAgent: {
81
+ field: 'created_user_agent',
82
+ type: sequelize_1.DataTypes.TEXT,
83
+ allowNull: true
84
+ },
85
+ createdBrowser: {
86
+ field: 'created_browser',
87
+ type: sequelize_1.DataTypes.STRING(120),
88
+ allowNull: true
89
+ },
90
+ createdOs: {
91
+ field: 'created_os',
92
+ type: sequelize_1.DataTypes.STRING(120),
93
+ allowNull: true
94
+ },
95
+ createdDevice: {
96
+ field: 'created_device',
97
+ type: sequelize_1.DataTypes.STRING(120),
98
+ allowNull: true
99
+ },
100
+ createdIp: {
101
+ field: 'created_ip',
102
+ type: sequelize_1.DataTypes.STRING(45),
103
+ allowNull: true
70
104
  }
71
105
  }, {
72
106
  sequelize,
@@ -96,10 +130,6 @@ function initPasskeyChallengeModel(sequelize) {
96
130
  type: sequelize_1.DataTypes.STRING(128),
97
131
  allowNull: true
98
132
  },
99
- metadata: {
100
- type: sequelize_1.DataTypes.JSON,
101
- allowNull: true
102
- },
103
133
  expiresAt: {
104
134
  field: 'expires_at',
105
135
  type: sequelize_1.DataTypes.DATE,
@@ -10,6 +10,13 @@ declare class PasskeyCredentialModel extends Model<InferAttributes<PasskeyCreden
10
10
  transports: string[] | null;
11
11
  backedUp: boolean;
12
12
  deviceType: string;
13
+ label: string | null;
14
+ createdDomain: string | null;
15
+ createdUserAgent: string | null;
16
+ createdBrowser: string | null;
17
+ createdOs: string | null;
18
+ createdDevice: string | null;
19
+ createdIp: string | null;
13
20
  createdAt?: Date;
14
21
  updatedAt?: Date;
15
22
  }
@@ -18,7 +25,6 @@ declare class PasskeyChallengeModel extends Model<InferAttributes<PasskeyChallen
18
25
  action: 'register' | 'authenticate';
19
26
  userId: number | null;
20
27
  login: string | null;
21
- metadata: Record<string, unknown> | null;
22
28
  expiresAt: Date;
23
29
  createdAt?: Date;
24
30
  updatedAt?: Date;
@@ -76,6 +76,40 @@ function initPasskeyCredentialModel(sequelize) {
76
76
  type: sequelize_1.DataTypes.STRING(32),
77
77
  allowNull: false,
78
78
  defaultValue: 'multiDevice'
79
+ },
80
+ label: {
81
+ type: sequelize_1.DataTypes.STRING(120),
82
+ allowNull: true
83
+ },
84
+ createdDomain: {
85
+ field: 'created_domain',
86
+ type: sequelize_1.DataTypes.STRING(255),
87
+ allowNull: true
88
+ },
89
+ createdUserAgent: {
90
+ field: 'created_user_agent',
91
+ type: sequelize_1.DataTypes.TEXT,
92
+ allowNull: true
93
+ },
94
+ createdBrowser: {
95
+ field: 'created_browser',
96
+ type: sequelize_1.DataTypes.STRING(120),
97
+ allowNull: true
98
+ },
99
+ createdOs: {
100
+ field: 'created_os',
101
+ type: sequelize_1.DataTypes.STRING(120),
102
+ allowNull: true
103
+ },
104
+ createdDevice: {
105
+ field: 'created_device',
106
+ type: sequelize_1.DataTypes.STRING(120),
107
+ allowNull: true
108
+ },
109
+ createdIp: {
110
+ field: 'created_ip',
111
+ type: sequelize_1.DataTypes.STRING(45),
112
+ allowNull: true
79
113
  }
80
114
  }, {
81
115
  sequelize,
@@ -105,10 +139,6 @@ function initPasskeyChallengeModel(sequelize) {
105
139
  type: sequelize_1.DataTypes.STRING(128),
106
140
  allowNull: true
107
141
  },
108
- metadata: {
109
- type: sequelize_1.DataTypes.JSON,
110
- allowNull: true
111
- },
112
142
  expiresAt: {
113
143
  field: 'expires_at',
114
144
  type: sequelize_1.DataTypes.DATE,
@@ -148,6 +178,13 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
148
178
  transports: (model.transports ?? undefined),
149
179
  backedUp: model.backedUp,
150
180
  deviceType: model.deviceType,
181
+ label: model.label ?? undefined,
182
+ createdDomain: model.createdDomain ?? undefined,
183
+ createdUserAgent: model.createdUserAgent ?? undefined,
184
+ createdBrowser: model.createdBrowser ?? undefined,
185
+ createdOs: model.createdOs ?? undefined,
186
+ createdDevice: model.createdDevice ?? undefined,
187
+ createdIp: model.createdIp ?? undefined,
151
188
  createdAt: model.createdAt ?? undefined,
152
189
  updatedAt: model.updatedAt ?? undefined
153
190
  }));
@@ -170,6 +207,13 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
170
207
  transports: (model.transports ?? undefined),
171
208
  backedUp: model.backedUp,
172
209
  deviceType: model.deviceType,
210
+ label: model.label ?? undefined,
211
+ createdDomain: model.createdDomain ?? undefined,
212
+ createdUserAgent: model.createdUserAgent ?? undefined,
213
+ createdBrowser: model.createdBrowser ?? undefined,
214
+ createdOs: model.createdOs ?? undefined,
215
+ createdDevice: model.createdDevice ?? undefined,
216
+ createdIp: model.createdIp ?? undefined,
173
217
  createdAt: model.createdAt ?? undefined,
174
218
  updatedAt: model.updatedAt ?? undefined
175
219
  };
@@ -182,7 +226,14 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
182
226
  counter: record.counter,
183
227
  transports: record.transports ?? null,
184
228
  backedUp: record.backedUp,
185
- deviceType: record.deviceType
229
+ deviceType: record.deviceType,
230
+ label: record.label ?? null,
231
+ createdDomain: record.createdDomain ?? null,
232
+ createdUserAgent: record.createdUserAgent ?? null,
233
+ createdBrowser: record.createdBrowser ?? null,
234
+ createdOs: record.createdOs ?? null,
235
+ createdDevice: record.createdDevice ?? null,
236
+ createdIp: record.createdIp ?? null
186
237
  });
187
238
  }
188
239
  async updateCredentialCounter(credentialId, counter) {
@@ -194,7 +245,6 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
194
245
  action: record.action,
195
246
  userId: record.userId !== undefined ? normalizeUserId(record.userId) : null,
196
247
  login: record.login ?? null,
197
- metadata: (record.metadata ?? {}),
198
248
  expiresAt: record.expiresAt
199
249
  });
200
250
  }
@@ -209,8 +259,7 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
209
259
  action: model.action,
210
260
  userId: model.userId ?? undefined,
211
261
  login: model.login ?? undefined,
212
- expiresAt: model.expiresAt,
213
- metadata: model.metadata ?? {}
262
+ expiresAt: model.expiresAt
214
263
  };
215
264
  }
216
265
  async cleanupChallenges(now) {
@@ -1,6 +1,6 @@
1
1
  import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyStorageAdapter, PasskeyVerificationParams, PasskeyVerificationResult, PasskeyServiceConfig, StoredPasskeyCredential } from './types.js';
2
2
  import type { AuthIdentifier } from '../auth-api/types.js';
3
- export type { CredentialDeviceType, PasskeyChallenge, PasskeyChallengeParams, PasskeyChallengeRecord, PasskeyChallengeMetadata, PasskeyUserDescriptor, PasskeyVerificationParams, PasskeyVerificationResult, PasskeyServiceConfig, PasskeyStorageAdapter, StoredPasskeyCredential } from './types.js';
3
+ export type { CredentialDeviceType, PasskeyChallenge, PasskeyChallengeParams, PasskeyChallengeRecord, PasskeyUserDescriptor, PasskeyVerificationParams, PasskeyVerificationResult, PasskeyServiceConfig, PasskeyStorageAdapter, StoredPasskeyCredential } from './types.js';
4
4
  type Logger = Pick<typeof console, 'error' | 'warn'>;
5
5
  export declare class PasskeyService {
6
6
  private readonly config;
@@ -54,6 +54,13 @@ function sanitizeTransports(input) {
54
54
  .filter((value) => ALLOWED_TRANSPORTS.includes(value));
55
55
  return filtered.length > 0 ? filtered : undefined;
56
56
  }
57
+ function toOptionalString(value) {
58
+ if (typeof value !== 'string') {
59
+ return undefined;
60
+ }
61
+ const trimmed = value.trim();
62
+ return trimmed.length > 0 ? trimmed : undefined;
63
+ }
57
64
  function toBase64Url(buffer) {
58
65
  return helpers_1.isoBase64URL.fromBuffer(new Uint8Array(buffer));
59
66
  }
@@ -127,17 +134,11 @@ class PasskeyService {
127
134
  }
128
135
  async createChallenge(params) {
129
136
  await this.adapter.cleanupChallenges?.(new Date());
130
- const metadata = {
131
- domain: typeof params.domain === 'string' ? params.domain : undefined,
132
- fingerprint: typeof params.fingerprint === 'string' ? params.fingerprint : undefined,
133
- label: typeof params.label === 'string' ? params.label : undefined,
134
- userAgent: typeof params.userAgent === 'string' ? params.userAgent : undefined
135
- };
136
137
  if (params.action === 'register') {
137
- return this.createRegistrationChallenge(params, metadata);
138
+ return this.createRegistrationChallenge(params);
138
139
  }
139
140
  if (params.action === 'authenticate') {
140
- return this.createAuthenticationChallenge(params, metadata);
141
+ return this.createAuthenticationChallenge(params);
141
142
  }
142
143
  throw new Error(`Unsupported passkey action: ${String(params.action)}`);
143
144
  }
@@ -163,7 +164,7 @@ class PasskeyService {
163
164
  }
164
165
  return { verified: false };
165
166
  }
166
- async createRegistrationChallenge(params, metadata) {
167
+ async createRegistrationChallenge(params) {
167
168
  const user = await this.requireUser({ userId: params.userId, login: params.login });
168
169
  const existing = await this.adapter.listUserCredentials(user.id);
169
170
  const excludeCredentials = existing.map((credential) => {
@@ -186,8 +187,7 @@ class PasskeyService {
186
187
  action: 'register',
187
188
  userId: user.id,
188
189
  login: user.login,
189
- expiresAt,
190
- metadata
190
+ expiresAt
191
191
  });
192
192
  return {
193
193
  challenge: options.challenge,
@@ -196,7 +196,7 @@ class PasskeyService {
196
196
  publicKey: options
197
197
  };
198
198
  }
199
- async createAuthenticationChallenge(params, metadata) {
199
+ async createAuthenticationChallenge(params) {
200
200
  const user = await this.requireUser({ userId: params.userId, login: params.login });
201
201
  const credentials = await this.adapter.listUserCredentials(user.id);
202
202
  const allowCredentials = credentials.map((credential) => {
@@ -216,8 +216,7 @@ class PasskeyService {
216
216
  action: 'authenticate',
217
217
  userId: user.id,
218
218
  login: user.login,
219
- expiresAt,
220
- metadata
219
+ expiresAt
221
220
  });
222
221
  return {
223
222
  challenge: options.challenge,
@@ -295,7 +294,14 @@ class PasskeyService {
295
294
  counter: registrationInfo.counter ?? 0,
296
295
  transports: sanitizeTransports(params.response.transports),
297
296
  backedUp: registrationInfo.credentialDeviceType === 'multiDevice',
298
- deviceType: registrationInfo.credentialDeviceType
297
+ deviceType: registrationInfo.credentialDeviceType,
298
+ label: toOptionalString(params.label),
299
+ createdDomain: toOptionalString(params.domain),
300
+ createdUserAgent: toOptionalString(params.userAgent),
301
+ createdBrowser: toOptionalString(params.browser),
302
+ createdOs: toOptionalString(params.os),
303
+ createdDevice: toOptionalString(params.device),
304
+ createdIp: toOptionalString(params.ip)
299
305
  });
300
306
  return { verified: true, userId: user.id, login: user.login };
301
307
  }
@@ -9,19 +9,12 @@ export interface PasskeyServiceConfig {
9
9
  timeoutMs: number;
10
10
  userVerification?: 'preferred' | 'required' | 'discouraged';
11
11
  }
12
- export interface PasskeyChallengeMetadata {
13
- domain?: string;
14
- fingerprint?: string;
15
- label?: string;
16
- userAgent?: string;
17
- }
18
12
  export interface PasskeyChallengeRecord {
19
13
  challenge: string;
20
14
  action: 'register' | 'authenticate';
21
15
  userId?: AuthIdentifier;
22
16
  login?: string;
23
17
  expiresAt: Date;
24
- metadata: PasskeyChallengeMetadata;
25
18
  }
26
19
  export interface PasskeyUserDescriptor {
27
20
  id: AuthIdentifier;
@@ -36,6 +29,13 @@ export interface StoredPasskeyCredential {
36
29
  transports?: AuthenticatorTransportFuture[];
37
30
  backedUp: boolean;
38
31
  deviceType: CredentialDeviceType;
32
+ label?: string;
33
+ createdDomain?: string;
34
+ createdUserAgent?: string;
35
+ createdBrowser?: string;
36
+ createdOs?: string;
37
+ createdDevice?: string;
38
+ createdIp?: string;
39
39
  createdAt?: Date;
40
40
  updatedAt?: Date;
41
41
  }
@@ -53,10 +53,9 @@ export interface PasskeyStorageAdapter {
53
53
  consumeChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
54
54
  cleanupChallenges?(now: Date): Promise<void>;
55
55
  }
56
- export interface PasskeyChallengeParams extends Partial<Omit<Token, 'userId'>> {
56
+ export interface PasskeyChallengeParams {
57
57
  action: 'register' | 'authenticate';
58
58
  login?: string;
59
- userAgent?: string;
60
59
  userId?: AuthIdentifier;
61
60
  }
62
61
  export interface PasskeyChallenge extends Record<string, unknown> {
@@ -69,6 +68,7 @@ export interface PasskeyVerificationParams extends Partial<Omit<Token, 'userId'>
69
68
  login?: string;
70
69
  response: Record<string, unknown>;
71
70
  userId?: AuthIdentifier;
71
+ userAgent?: string;
72
72
  }
73
73
  export interface PasskeyVerificationResult extends Record<string, unknown> {
74
74
  login?: string;
@@ -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;
@@ -190,6 +192,8 @@ export declare class ApiServer {
190
192
  protected authorize(apiReq: ApiRequest, requiredClass: ApiAuthClass): Promise<void>;
191
193
  private middlewares;
192
194
  private installPingHandler;
195
+ private loadSwaggerSpec;
196
+ private installSwaggerHandler;
193
197
  private normalizeApiBasePath;
194
198
  private installApiNotFoundHandler;
195
199
  private ensureApiNotFoundOrdering;