@technomoron/api-server-base 2.0.0-beta.11 → 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.
@@ -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);
@@ -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;
@@ -598,15 +598,7 @@ class AuthModule extends BaseAuthModule {
598
598
  const params = {
599
599
  action,
600
600
  login: toStringOrNull(body.login) ?? undefined,
601
- userId: isAuthIdentifier(body.userId) ? body.userId : undefined,
602
- userAgent: toStringOrNull(body.userAgent) ?? undefined,
603
- domain: toStringOrNull(body.domain) ?? undefined,
604
- fingerprint: toStringOrNull(body.fingerprint) ?? undefined,
605
- label: toStringOrNull(body.label) ?? undefined,
606
- browser: toStringOrNull(body.browser) ?? undefined,
607
- device: toStringOrNull(body.device) ?? undefined,
608
- ip: toStringOrNull(body.ip) ?? undefined,
609
- os: toStringOrNull(body.os) ?? undefined
601
+ userId: isAuthIdentifier(body.userId) ? body.userId : undefined
610
602
  };
611
603
  const challenge = await this.storage.createPasskeyChallenge(params);
612
604
  return [200, challenge];
@@ -622,18 +614,25 @@ class AuthModule extends BaseAuthModule {
622
614
  if (!expectedChallenge || typeof response !== 'object' || response === null) {
623
615
  throw new ApiError({ code: 400, message: 'Malformed passkey verification payload' });
624
616
  }
625
- const params = {
626
- expectedChallenge,
627
- response: response,
628
- login: toStringOrNull(body.login) ?? undefined,
629
- userId: isAuthIdentifier(body.userId) ? body.userId : undefined,
617
+ const rawMetadata = {
630
618
  domain: toStringOrNull(body.domain) ?? undefined,
631
619
  fingerprint: toStringOrNull(body.fingerprint) ?? undefined,
632
620
  label: toStringOrNull(body.label) ?? undefined,
633
621
  browser: toStringOrNull(body.browser) ?? undefined,
634
622
  device: toStringOrNull(body.device) ?? undefined,
635
623
  ip: toStringOrNull(body.ip) ?? undefined,
636
- 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,
637
636
  ...sessionPrefs
638
637
  };
639
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.11",
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",