@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,5 +1,5 @@
1
1
  import { generateAuthenticationOptions, generateRegistrationOptions, verifyAuthenticationResponse, verifyRegistrationResponse } from '@simplewebauthn/server';
2
- import { isoBase64URL } from '@simplewebauthn/server/helpers';
2
+ import { isoBase64URL, isoCBOR, decodeAttestationObject, parseAuthenticatorData } from '@simplewebauthn/server/helpers';
3
3
  const ALLOWED_TRANSPORTS = [
4
4
  'ble',
5
5
  'cable',
@@ -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
  }
@@ -31,30 +38,112 @@ function toBuffer(value) {
31
38
  const view = value instanceof Uint8Array ? value : new Uint8Array(value);
32
39
  return Buffer.from(view);
33
40
  }
41
+ function toBufferOrNull(value) {
42
+ if (!value) {
43
+ return null;
44
+ }
45
+ if (Buffer.isBuffer(value)) {
46
+ return value;
47
+ }
48
+ if (value instanceof ArrayBuffer || value instanceof Uint8Array) {
49
+ return toBuffer(value);
50
+ }
51
+ if (typeof value === 'string') {
52
+ try {
53
+ return fromBase64Url(value);
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+ async function spkiToCosePublicKey(spki) {
62
+ try {
63
+ const subtle = (globalThis.crypto?.subtle ??
64
+ (await import('crypto')).webcrypto?.subtle);
65
+ if (!subtle) {
66
+ return null;
67
+ }
68
+ const spkiView = new Uint8Array(spki);
69
+ const ecCurves = [
70
+ { namedCurve: 'P-256', crv: 1, alg: -7, rawLength: 65 },
71
+ { namedCurve: 'P-384', crv: 2, alg: -35, rawLength: 97 },
72
+ { namedCurve: 'P-521', crv: 3, alg: -36, rawLength: 133 }
73
+ ];
74
+ for (const curve of ecCurves) {
75
+ try {
76
+ const key = await subtle.importKey('spki', spkiView, { name: 'ECDSA', namedCurve: curve.namedCurve }, true, ['verify']);
77
+ const raw = Buffer.from(await subtle.exportKey('raw', key));
78
+ if (raw.length !== curve.rawLength || raw[0] !== 0x04) {
79
+ continue;
80
+ }
81
+ const coordLength = (raw.length - 1) / 2;
82
+ const x = raw.slice(1, 1 + coordLength);
83
+ const y = raw.slice(1 + coordLength);
84
+ const coseMap = new Map([
85
+ [1, 2], // kty: EC2
86
+ [3, curve.alg],
87
+ [-1, curve.crv],
88
+ [-2, x],
89
+ [-3, y]
90
+ ]);
91
+ return Buffer.from(isoCBOR.encode(coseMap));
92
+ }
93
+ catch {
94
+ // try next algorithm
95
+ }
96
+ }
97
+ try {
98
+ const edKey = await subtle.importKey('spki', spkiView, { name: 'Ed25519' }, true, ['verify']);
99
+ const raw = Buffer.from(await subtle.exportKey('raw', edKey));
100
+ if (raw.length !== 32) {
101
+ return null;
102
+ }
103
+ const coseMap = new Map([
104
+ [1, 1], // kty: OKP
105
+ [3, -8], // alg: EdDSA
106
+ [-1, 6], // crv: Ed25519
107
+ [-2, raw]
108
+ ]);
109
+ return Buffer.from(isoCBOR.encode(coseMap));
110
+ }
111
+ catch {
112
+ return null;
113
+ }
114
+ }
115
+ catch {
116
+ return null;
117
+ }
118
+ }
34
119
  export class PasskeyService {
35
120
  constructor(config, adapter, logger = console) {
36
121
  this.config = config;
37
122
  this.adapter = adapter;
38
123
  this.logger = logger;
39
124
  }
125
+ async listUserCredentials(userId) {
126
+ return this.adapter.listUserCredentials(userId);
127
+ }
128
+ async deleteCredential(credentialId) {
129
+ return this.adapter.deleteCredential(credentialId);
130
+ }
40
131
  async createChallenge(params) {
41
132
  await this.adapter.cleanupChallenges?.(new Date());
42
- const metadata = {
43
- domain: typeof params.domain === 'string' ? params.domain : undefined,
44
- fingerprint: typeof params.fingerprint === 'string' ? params.fingerprint : undefined,
45
- label: typeof params.label === 'string' ? params.label : undefined,
46
- userAgent: typeof params.userAgent === 'string' ? params.userAgent : undefined
47
- };
48
133
  if (params.action === 'register') {
49
- return this.createRegistrationChallenge(params, metadata);
134
+ return this.createRegistrationChallenge(params);
50
135
  }
51
136
  if (params.action === 'authenticate') {
52
- return this.createAuthenticationChallenge(params, metadata);
137
+ return this.createAuthenticationChallenge(params);
53
138
  }
54
139
  throw new Error(`Unsupported passkey action: ${String(params.action)}`);
55
140
  }
56
141
  async verifyResponse(params) {
57
142
  await this.adapter.cleanupChallenges?.(new Date());
143
+ const existing = this.adapter.getChallenge ? await this.adapter.getChallenge(params.expectedChallenge) : null;
144
+ if (existing && existing.expiresAt.getTime() <= Date.now()) {
145
+ return { verified: false };
146
+ }
58
147
  const record = await this.adapter.consumeChallenge(params.expectedChallenge);
59
148
  if (!record) {
60
149
  return { verified: false };
@@ -75,7 +164,7 @@ export class PasskeyService {
75
164
  }
76
165
  return { verified: false };
77
166
  }
78
- async createRegistrationChallenge(params, metadata) {
167
+ async createRegistrationChallenge(params) {
79
168
  const user = await this.requireUser({ userId: params.userId, login: params.login });
80
169
  const existing = await this.adapter.listUserCredentials(user.id);
81
170
  const excludeCredentials = existing.map((credential) => {
@@ -98,16 +187,16 @@ export class PasskeyService {
98
187
  action: 'register',
99
188
  userId: user.id,
100
189
  login: user.login,
101
- expiresAt,
102
- metadata
190
+ expiresAt
103
191
  });
104
192
  return {
105
193
  challenge: options.challenge,
106
194
  expiresAt: expiresAt.toISOString(),
107
- userId: user.id
195
+ userId: user.id,
196
+ publicKey: options
108
197
  };
109
198
  }
110
- async createAuthenticationChallenge(params, metadata) {
199
+ async createAuthenticationChallenge(params) {
111
200
  const user = await this.requireUser({ userId: params.userId, login: params.login });
112
201
  const credentials = await this.adapter.listUserCredentials(user.id);
113
202
  const allowCredentials = credentials.map((credential) => {
@@ -127,13 +216,13 @@ export class PasskeyService {
127
216
  action: 'authenticate',
128
217
  userId: user.id,
129
218
  login: user.login,
130
- expiresAt,
131
- metadata
219
+ expiresAt
132
220
  });
133
221
  return {
134
222
  challenge: options.challenge,
135
223
  expiresAt: expiresAt.toISOString(),
136
- userId: user.id
224
+ userId: user.id,
225
+ publicKey: options
137
226
  };
138
227
  }
139
228
  async verifyRegistration(params, record) {
@@ -149,20 +238,71 @@ export class PasskeyService {
149
238
  expectedChallenge: record.challenge,
150
239
  expectedOrigin: this.config.origins,
151
240
  expectedRPID: this.config.rpId,
152
- requireUserVerification: true
241
+ requireUserVerification: this.requireUserVerification()
153
242
  });
154
243
  if (!result.verified || !result.registrationInfo) {
244
+ if (!result.verified) {
245
+ const err = result.error ?? result;
246
+ this.logger.error?.('Passkey registration verification failed', err);
247
+ }
155
248
  return { verified: false };
156
249
  }
157
250
  const registrationInfo = result.registrationInfo;
251
+ const attestationResponse = params.response.response;
252
+ const credentialIdPrimary = toBufferOrNull(registrationInfo.credentialID);
253
+ const credentialIdFallback = toBufferOrNull(params.response.id);
254
+ const credentialId = credentialIdPrimary && credentialIdPrimary.length > 0 ? credentialIdPrimary : credentialIdFallback;
255
+ const publicKeyPrimary = toBufferOrNull(registrationInfo.credentialPublicKey);
256
+ let publicKeyFallback = toBufferOrNull(attestationResponse?.publicKey);
257
+ if ((!publicKeyPrimary || publicKeyPrimary.length === 0) && attestationResponse?.attestationObject) {
258
+ try {
259
+ const attestationObject = String(attestationResponse.attestationObject);
260
+ const attObj = decodeAttestationObject(isoBase64URL.toBuffer(attestationObject));
261
+ const parsedAuth = parseAuthenticatorData(attObj.get('authData'));
262
+ publicKeyFallback = toBufferOrNull(parsedAuth.credentialPublicKey) ?? publicKeyFallback;
263
+ }
264
+ catch (error) {
265
+ this.logger.warn?.('Passkey registration: failed to parse attestationObject', error);
266
+ }
267
+ }
268
+ const publicKey = publicKeyPrimary && publicKeyPrimary.length > 0 ? publicKeyPrimary : publicKeyFallback;
269
+ if (this.config.debug && this.logger?.warn) {
270
+ const pkPrimaryHex = publicKeyPrimary ? publicKeyPrimary.slice(0, 4).toString('hex') : 'null';
271
+ const pkFallbackHex = publicKeyFallback ? publicKeyFallback.slice(0, 4).toString('hex') : 'null';
272
+ this.logger.warn(`Passkey registration: pkPrimary len=${publicKeyPrimary?.length ?? 0} head=${pkPrimaryHex}, pkFallback len=${publicKeyFallback?.length ?? 0} head=${pkFallbackHex}`);
273
+ }
274
+ if (!credentialId || credentialId.length === 0) {
275
+ return { verified: false, message: 'Missing credential id in registration response' };
276
+ }
277
+ if (!publicKey || publicKey.length === 0) {
278
+ return { verified: false, message: 'Missing public key in registration response' };
279
+ }
280
+ let storedPublicKey = Buffer.isBuffer(publicKey) ? publicKey : toBuffer(publicKey);
281
+ // If fallback is an SPKI key (DER, starts with 0x30) convert to COSE so verification succeeds.
282
+ if (storedPublicKey[0] === 0x30) {
283
+ const converted = await spkiToCosePublicKey(storedPublicKey);
284
+ if (converted) {
285
+ storedPublicKey = converted;
286
+ }
287
+ else {
288
+ this.logger.warn?.('Passkey registration: failed to convert SPKI public key to COSE');
289
+ }
290
+ }
158
291
  await this.adapter.saveCredential({
159
292
  userId: user.id,
160
- credentialId: toBuffer(registrationInfo.credentialID),
161
- publicKey: toBuffer(registrationInfo.credentialPublicKey),
162
- counter: registrationInfo.counter,
293
+ credentialId,
294
+ publicKey: storedPublicKey,
295
+ counter: registrationInfo.counter ?? 0,
163
296
  transports: sanitizeTransports(params.response.transports),
164
297
  backedUp: registrationInfo.credentialDeviceType === 'multiDevice',
165
- deviceType: registrationInfo.credentialDeviceType
298
+ deviceType: registrationInfo.credentialDeviceType,
299
+ label: toOptionalString(params.label),
300
+ createdDomain: toOptionalString(params.domain),
301
+ createdUserAgent: toOptionalString(params.userAgent),
302
+ createdBrowser: toOptionalString(params.browser),
303
+ createdOs: toOptionalString(params.os),
304
+ createdDevice: toOptionalString(params.device),
305
+ createdIp: toOptionalString(params.ip)
166
306
  });
167
307
  return { verified: true, userId: user.id, login: user.login };
168
308
  }
@@ -179,22 +319,24 @@ export class PasskeyService {
179
319
  }
180
320
  const user = await this.requireUser({ userId: credential.userId, login: record.login });
181
321
  const storedAuthData = {
182
- credentialID: credential.credentialId,
322
+ id: toBase64Url(credential.credentialId),
323
+ publicKey: new Uint8Array(toBuffer(credential.publicKey)),
183
324
  counter: credential.counter,
184
- credentialBackedUp: credential.backedUp,
185
- credentialDeviceType: credential.deviceType,
186
- credentialPublicKey: credential.publicKey,
187
325
  transports: credential.transports ?? undefined
326
+ // simplewebauthn accepts either Uint8Array or Buffer; ensure Buffer
327
+ // see https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/server/src/authentication/verifyAuthenticationResponse.ts
188
328
  };
189
329
  const result = await verifyAuthenticationResponse({
190
330
  response,
191
331
  expectedChallenge: record.challenge,
192
332
  expectedOrigin: this.config.origins,
193
333
  expectedRPID: this.config.rpId,
194
- authenticator: storedAuthData,
195
- requireUserVerification: true
334
+ credential: storedAuthData,
335
+ requireUserVerification: this.requireUserVerification()
196
336
  });
197
337
  if (!result.verified) {
338
+ const err = result.error ?? result;
339
+ this.logger.error?.('Passkey authentication verification failed', err);
198
340
  return { verified: false };
199
341
  }
200
342
  await this.adapter.updateCredentialCounter(credential.credentialId, result.authenticationInfo.newCounter);
@@ -214,4 +356,7 @@ export class PasskeyService {
214
356
  createExpiry() {
215
357
  return new Date(Date.now() + this.config.timeoutMs);
216
358
  }
359
+ requireUserVerification() {
360
+ return this.config.userVerification !== 'discouraged';
361
+ }
217
362
  }
@@ -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,48 @@
1
+ import { DataTypes } from 'sequelize';
2
+ export const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
3
+ export function integerIdType(sequelize) {
4
+ return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? DataTypes.INTEGER.UNSIGNED : DataTypes.INTEGER;
5
+ }
6
+ export function tableOptions(sequelize, tableName, tablePrefix, extra) {
7
+ const opts = { sequelize, tableName: applyTablePrefix(tablePrefix, tableName) };
8
+ if (extra) {
9
+ Object.assign(opts, extra);
10
+ }
11
+ if (DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())) {
12
+ opts.charset = 'utf8mb4';
13
+ opts.collate = 'utf8mb4_unicode_ci';
14
+ }
15
+ return opts;
16
+ }
17
+ export function normalizeTablePrefix(prefix) {
18
+ if (!prefix) {
19
+ return undefined;
20
+ }
21
+ const trimmed = prefix.trim();
22
+ return trimmed.length > 0 ? trimmed : undefined;
23
+ }
24
+ export function applyTablePrefix(prefix, tableName) {
25
+ const normalized = normalizeTablePrefix(prefix);
26
+ return normalized ? `${normalized}${tableName}` : tableName;
27
+ }
28
+ export function encodeStringArray(values) {
29
+ return JSON.stringify(values ?? []);
30
+ }
31
+ export function decodeStringArray(raw) {
32
+ if (!raw) {
33
+ return [];
34
+ }
35
+ try {
36
+ const parsed = JSON.parse(raw);
37
+ if (Array.isArray(parsed)) {
38
+ return parsed.filter((entry) => typeof entry === 'string' && entry.length > 0);
39
+ }
40
+ }
41
+ catch {
42
+ // ignore malformed values
43
+ }
44
+ return raw
45
+ .split(/\s+/)
46
+ .map((entry) => entry.trim())
47
+ .filter((entry) => entry.length > 0);
48
+ }
@@ -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
  }
@@ -43,10 +43,10 @@ function normalizeTokenInternal(input) {
43
43
  const status = input.status === 'revoked' ? 'revoked' : expires.getTime() < Date.now() ? 'expired' : 'active';
44
44
  const sessionCookie = typeof input.sessionCookie === 'boolean' ? input.sessionCookie : false;
45
45
  return {
46
- ...input,
47
46
  accessToken: input.accessToken,
48
47
  refreshToken: input.refreshToken,
49
48
  userId,
49
+ clientId: typeof input.clientId === 'string' && input.clientId.length > 0 ? input.clientId : undefined,
50
50
  domain: typeof input.domain === 'string' ? input.domain : '',
51
51
  fingerprint: typeof input.fingerprint === 'string' ? input.fingerprint : '',
52
52
  label: typeof input.label === 'string' ? input.label : '',
@@ -70,6 +70,8 @@ export class TokenStore {
70
70
  normalizeToken(token) {
71
71
  return normalizeTokenInternal(token);
72
72
  }
73
+ // JWT helpers live on TokenStore so every adapter automatically exposes
74
+ // signing/verification without additional wiring.
73
75
  jwtSign(payload, secret, expiresInSeconds, options) {
74
76
  const opts = { ...(options ?? {}), expiresIn: expiresInSeconds };
75
77
  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
  }