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

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 (116) hide show
  1. package/README.txt +25 -2
  2. package/dist/cjs/api-server-base.cjs +448 -111
  3. package/dist/cjs/api-server-base.d.ts +91 -34
  4. package/dist/cjs/auth-api/auth-module.d.ts +105 -0
  5. package/dist/cjs/auth-api/auth-module.js +1180 -0
  6. package/dist/cjs/auth-api/compat-auth-storage.d.ts +57 -0
  7. package/dist/cjs/auth-api/compat-auth-storage.js +128 -0
  8. package/dist/cjs/auth-api/mem-auth-store.d.ts +68 -0
  9. package/dist/cjs/auth-api/mem-auth-store.js +141 -0
  10. package/dist/cjs/{auth-module.d.ts → auth-api/module.d.ts} +7 -7
  11. package/dist/cjs/{auth-module.cjs → auth-api/module.js} +1 -1
  12. package/dist/cjs/auth-api/sql-auth-store.d.ts +77 -0
  13. package/dist/cjs/auth-api/sql-auth-store.js +172 -0
  14. package/dist/cjs/auth-api/storage.d.ts +38 -0
  15. package/dist/cjs/{auth-storage.cjs → auth-api/storage.js} +17 -7
  16. package/dist/cjs/auth-api/types.d.ts +34 -0
  17. package/dist/cjs/auth-api/types.js +2 -0
  18. package/dist/cjs/index.cjs +41 -7
  19. package/dist/cjs/index.d.ts +29 -5
  20. package/dist/cjs/oauth/base.d.ts +10 -0
  21. package/dist/cjs/oauth/base.js +6 -0
  22. package/dist/cjs/oauth/memory.d.ts +16 -0
  23. package/dist/cjs/oauth/memory.js +99 -0
  24. package/dist/cjs/oauth/models.d.ts +45 -0
  25. package/dist/cjs/oauth/models.js +58 -0
  26. package/dist/cjs/oauth/sequelize.d.ts +68 -0
  27. package/dist/cjs/oauth/sequelize.js +210 -0
  28. package/dist/cjs/oauth/types.d.ts +50 -0
  29. package/dist/cjs/oauth/types.js +3 -0
  30. package/dist/cjs/passkey/base.d.ts +16 -0
  31. package/dist/cjs/passkey/base.js +6 -0
  32. package/dist/cjs/passkey/memory.d.ts +27 -0
  33. package/dist/cjs/passkey/memory.js +86 -0
  34. package/dist/cjs/passkey/models.d.ts +25 -0
  35. package/dist/cjs/passkey/models.js +115 -0
  36. package/dist/cjs/passkey/sequelize.d.ts +55 -0
  37. package/dist/cjs/passkey/sequelize.js +220 -0
  38. package/dist/cjs/passkey/service.d.ts +20 -0
  39. package/dist/cjs/passkey/service.js +356 -0
  40. package/dist/cjs/passkey/types.d.ts +78 -0
  41. package/dist/cjs/passkey/types.js +2 -0
  42. package/dist/cjs/token/base.d.ts +38 -0
  43. package/dist/cjs/token/base.js +114 -0
  44. package/dist/cjs/token/memory.d.ts +19 -0
  45. package/dist/cjs/token/memory.js +149 -0
  46. package/dist/cjs/token/sequelize.d.ts +58 -0
  47. package/dist/cjs/token/sequelize.js +404 -0
  48. package/dist/cjs/token/types.d.ts +27 -0
  49. package/dist/cjs/token/types.js +2 -0
  50. package/dist/cjs/user/base.d.ts +26 -0
  51. package/dist/cjs/user/base.js +45 -0
  52. package/dist/cjs/user/memory.d.ts +35 -0
  53. package/dist/cjs/user/memory.js +173 -0
  54. package/dist/cjs/user/sequelize.d.ts +41 -0
  55. package/dist/cjs/user/sequelize.js +182 -0
  56. package/dist/cjs/user/types.d.ts +11 -0
  57. package/dist/cjs/user/types.js +2 -0
  58. package/dist/esm/api-server-base.d.ts +91 -34
  59. package/dist/esm/api-server-base.js +447 -110
  60. package/dist/esm/auth-api/auth-module.d.ts +105 -0
  61. package/dist/esm/auth-api/auth-module.js +1178 -0
  62. package/dist/esm/auth-api/compat-auth-storage.d.ts +57 -0
  63. package/dist/esm/auth-api/compat-auth-storage.js +124 -0
  64. package/dist/esm/auth-api/mem-auth-store.d.ts +68 -0
  65. package/dist/esm/auth-api/mem-auth-store.js +137 -0
  66. package/dist/esm/{auth-module.d.ts → auth-api/module.d.ts} +7 -7
  67. package/dist/esm/{auth-module.js → auth-api/module.js} +1 -1
  68. package/dist/esm/auth-api/sql-auth-store.d.ts +77 -0
  69. package/dist/esm/auth-api/sql-auth-store.js +168 -0
  70. package/dist/esm/auth-api/storage.d.ts +38 -0
  71. package/dist/esm/{auth-storage.js → auth-api/storage.js} +15 -5
  72. package/dist/esm/auth-api/types.d.ts +34 -0
  73. package/dist/esm/auth-api/types.js +1 -0
  74. package/dist/esm/index.d.ts +29 -5
  75. package/dist/esm/index.js +19 -2
  76. package/dist/esm/oauth/base.d.ts +10 -0
  77. package/dist/esm/oauth/base.js +2 -0
  78. package/dist/esm/oauth/memory.d.ts +16 -0
  79. package/dist/esm/oauth/memory.js +92 -0
  80. package/dist/esm/oauth/models.d.ts +45 -0
  81. package/dist/esm/oauth/models.js +51 -0
  82. package/dist/esm/oauth/sequelize.d.ts +68 -0
  83. package/dist/esm/oauth/sequelize.js +199 -0
  84. package/dist/esm/oauth/types.d.ts +50 -0
  85. package/dist/esm/oauth/types.js +2 -0
  86. package/dist/esm/passkey/base.d.ts +16 -0
  87. package/dist/esm/passkey/base.js +2 -0
  88. package/dist/esm/passkey/memory.d.ts +27 -0
  89. package/dist/esm/passkey/memory.js +82 -0
  90. package/dist/esm/passkey/models.d.ts +25 -0
  91. package/dist/esm/passkey/models.js +108 -0
  92. package/dist/esm/passkey/sequelize.d.ts +55 -0
  93. package/dist/esm/passkey/sequelize.js +216 -0
  94. package/dist/esm/passkey/service.d.ts +20 -0
  95. package/dist/esm/passkey/service.js +319 -0
  96. package/dist/esm/passkey/types.d.ts +78 -0
  97. package/dist/esm/passkey/types.js +1 -0
  98. package/dist/esm/token/base.d.ts +38 -0
  99. package/dist/esm/token/base.js +107 -0
  100. package/dist/esm/token/memory.d.ts +19 -0
  101. package/dist/esm/token/memory.js +145 -0
  102. package/dist/esm/token/sequelize.d.ts +58 -0
  103. package/dist/esm/token/sequelize.js +400 -0
  104. package/dist/esm/token/types.d.ts +27 -0
  105. package/dist/esm/token/types.js +1 -0
  106. package/dist/esm/user/base.d.ts +26 -0
  107. package/dist/esm/user/base.js +38 -0
  108. package/dist/esm/user/memory.d.ts +35 -0
  109. package/dist/esm/user/memory.js +169 -0
  110. package/dist/esm/user/sequelize.d.ts +41 -0
  111. package/dist/esm/user/sequelize.js +176 -0
  112. package/dist/esm/user/types.d.ts +11 -0
  113. package/dist/esm/user/types.js +1 -0
  114. package/package.json +13 -3
  115. package/dist/cjs/auth-storage.d.ts +0 -133
  116. package/dist/esm/auth-storage.d.ts +0 -133
