@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
@@ -11,7 +11,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.ApiServer = exports.ApiError = exports.ApiModule = void 0;
13
13
  const node_crypto_1 = require("node:crypto");
14
- const node_fs_1 = __importDefault(require("node:fs"));
14
+ const promises_1 = require("node:fs/promises");
15
15
  const node_module_1 = require("node:module");
16
16
  const node_path_1 = __importDefault(require("node:path"));
17
17
  const cookie_parser_1 = __importDefault(require("cookie-parser"));
@@ -20,6 +20,7 @@ const express_1 = __importDefault(require("express"));
20
20
  const multer_1 = __importDefault(require("multer"));
21
21
  const module_js_1 = require("./auth-api/module.js");
22
22
  const storage_js_1 = require("./auth-api/storage.js");
23
+ const user_id_js_1 = require("./auth-api/user-id.js");
23
24
  const auth_cookie_options_js_1 = require("./auth-cookie-options.js");
24
25
  const base_js_1 = require("./token/base.js");
25
26
  class JwtHelperStore extends base_js_1.TokenStore {
@@ -274,7 +275,9 @@ function collectClientIpChain(req) {
274
275
  }
275
276
  const realIp = req.headers['x-real-ip'];
276
277
  if (Array.isArray(realIp)) {
277
- realIp.forEach((value) => pushNormalized(normalizeIpAddress(value)));
278
+ for (const value of realIp) {
279
+ pushNormalized(normalizeIpAddress(value));
280
+ }
278
281
  }
279
282
  else if (typeof realIp === 'string') {
280
283
  pushNormalized(normalizeIpAddress(realIp));
@@ -429,6 +432,10 @@ class ApiServer {
429
432
  this.canImpersonateAdapter = canImpersonate ?? null;
430
433
  this.storageAdapter = this.getServerAuthAdapter();
431
434
  }
435
+ if ((this.config.authApi || this.config.authStores) &&
436
+ (!this.config.accessSecret || !this.config.refreshSecret)) {
437
+ console.warn('[api-server-base] Auth is enabled but accessSecret and/or refreshSecret are empty. JWT signing will fail at runtime.');
438
+ }
432
439
  this.app = (0, express_1.default)();
433
440
  // Mount API modules and any custom endpoints under `apiBasePath` on this router so we can keep
434
441
  // the API 404 handler ordered last without relying on Express internals.
@@ -616,8 +623,8 @@ class ApiServer {
616
623
  async getToken(query, opts) {
617
624
  const normalized = {
618
625
  ...query,
619
- userId: query.userId !== undefined && query.userId !== null ? String(query.userId) : undefined,
620
- ruid: query.ruid !== undefined && query.ruid !== null ? String(query.ruid) : undefined
626
+ userId: (0, user_id_js_1.toOptionalStringId)(query.userId),
627
+ ruid: (0, user_id_js_1.toOptionalStringId)(query.ruid)
621
628
  };
622
629
  if (this.tokenStoreAdapter) {
623
630
  return this.tokenStoreAdapter.get(normalized, opts);
@@ -631,8 +638,8 @@ class ApiServer {
631
638
  async deleteToken(query) {
632
639
  const normalized = {
633
640
  ...query,
634
- userId: query.userId !== undefined && query.userId !== null ? String(query.userId) : undefined,
635
- ruid: query.ruid !== undefined && query.ruid !== null ? String(query.ruid) : undefined
641
+ userId: (0, user_id_js_1.toOptionalStringId)(query.userId),
642
+ ruid: (0, user_id_js_1.toOptionalStringId)(query.ruid)
636
643
  };
637
644
  if (this.tokenStoreAdapter) {
638
645
  return this.tokenStoreAdapter.delete(normalized);
@@ -795,7 +802,7 @@ class ApiServer {
795
802
  res.status(200).json({ success: true, code: 200, message: 'Success', data: payload, errors: {} });
796
803
  });
797
804
  }
798
- loadSwaggerSpec() {
805
+ async loadSwaggerSpec() {
799
806
  const candidates = [node_path_1.default.resolve(process.cwd(), 'docs/swagger/openapi.json')];
800
807
  if (typeof __dirname === 'string') {
801
808
  candidates.push(node_path_1.default.resolve(__dirname, '../../docs/swagger/openapi.json'));
@@ -810,11 +817,14 @@ class ApiServer {
810
817
  // Ignore resolution failures; fall back to any existing candidate.
811
818
  }
812
819
  for (const candidate of candidates) {
813
- if (!node_fs_1.default.existsSync(candidate)) {
820
+ try {
821
+ await (0, promises_1.access)(candidate);
822
+ }
823
+ catch {
814
824
  continue;
815
825
  }
816
826
  try {
817
- const raw = node_fs_1.default.readFileSync(candidate, 'utf8');
827
+ const raw = await (0, promises_1.readFile)(candidate, 'utf8');
818
828
  return JSON.parse(raw);
819
829
  }
820
830
  catch {
@@ -832,12 +842,12 @@ class ApiServer {
832
842
  const base = this.apiBasePath === '/' ? '' : this.apiBasePath;
833
843
  const resolved = rawPath.length > 0 ? rawPath : `${base}/swagger`;
834
844
  const path = resolved.startsWith('/') ? resolved : `/${resolved}`;
835
- let specCache;
836
- this.app.get(path, (_req, res) => {
837
- if (specCache === undefined) {
838
- specCache = this.loadSwaggerSpec();
845
+ let specPromise;
846
+ this.app.get(path, async (_req, res) => {
847
+ if (!specPromise) {
848
+ specPromise = this.loadSwaggerSpec();
839
849
  }
840
- const spec = specCache;
850
+ const spec = await specPromise;
841
851
  if (!spec) {
842
852
  res.status(500).json({
843
853
  success: false,
@@ -1050,7 +1060,7 @@ class ApiServer {
1050
1060
  let tokenData;
1051
1061
  let error;
1052
1062
  let expired = false;
1053
- if (!token || token === null) {
1063
+ if (!token) {
1054
1064
  if (authType === 'maybe') {
1055
1065
  if (!this.config.refreshMaybe) {
1056
1066
  return null;
@@ -1203,9 +1213,6 @@ class ApiServer {
1203
1213
  if (rawReal === null) {
1204
1214
  return effectiveUserId;
1205
1215
  }
1206
- if (typeof rawReal === 'number' && rawReal === 0) {
1207
- return effectiveUserId;
1208
- }
1209
1216
  return rawReal;
1210
1217
  }
1211
1218
  useExpress(pathOrHandler, ...handlers) {
@@ -1429,9 +1436,29 @@ class ApiServer {
1429
1436
  console.log('URL:', url);
1430
1437
  console.log('Method:', req.method);
1431
1438
  console.log('Query Params:', req.query || {});
1432
- console.log('Body Params:', req.body || {});
1433
- console.log('Cookies:', req.cookies || {});
1434
- console.log('Headers:', req.headers);
1439
+ const sensitiveBodyKeys = ['password', 'client_secret', 'clientSecret', 'secret'];
1440
+ const body = req.body && typeof req.body === 'object' ? { ...req.body } : req.body;
1441
+ if (body && typeof body === 'object') {
1442
+ for (const key of sensitiveBodyKeys) {
1443
+ if (key in body) {
1444
+ body[key] = '[REDACTED]';
1445
+ }
1446
+ }
1447
+ }
1448
+ console.log('Body Params:', body || {});
1449
+ const cookies = req.cookies ? { ...req.cookies } : {};
1450
+ const sensitiveCookieKeys = [this.config.accessCookie, this.config.refreshCookie];
1451
+ for (const key of sensitiveCookieKeys) {
1452
+ if (key in cookies) {
1453
+ cookies[key] = '[REDACTED]';
1454
+ }
1455
+ }
1456
+ console.log('Cookies:', cookies);
1457
+ const headers = { ...req.headers };
1458
+ if (headers.authorization) {
1459
+ headers.authorization = '[REDACTED]';
1460
+ }
1461
+ console.log('Headers:', headers);
1435
1462
  console.log('------------------------');
1436
1463
  }
1437
1464
  formatDebugValue(value, maxLength = 50, seen = new WeakSet()) {
@@ -157,6 +157,9 @@ class AuthModule extends module_js_1.BaseAuthModule {
157
157
  return candidate ? {} : { sessionCookie: true, refreshTtlSeconds: this.sessionRefreshTtlSeconds() };
158
158
  }
159
159
  if (typeof candidate === 'number') {
160
+ if (candidate === 0) {
161
+ return { sessionCookie: true, refreshTtlSeconds: this.sessionRefreshTtlSeconds() };
162
+ }
160
163
  const ttl = this.normalizeRefreshTtlSeconds(candidate);
161
164
  return ttl ? { sessionCookie: false, refreshTtlSeconds: ttl } : {};
162
165
  }
@@ -449,20 +452,12 @@ class AuthModule extends module_js_1.BaseAuthModule {
449
452
  this.assertAuthReady();
450
453
  const { login, password, ...metadata } = this.parseLoginBody(apiReq);
451
454
  const user = await this.storage.getUser(login);
452
- if (!user) {
455
+ const hash = user ? this.storage.getUserPasswordHash(user) : '';
456
+ const verified = user ? await this.storage.verifyPassword(password, hash) : false;
457
+ if (!user || !verified) {
453
458
  throw new api_server_base_js_1.ApiError({
454
459
  code: 400,
455
- message: 'Invalid credentials',
456
- errors: { login: 'Unknown user' }
457
- });
458
- }
459
- const hash = this.storage.getUserPasswordHash(user);
460
- const verified = await this.storage.verifyPassword(password, hash);
461
- if (!verified) {
462
- throw new api_server_base_js_1.ApiError({
463
- code: 400,
464
- message: 'Invalid credentials',
465
- errors: { password: 'Wrong password' }
460
+ message: 'Invalid credentials'
466
461
  });
467
462
  }
468
463
  const pair = await this.issueTokens(apiReq, user, metadata);
@@ -504,6 +499,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
504
499
  refreshTtlSeconds: sessionPrefs.refreshTtlSeconds ?? stored.refreshTtlSeconds,
505
500
  sessionCookie: sessionPrefs.sessionCookie ?? stored.sessionCookie
506
501
  };
502
+ await this.storage.deleteToken({ refreshToken: providedToken });
507
503
  const pair = await this.issueTokens(apiReq, user, metadata);
508
504
  const publicUser = this.storage.filterUser(user);
509
505
  return [200, { ...pair, user: publicUser }];
@@ -913,7 +909,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
913
909
  }
914
910
  }
915
911
  }
916
- else if (!clientSecretProvided && client.clientSecret) {
912
+ else if (!clientSecretProvided && (client.hasSecret ?? Boolean(client.clientSecret))) {
917
913
  throw new api_server_base_js_1.ApiError({ code: 400, message: 'Client authentication required when no PKCE challenge present' });
918
914
  }
919
915
  const user = await this.getUserOrThrow(record.userId, 'User not found');
@@ -948,6 +944,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
948
944
  throw new api_server_base_js_1.ApiError({ code: 400, message: 'Refresh token issued to another client' });
949
945
  }
950
946
  const user = await this.getUserOrThrow(stored.userId ?? verify.data.uid, 'User not found');
947
+ await this.storage.deleteToken({ refreshToken });
951
948
  const tokens = await this.issueTokens(apiReq, user, {
952
949
  clientId: client.clientId,
953
950
  scope: stored.scope,
@@ -956,11 +953,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
956
953
  loginType: stored.loginType ?? 'oauth'
957
954
  });
958
955
  this.clearOAuthCookies(apiReq);
959
- const scope = Array.isArray(stored.scope)
960
- ? stored.scope
961
- : typeof stored.scope === 'string'
962
- ? stored.scope.split(/\s+/).filter((entry) => entry.length > 0)
963
- : [];
956
+ const scope = Array.isArray(stored.scope) ? stored.scope : [];
964
957
  return [200, this.buildTokenResponse(tokens, client, scope)];
965
958
  }
966
959
  clearOAuthCookies(apiReq) {
@@ -1023,7 +1016,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
1023
1016
  if (!client) {
1024
1017
  throw new api_server_base_js_1.ApiError({ code: 400, message: 'Unknown client_id' });
1025
1018
  }
1026
- const requiresSecret = !!client.clientSecret;
1019
+ const requiresSecret = client.hasSecret ?? Boolean(client.clientSecret);
1027
1020
  if (requiresSecret) {
1028
1021
  if (!secretProvided) {
1029
1022
  throw new api_server_base_js_1.ApiError({ code: 400, message: 'Client authentication is required' });
@@ -1059,17 +1052,10 @@ class AuthModule extends module_js_1.BaseAuthModule {
1059
1052
  const password = toStringOrNull(body.password);
1060
1053
  if (login && password) {
1061
1054
  const user = await this.storage.getUser(login);
1062
- if (!user) {
1063
- throw new api_server_base_js_1.ApiError({ code: 400, message: 'Invalid credentials', errors: { login: 'Unknown user' } });
1064
- }
1065
- const hash = this.storage.getUserPasswordHash(user);
1066
- const verified = await this.storage.verifyPassword(password, hash);
1067
- if (!verified) {
1068
- throw new api_server_base_js_1.ApiError({
1069
- code: 400,
1070
- message: 'Invalid credentials',
1071
- errors: { password: 'Wrong password' }
1072
- });
1055
+ const hash = user ? this.storage.getUserPasswordHash(user) : '';
1056
+ const verified = user ? await this.storage.verifyPassword(password, hash) : false;
1057
+ if (!user || !verified) {
1058
+ throw new api_server_base_js_1.ApiError({ code: 400, message: 'Invalid credentials' });
1073
1059
  }
1074
1060
  return user;
1075
1061
  }
@@ -7,6 +7,7 @@ const memory_js_2 = require("../passkey/memory.js");
7
7
  const memory_js_3 = require("../token/memory.js");
8
8
  const memory_js_4 = require("../user/memory.js");
9
9
  const compat_auth_storage_js_1 = require("./compat-auth-storage.js");
10
+ const user_id_js_1 = require("./user-id.js");
10
11
  class MemAuthStore {
11
12
  constructor(params = {}) {
12
13
  this.userStore = new memory_js_4.MemoryUserStore({
@@ -73,16 +74,16 @@ class MemAuthStore {
73
74
  async getToken(query, opts) {
74
75
  const normalized = {
75
76
  ...query,
76
- userId: query.userId !== undefined && query.userId !== null ? String(query.userId) : undefined,
77
- ruid: query.ruid !== undefined && query.ruid !== null ? String(query.ruid) : undefined
77
+ userId: (0, user_id_js_1.toOptionalStringId)(query.userId),
78
+ ruid: (0, user_id_js_1.toOptionalStringId)(query.ruid)
78
79
  };
79
80
  return this.adapter.getToken(normalized, opts);
80
81
  }
81
82
  async deleteToken(query) {
82
83
  const normalized = {
83
84
  ...query,
84
- userId: query.userId !== undefined && query.userId !== null ? String(query.userId) : undefined,
85
- ruid: query.ruid !== undefined && query.ruid !== null ? String(query.ruid) : undefined
85
+ userId: (0, user_id_js_1.toOptionalStringId)(query.userId),
86
+ ruid: (0, user_id_js_1.toOptionalStringId)(query.ruid)
86
87
  };
87
88
  return this.adapter.deleteToken(normalized);
88
89
  }
@@ -8,6 +8,7 @@ const sequelize_utils_js_1 = require("../sequelize-utils.js");
8
8
  const sequelize_js_3 = require("../token/sequelize.js");
9
9
  const sequelize_js_4 = require("../user/sequelize.js");
10
10
  const compat_auth_storage_js_1 = require("./compat-auth-storage.js");
11
+ const user_id_js_1 = require("./user-id.js");
11
12
  function resolveTablePrefix(...prefixes) {
12
13
  for (const prefix of prefixes) {
13
14
  const normalized = (0, sequelize_utils_js_1.normalizeTablePrefix)(prefix);
@@ -106,6 +107,7 @@ class SqlAuthStore {
106
107
  }
107
108
  }
108
109
  finally {
110
+ // Prevent double-close errors when the same Sequelize instance is shared with other code.
109
111
  this.sequelize.close = async () => { };
110
112
  }
111
113
  }
@@ -130,16 +132,16 @@ class SqlAuthStore {
130
132
  async getToken(query, opts) {
131
133
  const normalized = {
132
134
  ...query,
133
- userId: query.userId !== undefined && query.userId !== null ? String(query.userId) : undefined,
134
- ruid: query.ruid !== undefined && query.ruid !== null ? String(query.ruid) : undefined
135
+ userId: (0, user_id_js_1.toOptionalStringId)(query.userId),
136
+ ruid: (0, user_id_js_1.toOptionalStringId)(query.ruid)
135
137
  };
136
138
  return this.adapter.getToken(normalized, opts);
137
139
  }
138
140
  async deleteToken(query) {
139
141
  const normalized = {
140
142
  ...query,
141
- userId: query.userId !== undefined && query.userId !== null ? String(query.userId) : undefined,
142
- ruid: query.ruid !== undefined && query.ruid !== null ? String(query.ruid) : undefined
143
+ userId: (0, user_id_js_1.toOptionalStringId)(query.userId),
144
+ ruid: (0, user_id_js_1.toOptionalStringId)(query.ruid)
143
145
  };
144
146
  return this.adapter.deleteToken(normalized);
145
147
  }
@@ -2,3 +2,4 @@ import type { AuthIdentifier } from './types.js';
2
2
  export declare function normalizeComparableUserId(identifier: AuthIdentifier): string;
3
3
  export declare function normalizeNumericUserId(identifier: AuthIdentifier): number;
4
4
  export declare function normalizeStringUserId(identifier: AuthIdentifier): string;
5
+ export declare function toOptionalStringId(value: AuthIdentifier | undefined | null): string | undefined;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.normalizeComparableUserId = normalizeComparableUserId;
4
4
  exports.normalizeNumericUserId = normalizeNumericUserId;
5
5
  exports.normalizeStringUserId = normalizeStringUserId;
6
+ exports.toOptionalStringId = toOptionalStringId;
6
7
  function normalizeComparableUserId(identifier) {
7
8
  if (typeof identifier === 'number' && Number.isFinite(identifier)) {
8
9
  return String(identifier);
@@ -29,3 +30,9 @@ function normalizeNumericUserId(identifier) {
29
30
  function normalizeStringUserId(identifier) {
30
31
  return normalizeComparableUserId(identifier);
31
32
  }
33
+ function toOptionalStringId(value) {
34
+ if (value === undefined || value === null) {
35
+ return undefined;
36
+ }
37
+ return String(value);
38
+ }
@@ -31,7 +31,16 @@ function buildAuthCookieOptions(config, req) {
31
31
  const forwardedProto = firstHeaderValue(req.headers['x-forwarded-proto']).split(',')[0].trim().toLowerCase();
32
32
  const isHttps = forwardedProto === 'https' || req.protocol === 'https';
33
33
  const origin = firstHeaderValue(req.headers.origin ?? req.headers.referer);
34
- const secure = config.cookieSecure === true ? true : config.cookieSecure === false ? false : /* auto */ Boolean(isHttps);
34
+ let secure;
35
+ if (config.cookieSecure === true) {
36
+ secure = true;
37
+ }
38
+ else if (config.cookieSecure === false) {
39
+ secure = false;
40
+ }
41
+ else {
42
+ secure = isHttps;
43
+ }
35
44
  let sameSite = config.cookieSameSite ?? 'lax';
36
45
  if (sameSite !== 'lax' && sameSite !== 'strict' && sameSite !== 'none') {
37
46
  sameSite = 'lax';
@@ -1,11 +1,15 @@
1
1
  import { OAuthStore, type AuthCode, type OAuthClient } from './base.js';
2
2
  export interface MemoryOAuthStoreOptions {
3
3
  bcryptRounds?: number;
4
+ maxClients?: number;
5
+ maxAuthCodes?: number;
4
6
  }
5
7
  export declare class MemoryOAuthStore extends OAuthStore {
6
8
  private readonly clients;
7
9
  private readonly codes;
8
10
  private readonly bcryptRounds;
11
+ private readonly maxClients?;
12
+ private readonly maxAuthCodes?;
9
13
  constructor(options?: MemoryOAuthStoreOptions);
10
14
  getClient(clientId: string): Promise<OAuthClient | null>;
11
15
  createClient(input: OAuthClient): Promise<OAuthClient>;
@@ -13,4 +17,6 @@ export declare class MemoryOAuthStore extends OAuthStore {
13
17
  createAuthCode(code: AuthCode): Promise<void>;
14
18
  consumeAuthCode(code: string): Promise<AuthCode | null>;
15
19
  close(): Promise<void>;
20
+ private enforceClientCapacity;
21
+ private enforceCodeCapacity;
16
22
  }
@@ -13,8 +13,7 @@ function cloneClient(client) {
13
13
  }
14
14
  return {
15
15
  clientId: client.clientId,
16
- // clientSecret is stored hashed; do not return the hash.
17
- clientSecret: client.clientSecret ? '__stored__' : undefined,
16
+ hasSecret: Boolean(client.clientSecret),
18
17
  name: client.name,
19
18
  redirectUris: [...client.redirectUris],
20
19
  scope: client.scope ? [...client.scope] : undefined,
@@ -25,18 +24,28 @@ function cloneClient(client) {
25
24
  function cloneCode(code) {
26
25
  return {
27
26
  ...code,
27
+ userId: (0, user_id_js_1.normalizeComparableUserId)(code.userId),
28
28
  scope: code.scope ? [...code.scope] : undefined,
29
29
  expiresAt: new Date(code.expiresAt),
30
30
  metadata: code.metadata ? { ...code.metadata } : undefined
31
31
  };
32
32
  }
33
- const normalizeUserId = user_id_js_1.normalizeNumericUserId;
34
33
  class MemoryOAuthStore extends base_js_1.OAuthStore {
35
34
  constructor(options = {}) {
36
35
  super();
37
36
  this.clients = new Map();
38
37
  this.codes = new Map();
39
38
  this.bcryptRounds = options.bcryptRounds ?? 12;
39
+ this.maxClients =
40
+ typeof options.maxClients === 'number' && Number.isFinite(options.maxClients) && options.maxClients > 0
41
+ ? Math.floor(options.maxClients)
42
+ : undefined;
43
+ this.maxAuthCodes =
44
+ typeof options.maxAuthCodes === 'number' &&
45
+ Number.isFinite(options.maxAuthCodes) &&
46
+ options.maxAuthCodes > 0
47
+ ? Math.floor(options.maxAuthCodes)
48
+ : undefined;
40
49
  }
41
50
  async getClient(clientId) {
42
51
  return cloneClient(this.clients.get(clientId));
@@ -53,6 +62,7 @@ class MemoryOAuthStore extends base_js_1.OAuthStore {
53
62
  firstParty: input.firstParty
54
63
  };
55
64
  this.clients.set(stored.clientId, stored);
65
+ this.enforceClientCapacity();
56
66
  return cloneClient(stored);
57
67
  }
58
68
  async verifyClientSecret(clientId, secret) {
@@ -71,23 +81,52 @@ class MemoryOAuthStore extends base_js_1.OAuthStore {
71
81
  async createAuthCode(code) {
72
82
  const record = {
73
83
  ...code,
74
- userId: normalizeUserId(code.userId),
84
+ userId: (0, user_id_js_1.normalizeComparableUserId)(code.userId),
75
85
  scope: code.scope ? [...code.scope] : undefined,
76
86
  expiresAt: code.expiresAt,
77
87
  metadata: code.metadata ? { ...code.metadata } : undefined
78
88
  };
79
89
  this.codes.set(record.code, record);
90
+ this.enforceCodeCapacity();
80
91
  }
81
92
  async consumeAuthCode(code) {
82
93
  const record = this.codes.get(code);
83
94
  if (!record) {
84
95
  return null;
85
96
  }
97
+ if (record.expiresAt.getTime() <= Date.now()) {
98
+ this.codes.delete(code);
99
+ return null;
100
+ }
86
101
  this.codes.delete(code);
87
102
  return cloneCode(record);
88
103
  }
89
104
  async close() {
90
105
  return;
91
106
  }
107
+ enforceClientCapacity() {
108
+ if (!this.maxClients) {
109
+ return;
110
+ }
111
+ while (this.clients.size > this.maxClients) {
112
+ const oldest = this.clients.keys().next().value;
113
+ if (!oldest) {
114
+ return;
115
+ }
116
+ this.clients.delete(oldest);
117
+ }
118
+ }
119
+ enforceCodeCapacity() {
120
+ if (!this.maxAuthCodes) {
121
+ return;
122
+ }
123
+ while (this.codes.size > this.maxAuthCodes) {
124
+ const oldest = this.codes.keys().next().value;
125
+ if (!oldest) {
126
+ return;
127
+ }
128
+ this.codes.delete(oldest);
129
+ }
130
+ }
92
131
  }
93
132
  exports.MemoryOAuthStore = MemoryOAuthStore;
@@ -1,4 +1,5 @@
1
1
  import { Model, type Optional, type Sequelize } from 'sequelize';
2
+ export { integerIdType, tableOptions } from '../sequelize-utils.js';
2
3
  export interface OAuthClientAttributes {
3
4
  client_id: string;
4
5
  client_secret: string;
@@ -1,24 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OAuthCodeModel = exports.OAuthClientModel = void 0;
3
+ exports.OAuthCodeModel = exports.OAuthClientModel = exports.tableOptions = exports.integerIdType = void 0;
4
4
  exports.initOAuthClientModel = initOAuthClientModel;
5
5
  exports.initOAuthCodeModel = initOAuthCodeModel;
6
6
  const sequelize_1 = require("sequelize");
7
7
  const sequelize_utils_js_1 = require("../sequelize-utils.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
- }
11
- function tableOptions(sequelize, tableName, tablePrefix, extra) {
12
- const opts = { sequelize, tableName: (0, sequelize_utils_js_1.applyTablePrefix)(tablePrefix, tableName) };
13
- if (extra) {
14
- Object.assign(opts, extra);
15
- }
16
- if (sequelize_utils_js_1.DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())) {
17
- opts.charset = 'utf8mb4';
18
- opts.collate = 'utf8mb4_unicode_ci';
19
- }
20
- return opts;
21
- }
8
+ var sequelize_utils_js_2 = require("../sequelize-utils.js");
9
+ Object.defineProperty(exports, "integerIdType", { enumerable: true, get: function () { return sequelize_utils_js_2.integerIdType; } });
10
+ Object.defineProperty(exports, "tableOptions", { enumerable: true, get: function () { return sequelize_utils_js_2.tableOptions; } });
22
11
  class OAuthClientModel extends sequelize_1.Model {
23
12
  }
24
13
  exports.OAuthClientModel = OAuthClientModel;
@@ -32,7 +21,7 @@ function initOAuthClientModel(sequelize, options = {}) {
32
21
  metadata: { type: sequelize_1.DataTypes.TEXT, allowNull: true, defaultValue: null },
33
22
  first_party: { type: sequelize_1.DataTypes.BOOLEAN, allowNull: false, defaultValue: false }
34
23
  }, {
35
- ...tableOptions(sequelize, 'oauth_clients', options.tablePrefix, { timestamps: false })
24
+ ...(0, sequelize_utils_js_1.tableOptions)(sequelize, 'oauth_clients', options.tablePrefix, { timestamps: false })
36
25
  });
37
26
  return OAuthClientModel;
38
27
  }
@@ -40,7 +29,7 @@ class OAuthCodeModel extends sequelize_1.Model {
40
29
  }
41
30
  exports.OAuthCodeModel = OAuthCodeModel;
42
31
  function initOAuthCodeModel(sequelize, options = {}) {
43
- const idType = integerIdType(sequelize);
32
+ const idType = (0, sequelize_utils_js_1.integerIdType)(sequelize);
44
33
  OAuthCodeModel.init({
45
34
  code: { type: sequelize_1.DataTypes.STRING(128), allowNull: false, primaryKey: true },
46
35
  client_id: { type: sequelize_1.DataTypes.STRING(128), allowNull: false },
@@ -52,7 +41,7 @@ function initOAuthCodeModel(sequelize, options = {}) {
52
41
  expires: { type: sequelize_1.DataTypes.DATE, allowNull: false },
53
42
  metadata: { type: sequelize_1.DataTypes.TEXT, allowNull: true, defaultValue: null }
54
43
  }, {
55
- ...tableOptions(sequelize, 'oauth_codes', options.tablePrefix, { timestamps: false })
44
+ ...(0, sequelize_utils_js_1.tableOptions)(sequelize, 'oauth_codes', options.tablePrefix, { timestamps: false })
56
45
  });
57
46
  return OAuthCodeModel;
58
47
  }
@@ -1,66 +1,19 @@
1
- import { Model, type Optional, type Sequelize } from 'sequelize';
2
1
  import { OAuthStore, type AuthCode, type OAuthClient } from './base.js';
3
- export interface OAuthClientAttributes {
4
- client_id: string;
5
- client_secret: string;
6
- name: string | null;
7
- redirect_uris: string;
8
- scope: string;
9
- metadata: string | null;
10
- first_party: boolean;
11
- }
12
- export type OAuthClientCreationAttributes = Optional<OAuthClientAttributes, 'client_secret' | 'name' | 'scope' | 'metadata' | 'first_party'>;
13
- export declare class OAuthClientModel extends Model<OAuthClientAttributes, OAuthClientCreationAttributes> implements OAuthClientAttributes {
14
- client_id: string;
15
- client_secret: string;
16
- name: string | null;
17
- redirect_uris: string;
18
- scope: string;
19
- metadata: string | null;
20
- first_party: boolean;
21
- }
22
- export declare function initOAuthClientModel(sequelize: Sequelize, options?: {
23
- tablePrefix?: string;
24
- }): typeof OAuthClientModel;
25
- export interface OAuthCodeAttributes {
26
- code: string;
27
- client_id: string;
28
- user_id: number;
29
- redirect_uri: string;
30
- scope: string;
31
- code_challenge: string | null;
32
- code_challenge_method: 'plain' | 'S256' | null;
33
- expires: Date;
34
- metadata: string | null;
35
- }
36
- export type OAuthCodeCreationAttributes = Optional<OAuthCodeAttributes, 'code_challenge' | 'code_challenge_method' | 'metadata'>;
37
- export declare class OAuthCodeModel extends Model<OAuthCodeAttributes, OAuthCodeCreationAttributes> implements OAuthCodeAttributes {
38
- code: string;
39
- client_id: string;
40
- user_id: number;
41
- redirect_uri: string;
42
- scope: string;
43
- code_challenge: string | null;
44
- code_challenge_method: 'plain' | 'S256' | null;
45
- expires: Date;
46
- metadata: string | null;
47
- }
48
- export declare function initOAuthCodeModel(sequelize: Sequelize, options?: {
49
- tablePrefix?: string;
50
- }): typeof OAuthCodeModel;
2
+ import { OAuthClientModel, OAuthCodeModel } from './models.js';
51
3
  export interface SequelizeOAuthStoreOptions {
52
- sequelize: Sequelize;
4
+ sequelize: import('sequelize').Sequelize;
53
5
  tablePrefix?: string;
54
6
  clientModel?: typeof OAuthClientModel;
55
7
  codeModel?: typeof OAuthCodeModel;
56
- clientModelFactory?: (sequelize: Sequelize, options?: {
8
+ clientModelFactory?: (sequelize: import('sequelize').Sequelize, options?: {
57
9
  tablePrefix?: string;
58
10
  }) => typeof OAuthClientModel;
59
- codeModelFactory?: (sequelize: Sequelize, options?: {
11
+ codeModelFactory?: (sequelize: import('sequelize').Sequelize, options?: {
60
12
  tablePrefix?: string;
61
13
  }) => typeof OAuthCodeModel;
62
14
  bcryptRounds?: number;
63
15
  }
16
+ export { OAuthClientModel, OAuthCodeModel, initOAuthClientModel, initOAuthCodeModel, type OAuthClientAttributes, type OAuthClientCreationAttributes, type OAuthCodeAttributes, type OAuthCodeCreationAttributes } from './models.js';
64
17
  export declare class SequelizeOAuthStore extends OAuthStore {
65
18
  private readonly clients;
66
19
  private readonly codes;