@technomoron/api-server-base 2.0.0-beta.19 → 2.0.0-beta.20

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 (73) hide show
  1. package/dist/cjs/api-server-base.cjs +48 -21
  2. package/dist/cjs/auth-api/auth-module.js +16 -30
  3. package/dist/cjs/auth-api/mem-auth-store.js +5 -4
  4. package/dist/cjs/auth-api/sql-auth-store.js +6 -4
  5. package/dist/cjs/auth-api/user-id.d.ts +1 -0
  6. package/dist/cjs/auth-api/user-id.js +7 -0
  7. package/dist/cjs/auth-cookie-options.js +10 -1
  8. package/dist/cjs/oauth/memory.d.ts +6 -0
  9. package/dist/cjs/oauth/memory.js +43 -4
  10. package/dist/cjs/oauth/models.d.ts +1 -0
  11. package/dist/cjs/oauth/models.js +7 -18
  12. package/dist/cjs/oauth/sequelize.d.ts +5 -52
  13. package/dist/cjs/oauth/sequelize.js +34 -93
  14. package/dist/cjs/oauth/types.d.ts +1 -0
  15. package/dist/cjs/passkey/base.d.ts +1 -0
  16. package/dist/cjs/passkey/memory.d.ts +7 -0
  17. package/dist/cjs/passkey/memory.js +47 -5
  18. package/dist/cjs/passkey/models.js +2 -5
  19. package/dist/cjs/passkey/sequelize.d.ts +5 -29
  20. package/dist/cjs/passkey/sequelize.js +48 -191
  21. package/dist/cjs/passkey/service.d.ts +1 -0
  22. package/dist/cjs/passkey/service.js +52 -15
  23. package/dist/cjs/passkey/types.d.ts +1 -0
  24. package/dist/cjs/sequelize-utils.d.ts +5 -0
  25. package/dist/cjs/sequelize-utils.js +40 -0
  26. package/dist/cjs/token/base.js +3 -1
  27. package/dist/cjs/token/memory.d.ts +6 -0
  28. package/dist/cjs/token/memory.js +32 -7
  29. package/dist/cjs/token/sequelize.d.ts +0 -3
  30. package/dist/cjs/token/sequelize.js +50 -81
  31. package/dist/cjs/token/types.d.ts +1 -1
  32. package/dist/cjs/user/base.d.ts +1 -0
  33. package/dist/cjs/user/base.js +11 -4
  34. package/dist/cjs/user/memory.d.ts +2 -0
  35. package/dist/cjs/user/memory.js +8 -2
  36. package/dist/cjs/user/sequelize.js +12 -22
  37. package/dist/esm/api-server-base.js +48 -21
  38. package/dist/esm/auth-api/auth-module.js +16 -30
  39. package/dist/esm/auth-api/mem-auth-store.js +5 -4
  40. package/dist/esm/auth-api/sql-auth-store.js +6 -4
  41. package/dist/esm/auth-api/user-id.d.ts +1 -0
  42. package/dist/esm/auth-api/user-id.js +6 -0
  43. package/dist/esm/auth-cookie-options.js +10 -1
  44. package/dist/esm/oauth/memory.d.ts +6 -0
  45. package/dist/esm/oauth/memory.js +44 -5
  46. package/dist/esm/oauth/models.d.ts +1 -0
  47. package/dist/esm/oauth/models.js +2 -15
  48. package/dist/esm/oauth/sequelize.d.ts +5 -52
  49. package/dist/esm/oauth/sequelize.js +21 -80
  50. package/dist/esm/oauth/types.d.ts +1 -0
  51. package/dist/esm/passkey/base.d.ts +1 -0
  52. package/dist/esm/passkey/memory.d.ts +7 -0
  53. package/dist/esm/passkey/memory.js +47 -5
  54. package/dist/esm/passkey/models.js +1 -4
  55. package/dist/esm/passkey/sequelize.d.ts +5 -29
  56. package/dist/esm/passkey/sequelize.js +47 -190
  57. package/dist/esm/passkey/service.d.ts +1 -0
  58. package/dist/esm/passkey/service.js +52 -15
  59. package/dist/esm/passkey/types.d.ts +1 -0
  60. package/dist/esm/sequelize-utils.d.ts +5 -0
  61. package/dist/esm/sequelize-utils.js +36 -0
  62. package/dist/esm/token/base.js +3 -1
  63. package/dist/esm/token/memory.d.ts +6 -0
  64. package/dist/esm/token/memory.js +32 -7
  65. package/dist/esm/token/sequelize.d.ts +0 -3
  66. package/dist/esm/token/sequelize.js +51 -82
  67. package/dist/esm/token/types.d.ts +1 -1
  68. package/dist/esm/user/base.d.ts +1 -0
  69. package/dist/esm/user/base.js +11 -4
  70. package/dist/esm/user/memory.d.ts +2 -0
  71. package/dist/esm/user/memory.js +8 -2
  72. package/dist/esm/user/sequelize.js +13 -23
  73. package/package.json +5 -5
