@technomoron/api-server-base 2.0.0-beta.10 → 2.0.0-beta.12

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.
@@ -619,6 +619,7 @@ class ApiServer {
619
619
  const path = `${this.apiBasePath}/v1/ping`;
620
620
  this.app.get(path, (_req, res) => {
621
621
  const payload = {
622
+ success: true,
622
623
  status: 'ok',
623
624
  apiVersion: this.config.apiVersion ?? '',
624
625
  minClientVersion: this.config.minClientVersion ?? '',
@@ -626,7 +627,7 @@ class ApiServer {
626
627
  startedAt: this.startedAt,
627
628
  timestamp: new Date().toISOString()
628
629
  };
629
- res.status(200).json({ code: 200, message: 'Success', data: payload });
630
+ res.status(200).json({ success: true, code: 200, message: 'Success', data: payload, errors: {} });
630
631
  });
631
632
  }
632
633
  normalizeApiBasePath(path) {
@@ -649,6 +650,7 @@ class ApiServer {
649
650
  }
650
651
  this.apiNotFoundHandler = (req, res) => {
651
652
  const payload = {
653
+ success: false,
652
654
  code: 404,
653
655
  message: this.describeMissingEndpoint(req),
654
656
  data: null,
@@ -1068,6 +1070,7 @@ class ApiServer {
1068
1070
  ? apiError.errors
1069
1071
  : {};
1070
1072
  const errorPayload = {
1073
+ success: false,
1071
1074
  code: apiError.code,
1072
1075
  message: apiError.message,
1073
1076
  data: apiError.data ?? null,
@@ -1077,6 +1080,7 @@ class ApiServer {
1077
1080
  return;
1078
1081
  }
1079
1082
  const errorPayload = {
1083
+ success: false,
1080
1084
  code: 500,
1081
1085
  message: this.guessExceptionText(error),
1082
1086
  data: null,
@@ -1111,7 +1115,7 @@ class ApiServer {
1111
1115
  throw new ApiError({ code: 500, message: 'Handler result must start with a numeric status code' });
1112
1116
  }
1113
1117
  const message = typeof rawMessage === 'string' ? rawMessage : 'Success';
1114
- const responsePayload = { code, message, data };
1118
+ const responsePayload = { success: true, code, message, data, errors: {} };
1115
1119
  if (this.config.debug) {
1116
1120
  this.dumpResponse(apiReq, responsePayload, code);
1117
1121
  }
@@ -1124,6 +1128,7 @@ class ApiServer {
1124
1128
  ? apiError.errors
1125
1129
  : {};
1126
1130
  const errorPayload = {
1131
+ success: false,
1127
1132
  code: apiError.code,
1128
1133
  message: apiError.message,
1129
1134
  data: apiError.data ?? null,
@@ -1136,6 +1141,7 @@ class ApiServer {
1136
1141
  }
1137
1142
  else {
1138
1143
  const errorPayload = {
1144
+ success: false,
1139
1145
  code: 500,
1140
1146
  message: this.guessExceptionText(error),
1141
1147
  data: null,
@@ -565,7 +565,28 @@ class AuthModule extends module_js_1.BaseAuthModule {
565
565
  : cookiePrefs.refreshTtlSeconds;
566
566
  this.setJwtCookies(apiReq, { accessToken: access.token, refreshToken }, { sessionCookie: cookiePrefs.sessionCookie ?? false, refreshTtlSeconds: refreshTtlForCookie });
567
567
  }
568
- return [200, this.storage.filterUser(user)];
568
+ const tokenClaims = verify.data;
569
+ const effectiveUserId = this.storage.getUserId(user);
570
+ const effectiveId = String(effectiveUserId);
571
+ const rawRealId = stored.ruid ?? tokenClaims.ruid;
572
+ const normalizedRealId = rawRealId === undefined || rawRealId === null ? null : String(rawRealId).trim() || null;
573
+ const isImpersonating = normalizedRealId !== null && normalizedRealId !== effectiveId;
574
+ let realUser;
575
+ let realUserId;
576
+ if (isImpersonating && normalizedRealId !== null) {
577
+ const realUserEntity = await this.getUserOrThrow(normalizedRealId, 'Real user not found');
578
+ realUser = this.storage.filterUser(realUserEntity);
579
+ realUserId = this.storage.getUserId(realUserEntity);
580
+ }
581
+ return [
582
+ 200,
583
+ {
584
+ user: this.storage.filterUser(user),
585
+ isImpersonating,
586
+ realUser,
587
+ realUserId
588
+ }
589
+ ];
569
590
  }
570
591
  async postPasskeyChallenge(apiReq) {
571
592
  if (typeof this.storage.createPasskeyChallenge !== 'function') {
@@ -579,15 +600,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
579
600
  const params = {
580
601
  action,
581
602
  login: toStringOrNull(body.login) ?? undefined,
582
- userId: isAuthIdentifier(body.userId) ? body.userId : undefined,
583
- userAgent: toStringOrNull(body.userAgent) ?? undefined,
584
- domain: toStringOrNull(body.domain) ?? undefined,
585
- fingerprint: toStringOrNull(body.fingerprint) ?? undefined,
586
- label: toStringOrNull(body.label) ?? undefined,
587
- browser: toStringOrNull(body.browser) ?? undefined,
588
- device: toStringOrNull(body.device) ?? undefined,
589
- ip: toStringOrNull(body.ip) ?? undefined,
590
- os: toStringOrNull(body.os) ?? undefined
603
+ userId: isAuthIdentifier(body.userId) ? body.userId : undefined
591
604
  };
592
605
  const challenge = await this.storage.createPasskeyChallenge(params);
593
606
  return [200, challenge];
@@ -603,18 +616,25 @@ class AuthModule extends module_js_1.BaseAuthModule {
603
616
  if (!expectedChallenge || typeof response !== 'object' || response === null) {
604
617
  throw new api_server_base_js_1.ApiError({ code: 400, message: 'Malformed passkey verification payload' });
605
618
  }
606
- const params = {
607
- expectedChallenge,
608
- response: response,
609
- login: toStringOrNull(body.login) ?? undefined,
610
- userId: isAuthIdentifier(body.userId) ? body.userId : undefined,
619
+ const rawMetadata = {
611
620
  domain: toStringOrNull(body.domain) ?? undefined,
612
621
  fingerprint: toStringOrNull(body.fingerprint) ?? undefined,
613
622
  label: toStringOrNull(body.label) ?? undefined,
614
623
  browser: toStringOrNull(body.browser) ?? undefined,
615
624
  device: toStringOrNull(body.device) ?? undefined,
616
625
  ip: toStringOrNull(body.ip) ?? undefined,
617
- 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,
618
638
  ...sessionPrefs
619
639
  };
620
640
  const result = await this.storage.verifyPasskeyResponse(params);
@@ -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;
@@ -611,6 +611,7 @@ export class ApiServer {
611
611
  const path = `${this.apiBasePath}/v1/ping`;
612
612
  this.app.get(path, (_req, res) => {
613
613
  const payload = {
614
+ success: true,
614
615
  status: 'ok',
615
616
  apiVersion: this.config.apiVersion ?? '',
616
617
  minClientVersion: this.config.minClientVersion ?? '',
@@ -618,7 +619,7 @@ export class ApiServer {
618
619
  startedAt: this.startedAt,
619
620
  timestamp: new Date().toISOString()
620
621
  };
621
- res.status(200).json({ code: 200, message: 'Success', data: payload });
622
+ res.status(200).json({ success: true, code: 200, message: 'Success', data: payload, errors: {} });
622
623
  });
623
624
  }
624
625
  normalizeApiBasePath(path) {
@@ -641,6 +642,7 @@ export class ApiServer {
641
642
  }
642
643
  this.apiNotFoundHandler = (req, res) => {
643
644
  const payload = {
645
+ success: false,
644
646
  code: 404,
645
647
  message: this.describeMissingEndpoint(req),
646
648
  data: null,
@@ -1060,6 +1062,7 @@ export class ApiServer {
1060
1062
  ? apiError.errors
1061
1063
  : {};
1062
1064
  const errorPayload = {
1065
+ success: false,
1063
1066
  code: apiError.code,
1064
1067
  message: apiError.message,
1065
1068
  data: apiError.data ?? null,
@@ -1069,6 +1072,7 @@ export class ApiServer {
1069
1072
  return;
1070
1073
  }
1071
1074
  const errorPayload = {
1075
+ success: false,
1072
1076
  code: 500,
1073
1077
  message: this.guessExceptionText(error),
1074
1078
  data: null,
@@ -1103,7 +1107,7 @@ export class ApiServer {
1103
1107
  throw new ApiError({ code: 500, message: 'Handler result must start with a numeric status code' });
1104
1108
  }
1105
1109
  const message = typeof rawMessage === 'string' ? rawMessage : 'Success';
1106
- const responsePayload = { code, message, data };
1110
+ const responsePayload = { success: true, code, message, data, errors: {} };
1107
1111
  if (this.config.debug) {
1108
1112
  this.dumpResponse(apiReq, responsePayload, code);
1109
1113
  }
@@ -1116,6 +1120,7 @@ export class ApiServer {
1116
1120
  ? apiError.errors
1117
1121
  : {};
1118
1122
  const errorPayload = {
1123
+ success: false,
1119
1124
  code: apiError.code,
1120
1125
  message: apiError.message,
1121
1126
  data: apiError.data ?? null,
@@ -1128,6 +1133,7 @@ export class ApiServer {
1128
1133
  }
1129
1134
  else {
1130
1135
  const errorPayload = {
1136
+ success: false,
1131
1137
  code: 500,
1132
1138
  message: this.guessExceptionText(error),
1133
1139
  data: null,
@@ -563,7 +563,28 @@ class AuthModule extends BaseAuthModule {
563
563
  : cookiePrefs.refreshTtlSeconds;
564
564
  this.setJwtCookies(apiReq, { accessToken: access.token, refreshToken }, { sessionCookie: cookiePrefs.sessionCookie ?? false, refreshTtlSeconds: refreshTtlForCookie });
565
565
  }
566
- return [200, this.storage.filterUser(user)];
566
+ const tokenClaims = verify.data;
567
+ const effectiveUserId = this.storage.getUserId(user);
568
+ const effectiveId = String(effectiveUserId);
569
+ const rawRealId = stored.ruid ?? tokenClaims.ruid;
570
+ const normalizedRealId = rawRealId === undefined || rawRealId === null ? null : String(rawRealId).trim() || null;
571
+ const isImpersonating = normalizedRealId !== null && normalizedRealId !== effectiveId;
572
+ let realUser;
573
+ let realUserId;
574
+ if (isImpersonating && normalizedRealId !== null) {
575
+ const realUserEntity = await this.getUserOrThrow(normalizedRealId, 'Real user not found');
576
+ realUser = this.storage.filterUser(realUserEntity);
577
+ realUserId = this.storage.getUserId(realUserEntity);
578
+ }
579
+ return [
580
+ 200,
581
+ {
582
+ user: this.storage.filterUser(user),
583
+ isImpersonating,
584
+ realUser,
585
+ realUserId
586
+ }
587
+ ];
567
588
  }
568
589
  async postPasskeyChallenge(apiReq) {
569
590
  if (typeof this.storage.createPasskeyChallenge !== 'function') {
@@ -577,15 +598,7 @@ class AuthModule extends BaseAuthModule {
577
598
  const params = {
578
599
  action,
579
600
  login: toStringOrNull(body.login) ?? undefined,
580
- userId: isAuthIdentifier(body.userId) ? body.userId : undefined,
581
- userAgent: toStringOrNull(body.userAgent) ?? undefined,
582
- domain: toStringOrNull(body.domain) ?? undefined,
583
- fingerprint: toStringOrNull(body.fingerprint) ?? undefined,
584
- label: toStringOrNull(body.label) ?? undefined,
585
- browser: toStringOrNull(body.browser) ?? undefined,
586
- device: toStringOrNull(body.device) ?? undefined,
587
- ip: toStringOrNull(body.ip) ?? undefined,
588
- os: toStringOrNull(body.os) ?? undefined
601
+ userId: isAuthIdentifier(body.userId) ? body.userId : undefined
589
602
  };
590
603
  const challenge = await this.storage.createPasskeyChallenge(params);
591
604
  return [200, challenge];
@@ -601,18 +614,25 @@ class AuthModule extends BaseAuthModule {
601
614
  if (!expectedChallenge || typeof response !== 'object' || response === null) {
602
615
  throw new ApiError({ code: 400, message: 'Malformed passkey verification payload' });
603
616
  }
604
- const params = {
605
- expectedChallenge,
606
- response: response,
607
- login: toStringOrNull(body.login) ?? undefined,
608
- userId: isAuthIdentifier(body.userId) ? body.userId : undefined,
617
+ const rawMetadata = {
609
618
  domain: toStringOrNull(body.domain) ?? undefined,
610
619
  fingerprint: toStringOrNull(body.fingerprint) ?? undefined,
611
620
  label: toStringOrNull(body.label) ?? undefined,
612
621
  browser: toStringOrNull(body.browser) ?? undefined,
613
622
  device: toStringOrNull(body.device) ?? undefined,
614
623
  ip: toStringOrNull(body.ip) ?? undefined,
615
- os: toStringOrNull(body.os) ?? undefined,
624
+ os: toStringOrNull(body.os) ?? undefined
625
+ };
626
+ const clientInfo = apiReq.getClientInfo();
627
+ const userAgent = toStringOrNull(body.userAgent) ?? (clientInfo.ua ? clientInfo.ua : null);
628
+ const requestMetadata = this.enrichTokenMetadata(apiReq, rawMetadata);
629
+ const params = {
630
+ expectedChallenge,
631
+ response: response,
632
+ login: toStringOrNull(body.login) ?? undefined,
633
+ userId: isAuthIdentifier(body.userId) ? body.userId : undefined,
634
+ userAgent: userAgent ?? undefined,
635
+ ...requestMetadata,
616
636
  ...sessionPrefs
617
637
  };
618
638
  const result = await this.storage.verifyPasskeyResponse(params);
@@ -59,9 +59,11 @@ export class MemoryPasskeyStore extends PasskeyStore {
59
59
  }
60
60
  async saveChallenge(record) {
61
61
  this.challenges.set(record.challenge, {
62
- ...record,
62
+ challenge: record.challenge,
63
+ action: record.action,
63
64
  userId: record.userId !== undefined ? normalizeUserId(record.userId) : undefined,
64
- metadata: record.metadata ? { ...record.metadata } : {}
65
+ login: record.login ?? undefined,
66
+ expiresAt: record.expiresAt
65
67
  });
66
68
  }
67
69
  async consumeChallenge(challenge) {
@@ -70,7 +72,7 @@ export class MemoryPasskeyStore extends PasskeyStore {
70
72
  return null;
71
73
  }
72
74
  this.challenges.delete(challenge);
73
- return { ...record, metadata: record.metadata ? { ...record.metadata } : {} };
75
+ return { ...record };
74
76
  }
75
77
  async cleanupChallenges(now) {
76
78
  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;
@@ -60,6 +60,40 @@ export function initPasskeyCredentialModel(sequelize) {
60
60
  type: DataTypes.STRING(32),
61
61
  allowNull: false,
62
62
  defaultValue: 'multiDevice'
63
+ },
64
+ label: {
65
+ type: DataTypes.STRING(120),
66
+ allowNull: true
67
+ },
68
+ createdDomain: {
69
+ field: 'created_domain',
70
+ type: DataTypes.STRING(255),
71
+ allowNull: true
72
+ },
73
+ createdUserAgent: {
74
+ field: 'created_user_agent',
75
+ type: DataTypes.TEXT,
76
+ allowNull: true
77
+ },
78
+ createdBrowser: {
79
+ field: 'created_browser',
80
+ type: DataTypes.STRING(120),
81
+ allowNull: true
82
+ },
83
+ createdOs: {
84
+ field: 'created_os',
85
+ type: DataTypes.STRING(120),
86
+ allowNull: true
87
+ },
88
+ createdDevice: {
89
+ field: 'created_device',
90
+ type: DataTypes.STRING(120),
91
+ allowNull: true
92
+ },
93
+ createdIp: {
94
+ field: 'created_ip',
95
+ type: DataTypes.STRING(45),
96
+ allowNull: true
63
97
  }
64
98
  }, {
65
99
  sequelize,
@@ -89,10 +123,6 @@ export function initPasskeyChallengeModel(sequelize) {
89
123
  type: DataTypes.STRING(128),
90
124
  allowNull: true
91
125
  },
92
- metadata: {
93
- type: DataTypes.JSON,
94
- allowNull: true
95
- },
96
126
  expiresAt: {
97
127
  field: 'expires_at',
98
128
  type: 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;
@@ -73,6 +73,40 @@ function initPasskeyCredentialModel(sequelize) {
73
73
  type: DataTypes.STRING(32),
74
74
  allowNull: false,
75
75
  defaultValue: 'multiDevice'
76
+ },
77
+ label: {
78
+ type: DataTypes.STRING(120),
79
+ allowNull: true
80
+ },
81
+ createdDomain: {
82
+ field: 'created_domain',
83
+ type: DataTypes.STRING(255),
84
+ allowNull: true
85
+ },
86
+ createdUserAgent: {
87
+ field: 'created_user_agent',
88
+ type: DataTypes.TEXT,
89
+ allowNull: true
90
+ },
91
+ createdBrowser: {
92
+ field: 'created_browser',
93
+ type: DataTypes.STRING(120),
94
+ allowNull: true
95
+ },
96
+ createdOs: {
97
+ field: 'created_os',
98
+ type: DataTypes.STRING(120),
99
+ allowNull: true
100
+ },
101
+ createdDevice: {
102
+ field: 'created_device',
103
+ type: DataTypes.STRING(120),
104
+ allowNull: true
105
+ },
106
+ createdIp: {
107
+ field: 'created_ip',
108
+ type: DataTypes.STRING(45),
109
+ allowNull: true
76
110
  }
77
111
  }, {
78
112
  sequelize,
@@ -102,10 +136,6 @@ function initPasskeyChallengeModel(sequelize) {
102
136
  type: DataTypes.STRING(128),
103
137
  allowNull: true
104
138
  },
105
- metadata: {
106
- type: DataTypes.JSON,
107
- allowNull: true
108
- },
109
139
  expiresAt: {
110
140
  field: 'expires_at',
111
141
  type: DataTypes.DATE,
@@ -145,6 +175,13 @@ export class SequelizePasskeyStore extends PasskeyStore {
145
175
  transports: (model.transports ?? undefined),
146
176
  backedUp: model.backedUp,
147
177
  deviceType: model.deviceType,
178
+ label: model.label ?? undefined,
179
+ createdDomain: model.createdDomain ?? undefined,
180
+ createdUserAgent: model.createdUserAgent ?? undefined,
181
+ createdBrowser: model.createdBrowser ?? undefined,
182
+ createdOs: model.createdOs ?? undefined,
183
+ createdDevice: model.createdDevice ?? undefined,
184
+ createdIp: model.createdIp ?? undefined,
148
185
  createdAt: model.createdAt ?? undefined,
149
186
  updatedAt: model.updatedAt ?? undefined
150
187
  }));
@@ -167,6 +204,13 @@ export class SequelizePasskeyStore extends PasskeyStore {
167
204
  transports: (model.transports ?? undefined),
168
205
  backedUp: model.backedUp,
169
206
  deviceType: model.deviceType,
207
+ label: model.label ?? undefined,
208
+ createdDomain: model.createdDomain ?? undefined,
209
+ createdUserAgent: model.createdUserAgent ?? undefined,
210
+ createdBrowser: model.createdBrowser ?? undefined,
211
+ createdOs: model.createdOs ?? undefined,
212
+ createdDevice: model.createdDevice ?? undefined,
213
+ createdIp: model.createdIp ?? undefined,
170
214
  createdAt: model.createdAt ?? undefined,
171
215
  updatedAt: model.updatedAt ?? undefined
172
216
  };
@@ -179,7 +223,14 @@ export class SequelizePasskeyStore extends PasskeyStore {
179
223
  counter: record.counter,
180
224
  transports: record.transports ?? null,
181
225
  backedUp: record.backedUp,
182
- deviceType: record.deviceType
226
+ deviceType: record.deviceType,
227
+ label: record.label ?? null,
228
+ createdDomain: record.createdDomain ?? null,
229
+ createdUserAgent: record.createdUserAgent ?? null,
230
+ createdBrowser: record.createdBrowser ?? null,
231
+ createdOs: record.createdOs ?? null,
232
+ createdDevice: record.createdDevice ?? null,
233
+ createdIp: record.createdIp ?? null
183
234
  });
184
235
  }
185
236
  async updateCredentialCounter(credentialId, counter) {
@@ -191,7 +242,6 @@ export class SequelizePasskeyStore extends PasskeyStore {
191
242
  action: record.action,
192
243
  userId: record.userId !== undefined ? normalizeUserId(record.userId) : null,
193
244
  login: record.login ?? null,
194
- metadata: (record.metadata ?? {}),
195
245
  expiresAt: record.expiresAt
196
246
  });
197
247
  }
@@ -206,8 +256,7 @@ export class SequelizePasskeyStore extends PasskeyStore {
206
256
  action: model.action,
207
257
  userId: model.userId ?? undefined,
208
258
  login: model.login ?? undefined,
209
- expiresAt: model.expiresAt,
210
- metadata: model.metadata ?? {}
259
+ expiresAt: model.expiresAt
211
260
  };
212
261
  }
213
262
  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;
@@ -18,6 +18,13 @@ function sanitizeTransports(input) {
18
18
  .filter((value) => ALLOWED_TRANSPORTS.includes(value));
19
19
  return filtered.length > 0 ? filtered : undefined;
20
20
  }
21
+ function toOptionalString(value) {
22
+ if (typeof value !== 'string') {
23
+ return undefined;
24
+ }
25
+ const trimmed = value.trim();
26
+ return trimmed.length > 0 ? trimmed : undefined;
27
+ }
21
28
  function toBase64Url(buffer) {
22
29
  return isoBase64URL.fromBuffer(new Uint8Array(buffer));
23
30
  }
@@ -91,17 +98,11 @@ export class PasskeyService {
91
98
  }
92
99
  async createChallenge(params) {
93
100
  await this.adapter.cleanupChallenges?.(new Date());
94
- const metadata = {
95
- domain: typeof params.domain === 'string' ? params.domain : undefined,
96
- fingerprint: typeof params.fingerprint === 'string' ? params.fingerprint : undefined,
97
- label: typeof params.label === 'string' ? params.label : undefined,
98
- userAgent: typeof params.userAgent === 'string' ? params.userAgent : undefined
99
- };
100
101
  if (params.action === 'register') {
101
- return this.createRegistrationChallenge(params, metadata);
102
+ return this.createRegistrationChallenge(params);
102
103
  }
103
104
  if (params.action === 'authenticate') {
104
- return this.createAuthenticationChallenge(params, metadata);
105
+ return this.createAuthenticationChallenge(params);
105
106
  }
106
107
  throw new Error(`Unsupported passkey action: ${String(params.action)}`);
107
108
  }
@@ -127,7 +128,7 @@ export class PasskeyService {
127
128
  }
128
129
  return { verified: false };
129
130
  }
130
- async createRegistrationChallenge(params, metadata) {
131
+ async createRegistrationChallenge(params) {
131
132
  const user = await this.requireUser({ userId: params.userId, login: params.login });
132
133
  const existing = await this.adapter.listUserCredentials(user.id);
133
134
  const excludeCredentials = existing.map((credential) => {
@@ -150,8 +151,7 @@ export class PasskeyService {
150
151
  action: 'register',
151
152
  userId: user.id,
152
153
  login: user.login,
153
- expiresAt,
154
- metadata
154
+ expiresAt
155
155
  });
156
156
  return {
157
157
  challenge: options.challenge,
@@ -160,7 +160,7 @@ export class PasskeyService {
160
160
  publicKey: options
161
161
  };
162
162
  }
163
- async createAuthenticationChallenge(params, metadata) {
163
+ async createAuthenticationChallenge(params) {
164
164
  const user = await this.requireUser({ userId: params.userId, login: params.login });
165
165
  const credentials = await this.adapter.listUserCredentials(user.id);
166
166
  const allowCredentials = credentials.map((credential) => {
@@ -180,8 +180,7 @@ export class PasskeyService {
180
180
  action: 'authenticate',
181
181
  userId: user.id,
182
182
  login: user.login,
183
- expiresAt,
184
- metadata
183
+ expiresAt
185
184
  });
186
185
  return {
187
186
  challenge: options.challenge,
@@ -259,7 +258,14 @@ export class PasskeyService {
259
258
  counter: registrationInfo.counter ?? 0,
260
259
  transports: sanitizeTransports(params.response.transports),
261
260
  backedUp: registrationInfo.credentialDeviceType === 'multiDevice',
262
- deviceType: registrationInfo.credentialDeviceType
261
+ deviceType: registrationInfo.credentialDeviceType,
262
+ label: toOptionalString(params.label),
263
+ createdDomain: toOptionalString(params.domain),
264
+ createdUserAgent: toOptionalString(params.userAgent),
265
+ createdBrowser: toOptionalString(params.browser),
266
+ createdOs: toOptionalString(params.os),
267
+ createdDevice: toOptionalString(params.device),
268
+ createdIp: toOptionalString(params.ip)
263
269
  });
264
270
  return { verified: true, userId: user.id, login: user.login };
265
271
  }
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@technomoron/api-server-base",
3
- "version": "2.0.0-beta.10",
3
+ "version": "2.0.0-beta.12",
4
4
  "description": "Api Server Skeleton / Base Class",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.cjs",
@@ -42,18 +42,18 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@simplewebauthn/server": "^13.2.2",
45
- "@types/cookie-parser": "^1.4.9",
45
+ "@types/cookie-parser": "^1.4.10",
46
46
  "@types/cors": "^2.8.19",
47
- "@types/express": "^4.17.23",
47
+ "@types/express": "^4.17.25",
48
48
  "@types/jsonwebtoken": "^9.0.10",
49
49
  "@types/multer": "^1.4.13",
50
50
  "bcryptjs": "^2.4.3",
51
51
  "cookie-parser": "^1.4.7",
52
52
  "cors": "^2.8.5",
53
- "express": "^4.21.2",
54
- "jsonwebtoken": "^9.0.2",
53
+ "express": "^4.22.1",
54
+ "jsonwebtoken": "^9.0.3",
55
55
  "multer": "^2.0.2",
56
- "mysql2": "^3.15.3",
56
+ "mysql2": "^3.16.0",
57
57
  "pg": "^8.16.3",
58
58
  "sequelize": "^6.37.7",
59
59
  "sqlite3": "^5.1.7"
@@ -62,14 +62,14 @@
62
62
  "@types/bcryptjs": "^2.4.6",
63
63
  "@types/express-serve-static-core": "^5.1.0",
64
64
  "@types/supertest": "^6.0.3",
65
- "@typescript-eslint/eslint-plugin": "^8.46.1",
66
- "@typescript-eslint/parser": "^8.46.1",
65
+ "@typescript-eslint/eslint-plugin": "^8.50.0",
66
+ "@typescript-eslint/parser": "^8.50.0",
67
67
  "@vue/eslint-config-prettier": "10.2.0",
68
68
  "@vue/eslint-config-typescript": "14.5.0",
69
- "eslint": "^9.37.0",
69
+ "eslint": "^9.39.2",
70
70
  "eslint-plugin-import": "^2.32.0",
71
- "eslint-plugin-vue": "^10.5.1",
72
- "prettier": "^3.6.2",
71
+ "eslint-plugin-vue": "^10.6.2",
72
+ "prettier": "^3.7.4",
73
73
  "supertest": "^7.1.4",
74
74
  "typescript": "^5.9.3",
75
75
  "vitest": "^3.2.4"