@technomoron/api-server-base 2.0.0-beta.2 → 2.0.0-beta.21

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.
Files changed (115) hide show
  1. package/README.txt +81 -28
  2. package/dist/cjs/api-module.cjs +9 -0
  3. package/dist/cjs/api-module.d.ts +7 -4
  4. package/dist/cjs/api-server-base.cjs +607 -99
  5. package/dist/cjs/api-server-base.d.ts +80 -23
  6. package/dist/cjs/auth-api/auth-module.d.ts +23 -3
  7. package/dist/cjs/auth-api/auth-module.js +320 -124
  8. package/dist/cjs/auth-api/compat-auth-storage.d.ts +7 -5
  9. package/dist/cjs/auth-api/compat-auth-storage.js +15 -3
  10. package/dist/cjs/auth-api/mem-auth-store.d.ts +5 -3
  11. package/dist/cjs/auth-api/mem-auth-store.js +14 -28
  12. package/dist/cjs/auth-api/module.d.ts +1 -1
  13. package/dist/cjs/auth-api/sql-auth-store.d.ts +16 -4
  14. package/dist/cjs/auth-api/sql-auth-store.js +43 -30
  15. package/dist/cjs/auth-api/storage.d.ts +6 -4
  16. package/dist/cjs/auth-api/storage.js +15 -5
  17. package/dist/cjs/auth-api/types.d.ts +7 -2
  18. package/dist/cjs/auth-api/user-id.d.ts +5 -0
  19. package/dist/cjs/auth-api/user-id.js +38 -0
  20. package/dist/cjs/auth-cookie-options.d.ts +11 -0
  21. package/dist/cjs/auth-cookie-options.js +66 -0
  22. package/dist/cjs/index.cjs +4 -14
  23. package/dist/cjs/index.d.ts +4 -9
  24. package/dist/cjs/oauth/memory.d.ts +6 -0
  25. package/dist/cjs/oauth/memory.js +44 -11
  26. package/dist/cjs/oauth/models.d.ts +7 -2
  27. package/dist/cjs/oauth/models.js +10 -21
  28. package/dist/cjs/oauth/sequelize.d.ts +10 -48
  29. package/dist/cjs/oauth/sequelize.js +44 -99
  30. package/dist/cjs/oauth/types.d.ts +1 -0
  31. package/dist/cjs/passkey/base.d.ts +2 -0
  32. package/dist/cjs/passkey/config.d.ts +2 -0
  33. package/dist/cjs/passkey/config.js +26 -0
  34. package/dist/cjs/passkey/memory.d.ts +8 -0
  35. package/dist/cjs/passkey/memory.js +57 -16
  36. package/dist/cjs/passkey/models.d.ts +13 -4
  37. package/dist/cjs/passkey/models.js +41 -14
  38. package/dist/cjs/passkey/sequelize.d.ts +13 -25
  39. package/dist/cjs/passkey/sequelize.js +68 -153
  40. package/dist/cjs/passkey/service.d.ts +6 -2
  41. package/dist/cjs/passkey/service.js +205 -27
  42. package/dist/cjs/passkey/types.d.ts +18 -9
  43. package/dist/cjs/sequelize-utils.d.ts +8 -0
  44. package/dist/cjs/sequelize-utils.js +57 -0
  45. package/dist/cjs/token/base.d.ts +2 -1
  46. package/dist/cjs/token/base.js +3 -1
  47. package/dist/cjs/token/memory.d.ts +10 -0
  48. package/dist/cjs/token/memory.js +122 -32
  49. package/dist/cjs/token/sequelize.d.ts +4 -4
  50. package/dist/cjs/token/sequelize.js +67 -85
  51. package/dist/cjs/token/types.d.ts +8 -1
  52. package/dist/cjs/user/base.d.ts +1 -0
  53. package/dist/cjs/user/base.js +11 -4
  54. package/dist/cjs/user/memory.d.ts +2 -0
  55. package/dist/cjs/user/memory.js +9 -10
  56. package/dist/cjs/user/sequelize.d.ts +7 -2
  57. package/dist/cjs/user/sequelize.js +19 -32
  58. package/dist/esm/api-module.d.ts +7 -4
  59. package/dist/esm/api-module.js +9 -0
  60. package/dist/esm/api-server-base.d.ts +80 -23
  61. package/dist/esm/api-server-base.js +608 -100
  62. package/dist/esm/auth-api/auth-module.d.ts +23 -3
  63. package/dist/esm/auth-api/auth-module.js +321 -125
  64. package/dist/esm/auth-api/compat-auth-storage.d.ts +7 -5
  65. package/dist/esm/auth-api/compat-auth-storage.js +13 -1
  66. package/dist/esm/auth-api/mem-auth-store.d.ts +5 -3
  67. package/dist/esm/auth-api/mem-auth-store.js +14 -28
  68. package/dist/esm/auth-api/module.d.ts +1 -1
  69. package/dist/esm/auth-api/sql-auth-store.d.ts +16 -4
  70. package/dist/esm/auth-api/sql-auth-store.js +43 -30
  71. package/dist/esm/auth-api/storage.d.ts +6 -4
  72. package/dist/esm/auth-api/storage.js +13 -3
  73. package/dist/esm/auth-api/types.d.ts +7 -2
  74. package/dist/esm/auth-api/user-id.d.ts +5 -0
  75. package/dist/esm/auth-api/user-id.js +32 -0
  76. package/dist/esm/auth-cookie-options.d.ts +11 -0
  77. package/dist/esm/auth-cookie-options.js +63 -0
  78. package/dist/esm/index.d.ts +4 -9
  79. package/dist/esm/index.js +2 -7
  80. package/dist/esm/oauth/memory.d.ts +6 -0
  81. package/dist/esm/oauth/memory.js +44 -11
  82. package/dist/esm/oauth/models.d.ts +7 -2
  83. package/dist/esm/oauth/models.js +6 -19
  84. package/dist/esm/oauth/sequelize.d.ts +10 -48
  85. package/dist/esm/oauth/sequelize.js +32 -87
  86. package/dist/esm/oauth/types.d.ts +1 -0
  87. package/dist/esm/passkey/base.d.ts +2 -0
  88. package/dist/esm/passkey/config.d.ts +2 -0
  89. package/dist/esm/passkey/config.js +23 -0
  90. package/dist/esm/passkey/memory.d.ts +8 -0
  91. package/dist/esm/passkey/memory.js +57 -16
  92. package/dist/esm/passkey/models.d.ts +13 -4
  93. package/dist/esm/passkey/models.js +39 -12
  94. package/dist/esm/passkey/sequelize.d.ts +13 -25
  95. package/dist/esm/passkey/sequelize.js +69 -154
  96. package/dist/esm/passkey/service.d.ts +6 -2
  97. package/dist/esm/passkey/service.js +173 -28
  98. package/dist/esm/passkey/types.d.ts +18 -9
  99. package/dist/esm/sequelize-utils.d.ts +8 -0
  100. package/dist/esm/sequelize-utils.js +48 -0
  101. package/dist/esm/token/base.d.ts +2 -1
  102. package/dist/esm/token/base.js +3 -1
  103. package/dist/esm/token/memory.d.ts +10 -0
  104. package/dist/esm/token/memory.js +122 -32
  105. package/dist/esm/token/sequelize.d.ts +4 -4
  106. package/dist/esm/token/sequelize.js +67 -85
  107. package/dist/esm/token/types.d.ts +8 -1
  108. package/dist/esm/user/base.d.ts +1 -0
  109. package/dist/esm/user/base.js +11 -4
  110. package/dist/esm/user/memory.d.ts +2 -0
  111. package/dist/esm/user/memory.js +9 -10
  112. package/dist/esm/user/sequelize.d.ts +7 -2
  113. package/dist/esm/user/sequelize.js +19 -32
  114. package/docs/swagger/openapi.json +1876 -0
  115. package/package.json +84 -34
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.PasskeyService = void 0;
4
37
  const server_1 = require("@simplewebauthn/server");