@@ -0,0 +1,404 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SequelizeTokenStore = void 0;
4
+ const sequelize_1 = require("sequelize");
5
+ const base_js_1 = require("./base.js");
6
+ const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
7
+ class TokenModel extends sequelize_1.Model {
8
+ }
9
+ function tokenTableOptions(sequelize) {
10
+ const opts = {
11
+ sequelize,
12
+ tableName: 'jwttokens',
13
+ timestamps: false
14
+ };
15
+ if (DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())) {
16
+ opts.charset = 'utf8mb4';
17
+ opts.collate = 'utf8mb4_unicode_ci';
18
+ }
19
+ return opts;
20
+ }
21
+ function initTokenModel(sequelize) {
22
+ TokenModel.init({
23
+ token_id: {
24
+ type: DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())
25
+ ? sequelize_1.DataTypes.INTEGER.UNSIGNED
26
+ : sequelize_1.DataTypes.INTEGER,
27
+ autoIncrement: true,
28
+ allowNull: false,
29
+ primaryKey: true
30
+ },
31
+ user_id: {
32
+ type: sequelize_1.DataTypes.STRING(191),
33
+ allowNull: false
34
+ },
35
+ real_user_id: {
36
+ type: sequelize_1.DataTypes.STRING(191),
37
+ allowNull: true,
38
+ defaultValue: null
39
+ },
40
+ expires: {
41
+ type: sequelize_1.DataTypes.DATE,
42
+ allowNull: false
43
+ },
44
+ issued_at: {
45
+ type: sequelize_1.DataTypes.DATE,
46
+ allowNull: false,
47
+ defaultValue: sequelize_1.DataTypes.NOW
48
+ },
49
+ last_seen_at: {
50
+ type: sequelize_1.DataTypes.DATE,
51
+ allowNull: false,
52
+ defaultValue: sequelize_1.DataTypes.NOW
53
+ },
54
+ access: {
55
+ type: sequelize_1.DataTypes.STRING(512),
56
+ allowNull: false
57
+ },
58
+ refresh: {
59
+ type: sequelize_1.DataTypes.STRING(512),
60
+ allowNull: false
61
+ },
62
+ domain: {
63
+ type: sequelize_1.DataTypes.STRING(64),
64
+ allowNull: false,
65
+ defaultValue: ''
66
+ },
67
+ fingerprint: {
68
+ type: sequelize_1.DataTypes.STRING(64),
69
+ allowNull: false,
70
+ defaultValue: ''
71
+ },
72
+ label: {
73
+ type: sequelize_1.DataTypes.STRING(128),
74
+ allowNull: false,
75
+ defaultValue: ''
76
+ },
77
+ browser: {
78
+ type: sequelize_1.DataTypes.STRING(64),
79
+ allowNull: false,
80
+ defaultValue: ''
81
+ },
82
+ device: {
83
+ type: sequelize_1.DataTypes.STRING(64),
84
+ allowNull: false,
85
+ defaultValue: ''
86
+ },
87
+ ip: {
88
+ type: sequelize_1.DataTypes.STRING(45),
89
+ allowNull: false,
90
+ defaultValue: ''
91
+ },
92
+ os: {
93
+ type: sequelize_1.DataTypes.STRING(64),
94
+ allowNull: false,
95
+ defaultValue: ''
96
+ },
97
+ login_type: {
98
+ type: sequelize_1.DataTypes.STRING(64),
99
+ allowNull: false,
100
+ defaultValue: ''
101
+ },
102
+ refresh_ttl_seconds: {
103
+ type: sequelize_1.DataTypes.INTEGER,
104
+ allowNull: true,
105
+ defaultValue: null
106
+ },
107
+ session_cookie: {
108
+ type: sequelize_1.DataTypes.BOOLEAN,
109
+ allowNull: false,
110
+ defaultValue: false
111
+ },
112
+ client_id: {
113
+ type: sequelize_1.DataTypes.STRING(128),
114
+ allowNull: true,
115
+ defaultValue: null
116
+ },
117
+ scope: {
118
+ type: sequelize_1.DataTypes.TEXT,
119
+ allowNull: false,
120
+ defaultValue: '[]'
121
+ }
122
+ }, {
123
+ ...tokenTableOptions(sequelize),
124
+ indexes: [
125
+ { name: 'jwt_access_unique', unique: true, fields: ['access'] },
126
+ { name: 'jwt_refresh_unique', unique: true, fields: ['refresh'] }
127
+ ]
128
+ });
129
+ return TokenModel;
130
+ }
131
+ class SequelizeTokenStore extends base_js_1.TokenStore {
132
+ constructor(options) {
133
+ super();
134
+ if (!options?.sequelize) {
135
+ throw new Error('SequelizeTokenStore requires an initialised Sequelize instance');
136
+ }
137
+ this.Tokens = options.tokenModel ?? (options.tokenModelFactory ?? initTokenModel)(options.sequelize);
138
+ }
139
+ async save(record) {
140
+ const normalized = this.normalizeToken(record);
141
+ const resolvedUserId = this.normalizeUserId(normalized.userId);
142
+ const resolvedRealUserId = this.resolveRealUserId(normalized.ruid);
143
+ const domain = normalized.domain;
144
+ const fingerprint = normalized.fingerprint;
145
+ const browser = normalized.browser;
146
+ const device = normalized.device;
147
+ const ip = normalized.ip;
148
+ const os = normalized.os;
149
+ const label = normalized.label;
150
+ const loginType = normalized.loginType ?? '';
151
+ const refreshTtlSeconds = typeof normalized.refreshTtlSeconds === 'number' && normalized.refreshTtlSeconds > 0
152
+ ? Math.floor(normalized.refreshTtlSeconds)
153
+ : null;
154
+ const issuedAt = normalized.issuedAt ?? new Date();
155
+ const lastSeenAt = normalized.lastSeenAt ?? issuedAt;
156
+ const sessionCookie = normalized.sessionCookie ?? false;
157
+ const removalWhere = { user_id: resolvedUserId };
158
+ if (normalized.domain !== undefined && record.domain !== undefined) {
159
+ removalWhere.domain = domain;
160
+ }
161
+ if (normalized.fingerprint !== undefined && record.fingerprint !== undefined) {
162
+ removalWhere.fingerprint = fingerprint;
163
+ }
164
+ if (normalized.clientId) {
165
+ removalWhere.client_id = normalized.clientId;
166
+ }
167
+ await this.Tokens.destroy({ where: removalWhere });
168
+ await this.Tokens.create({
169
+ user_id: resolvedUserId,
170
+ real_user_id: resolvedRealUserId,
171
+ access: normalized.accessToken ?? '',
172
+ refresh: normalized.refreshToken,
173
+ expires: normalized.expires,
174
+ issued_at: issuedAt,
175
+ last_seen_at: lastSeenAt,
176
+ domain,
177
+ fingerprint,
178
+ label,
179
+ browser,
180
+ device,
181
+ ip,
182
+ os,
183
+ client_id: normalized.clientId ?? null,
184
+ scope: this.encodeScope(normalized.scope),
185
+ login_type: loginType,
186
+ refresh_ttl_seconds: refreshTtlSeconds,
187
+ session_cookie: sessionCookie
188
+ });
189
+ }
190
+ async get(query, opts) {
191
+ if (!query.refreshToken && !query.accessToken && query.userId === undefined) {
192
+ throw new Error('At least one token lookup field must be provided');
193
+ }
194
+ const where = {};
195
+ if (query.refreshToken) {
196
+ where.refresh = query.refreshToken;
197
+ }
198
+ if (query.accessToken) {
199
+ where.access = query.accessToken;
200
+ }
201
+ if (query.userId !== undefined) {
202
+ where.user_id = this.normalizeUserId(query.userId);
203
+ }
204
+ if (query.clientId) {
205
+ where.client_id = query.clientId;
206
+ }
207
+ if (query.domain !== undefined && query.domain !== null) {
208
+ where.domain = query.domain;
209
+ }
210
+ if (query.fingerprint !== undefined && query.fingerprint !== null) {
211
+ where.fingerprint = query.fingerprint;
212
+ }
213
+ if (query.label) {
214
+ where.label = query.label;
215
+ }
216
+ if (!(opts?.includeExpired ?? false)) {
217
+ where.expires = { [sequelize_1.Op.gt]: new Date() };
218
+ }
219
+ const model = await this.Tokens.findOne({ where });
220
+ return model ? this.toTokenRecord(model) : null;
221
+ }
222
+ async delete(query) {
223
+ if (!query.refreshToken && !query.accessToken && query.userId === undefined && !query.clientId) {
224
+ return 0;
225
+ }
226
+ const where = {};
227
+ if (query.refreshToken) {
228
+ where.refresh = query.refreshToken;
229
+ }
230
+ if (query.accessToken) {
231
+ where.access = query.accessToken;
232
+ }
233
+ if (query.userId !== undefined) {
234
+ where.user_id = this.normalizeUserId(query.userId);
235
+ }
236
+ if (query.clientId) {
237
+ where.client_id = query.clientId;
238
+ }
239
+ if (query.domain !== undefined && query.domain !== null) {
240
+ where.domain = query.domain;
241
+ }
242
+ if (query.fingerprint !== undefined && query.fingerprint !== null) {
243
+ where.fingerprint = query.fingerprint;
244
+ }
245
+ if (query.label) {
246
+ where.label = query.label;
247
+ }
248
+ return this.Tokens.destroy({ where });
249
+ }
250
+ async update(params) {
251
+ const where = { refresh: params.refreshToken };
252
+ if (params.clientId) {
253
+ where.client_id = params.clientId;
254
+ }
255
+ const updates = {};
256
+ if (params.accessToken !== undefined) {
257
+ updates.access = params.accessToken ?? null;
258
+ }
259
+ if (params.expires !== undefined) {
260
+ updates.expires = params.expires ?? null;
261
+ }
262
+ if (params.scope !== undefined) {
263
+ updates.scope = this.encodeScope(params.scope);
264
+ }
265
+ if (params.label !== undefined) {
266
+ updates.label = params.label ?? '';
267
+ }
268
+ if (params.domain !== undefined) {
269
+ updates.domain = params.domain ?? '';
270
+ }
271
+ if (params.fingerprint !== undefined) {
272
+ updates.fingerprint = params.fingerprint ?? '';
273
+ }
274
+ if (params.browser !== undefined) {
275
+ updates.browser = params.browser ?? '';
276
+ }
277
+ if (params.device !== undefined) {
278
+ updates.device = params.device ?? '';
279
+ }
280
+ if (params.ip !== undefined) {
281
+ updates.ip = params.ip ?? '';
282
+ }
283
+ if (params.os !== undefined) {
284
+ updates.os = params.os ?? '';
285
+ }
286
+ if (params.refreshTtlSeconds !== undefined) {
287
+ updates.refresh_ttl_seconds =
288
+ typeof params.refreshTtlSeconds === 'number' && params.refreshTtlSeconds > 0
289
+ ? Math.floor(params.refreshTtlSeconds)
290
+ : null;
291
+ }
292
+ if (params.loginType !== undefined) {
293
+ updates.login_type = params.loginType ?? '';
294
+ }
295
+ if (params.sessionCookie !== undefined) {
296
+ updates.session_cookie = params.sessionCookie;
297
+ }
298
+ if (params.issuedAt !== undefined) {
299
+ updates.issued_at = params.issuedAt ?? null;
300
+ }
301
+ if (params.lastSeenAt !== undefined) {
302
+ updates.last_seen_at = params.lastSeenAt ?? null;
303
+ }
304
+ if (Object.keys(updates).length === 0) {
305
+ return false;
306
+ }
307
+ const [updated] = await this.Tokens.update(updates, { where });
308
+ return updated > 0;
309
+ }
310
+ async list(userId, opts = {}) {
311
+ const where = { user_id: this.normalizeUserId(userId) };
312
+ if (!(opts.includeExpired ?? false)) {
313
+ where.expires = { [sequelize_1.Op.gt]: new Date() };
314
+ }
315
+ const models = await this.Tokens.findAll({
316
+ where,
317
+ order: [['issued_at', 'DESC']],
318
+ limit: opts.limit,
319
+ offset: opts.offset
320
+ });
321
+ return models.map((model) => this.toTokenRecord(model));
322
+ }
323
+ async close() {
324
+ return;
325
+ }
326
+ normalizeUserId(identifier) {
327
+ if (identifier === undefined || identifier === null) {
328
+ throw new Error(`Unable to normalise user identifier: ${identifier}`);
329
+ }
330
+ return String(identifier);
331
+ }
332
+ resolveRealUserId(ruid) {
333
+ if (ruid === undefined || ruid === null) {
334
+ return null;
335
+ }
336
+ const value = String(ruid);
337
+ if (value.length === 0 || value === '0') {
338
+ return null;
339
+ }
340
+ return value;
341
+ }
342
+ encodeStringArray(values) {
343
+ return JSON.stringify(values ?? []);
344
+ }
345
+ decodeStringArray(raw) {
346
+ if (!raw) {
347
+ return [];
348
+ }
349
+ try {
350
+ const parsed = JSON.parse(raw);
351
+ if (Array.isArray(parsed)) {
352
+ return parsed.filter((entry) => typeof entry === 'string' && entry.length > 0);
353
+ }
354
+ }
355
+ catch {
356
+ // ignore malformed values
357
+ }
358
+ return raw
359
+ .split(/\s+/)
360
+ .map((entry) => entry.trim())
361
+ .filter((entry) => entry.length > 0);
362
+ }
363
+ encodeScope(scope) {
364
+ if (!scope || (Array.isArray(scope) && scope.length === 0)) {
365
+ return '[]';
366
+ }
367
+ if (Array.isArray(scope)) {
368
+ return this.encodeStringArray(scope);
369
+ }
370
+ return this.encodeStringArray(scope.split(/\s+/).filter((entry) => entry.length > 0));
371
+ }
372
+ decodeScope(raw) {
373
+ return this.decodeStringArray(raw);
374
+ }
375
+ toTokenRecord(model) {
376
+ const scope = this.decodeScope(model.scope);
377
+ const normalized = this.normalizeToken({
378
+ userId: model.user_id,
379
+ refreshToken: model.refresh,
380
+ accessToken: model.access,
381
+ expires: model.expires,
382
+ issuedAt: model.issued_at,
383
+ lastSeenAt: model.last_seen_at,
384
+ domain: model.domain,
385
+ fingerprint: model.fingerprint,
386
+ label: model.label,
387
+ browser: model.browser,
388
+ device: model.device,
389
+ ip: model.ip,
390
+ os: model.os,
391
+ clientId: model.client_id ?? undefined,
392
+ scope,
393
+ loginType: model.login_type || undefined,
394
+ refreshTtlSeconds: model.refresh_ttl_seconds ?? undefined,
395
+ ruid: model.real_user_id ?? undefined,
396
+ sessionCookie: model.session_cookie
397
+ });
398
+ return {
399
+ ...normalized,
400
+ scope: normalized.scope ? [...normalized.scope] : undefined
401
+ };
402
+ }
403
+ }
404
+ exports.SequelizeTokenStore = SequelizeTokenStore;
@@ -0,0 +1,27 @@
1
+ export type TokenStatus = 'active' | 'expired' | 'revoked';
2
+ export interface Token {
3
+ accessToken: string;
4
+ refreshToken: string;
5
+ userId: string;
6
+ expires?: Date;
7
+ issuedAt?: Date;
8
+ lastSeenAt?: Date;
9
+ status?: TokenStatus;
10
+ ruid?: string;
11
+ clientId?: string;
12
+ domain?: string;
13
+ fingerprint?: string;
14
+ label?: string;
15
+ browser?: string;
16
+ device?: string;
17
+ ip?: string;
18
+ os?: string;
19
+ scope?: string | string[];
20
+ loginType?: string;
21
+ refreshTtlSeconds?: number;
22
+ sessionCookie?: boolean;
23
+ }
24
+ export interface TokenPair {
25
+ accessToken: string;
26
+ refreshToken: string;
27
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,26 @@
1
+ import type { CreateUserInput, PublicUserMapper, UpdateUserInput } from './types.js';
2
+ import type { AuthIdentifier } from '../auth-api/types.js';
3
+ export declare abstract class UserStore<User, PublicUser> {
4
+ protected readonly toPublicUser: PublicUserMapper<User, PublicUser>;
5
+ private readonly bcryptRounds;
6
+ private readonly bcryptPepper?;
7
+ constructor(opts?: {
8
+ toPublic?: PublicUserMapper<User, PublicUser>;
9
+ bcryptRounds?: number;
10
+ bcryptPepper?: string;
11
+ });
12
+ protected hashPassword(plain: string): Promise<string>;
13
+ verifyPassword(plain: string, hashed: string): Promise<boolean>;
14
+ protected normalizeUserInput(input: Partial<CreateUserInput>): CreateUserInput;
15
+ abstract findUser(identifier: AuthIdentifier | string): Promise<User | null>;
16
+ abstract findById(id: AuthIdentifier): Promise<User | null>;
17
+ abstract findByLoginOrEmail(loginOrEmail: string): Promise<User | null>;
18
+ abstract createUser(input: CreateUserInput): Promise<User>;
19
+ abstract upsertUser(input: CreateUserInput): Promise<User>;
20
+ abstract updateUser(id: AuthIdentifier, patch: UpdateUserInput): Promise<User>;
21
+ abstract setPasswordHash(id: AuthIdentifier, hash: string): Promise<void>;
22
+ abstract getPasswordHash(user: User): string | null;
23
+ abstract getUserId(user: User): AuthIdentifier;
24
+ toPublic(user: User): PublicUser;
25
+ }
26
+ export type { CreateUserInput, UpdateUserInput, PublicUserMapper } from './types.js';
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.UserStore = void 0;
7
+ const bcryptjs_1 = __importDefault(require("bcryptjs"));
8
+ class UserStore {
9
+ constructor(opts = {}) {
10
+ this.toPublicUser = opts.toPublic ?? ((u) => u);
11
+ const rounds = typeof opts.bcryptRounds === 'number' && Number.isFinite(opts.bcryptRounds)
12
+ ? Math.max(4, Math.floor(opts.bcryptRounds))
13
+ : 12;
14
+ this.bcryptRounds = rounds;
15
+ this.bcryptPepper =
16
+ typeof opts.bcryptPepper === 'string' && opts.bcryptPepper.length > 0 ? opts.bcryptPepper : undefined;
17
+ }
18
+ async hashPassword(plain) {
19
+ const candidate = this.bcryptPepper ? `${plain}${this.bcryptPepper}` : plain;
20
+ return bcryptjs_1.default.hash(candidate, this.bcryptRounds);
21
+ }
22
+ async verifyPassword(plain, hashed) {
23
+ const candidate = this.bcryptPepper ? `${plain}${this.bcryptPepper}` : plain;
24
+ return bcryptjs_1.default.compare(candidate, hashed);
25
+ }
26
+ normalizeUserInput(input) {
27
+ const login = typeof input.login === 'string' ? input.login.trim() : '';
28
+ const email = typeof input.email === 'string' ? input.email.trim() : '';
29
+ if (!login || !email) {
30
+ throw new Error('login and email are required');
31
+ }
32
+ const password = typeof input.password === 'string' ? input.password : undefined;
33
+ return { ...input, login, email, password };
34
+ }
35
+ toPublic(user) {
36
+ const mapped = this.toPublicUser(user);
37
+ if (mapped && typeof mapped === 'object') {
38
+ const { password: _password, ...rest } = mapped;
39
+ void _password;
40
+ return rest;
41
+ }
42
+ return mapped;
43
+ }
44
+ }
45
+ exports.UserStore = UserStore;
@@ -0,0 +1,35 @@
1
+ import { UserStore } from './base.js';
2
+ import type { CreateUserInput, PublicUserMapper, UpdateUserInput } from './types.js';
3
+ import type { AuthIdentifier } from '../auth-api/types.js';
4
+ export interface MemoryUserAttributes extends Record<string, unknown> {
5
+ user_id: number;
6
+ login: string;
7
+ email: string;
8
+ password: string;
9
+ }
10
+ export type MemoryPublicUser<UserAttributes extends MemoryUserAttributes = MemoryUserAttributes> = Omit<UserAttributes, 'password'>;
11
+ export interface MemoryUserStoreOptions<UserAttributes extends MemoryUserAttributes = MemoryUserAttributes, PublicUserShape extends Omit<UserAttributes, 'password'> = Omit<UserAttributes, 'password'>> {
12
+ bcryptRounds?: number;
13
+ bcryptPepper?: string;
14
+ toPublic?: PublicUserMapper<UserAttributes, PublicUserShape>;
15
+ userIdFactory?: () => number;
16
+ startingUserId?: number;
17
+ }
18
+ export declare class MemoryUserStore<UserAttributes extends MemoryUserAttributes = MemoryUserAttributes, PublicUserShape extends Omit<UserAttributes, 'password'> = Omit<UserAttributes, 'password'>> extends UserStore<UserAttributes, PublicUserShape> {
19
+ private readonly usersById;
20
+ private readonly loginToId;
21
+ private readonly emailToId;
22
+ private readonly userIdFactory;
23
+ private nextUserId;
24
+ constructor(options?: MemoryUserStoreOptions<UserAttributes, PublicUserShape>);
25
+ findUser(identifier: AuthIdentifier | string): Promise<UserAttributes | null>;
26
+ findById(id: AuthIdentifier): Promise<UserAttributes | null>;
27
+ findByLoginOrEmail(loginOrEmail: string): Promise<UserAttributes | null>;
28
+ createUser(input: CreateUserInput): Promise<UserAttributes>;
29
+ upsertUser(input: CreateUserInput): Promise<UserAttributes>;
30
+ updateUser(id: AuthIdentifier, patch: UpdateUserInput): Promise<UserAttributes>;
31
+ setPasswordHash(id: AuthIdentifier, hash: string): Promise<void>;
32
+ getPasswordHash(user: UserAttributes): string | null;
33
+ getUserId(user: UserAttributes): AuthIdentifier;
34
+ private persistUser;
35
+ }