@@ -3,150 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SequelizePasskeyStore = void 0;
4
4
  const sequelize_1 = require("sequelize");
5
5
  const user_id_js_1 = require("../auth-api/user-id.js");
6
- const sequelize_utils_js_1 = require("../sequelize-utils.js");
7
6
  const base_js_1 = require("./base.js");
8
- function integerIdType(sequelize) {
9
- return sequelize_utils_js_1.DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
10
- }
7
+ const models_js_1 = require("./models.js");
11
8
  function encodeCredentialId(value) {
12
9
  return Buffer.isBuffer(value) ? value.toString('base64') : value;
13
10
  }
14
- function normalizeUserId(identifier) {
15
- return (0, user_id_js_1.normalizeNumericUserId)(identifier);
16
- }
17
- class PasskeyCredentialModel extends sequelize_1.Model {
18
- }
19
- class PasskeyChallengeModel extends sequelize_1.Model {
20
- }
21
- function initPasskeyCredentialModel(sequelize, options = {}) {
22
- const idType = integerIdType(sequelize);
23
- return PasskeyCredentialModel.init({
24
- credentialId: {
25
- field: 'credential_id',
26
- type: sequelize_1.DataTypes.STRING(768),
27
- primaryKey: true,
28
- allowNull: false,
29
- get() {
30
- const raw = this.getDataValue('credentialId');
31
- if (!raw) {
32
- return raw;
33
- }
34
- if (Buffer.isBuffer(raw)) {
35
- return raw;
36
- }
37
- return Buffer.from(raw, 'base64');
38
- },
39
- set(value) {
40
- const encoded = typeof value === 'string' ? value : value.toString('base64');
41
- this.setDataValue('credentialId', encoded);
42
- }
43
- },
44
- userId: {
45
- field: 'user_id',
46
- type: idType,
47
- allowNull: false
48
- },
49
- publicKey: {
50
- field: 'public_key',
51
- type: sequelize_1.DataTypes.BLOB,
52
- allowNull: false
53
- },
54
- counter: {
55
- type: sequelize_1.DataTypes.INTEGER,
56
- allowNull: false,
57
- defaultValue: 0
58
- },
59
- transports: {
60
- type: sequelize_1.DataTypes.JSON,
61
- allowNull: true
62
- },
63
- backedUp: {
64
- field: 'backed_up',
65
- type: sequelize_1.DataTypes.BOOLEAN,
66
- allowNull: false,
67
- defaultValue: false
68
- },
69
- deviceType: {
70
- field: 'device_type',
71
- type: sequelize_1.DataTypes.STRING(32),
72
- allowNull: false,
73
- defaultValue: 'multiDevice'
74
- },
75
- label: {
76
- type: sequelize_1.DataTypes.STRING(120),
77
- allowNull: true
78
- },
79
- createdDomain: {
80
- field: 'created_domain',
81
- type: sequelize_1.DataTypes.STRING(255),
82
- allowNull: true
83
- },
84
- createdUserAgent: {
85
- field: 'created_user_agent',
86
- type: sequelize_1.DataTypes.TEXT,
87
- allowNull: true
88
- },
89
- createdBrowser: {
90
- field: 'created_browser',
91
- type: sequelize_1.DataTypes.STRING(120),
92
- allowNull: true
93
- },
94
- createdOs: {
95
- field: 'created_os',
96
- type: sequelize_1.DataTypes.STRING(120),
97
- allowNull: true
98
- },
99
- createdDevice: {
100
- field: 'created_device',
101
- type: sequelize_1.DataTypes.STRING(120),
102
- allowNull: true
103
- },
104
- createdIp: {
105
- field: 'created_ip',
106
- type: sequelize_1.DataTypes.STRING(45),
107
- allowNull: true
108
- }
109
- }, {
110
- sequelize,
111
- tableName: (0, sequelize_utils_js_1.applyTablePrefix)(options.tablePrefix, 'passkey_credentials'),
112
- timestamps: true,
113
- underscored: true
114
- });
115
- }
116
- function initPasskeyChallengeModel(sequelize, options = {}) {
117
- const idType = integerIdType(sequelize);
118
- return PasskeyChallengeModel.init({
119
- challenge: {
120
- type: sequelize_1.DataTypes.STRING(255),
121
- primaryKey: true,
122
- allowNull: false
123
- },
124
- action: {
125
- type: sequelize_1.DataTypes.STRING(16),
126
- allowNull: false
127
- },
128
- userId: {
129
- field: 'user_id',
130
- type: idType,
131
- allowNull: true
132
- },
133
- login: {
134
- type: sequelize_1.DataTypes.STRING(128),
135
- allowNull: true
136
- },
137
- expiresAt: {
138
- field: 'expires_at',
139
- type: sequelize_1.DataTypes.DATE,
140
- allowNull: false
141
- }
142
- }, {
143
- sequelize,
144
- tableName: (0, sequelize_utils_js_1.applyTablePrefix)(options.tablePrefix, 'passkey_challenges'),
145
- timestamps: true,
146
- underscored: true,
147
- indexes: [{ fields: ['expires_at'] }, { fields: ['user_id'] }]
148
- });
149
- }
150
11
  class SequelizePasskeyStore extends base_js_1.PasskeyStore {
151
12
  constructor(options) {
152
13
  super();
@@ -156,12 +17,12 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
156
17
  this.resolveUserFn = options.resolveUser;
157
18
  this.credentials =
158
19
  options.credentialModel ??
159
- (options.credentialModelFactory ?? initPasskeyCredentialModel)(options.sequelize, {
20
+ (options.credentialModelFactory ?? models_js_1.initPasskeyCredentialModel)(options.sequelize, {
160
21
  tablePrefix: options.tablePrefix
161
22
  });
162
23
  this.challenges =
163
24
  options.challengeModel ??
164
- (options.challengeModelFactory ?? initPasskeyChallengeModel)(options.sequelize, {
25
+ (options.challengeModelFactory ?? models_js_1.initPasskeyChallengeModel)(options.sequelize, {
165
26
  tablePrefix: options.tablePrefix
166
27
  });
167
28
  }
@@ -169,25 +30,8 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
169
30
  return this.resolveUserFn(params);
170
31
  }
171
32
  async listUserCredentials(userId) {
172
- const models = await this.credentials.findAll({ where: { userId: normalizeUserId(userId) } });
173
- return models.map((model) => ({
174
- userId: model.userId,
175
- credentialId: model.credentialId,
176
- publicKey: model.publicKey,
177
- counter: model.counter,
178
- transports: (model.transports ?? undefined),
179
- backedUp: model.backedUp,
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,
188
- createdAt: model.createdAt ?? undefined,
189
- updatedAt: model.updatedAt ?? undefined
190
- }));
33
+ const models = await this.credentials.findAll({ where: { userId: (0, user_id_js_1.normalizeNumericUserId)(userId) } });
34
+ return models.map((model) => this.toStoredCredential(model));
191
35
  }
192
36
  async deleteCredential(credentialId) {
193
37
  const encoded = Buffer.isBuffer(credentialId) ? credentialId.toString('base64') : credentialId;
@@ -196,32 +40,12 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
196
40
  }
197
41
  async findCredentialById(credentialId) {
198
42
  const model = await this.credentials.findByPk(encodeCredentialId(credentialId));
199
- if (!model) {
200
- return null;
201
- }
202
- return {
203
- userId: model.userId,
204
- credentialId: model.credentialId,
205
- publicKey: model.publicKey,
206
- counter: model.counter,
207
- transports: (model.transports ?? undefined),
208
- backedUp: model.backedUp,
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,
217
- createdAt: model.createdAt ?? undefined,
218
- updatedAt: model.updatedAt ?? undefined
219
- };
43
+ return model ? this.toStoredCredential(model) : null;
220
44
  }
221
45
  async saveCredential(record) {
222
46
  await this.credentials.upsert({
223
47
  credentialId: record.credentialId,
224
- userId: normalizeUserId(record.userId),
48
+ userId: (0, user_id_js_1.normalizeNumericUserId)(record.userId),
225
49
  publicKey: record.publicKey,
226
50
  counter: record.counter,
227
51
  transports: record.transports ?? null,
@@ -243,27 +67,60 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
243
67
  await this.challenges.upsert({
244
68
  challenge: record.challenge,
245
69
  action: record.action,
246
- userId: record.userId !== undefined ? normalizeUserId(record.userId) : null,
70
+ userId: record.userId !== undefined ? (0, user_id_js_1.normalizeNumericUserId)(record.userId) : null,
247
71
  login: record.login ?? null,
248
72
  expiresAt: record.expiresAt
249
73
  });
250
74
  }
251
- async consumeChallenge(challenge) {
75
+ async getChallenge(challenge) {
252
76
  const model = await this.challenges.findByPk(challenge);
253
- if (!model) {
254
- return null;
77
+ return model ? this.toChallengeRecord(model) : null;
78
+ }
79
+ async consumeChallenge(challenge) {
80
+ const sequelize = this.challenges.sequelize;
81
+ if (!sequelize) {
82
+ throw new Error('Challenge model is not bound to a Sequelize instance');
255
83
  }
256
- await model.destroy();
84
+ return sequelize.transaction({ isolationLevel: sequelize_1.Transaction.ISOLATION_LEVELS.READ_COMMITTED }, async (transaction) => {
85
+ const model = await this.challenges.findByPk(challenge, { transaction, lock: true });
86
+ if (!model) {
87
+ return null;
88
+ }
89
+ await model.destroy({ transaction });
90
+ return this.toChallengeRecord(model);
91
+ });
92
+ }
93
+ async cleanupChallenges(now) {
94
+ await this.challenges.destroy({ where: { expiresAt: { [sequelize_1.Op.lte]: now } } });
95
+ }
96
+ toChallengeRecord(model) {
257
97
  return {
258
98
  challenge: model.challenge,
259
99
  action: model.action,
260
- userId: model.userId ?? undefined,
100
+ userId: model.userId !== null ? String(model.userId) : undefined,
261
101
  login: model.login ?? undefined,
262
102
  expiresAt: model.expiresAt
263
103
  };
264
104
  }
265
- async cleanupChallenges(now) {
266
- await this.challenges.destroy({ where: { expiresAt: { [sequelize_1.Op.lte]: now } } });
105
+ toStoredCredential(model) {
106
+ return {
107
+ userId: String(model.userId),
108
+ credentialId: model.credentialId,
109
+ publicKey: model.publicKey,
110
+ counter: model.counter,
111
+ transports: (model.transports ?? undefined),
112
+ backedUp: model.backedUp,
113
+ deviceType: model.deviceType,
114
+ label: model.label ?? undefined,
115
+ createdDomain: model.createdDomain ?? undefined,
116
+ createdUserAgent: model.createdUserAgent ?? undefined,
117
+ createdBrowser: model.createdBrowser ?? undefined,
118
+ createdOs: model.createdOs ?? undefined,
119
+ createdDevice: model.createdDevice ?? undefined,
120
+ createdIp: model.createdIp ?? undefined,
121
+ createdAt: model.createdAt ?? undefined,
122
+ updatedAt: model.updatedAt ?? undefined
123
+ };
267
124
  }
268
125
  }
269
126
  exports.SequelizePasskeyStore = SequelizePasskeyStore;
@@ -17,4 +17,5 @@ export declare class PasskeyService {
17
17
  private verifyAuthentication;
18
18
  private requireUser;
19
19
  private createExpiry;
20
+ private requireUserVerification;
20
21
  }
@@ -102,21 +102,51 @@ async function spkiToCosePublicKey(spki) {
102
102
  return null;
103
103
  }
104
104
  const spkiView = new Uint8Array(spki);
105
- const key = await subtle.importKey('spki', spkiView, { name: 'ECDSA', namedCurve: 'P-256' }, true, ['verify']);
106
- const raw = Buffer.from(await subtle.exportKey('raw', key));
107
- if (raw.length !== 65 || raw[0] !== 0x04) {
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 {
108
148
  return null;
109
149
  }
110
- const x = raw.slice(1, 33);
111
- const y = raw.slice(33, 65);
112
- const coseMap = new Map([
113
- [1, 2], // kty: EC2
114
- [3, -7], // alg: ES256
115
- [-1, 1], // crv: P-256
116
- [-2, x],
117
- [-3, y]
118
- ]);
119
- return Buffer.from(helpers_1.isoCBOR.encode(coseMap));
120
150
  }
121
151
  catch {
122
152
  return null;
@@ -146,6 +176,10 @@ class PasskeyService {
146
176
  }
147
177
  async verifyResponse(params) {
148
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
+ }
149
183
  const record = await this.adapter.consumeChallenge(params.expectedChallenge);
150
184
  if (!record) {
151
185
  return { verified: false };
@@ -240,7 +274,7 @@ class PasskeyService {
240
274
  expectedChallenge: record.challenge,
241
275
  expectedOrigin: this.config.origins,
242
276
  expectedRPID: this.config.rpId,
243
- requireUserVerification: true
277
+ requireUserVerification: this.requireUserVerification()
244
278
  });
245
279
  if (!result.verified || !result.registrationInfo) {
246
280
  if (!result.verified) {
@@ -334,7 +368,7 @@ class PasskeyService {
334
368
  expectedOrigin: this.config.origins,
335
369
  expectedRPID: this.config.rpId,
336
370
  credential: storedAuthData,
337
- requireUserVerification: true
371
+ requireUserVerification: this.requireUserVerification()
338
372
  });
339
373
  if (!result.verified) {
340
374
  const err = result.error ?? result;
@@ -358,5 +392,8 @@ class PasskeyService {
358
392
  createExpiry() {
359
393
  return new Date(Date.now() + this.config.timeoutMs);
360
394
  }
395
+ requireUserVerification() {
396
+ return this.config.userVerification !== 'discouraged';
397
+ }
361
398
  }
362
399
  exports.PasskeyService = PasskeyService;
@@ -55,6 +55,7 @@ export interface PasskeyStorageAdapter {
55
55
  saveCredential(record: StoredPasskeyCredential): Promise<void>;
56
56
  updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
57
57
  saveChallenge(record: PasskeyChallengeRecord): Promise<void>;
58
+ getChallenge?(challenge: string): Promise<PasskeyChallengeRecord | null>;
58
59
  consumeChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
59
60
  cleanupChallenges?(now: Date): Promise<void>;
60
61
  }
@@ -1,3 +1,8 @@
1
+ import { DataTypes, type InitOptions, type Model, type Sequelize } from 'sequelize';
1
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>;
2
5
  export declare function normalizeTablePrefix(prefix?: string): string | undefined;
3
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[];
@@ -1,9 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DIALECTS_SUPPORTING_UNSIGNED = void 0;
4
+ exports.integerIdType = integerIdType;
5
+ exports.tableOptions = tableOptions;
4
6
  exports.normalizeTablePrefix = normalizeTablePrefix;
5
7
  exports.applyTablePrefix = applyTablePrefix;
8
+ exports.encodeStringArray = encodeStringArray;
9
+ exports.decodeStringArray = decodeStringArray;
10
+ const sequelize_1 = require("sequelize");
6
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
+ }
7
26
  function normalizeTablePrefix(prefix) {
8
27
  if (!prefix) {
9
28
  return undefined;
@@ -15,3 +34,24 @@ function applyTablePrefix(prefix, tableName) {
15
34
  const normalized = normalizeTablePrefix(prefix);
16
35
  return normalized ? `${normalized}${tableName}` : tableName;
17
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
+ }
@@ -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,8 +1,13 @@
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;
5
8
  private readonly tokensByUser;
9
+ private readonly maxTokens?;
10
+ constructor(options?: MemoryTokenStoreOptions);
6
11
  private indexToken;
7
12
  private unindexToken;
8
13
  private removeByRefreshToken;
@@ -20,4 +25,5 @@ export declare class MemoryTokenStore extends TokenStore {
20
25
  includeExpired?: boolean;
21
26
  }): Promise<Token[]>;
22
27
  close(): Promise<void>;
28
+ private enforceCapacity;
23
29
  }
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MemoryTokenStore = void 0;
4
4
  const base_js_1 = require("./base.js");
5
5
  function comparableUserId(value) {
6
- if (value === undefined || value === null) {
6
+ if (value === undefined) {
7
7
  return undefined;
8
8
  }
9
9
  return String(value);
@@ -48,10 +48,14 @@ function matchesQuery(record, query, includeExpired) {
48
48
  return true;
49
49
  }
50
50
  class MemoryTokenStore extends base_js_1.TokenStore {
51
- constructor() {
52
- super(...arguments);
51
+ constructor(options = {}) {
52
+ super();
53
53
  this.tokens = new Map();
54
54
  this.tokensByUser = new Map();
55
+ this.maxTokens =
56
+ typeof options.maxTokens === 'number' && Number.isFinite(options.maxTokens) && options.maxTokens > 0
57
+ ? Math.floor(options.maxTokens)
58
+ : undefined;
55
59
  }
56
60
  indexToken(token) {
57
61
  const userId = comparableUserId(token.userId);
@@ -118,6 +122,7 @@ class MemoryTokenStore extends base_js_1.TokenStore {
118
122
  this.removeByRefreshToken(stored.refreshToken);
119
123
  this.tokens.set(stored.refreshToken, stored);
120
124
  this.indexToken(stored);
125
+ this.enforceCapacity();
121
126
  }
122
127
  async get(query, opts) {
123
128
  if (!query.refreshToken && !query.accessToken && query.userId === undefined) {
@@ -165,8 +170,12 @@ class MemoryTokenStore extends base_js_1.TokenStore {
165
170
  merged[key] = value;
166
171
  }
167
172
  };
168
- maybeAssign('accessToken');
169
- maybeAssign('expires');
173
+ if (params.accessToken !== undefined && params.accessToken !== null) {
174
+ merged.accessToken = params.accessToken;
175
+ }
176
+ if (params.expires !== undefined && params.expires !== null) {
177
+ merged.expires = params.expires;
178
+ }
170
179
  maybeAssign('scope');
171
180
  maybeAssign('label');
172
181
  maybeAssign('domain');
@@ -177,8 +186,12 @@ class MemoryTokenStore extends base_js_1.TokenStore {
177
186
  maybeAssign('os');
178
187
  maybeAssign('refreshTtlSeconds');
179
188
  maybeAssign('loginType');
180
- maybeAssign('issuedAt');
181
- maybeAssign('lastSeenAt');
189
+ if (params.issuedAt !== undefined && params.issuedAt !== null) {
190
+ merged.issuedAt = params.issuedAt;
191
+ }
192
+ if (params.lastSeenAt !== undefined && params.lastSeenAt !== null) {
193
+ merged.lastSeenAt = params.lastSeenAt;
194
+ }
182
195
  maybeAssign('sessionCookie');
183
196
  const normalized = this.normalizeToken(merged);
184
197
  const previousUserId = token.userId;
@@ -210,5 +223,17 @@ class MemoryTokenStore extends base_js_1.TokenStore {
210
223
  async close() {
211
224
  return;
212
225
  }
226
+ enforceCapacity() {
227
+ if (!this.maxTokens) {
228
+ return;
229
+ }
230
+ while (this.tokens.size > this.maxTokens) {
231
+ const oldestRefresh = this.tokens.keys().next().value;
232
+ if (!oldestRefresh) {
233
+ return;
234
+ }
235
+ this.removeByRefreshToken(oldestRefresh);
236
+ }
237
+ }
213
238
  }
214
239
  exports.MemoryTokenStore = MemoryTokenStore;
@@ -52,10 +52,7 @@ export declare class SequelizeTokenStore extends TokenStore {
52
52
  close(): Promise<void>;
53
53
  private normalizeUserId;
54
54
  private resolveRealUserId;
55
- private encodeStringArray;
56
- private decodeStringArray;
57
55
  private encodeScope;
58
- private decodeScope;
59
56
  private toTokenRecord;
60
57
  }
61
58
  export {};