@@ -21,6 +54,13 @@ function sanitizeTransports(input) {
21
54
  .filter((value) => ALLOWED_TRANSPORTS.includes(value));
22
55
  return filtered.length > 0 ? filtered : undefined;
23
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
+ }
24
64
  function toBase64Url(buffer) {
25
65
  return helpers_1.isoBase64URL.fromBuffer(new Uint8Array(buffer));
26
66
  }
@@ -34,30 +74,112 @@ function toBuffer(value) {
34
74
  const view = value instanceof Uint8Array ? value : new Uint8Array(value);
35
75
  return Buffer.from(view);
36
76
  }
77
+ function toBufferOrNull(value) {
78
+ if (!value) {
79
+ return null;
80
+ }
81
+ if (Buffer.isBuffer(value)) {
82
+ return value;
83
+ }
84
+ if (value instanceof ArrayBuffer || value instanceof Uint8Array) {
85
+ return toBuffer(value);
86
+ }
87
+ if (typeof value === 'string') {
88
+ try {
89
+ return fromBase64Url(value);
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ }
95
+ return null;
96
+ }
97
+ async function spkiToCosePublicKey(spki) {
98
+ try {
99
+ const subtle = (globalThis.crypto?.subtle ??
100
+ (await Promise.resolve().then(() => __importStar(require('crypto')))).webcrypto?.subtle);
101
+ if (!subtle) {
102
+ return null;
103
+ }
104
+ const spkiView = new Uint8Array(spki);
105
+ const ecCurves = [
106
+ { namedCurve: 'P-256', crv: 1, alg: -7, rawLength: 65 },
107
+ { namedCurve: 'P-384', crv: 2, alg: -35, rawLength: 97 },
108
+ { namedCurve: 'P-521', crv: 3, alg: -36, rawLength: 133 }
109
+ ];
110
+ for (const curve of ecCurves) {
111
+ try {
112
+ const key = await subtle.importKey('spki', spkiView, { name: 'ECDSA', namedCurve: curve.namedCurve }, true, ['verify']);
113
+ const raw = Buffer.from(await subtle.exportKey('raw', key));
114
+ if (raw.length !== curve.rawLength || raw[0] !== 0x04) {
115
+ continue;
116
+ }
117
+ const coordLength = (raw.length - 1) / 2;
118
+ const x = raw.slice(1, 1 + coordLength);
119
+ const y = raw.slice(1 + coordLength);
120
+ const coseMap = new Map([
121
+ [1, 2], // kty: EC2
122
+ [3, curve.alg],
123
+ [-1, curve.crv],
124
+ [-2, x],
125
+ [-3, y]
126
+ ]);
127
+ return Buffer.from(helpers_1.isoCBOR.encode(coseMap));
128
+ }
129
+ catch {
130
+ // try next algorithm
131
+ }
132
+ }
133
+ try {
134
+ const edKey = await subtle.importKey('spki', spkiView, { name: 'Ed25519' }, true, ['verify']);
135
+ const raw = Buffer.from(await subtle.exportKey('raw', edKey));
136
+ if (raw.length !== 32) {
137
+ return null;
138
+ }
139
+ const coseMap = new Map([
140
+ [1, 1], // kty: OKP
141
+ [3, -8], // alg: EdDSA
142
+ [-1, 6], // crv: Ed25519
143
+ [-2, raw]
144
+ ]);
145
+ return Buffer.from(helpers_1.isoCBOR.encode(coseMap));
146
+ }
147
+ catch {
148
+ return null;
149
+ }
150
+ }
151
+ catch {
152
+ return null;
153
+ }
154
+ }
37
155
  class PasskeyService {
38
156
  constructor(config, adapter, logger = console) {
39
157
  this.config = config;
40
158
  this.adapter = adapter;
41
159
  this.logger = logger;
42
160
  }
161
+ async listUserCredentials(userId) {
162
+ return this.adapter.listUserCredentials(userId);
163
+ }
164
+ async deleteCredential(credentialId) {
165
+ return this.adapter.deleteCredential(credentialId);
166
+ }
43
167
  async createChallenge(params) {
44
168
  await this.adapter.cleanupChallenges?.(new Date());
45
- const metadata = {
46
- domain: typeof params.domain === 'string' ? params.domain : undefined,
47
- fingerprint: typeof params.fingerprint === 'string' ? params.fingerprint : undefined,
48
- label: typeof params.label === 'string' ? params.label : undefined,
49
- userAgent: typeof params.userAgent === 'string' ? params.userAgent : undefined
50
- };
51
169
  if (params.action === 'register') {
52
- return this.createRegistrationChallenge(params, metadata);
170
+ return this.createRegistrationChallenge(params);
53
171
  }
54
172
  if (params.action === 'authenticate') {
55
- return this.createAuthenticationChallenge(params, metadata);
173
+ return this.createAuthenticationChallenge(params);
56
174
  }
57
175
  throw new Error(`Unsupported passkey action: ${String(params.action)}`);
58
176
  }
59
177
  async verifyResponse(params) {
60
178
  await this.adapter.cleanupChallenges?.(new Date());
179
+ const existing = this.adapter.getChallenge ? await this.adapter.getChallenge(params.expectedChallenge) : null;
180
+ if (existing && existing.expiresAt.getTime() <= Date.now()) {
181
+ return { verified: false };
182
+ }
61
183
  const record = await this.adapter.consumeChallenge(params.expectedChallenge);
62
184
  if (!record) {
63
185
  return { verified: false };
@@ -78,7 +200,7 @@ class PasskeyService {
78
200
  }
79
201
  return { verified: false };
80
202
  }
81
- async createRegistrationChallenge(params, metadata) {
203
+ async createRegistrationChallenge(params) {
82
204
  const user = await this.requireUser({ userId: params.userId, login: params.login });
83
205
  const existing = await this.adapter.listUserCredentials(user.id);
84
206
  const excludeCredentials = existing.map((credential) => {
@@ -101,16 +223,16 @@ class PasskeyService {
101
223
  action: 'register',
102
224
  userId: user.id,
103
225
  login: user.login,
104
- expiresAt,
105
- metadata
226
+ expiresAt
106
227
  });
107
228
  return {
108
229
  challenge: options.challenge,
109
230
  expiresAt: expiresAt.toISOString(),
110
- userId: user.id
231
+ userId: user.id,
232
+ publicKey: options
111
233
  };
112
234
  }
113
- async createAuthenticationChallenge(params, metadata) {
235
+ async createAuthenticationChallenge(params) {
114
236
  const user = await this.requireUser({ userId: params.userId, login: params.login });
115
237
  const credentials = await this.adapter.listUserCredentials(user.id);
116
238
  const allowCredentials = credentials.map((credential) => {
@@ -130,13 +252,13 @@ class PasskeyService {
130
252
  action: 'authenticate',
131
253
  userId: user.id,
132
254
  login: user.login,
133
- expiresAt,
134
- metadata
255
+ expiresAt
135
256
  });
136
257
  return {
137
258
  challenge: options.challenge,
138
259
  expiresAt: expiresAt.toISOString(),
139
- userId: user.id
260
+ userId: user.id,
261
+ publicKey: options
140
262
  };
141
263
  }
142
264
  async verifyRegistration(params, record) {
@@ -152,20 +274,71 @@ class PasskeyService {
152
274
  expectedChallenge: record.challenge,
153
275
  expectedOrigin: this.config.origins,
154
276
  expectedRPID: this.config.rpId,
155
- requireUserVerification: true
277
+ requireUserVerification: this.requireUserVerification()
156
278
  });
157
279
  if (!result.verified || !result.registrationInfo) {
280
+ if (!result.verified) {
281
+ const err = result.error ?? result;
282
+ this.logger.error?.('Passkey registration verification failed', err);
283
+ }
158
284
  return { verified: false };
159
285
  }
160
286
  const registrationInfo = result.registrationInfo;
287
+ const attestationResponse = params.response.response;
288
+ const credentialIdPrimary = toBufferOrNull(registrationInfo.credentialID);
289
+ const credentialIdFallback = toBufferOrNull(params.response.id);
290
+ const credentialId = credentialIdPrimary && credentialIdPrimary.length > 0 ? credentialIdPrimary : credentialIdFallback;
291
+ const publicKeyPrimary = toBufferOrNull(registrationInfo.credentialPublicKey);
292
+ let publicKeyFallback = toBufferOrNull(attestationResponse?.publicKey);
293
+ if ((!publicKeyPrimary || publicKeyPrimary.length === 0) && attestationResponse?.attestationObject) {
294
+ try {
295
+ const attestationObject = String(attestationResponse.attestationObject);
296
+ const attObj = (0, helpers_1.decodeAttestationObject)(helpers_1.isoBase64URL.toBuffer(attestationObject));
297
+ const parsedAuth = (0, helpers_1.parseAuthenticatorData)(attObj.get('authData'));
298
+ publicKeyFallback = toBufferOrNull(parsedAuth.credentialPublicKey) ?? publicKeyFallback;
299
+ }
300
+ catch (error) {
301
+ this.logger.warn?.('Passkey registration: failed to parse attestationObject', error);
302
+ }
303
+ }
304
+ const publicKey = publicKeyPrimary && publicKeyPrimary.length > 0 ? publicKeyPrimary : publicKeyFallback;
305
+ if (this.config.debug && this.logger?.warn) {
306
+ const pkPrimaryHex = publicKeyPrimary ? publicKeyPrimary.slice(0, 4).toString('hex') : 'null';
307
+ const pkFallbackHex = publicKeyFallback ? publicKeyFallback.slice(0, 4).toString('hex') : 'null';
308
+ this.logger.warn(`Passkey registration: pkPrimary len=${publicKeyPrimary?.length ?? 0} head=${pkPrimaryHex}, pkFallback len=${publicKeyFallback?.length ?? 0} head=${pkFallbackHex}`);
309
+ }
310
+ if (!credentialId || credentialId.length === 0) {
311
+ return { verified: false, message: 'Missing credential id in registration response' };
312
+ }
313
+ if (!publicKey || publicKey.length === 0) {
314
+ return { verified: false, message: 'Missing public key in registration response' };
315
+ }
316
+ let storedPublicKey = Buffer.isBuffer(publicKey) ? publicKey : toBuffer(publicKey);
317
+ // If fallback is an SPKI key (DER, starts with 0x30) convert to COSE so verification succeeds.
318
+ if (storedPublicKey[0] === 0x30) {
319
+ const converted = await spkiToCosePublicKey(storedPublicKey);
320
+ if (converted) {
321
+ storedPublicKey = converted;
322
+ }
323
+ else {
324
+ this.logger.warn?.('Passkey registration: failed to convert SPKI public key to COSE');
325
+ }
326
+ }
161
327
  await this.adapter.saveCredential({
162
328
  userId: user.id,
163
- credentialId: toBuffer(registrationInfo.credentialID),
164
- publicKey: toBuffer(registrationInfo.credentialPublicKey),
165
- counter: registrationInfo.counter,
329
+ credentialId,
330
+ publicKey: storedPublicKey,
331
+ counter: registrationInfo.counter ?? 0,
166
332
  transports: sanitizeTransports(params.response.transports),
167
333
  backedUp: registrationInfo.credentialDeviceType === 'multiDevice',
168
- deviceType: registrationInfo.credentialDeviceType
334
+ deviceType: registrationInfo.credentialDeviceType,
335
+ label: toOptionalString(params.label),
336
+ createdDomain: toOptionalString(params.domain),
337
+ createdUserAgent: toOptionalString(params.userAgent),
338
+ createdBrowser: toOptionalString(params.browser),
339
+ createdOs: toOptionalString(params.os),
340
+ createdDevice: toOptionalString(params.device),
341
+ createdIp: toOptionalString(params.ip)
169
342
  });
170
343
  return { verified: true, userId: user.id, login: user.login };
171
344
  }
@@ -182,22 +355,24 @@ class PasskeyService {
182
355
  }
183
356
  const user = await this.requireUser({ userId: credential.userId, login: record.login });
184
357
  const storedAuthData = {
185
- credentialID: credential.credentialId,
358
+ id: toBase64Url(credential.credentialId),
359
+ publicKey: new Uint8Array(toBuffer(credential.publicKey)),
186
360
  counter: credential.counter,
187
- credentialBackedUp: credential.backedUp,
188
- credentialDeviceType: credential.deviceType,
189
- credentialPublicKey: credential.publicKey,
190
361
  transports: credential.transports ?? undefined
362
+ // simplewebauthn accepts either Uint8Array or Buffer; ensure Buffer
363
+ // see https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/server/src/authentication/verifyAuthenticationResponse.ts
191
364
  };
192
365
  const result = await (0, server_1.verifyAuthenticationResponse)({
193
366
  response,
194
367
  expectedChallenge: record.challenge,
195
368
  expectedOrigin: this.config.origins,
196
369
  expectedRPID: this.config.rpId,
197
- authenticator: storedAuthData,
198
- requireUserVerification: true
370
+ credential: storedAuthData,
371
+ requireUserVerification: this.requireUserVerification()
199
372
  });
200
373
  if (!result.verified) {
374
+ const err = result.error ?? result;
375
+ this.logger.error?.('Passkey authentication verification failed', err);
201
376
  return { verified: false };
202
377
  }
203
378
  await this.adapter.updateCredentialCounter(credential.credentialId, result.authenticationInfo.newCounter);
@@ -217,5 +392,8 @@ class PasskeyService {
217
392
  createExpiry() {
218
393
  return new Date(Date.now() + this.config.timeoutMs);
219
394
  }
395
+ requireUserVerification() {
396
+ return this.config.userVerification !== 'discouraged';
397
+ }
220
398
  }
221
399
  exports.PasskeyService = PasskeyService;
@@ -8,12 +8,11 @@ export interface PasskeyServiceConfig {
8
8
  origins: string[];
9
9
  timeoutMs: number;
10
10
  userVerification?: 'preferred' | 'required' | 'discouraged';
11
- }
12
- export interface PasskeyChallengeMetadata {
13
- domain?: string;
14
- fingerprint?: string;
15
- label?: string;
16
- userAgent?: string;
11
+ /**
12
+ * When enabled, PasskeyService emits additional diagnostic logs during registration/authentication.
13
+ * Defaults to false.
14
+ */
15
+ debug?: boolean;
17
16
  }
18
17
  export interface PasskeyChallengeRecord {
19
18
  challenge: string;
@@ -21,7 +20,6 @@ export interface PasskeyChallengeRecord {
21
20
  userId?: AuthIdentifier;
22
21
  login?: string;
23
22
  expiresAt: Date;
24
- metadata: PasskeyChallengeMetadata;
25
23
  }
26
24
  export interface PasskeyUserDescriptor {
27
25
  id: AuthIdentifier;
@@ -36,6 +34,15 @@ export interface StoredPasskeyCredential {
36
34
  transports?: AuthenticatorTransportFuture[];
37
35
  backedUp: boolean;
38
36
  deviceType: CredentialDeviceType;
37
+ label?: string;
38
+ createdDomain?: string;
39
+ createdUserAgent?: string;
40
+ createdBrowser?: string;
41
+ createdOs?: string;
42
+ createdDevice?: string;
43
+ createdIp?: string;
44
+ createdAt?: Date;
45
+ updatedAt?: Date;
39
46
  }
40
47
  export interface PasskeyStorageAdapter {
41
48
  resolveUser(params: {
@@ -43,17 +50,18 @@ export interface PasskeyStorageAdapter {
43
50
  login?: string;
44
51
  }): Promise<PasskeyUserDescriptor | null>;
45
52
  listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
53
+ deleteCredential(credentialId: Buffer | string): Promise<boolean>;
46
54
  findCredentialById(credentialId: Buffer): Promise<StoredPasskeyCredential | null>;
47
55
  saveCredential(record: StoredPasskeyCredential): Promise<void>;
48
56
  updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
49
57
  saveChallenge(record: PasskeyChallengeRecord): Promise<void>;
58
+ getChallenge?(challenge: string): Promise<PasskeyChallengeRecord | null>;
50
59
  consumeChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
51
60
  cleanupChallenges?(now: Date): Promise<void>;
52
61
  }
53
- export interface PasskeyChallengeParams extends Partial<Omit<Token, 'userId'>> {
62
+ export interface PasskeyChallengeParams {
54
63
  action: 'register' | 'authenticate';
55
64
  login?: string;
56
- userAgent?: string;
57
65
  userId?: AuthIdentifier;
58
66
  }
59
67
  export interface PasskeyChallenge extends Record<string, unknown> {
@@ -66,6 +74,7 @@ export interface PasskeyVerificationParams extends Partial<Omit<Token, 'userId'>
66
74
  login?: string;
67
75
  response: Record<string, unknown>;
68
76
  userId?: AuthIdentifier;
77
+ userAgent?: string;
69
78
  }
70
79
  export interface PasskeyVerificationResult extends Record<string, unknown> {
71
80
  login?: string;
@@ -0,0 +1,8 @@
1
+ import { DataTypes, type InitOptions, type Model, type Sequelize } from 'sequelize';
2
+ export declare const DIALECTS_SUPPORTING_UNSIGNED: Set<string>;
3
+ export declare function integerIdType(sequelize: Sequelize): DataTypes.IntegerDataTypeConstructor;
4
+ export declare function tableOptions<ModelType extends Model>(sequelize: Sequelize, tableName: string, tablePrefix?: string, extra?: Partial<InitOptions<ModelType>>): InitOptions<ModelType>;
5
+ export declare function normalizeTablePrefix(prefix?: string): string | undefined;
6
+ export declare function applyTablePrefix(prefix: string | undefined, tableName: string): string;
7
+ export declare function encodeStringArray(values: string[] | undefined): string;
8
+ export declare function decodeStringArray(raw: string | null | undefined): string[];
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DIALECTS_SUPPORTING_UNSIGNED = void 0;
4
+ exports.integerIdType = integerIdType;
5
+ exports.tableOptions = tableOptions;
6
+ exports.normalizeTablePrefix = normalizeTablePrefix;
7
+ exports.applyTablePrefix = applyTablePrefix;
8
+ exports.encodeStringArray = encodeStringArray;
9
+ exports.decodeStringArray = decodeStringArray;
10
+ const sequelize_1 = require("sequelize");
11
+ exports.DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
12
+ function integerIdType(sequelize) {
13
+ return exports.DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
14
+ }
15
+ function tableOptions(sequelize, tableName, tablePrefix, extra) {
16
+ const opts = { sequelize, tableName: applyTablePrefix(tablePrefix, tableName) };
17
+ if (extra) {
18
+ Object.assign(opts, extra);
19
+ }
20
+ if (exports.DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())) {
21
+ opts.charset = 'utf8mb4';
22
+ opts.collate = 'utf8mb4_unicode_ci';
23
+ }
24
+ return opts;
25
+ }
26
+ function normalizeTablePrefix(prefix) {
27
+ if (!prefix) {
28
+ return undefined;
29
+ }
30
+ const trimmed = prefix.trim();
31
+ return trimmed.length > 0 ? trimmed : undefined;
32
+ }
33
+ function applyTablePrefix(prefix, tableName) {
34
+ const normalized = normalizeTablePrefix(prefix);
35
+ return normalized ? `${normalized}${tableName}` : tableName;
36
+ }
37
+ function encodeStringArray(values) {
38
+ return JSON.stringify(values ?? []);
39
+ }
40
+ function decodeStringArray(raw) {
41
+ if (!raw) {
42
+ return [];
43
+ }
44
+ try {
45
+ const parsed = JSON.parse(raw);
46
+ if (Array.isArray(parsed)) {
47
+ return parsed.filter((entry) => typeof entry === 'string' && entry.length > 0);
48
+ }
49
+ }
50
+ catch {
51
+ // ignore malformed values
52
+ }
53
+ return raw
54
+ .split(/\s+/)
55
+ .map((entry) => entry.trim())
56
+ .filter((entry) => entry.length > 0);
57
+ }
@@ -5,6 +5,7 @@ export interface JwtSignResult {
5
5
  token?: string;
6
6
  error?: string;
7
7
  }
8
+ export type JwtSignPayload = string | Buffer | Record<string, unknown>;
8
9
  export interface JwtVerifyResult<T> {
9
10
  success: boolean;
10
11
  data?: T;
@@ -32,7 +33,7 @@ export declare abstract class TokenStore {
32
33
  }): Promise<Token[]>;
33
34
  abstract close(): Promise<void>;
34
35
  normalizeToken(token: Partial<Token>): Token;
35
- jwtSign(payload: any, secret: string, expiresInSeconds: number, options?: SignOptions): JwtSignResult;
36
+ jwtSign(payload: JwtSignPayload, secret: string, expiresInSeconds: number, options?: SignOptions): JwtSignResult;
36
37
  jwtVerify<T>(token: string, secret: string, options?: VerifyOptions): JwtVerifyResult<T>;
37
38
  jwtDecode<T>(token: string, options?: DecodeOptions): JwtDecodeResult<T>;
38
39
  }
@@ -49,10 +49,10 @@ function normalizeTokenInternal(input) {
49
49
  const status = input.status === 'revoked' ? 'revoked' : expires.getTime() < Date.now() ? 'expired' : 'active';
50
50
  const sessionCookie = typeof input.sessionCookie === 'boolean' ? input.sessionCookie : false;
51
51
  return {
52
- ...input,
53
52
  accessToken: input.accessToken,
54
53
  refreshToken: input.refreshToken,
55
54
  userId,
55
+ clientId: typeof input.clientId === 'string' && input.clientId.length > 0 ? input.clientId : undefined,
56
56
  domain: typeof input.domain === 'string' ? input.domain : '',
57
57
  fingerprint: typeof input.fingerprint === 'string' ? input.fingerprint : '',
58
58
  label: typeof input.label === 'string' ? input.label : '',
@@ -76,6 +76,8 @@ class TokenStore {
76
76
  normalizeToken(token) {
77
77
  return normalizeTokenInternal(token);
78
78
  }
79
+ // JWT helpers live on TokenStore so every adapter automatically exposes
80
+ // signing/verification without additional wiring.
79
81
  jwtSign(payload, secret, expiresInSeconds, options) {
80
82
  const opts = { ...(options ?? {}), expiresIn: expiresInSeconds };
81
83
  try {
@@ -1,7 +1,16 @@
1
1
  import { TokenStore } from './base.js';
2
2
  import type { Token } from './types.js';
3
+ export interface MemoryTokenStoreOptions {
4
+ maxTokens?: number;
5
+ }
3
6
  export declare class MemoryTokenStore extends TokenStore {
4
7
  private readonly tokens;
8
+ private readonly tokensByUser;
9
+ private readonly maxTokens?;
10
+ constructor(options?: MemoryTokenStoreOptions);
11
+ private indexToken;
12
+ private unindexToken;
13
+ private removeByRefreshToken;
5
14
  save(record: Token): Promise<void>;
6
15
  get(query: Partial<Token>, opts?: {
7
16
  includeExpired?: boolean;
@@ -16,4 +25,5 @@ export declare class MemoryTokenStore extends TokenStore {
16
25
  includeExpired?: boolean;
17
26
  }): Promise<Token[]>;
18
27
  close(): Promise<void>;
28
+ private enforceCapacity;
19
29
  }