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

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 (177) hide show
  1. package/dist/cjs/common/types.cjs +10 -0
  2. package/dist/cjs/common/types.d.ts +137 -0
  3. package/dist/cjs/{api-module.cjs → server/src/api-module.cjs} +8 -0
  4. package/dist/{esm → cjs/server/src}/api-module.d.ts +15 -0
  5. package/dist/cjs/{api-server-base.cjs → server/src/api-server-base.cjs} +669 -627
  6. package/dist/{esm → cjs/server/src}/api-server-base.d.ts +105 -78
  7. package/dist/cjs/{auth-api/auth-module.js → server/src/auth-api/auth-module.cjs} +96 -76
  8. package/dist/cjs/{auth-api → server/src/auth-api}/auth-module.d.ts +1 -1
  9. package/dist/cjs/{auth-api/compat-auth-storage.js → server/src/auth-api/compat-auth-storage.cjs} +4 -4
  10. package/dist/cjs/{auth-api/mem-auth-store.js → server/src/auth-api/mem-auth-store.cjs} +7 -7
  11. package/dist/cjs/{auth-api/module.js → server/src/auth-api/module.cjs} +1 -1
  12. package/dist/cjs/server/src/auth-api/schemas.cjs +171 -0
  13. package/dist/cjs/server/src/auth-api/schemas.d.ts +21 -0
  14. package/dist/cjs/{auth-api/sql-auth-store.js → server/src/auth-api/sql-auth-store.cjs} +8 -8
  15. package/dist/cjs/{auth-api/user-id.js → server/src/auth-api/user-id.cjs} +12 -3
  16. package/dist/{esm → cjs/server/src}/auth-cookie-options.d.ts +5 -3
  17. package/dist/cjs/server/src/base/client-info.cjs +285 -0
  18. package/dist/cjs/server/src/base/client-info.d.ts +27 -0
  19. package/dist/cjs/server/src/base/error-utils.cjs +50 -0
  20. package/dist/cjs/server/src/base/error-utils.d.ts +16 -0
  21. package/dist/cjs/server/src/base/request-utils.cjs +27 -0
  22. package/dist/cjs/server/src/base/request-utils.d.ts +8 -0
  23. package/dist/cjs/{index.cjs → server/src/index.cjs} +24 -15
  24. package/dist/{esm → cjs/server/src}/index.d.ts +7 -0
  25. package/dist/cjs/server/src/limiter/auth-rate-limiter.cjs +35 -0
  26. package/dist/cjs/server/src/limiter/auth-rate-limiter.d.ts +12 -0
  27. package/dist/cjs/server/src/limiter/fixed-window.cjs +41 -0
  28. package/dist/cjs/server/src/limiter/fixed-window.d.ts +11 -0
  29. package/dist/cjs/{oauth/base.js → server/src/oauth/base.cjs} +1 -0
  30. package/dist/cjs/{oauth → server/src/oauth}/base.d.ts +8 -1
  31. package/dist/cjs/{oauth/memory.js → server/src/oauth/memory.cjs} +7 -4
  32. package/dist/{esm → cjs/server/src}/oauth/memory.d.ts +1 -1
  33. package/dist/cjs/{oauth/models.js → server/src/oauth/models.cjs} +2 -2
  34. package/dist/cjs/{oauth/sequelize.js → server/src/oauth/sequelize.cjs} +11 -7
  35. package/dist/{esm → cjs/server/src}/oauth/sequelize.d.ts +1 -1
  36. package/dist/cjs/{passkey/base.js → server/src/passkey/base.cjs} +1 -0
  37. package/dist/{esm → cjs/server/src}/passkey/base.d.ts +11 -0
  38. package/dist/cjs/{passkey/memory.js → server/src/passkey/memory.cjs} +2 -2
  39. package/dist/cjs/{passkey/models.js → server/src/passkey/models.cjs} +1 -1
  40. package/dist/cjs/{passkey/sequelize.js → server/src/passkey/sequelize.cjs} +3 -3
  41. package/dist/cjs/{passkey/service.js → server/src/passkey/service.cjs} +17 -3
  42. package/dist/{esm → cjs/server/src}/passkey/service.d.ts +1 -1
  43. package/dist/cjs/{sequelize-utils.js → server/src/sequelize-utils.cjs} +4 -5
  44. package/dist/cjs/{token/base.js → server/src/token/base.cjs} +4 -0
  45. package/dist/{esm → cjs/server/src}/token/base.d.ts +7 -0
  46. package/dist/cjs/{token/memory.js → server/src/token/memory.cjs} +15 -20
  47. package/dist/cjs/{token/sequelize.js → server/src/token/sequelize.cjs} +25 -11
  48. package/dist/cjs/server/src/upload/memory.cjs +92 -0
  49. package/dist/cjs/server/src/upload/memory.d.ts +17 -0
  50. package/dist/cjs/server/src/upload/tus-module.cjs +270 -0
  51. package/dist/cjs/server/src/upload/tus-module.d.ts +38 -0
  52. package/dist/cjs/server/src/upload/types.d.ts +8 -0
  53. package/dist/cjs/{user/base.js → server/src/user/base.cjs} +1 -0
  54. package/dist/cjs/{user → server/src/user}/base.d.ts +9 -0
  55. package/dist/cjs/{user/memory.js → server/src/user/memory.cjs} +29 -7
  56. package/dist/cjs/{user/sequelize.js → server/src/user/sequelize.cjs} +33 -8
  57. package/dist/cjs/server/src/user/types.cjs +2 -0
  58. package/dist/esm/common/types.d.ts +137 -0
  59. package/dist/esm/common/types.js +9 -0
  60. package/dist/{cjs → esm/server/src}/api-module.d.ts +15 -0
  61. package/dist/esm/{api-module.js → server/src/api-module.js} +8 -0
  62. package/dist/{cjs → esm/server/src}/api-server-base.d.ts +105 -78
  63. package/dist/esm/{api-server-base.js → server/src/api-server-base.js} +658 -616
  64. package/dist/esm/{auth-api → server/src/auth-api}/auth-module.d.ts +1 -1
  65. package/dist/esm/{auth-api → server/src/auth-api}/auth-module.js +92 -72
  66. package/dist/esm/{auth-api → server/src/auth-api}/compat-auth-storage.js +3 -3
  67. package/dist/esm/server/src/auth-api/schemas.d.ts +21 -0
  68. package/dist/esm/server/src/auth-api/schemas.js +168 -0
  69. package/dist/esm/{auth-api → server/src/auth-api}/user-id.js +12 -3
  70. package/dist/{cjs → esm/server/src}/auth-cookie-options.d.ts +5 -3
  71. package/dist/esm/server/src/base/client-info.d.ts +27 -0
  72. package/dist/esm/server/src/base/client-info.js +282 -0
  73. package/dist/esm/server/src/base/error-utils.d.ts +16 -0
  74. package/dist/esm/server/src/base/error-utils.js +44 -0
  75. package/dist/esm/server/src/base/request-utils.d.ts +8 -0
  76. package/dist/esm/server/src/base/request-utils.js +23 -0
  77. package/dist/{cjs → esm/server/src}/index.d.ts +7 -0
  78. package/dist/esm/{index.js → server/src/index.js} +4 -0
  79. package/dist/esm/server/src/limiter/auth-rate-limiter.d.ts +12 -0
  80. package/dist/esm/server/src/limiter/auth-rate-limiter.js +32 -0
  81. package/dist/esm/server/src/limiter/fixed-window.d.ts +11 -0
  82. package/dist/esm/server/src/limiter/fixed-window.js +37 -0
  83. package/dist/esm/{oauth → server/src/oauth}/base.d.ts +8 -1
  84. package/dist/esm/server/src/oauth/base.js +3 -0
  85. package/dist/{cjs → esm/server/src}/oauth/memory.d.ts +1 -1
  86. package/dist/esm/{oauth → server/src/oauth}/memory.js +5 -2
  87. package/dist/{cjs → esm/server/src}/oauth/sequelize.d.ts +1 -1
  88. package/dist/esm/{oauth → server/src/oauth}/sequelize.js +6 -2
  89. package/dist/{cjs → esm/server/src}/passkey/base.d.ts +11 -0
  90. package/dist/esm/server/src/passkey/base.js +3 -0
  91. package/dist/{cjs → esm/server/src}/passkey/service.d.ts +1 -1
  92. package/dist/esm/{passkey → server/src/passkey}/service.js +17 -3
  93. package/dist/esm/{sequelize-utils.js → server/src/sequelize-utils.js} +4 -5
  94. package/dist/{cjs → esm/server/src}/token/base.d.ts +7 -0
  95. package/dist/esm/{token → server/src/token}/base.js +4 -0
  96. package/dist/esm/{token → server/src/token}/memory.js +14 -19
  97. package/dist/esm/{token → server/src/token}/sequelize.js +22 -8
  98. package/dist/esm/server/src/upload/memory.d.ts +17 -0
  99. package/dist/esm/server/src/upload/memory.js +86 -0
  100. package/dist/esm/server/src/upload/tus-module.d.ts +38 -0
  101. package/dist/esm/server/src/upload/tus-module.js +266 -0
  102. package/dist/esm/server/src/upload/types.d.ts +8 -0
  103. package/dist/esm/{user → server/src/user}/base.d.ts +9 -0
  104. package/dist/esm/{user → server/src/user}/base.js +1 -0
  105. package/dist/esm/{user → server/src/user}/memory.js +27 -5
  106. package/dist/esm/{user → server/src/user}/sequelize.js +30 -5
  107. package/dist/esm/server/src/user/types.js +1 -0
  108. package/docs/swagger/openapi.json +411 -125
  109. package/package.json +129 -134
  110. package/README.txt +0 -213
  111. package/dist/esm/oauth/base.js +0 -2
  112. package/dist/esm/passkey/base.js +0 -2
  113. /package/dist/cjs/{auth-api → server/src/auth-api}/compat-auth-storage.d.ts +0 -0
  114. /package/dist/cjs/{auth-api → server/src/auth-api}/mem-auth-store.d.ts +0 -0
  115. /package/dist/cjs/{auth-api → server/src/auth-api}/module.d.ts +0 -0
  116. /package/dist/cjs/{auth-api → server/src/auth-api}/sql-auth-store.d.ts +0 -0
  117. /package/dist/cjs/{auth-api/storage.js → server/src/auth-api/storage.cjs} +0 -0
  118. /package/dist/cjs/{auth-api → server/src/auth-api}/storage.d.ts +0 -0
  119. /package/dist/cjs/{auth-api/types.js → server/src/auth-api/types.cjs} +0 -0
  120. /package/dist/cjs/{auth-api → server/src/auth-api}/types.d.ts +0 -0
  121. /package/dist/cjs/{auth-api → server/src/auth-api}/user-id.d.ts +0 -0
  122. /package/dist/cjs/{auth-cookie-options.js → server/src/auth-cookie-options.cjs} +0 -0
  123. /package/dist/cjs/{oauth → server/src/oauth}/models.d.ts +0 -0
  124. /package/dist/cjs/{oauth/types.js → server/src/oauth/types.cjs} +0 -0
  125. /package/dist/cjs/{oauth → server/src/oauth}/types.d.ts +0 -0
  126. /package/dist/cjs/{passkey/config.js → server/src/passkey/config.cjs} +0 -0
  127. /package/dist/cjs/{passkey → server/src/passkey}/config.d.ts +0 -0
  128. /package/dist/cjs/{passkey → server/src/passkey}/memory.d.ts +0 -0
  129. /package/dist/cjs/{passkey → server/src/passkey}/models.d.ts +0 -0
  130. /package/dist/cjs/{passkey → server/src/passkey}/sequelize.d.ts +0 -0
  131. /package/dist/cjs/{passkey/types.js → server/src/passkey/types.cjs} +0 -0
  132. /package/dist/cjs/{passkey → server/src/passkey}/types.d.ts +0 -0
  133. /package/dist/cjs/{sequelize-utils.d.ts → server/src/sequelize-utils.d.ts} +0 -0
  134. /package/dist/cjs/{token → server/src/token}/memory.d.ts +0 -0
  135. /package/dist/cjs/{token → server/src/token}/sequelize.d.ts +0 -0
  136. /package/dist/cjs/{token/types.js → server/src/token/types.cjs} +0 -0
  137. /package/dist/cjs/{token → server/src/token}/types.d.ts +0 -0
  138. /package/dist/cjs/{user/types.js → server/src/upload/types.cjs} +0 -0
  139. /package/dist/cjs/{user → server/src/user}/memory.d.ts +0 -0
  140. /package/dist/cjs/{user → server/src/user}/sequelize.d.ts +0 -0
  141. /package/dist/cjs/{user → server/src/user}/types.d.ts +0 -0
  142. /package/dist/esm/{auth-api → server/src/auth-api}/compat-auth-storage.d.ts +0 -0
  143. /package/dist/esm/{auth-api → server/src/auth-api}/mem-auth-store.d.ts +0 -0
  144. /package/dist/esm/{auth-api → server/src/auth-api}/mem-auth-store.js +0 -0
  145. /package/dist/esm/{auth-api → server/src/auth-api}/module.d.ts +0 -0
  146. /package/dist/esm/{auth-api → server/src/auth-api}/module.js +0 -0
  147. /package/dist/esm/{auth-api → server/src/auth-api}/sql-auth-store.d.ts +0 -0
  148. /package/dist/esm/{auth-api → server/src/auth-api}/sql-auth-store.js +0 -0
  149. /package/dist/esm/{auth-api → server/src/auth-api}/storage.d.ts +0 -0
  150. /package/dist/esm/{auth-api → server/src/auth-api}/storage.js +0 -0
  151. /package/dist/esm/{auth-api → server/src/auth-api}/types.d.ts +0 -0
  152. /package/dist/esm/{auth-api → server/src/auth-api}/types.js +0 -0
  153. /package/dist/esm/{auth-api → server/src/auth-api}/user-id.d.ts +0 -0
  154. /package/dist/esm/{auth-cookie-options.js → server/src/auth-cookie-options.js} +0 -0
  155. /package/dist/esm/{oauth → server/src/oauth}/models.d.ts +0 -0
  156. /package/dist/esm/{oauth → server/src/oauth}/models.js +0 -0
  157. /package/dist/esm/{oauth → server/src/oauth}/types.d.ts +0 -0
  158. /package/dist/esm/{oauth → server/src/oauth}/types.js +0 -0
  159. /package/dist/esm/{passkey → server/src/passkey}/config.d.ts +0 -0
  160. /package/dist/esm/{passkey → server/src/passkey}/config.js +0 -0
  161. /package/dist/esm/{passkey → server/src/passkey}/memory.d.ts +0 -0
  162. /package/dist/esm/{passkey → server/src/passkey}/memory.js +0 -0
  163. /package/dist/esm/{passkey → server/src/passkey}/models.d.ts +0 -0
  164. /package/dist/esm/{passkey → server/src/passkey}/models.js +0 -0
  165. /package/dist/esm/{passkey → server/src/passkey}/sequelize.d.ts +0 -0
  166. /package/dist/esm/{passkey → server/src/passkey}/sequelize.js +0 -0
  167. /package/dist/esm/{passkey → server/src/passkey}/types.d.ts +0 -0
  168. /package/dist/esm/{passkey → server/src/passkey}/types.js +0 -0
  169. /package/dist/esm/{sequelize-utils.d.ts → server/src/sequelize-utils.d.ts} +0 -0
  170. /package/dist/esm/{token → server/src/token}/memory.d.ts +0 -0
  171. /package/dist/esm/{token → server/src/token}/sequelize.d.ts +0 -0
  172. /package/dist/esm/{token → server/src/token}/types.d.ts +0 -0
  173. /package/dist/esm/{token → server/src/token}/types.js +0 -0
  174. /package/dist/esm/{user → server/src/upload}/types.js +0 -0
  175. /package/dist/esm/{user → server/src/user}/memory.d.ts +0 -0
  176. /package/dist/esm/{user → server/src/user}/sequelize.d.ts +0 -0
  177. /package/dist/esm/{user → server/src/user}/types.d.ts +0 -0
@@ -2,12 +2,17 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const node_crypto_1 = require("node:crypto");
4
4
  const helpers_1 = require("@simplewebauthn/server/helpers");
5
- const api_server_base_js_1 = require("../api-server-base.js");
6
- const auth_cookie_options_js_1 = require("../auth-cookie-options.js");
7
- const module_js_1 = require("./module.js");
8
- const storage_js_1 = require("./storage.js");
5
+ const api_server_base_js_1 = require("../api-server-base.cjs");
6
+ const auth_cookie_options_js_1 = require("../auth-cookie-options.cjs");
7
+ const module_js_1 = require("./module.cjs");
8
+ const schemas_js_1 = require("./schemas.cjs");
9
+ const storage_js_1 = require("./storage.cjs");
9
10
  function isAuthIdentifier(value) {
10
- return typeof value === 'string' || typeof value === 'number';
11
+ if (typeof value === 'string')
12
+ return value.length > 0;
13
+ if (typeof value === 'number')
14
+ return Number.isFinite(value);
15
+ return false;
11
16
  }
12
17
  function toStringOrNull(value) {
13
18
  if (typeof value === 'string') {
@@ -44,7 +49,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
44
49
  this.defaultDomain = options.defaultDomain;
45
50
  this.canImpersonateHook = options.canImpersonate;
46
51
  this.rateLimitHook = options.rateLimit;
47
- this.allowInsecurePkcePlain = options.allowInsecurePkcePlain ?? true;
52
+ this.allowInsecurePkcePlain = options.allowInsecurePkcePlain ?? false;
48
53
  }
49
54
  get storage() {
50
55
  return this.server.getAuthStorage();
@@ -60,16 +65,17 @@ class AuthModule extends module_js_1.BaseAuthModule {
60
65
  targetUser,
61
66
  effectiveUserId
62
67
  });
63
- if (allowed) {
68
+ if (allowed === true)
64
69
  return true;
65
- }
70
+ if (allowed === false)
71
+ return false;
66
72
  }
67
73
  const storageWithHook = this.storage;
68
74
  if (typeof storageWithHook.canImpersonate === 'function') {
69
75
  const allowed = await storageWithHook.canImpersonate({ realUserId, effectiveUserId });
70
76
  return !!allowed;
71
77
  }
72
- return realUserId === effectiveUserId;
78
+ return String(realUserId) === String(effectiveUserId);
73
79
  }
74
80
  async ensureImpersonationAllowed(apiReq, realUser, targetUser) {
75
81
  const permitted = await this.canImpersonate(apiReq, realUser, targetUser);
@@ -257,6 +263,9 @@ class AuthModule extends module_js_1.BaseAuthModule {
257
263
  const accessMaxAge = Math.max(1, conf.accessExpiry) * 1000;
258
264
  const refreshSeconds = Math.max(1, preferences.refreshTtlSeconds ?? conf.refreshExpiry);
259
265
  const refreshMaxAge = refreshSeconds * 1000;
266
+ // When sessionCookie is true we omit maxAge so the browser deletes the
267
+ // cookie on close. The server-side JWT still has its own expiry, which
268
+ // limits exposure if the browser crashes without running its cleanup.
260
269
  const accessOptions = sessionCookie ? options : { ...options, maxAge: accessMaxAge };
261
270
  const refreshOptions = sessionCookie ? options : { ...options, maxAge: refreshMaxAge };
262
271
  if (tokens.accessToken) {
@@ -346,19 +355,10 @@ class AuthModule extends module_js_1.BaseAuthModule {
346
355
  }
347
356
  parseLoginBody(apiReq) {
348
357
  const body = (apiReq.req.body ?? {});
349
- const login = toStringOrNull(body.login);
350
- const password = toStringOrNull(body.password);
358
+ // login and password are guaranteed present and non-empty by JSON Schema
359
+ const login = body.login;
360
+ const password = body.password;
351
361
  const sessionPrefs = this.resolveSessionPreferences(body.keepSession);
352
- if (!login || !password) {
353
- const errors = {};
354
- if (!login) {
355
- errors.login = 'Login is required';
356
- }
357
- if (!password) {
358
- errors.password = 'Password is required';
359
- }
360
- throw new api_server_base_js_1.ApiError({ code: 400, message: 'Missing credentials', errors });
361
- }
362
362
  return {
363
363
  login,
364
364
  password,
@@ -453,7 +453,9 @@ class AuthModule extends module_js_1.BaseAuthModule {
453
453
  const { login, password, ...metadata } = this.parseLoginBody(apiReq);
454
454
  const user = await this.storage.getUser(login);
455
455
  const hash = user ? this.storage.getUserPasswordHash(user) : '';
456
- const verified = user ? await this.storage.verifyPassword(password, hash) : false;
456
+ // Reject users with no password hash (e.g. OAuth/passkey-only accounts) before
457
+ // calling verifyPassword, since bcrypt behaviour on an empty hash is undefined.
458
+ const verified = user && hash ? await this.storage.verifyPassword(password, hash) : false;
457
459
  if (!user || !verified) {
458
460
  throw new api_server_base_js_1.ApiError({
459
461
  code: 400,
@@ -484,6 +486,13 @@ class AuthModule extends module_js_1.BaseAuthModule {
484
486
  message: verify.error ?? 'Unable to verify refresh token'
485
487
  });
486
488
  }
489
+ // Delete the token immediately after verification to narrow the TOCTOU window.
490
+ // This must happen before the slower getUserOrThrow call.
491
+ const deleted = await this.storage.deleteToken({ refreshToken: providedToken });
492
+ if (deleted === 0) {
493
+ // Another concurrent request already consumed this refresh token.
494
+ throw new api_server_base_js_1.ApiError({ code: 401, message: 'Invalid refresh token' });
495
+ }
487
496
  const user = await this.getUserOrThrow(stored.userId ?? verify.data.uid, 'User not found');
488
497
  const metadata = {
489
498
  domain: stored.domain,
@@ -499,7 +508,6 @@ class AuthModule extends module_js_1.BaseAuthModule {
499
508
  refreshTtlSeconds: sessionPrefs.refreshTtlSeconds ?? stored.refreshTtlSeconds,
500
509
  sessionCookie: sessionPrefs.sessionCookie ?? stored.sessionCookie
501
510
  };
502
- await this.storage.deleteToken({ refreshToken: providedToken });
503
511
  const pair = await this.issueTokens(apiReq, user, metadata);
504
512
  const publicUser = this.storage.filterUser(user);
505
513
  return [200, { ...pair, user: publicUser }];
@@ -539,8 +547,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
539
547
  apiReq.req.cookies[conf.accessCookie].trim().length > 0);
540
548
  const shouldRefresh = Boolean(body.refresh) || !hasAccessToken;
541
549
  if (shouldRefresh) {
542
- const updateToken = this.storage.updateToken;
543
- if (typeof updateToken !== 'function' || !this.storageImplements('updateToken')) {
550
+ if (typeof this.storage.updateToken !== 'function' || !this.storageImplements('updateToken')) {
544
551
  throw new api_server_base_js_1.ApiError({ code: 501, message: 'Token update storage is not configured' });
545
552
  }
546
553
  // Sign a new access token without embedding stored token secrets into the JWT payload.
@@ -564,7 +571,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
564
571
  if (!access.success || !access.token) {
565
572
  throw new api_server_base_js_1.ApiError({ code: 500, message: access.error ?? 'Unable to sign access token' });
566
573
  }
567
- const updated = await updateToken.call(this.storage, {
574
+ const updated = await this.storage.updateToken({
568
575
  refreshToken,
569
576
  accessToken: access.token,
570
577
  lastSeenAt: new Date()
@@ -610,12 +617,10 @@ class AuthModule extends module_js_1.BaseAuthModule {
610
617
  throw new api_server_base_js_1.ApiError({ code: 501, message: 'Passkey support is not configured' });
611
618
  }
612
619
  const body = (apiReq.req.body ?? {});
613
- const action = toStringOrNull(body.action);
614
- if (action !== 'register' && action !== 'authenticate') {
615
- throw new api_server_base_js_1.ApiError({ code: 400, message: 'Passkey action must be "register" or "authenticate"' });
616
- }
620
+ // action is guaranteed to be 'register' | 'authenticate' by JSON Schema
621
+ const action = body.action;
617
622
  const params = {
618
- action,
623
+ action: action,
619
624
  login: toStringOrNull(body.login) ?? undefined,
620
625
  userId: isAuthIdentifier(body.userId) ? body.userId : undefined
621
626
  };
@@ -628,11 +633,9 @@ class AuthModule extends module_js_1.BaseAuthModule {
628
633
  }
629
634
  const body = (apiReq.req.body ?? {});
630
635
  const sessionPrefs = this.resolveSessionPreferences(body.keepSession);
631
- const expectedChallenge = toStringOrNull(body.expectedChallenge);
636
+ // expectedChallenge (string) and response (object) are guaranteed by JSON Schema
637
+ const expectedChallenge = body.expectedChallenge;
632
638
  const response = body.response;
633
- if (!expectedChallenge || typeof response !== 'object' || response === null) {
634
- throw new api_server_base_js_1.ApiError({ code: 400, message: 'Malformed passkey verification payload' });
635
- }
636
639
  const rawMetadata = {
637
640
  domain: toStringOrNull(body.domain) ?? undefined,
638
641
  fingerprint: toStringOrNull(body.fingerprint) ?? undefined,
@@ -742,6 +745,15 @@ class AuthModule extends module_js_1.BaseAuthModule {
742
745
  }
743
746
  async deleteImpersonation(apiReq) {
744
747
  this.assertAuthReady();
748
+ if (!apiReq.isImpersonating()) {
749
+ throw new api_server_base_js_1.ApiError({ code: 400, message: 'Not currently impersonating' });
750
+ }
751
+ // Revoke the active impersonation refresh token before issuing new real-user tokens
752
+ // so that a captured impersonation token cannot be reused after impersonation ends.
753
+ const impersonationRefreshToken = this.extractRefreshToken(apiReq, {});
754
+ if (impersonationRefreshToken) {
755
+ await this.storage.deleteToken({ refreshToken: impersonationRefreshToken });
756
+ }
745
757
  const actor = await this.resolveActorContext(apiReq);
746
758
  const query = (apiReq.req.query ?? {});
747
759
  const metadata = this.buildImpersonationMetadata(query);
@@ -778,9 +790,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
778
790
  ? apiReq.req.body.extras
779
791
  : undefined
780
792
  };
781
- if (!params.provider) {
782
- throw new api_server_base_js_1.ApiError({ code: 400, message: 'OAuth provider is required' });
783
- }
793
+ // provider is guaranteed present and non-empty by params schema
784
794
  const result = await this.server.initiateOAuth(params);
785
795
  return [200, result];
786
796
  }
@@ -793,9 +803,6 @@ class AuthModule extends module_js_1.BaseAuthModule {
793
803
  query: apiReq.req.query,
794
804
  body: (apiReq.req.body ?? {})
795
805
  };
796
- if (!params.provider) {
797
- throw new api_server_base_js_1.ApiError({ code: 400, message: 'OAuth provider is required' });
798
- }
799
806
  const result = await this.server.completeOAuth(params);
800
807
  if (result.tokens?.accessToken && result.tokens.refreshToken) {
801
808
  this.setJwtCookies(apiReq, {
@@ -809,20 +816,16 @@ class AuthModule extends module_js_1.BaseAuthModule {
809
816
  if (typeof this.storage.getClient !== 'function' || typeof this.storage.createAuthCode !== 'function') {
810
817
  throw new api_server_base_js_1.ApiError({ code: 501, message: 'OAuth authorization storage is not configured' });
811
818
  }
819
+ await this.applyRateLimit(apiReq, 'oauth-authorize');
812
820
  const body = (apiReq.req.body ?? {});
813
- const clientId = toStringOrNull(body.clientId);
814
- const redirectUri = toStringOrNull(body.redirectUri);
821
+ // clientId and redirectUri are guaranteed present and non-empty by JSON Schema
822
+ const clientId = body.clientId;
823
+ const redirectUri = body.redirectUri;
815
824
  const scope = toScopeArray(body.scope) ?? [];
816
825
  const state = toStringOrNull(body.state) ?? undefined;
817
826
  const codeChallenge = toStringOrNull(body.codeChallenge) ?? undefined;
818
827
  const codeChallengeMethod = toStringOrNull(body.codeChallengeMethod) ?? undefined;
819
828
  const resolvedCodeChallengeMethod = this.resolvePkceChallengeMethod(codeChallengeMethod);
820
- if (!clientId) {
821
- throw new api_server_base_js_1.ApiError({ code: 400, message: 'clientId is required' });
822
- }
823
- if (!redirectUri) {
824
- throw new api_server_base_js_1.ApiError({ code: 400, message: 'redirectUri is required' });
825
- }
826
829
  const client = await this.storage.getClient(clientId);
827
830
  if (!client) {
828
831
  throw new api_server_base_js_1.ApiError({ code: 400, message: 'Unknown client_id' });
@@ -852,10 +855,8 @@ class AuthModule extends module_js_1.BaseAuthModule {
852
855
  throw new api_server_base_js_1.ApiError({ code: 501, message: 'OAuth token storage is not configured' });
853
856
  }
854
857
  const body = (apiReq.req.body ?? {});
855
- const grantType = toStringOrNull(body.grant_type);
856
- if (!grantType) {
857
- throw new api_server_base_js_1.ApiError({ code: 400, message: 'grant_type is required' });
858
- }
858
+ // grant_type is guaranteed to be 'authorization_code' | 'refresh_token' by JSON Schema
859
+ const grantType = body.grant_type;
859
860
  const { client, clientSecretProvided } = await this.resolveClientAuthentication(apiReq, body);
860
861
  switch (grantType) {
861
862
  case 'authorization_code':
@@ -909,7 +910,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
909
910
  }
910
911
  }
911
912
  }
912
- else if (!clientSecretProvided && (client.hasSecret ?? Boolean(client.clientSecret))) {
913
+ else if (!clientSecretProvided && (client.hasSecret ?? false)) {
913
914
  throw new api_server_base_js_1.ApiError({ code: 400, message: 'Client authentication required when no PKCE challenge present' });
914
915
  }
915
916
  const user = await this.getUserOrThrow(record.userId, 'User not found');
@@ -943,8 +944,12 @@ class AuthModule extends module_js_1.BaseAuthModule {
943
944
  if (stored.clientId && stored.clientId !== client.clientId) {
944
945
  throw new api_server_base_js_1.ApiError({ code: 400, message: 'Refresh token issued to another client' });
945
946
  }
947
+ // Delete the token immediately after verification to narrow the TOCTOU window.
948
+ const deleted = await this.storage.deleteToken({ refreshToken });
949
+ if (deleted === 0) {
950
+ throw new api_server_base_js_1.ApiError({ code: 401, message: 'Invalid refresh token' });
951
+ }
946
952
  const user = await this.getUserOrThrow(stored.userId ?? verify.data.uid, 'User not found');
947
- await this.storage.deleteToken({ refreshToken });
948
953
  const tokens = await this.issueTokens(apiReq, user, {
949
954
  clientId: client.clientId,
950
955
  scope: stored.scope,
@@ -1016,7 +1021,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
1016
1021
  if (!client) {
1017
1022
  throw new api_server_base_js_1.ApiError({ code: 400, message: 'Unknown client_id' });
1018
1023
  }
1019
- const requiresSecret = client.hasSecret ?? Boolean(client.clientSecret);
1024
+ const requiresSecret = client.hasSecret ?? false;
1020
1025
  if (requiresSecret) {
1021
1026
  if (!secretProvided) {
1022
1027
  throw new api_server_base_js_1.ApiError({ code: 400, message: 'Client authentication is required' });
@@ -1034,7 +1039,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
1034
1039
  }
1035
1040
  assertRedirectUriAllowed(client, redirectUri) {
1036
1041
  if (client.redirectUris.length === 0) {
1037
- return;
1042
+ throw new api_server_base_js_1.ApiError({ code: 400, message: 'Client has no registered redirect URIs' });
1038
1043
  }
1039
1044
  if (!client.redirectUris.includes(redirectUri)) {
1040
1045
  throw new api_server_base_js_1.ApiError({ code: 400, message: 'redirect_uri not registered for client' });
@@ -1043,9 +1048,13 @@ class AuthModule extends module_js_1.BaseAuthModule {
1043
1048
  async resolveUserForOAuth(apiReq, body) {
1044
1049
  const refreshToken = this.extractRefreshToken(apiReq, body);
1045
1050
  if (refreshToken) {
1051
+ const verify = this.server.jwtVerify(refreshToken, this.server.config.refreshSecret);
1052
+ if (!verify.success || !verify.data) {
1053
+ throw new api_server_base_js_1.ApiError({ code: 401, message: 'Invalid or expired refresh token' });
1054
+ }
1046
1055
  const stored = await this.storage.getToken({ refreshToken });
1047
1056
  if (stored) {
1048
- return this.getUserOrThrow(stored.userId, 'User not found for authorization');
1057
+ return this.getUserOrThrow(stored.userId ?? verify.data.uid, 'User not found for authorization');
1049
1058
  }
1050
1059
  }
1051
1060
  const login = toStringOrNull(body.login);
@@ -1053,7 +1062,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
1053
1062
  if (login && password) {
1054
1063
  const user = await this.storage.getUser(login);
1055
1064
  const hash = user ? this.storage.getUserPasswordHash(user) : '';
1056
- const verified = user ? await this.storage.verifyPassword(password, hash) : false;
1065
+ const verified = user && hash ? await this.storage.verifyPassword(password, hash) : false;
1057
1066
  if (!user || !verified) {
1058
1067
  throw new api_server_base_js_1.ApiError({ code: 400, message: 'Invalid credentials' });
1059
1068
  }
@@ -1069,8 +1078,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
1069
1078
  if (storageHints.adapter?.passkeyService || storageHints.adapter?.passkeyStore) {
1070
1079
  return true;
1071
1080
  }
1072
- const serverHints = this.server;
1073
- return !!serverHints.passkeyServiceAdapter;
1081
+ return false;
1074
1082
  }
1075
1083
  hasOAuthStore() {
1076
1084
  const storageHints = this.storage;
@@ -1080,8 +1088,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
1080
1088
  if (storageHints.adapter?.oauthStore) {
1081
1089
  return true;
1082
1090
  }
1083
- const serverHints = this.server;
1084
- return !!serverHints.oauthStoreAdapter;
1091
+ return false;
1085
1092
  }
1086
1093
  storageImplements(key) {
1087
1094
  const candidate = this.storage[key];
@@ -1131,32 +1138,38 @@ class AuthModule extends module_js_1.BaseAuthModule {
1131
1138
  method: 'post',
1132
1139
  path: '/v1/login',
1133
1140
  handler: (req) => this.postLogin(req),
1134
- auth: { type: 'none', req: 'any' }
1141
+ auth: { type: 'none', req: 'any' },
1142
+ schema: { body: schemas_js_1.loginBodySchema }
1135
1143
  }, {
1136
1144
  method: 'post',
1137
1145
  path: '/v1/refresh',
1138
1146
  handler: (req) => this.postRefresh(req),
1139
- auth: { type: 'none', req: 'any' }
1147
+ auth: { type: 'none', req: 'any' },
1148
+ schema: { body: schemas_js_1.refreshBodySchema }
1140
1149
  }, {
1141
1150
  method: 'post',
1142
1151
  path: '/v1/logout',
1143
1152
  handler: (req) => this.postLogout(req),
1144
- auth: { type: 'maybe', req: 'any' }
1153
+ auth: { type: 'maybe', req: 'any' },
1154
+ schema: { body: schemas_js_1.logoutBodySchema }
1145
1155
  }, {
1146
1156
  method: 'post',
1147
1157
  path: '/v1/whoami',
1148
1158
  handler: (req) => this.postWhoAmI(req),
1149
- auth: { type: 'maybe', req: 'any' }
1159
+ auth: { type: 'maybe', req: 'any' },
1160
+ schema: { body: schemas_js_1.whoamiBodySchema }
1150
1161
  }, {
1151
1162
  method: 'post',
1152
1163
  path: '/v1/impersonations',
1153
1164
  handler: (req) => this.postImpersonation(req),
1154
- auth: { type: 'strict', req: 'any' }
1165
+ auth: { type: 'strict', req: 'any' },
1166
+ schema: { body: schemas_js_1.impersonateBodySchema }
1155
1167
  }, {
1156
1168
  method: 'delete',
1157
1169
  path: '/v1/impersonations',
1158
1170
  handler: (req) => this.deleteImpersonation(req),
1159
- auth: { type: 'strict', req: 'any' }
1171
+ auth: { type: 'strict', req: 'any' },
1172
+ schema: { querystring: schemas_js_1.deleteImpersonationQuerySchema }
1160
1173
  });
1161
1174
  const passkeysSupported = this.hasPasskeyService() &&
1162
1175
  this.storageImplements('createPasskeyChallenge') &&
@@ -1169,12 +1182,14 @@ class AuthModule extends module_js_1.BaseAuthModule {
1169
1182
  method: 'post',
1170
1183
  path: '/v1/passkeys/challenge',
1171
1184
  handler: (req) => this.postPasskeyChallenge(req),
1172
- auth: { type: 'none', req: 'any' }
1185
+ auth: { type: 'none', req: 'any' },
1186
+ schema: { body: schemas_js_1.passkeyChallengeBodySchema }
1173
1187
  }, {
1174
1188
  method: 'post',
1175
1189
  path: '/v1/passkeys/verify',
1176
1190
  handler: (req) => this.postPasskeyVerify(req),
1177
- auth: { type: 'none', req: 'any' }
1191
+ auth: { type: 'none', req: 'any' },
1192
+ schema: { body: schemas_js_1.passkeyVerifyBodySchema }
1178
1193
  });
1179
1194
  if (passkeyCredentialsSupported) {
1180
1195
  routes.push({
@@ -1186,7 +1201,8 @@ class AuthModule extends module_js_1.BaseAuthModule {
1186
1201
  method: 'delete',
1187
1202
  path: '/v1/passkeys/:credentialId',
1188
1203
  handler: (req) => this.deletePasskey(req),
1189
- auth: { type: 'strict', req: 'any' }
1204
+ auth: { type: 'strict', req: 'any' },
1205
+ schema: { params: schemas_js_1.passkeyCredentialParamsSchema }
1190
1206
  });
1191
1207
  }
1192
1208
  }
@@ -1196,12 +1212,14 @@ class AuthModule extends module_js_1.BaseAuthModule {
1196
1212
  method: 'post',
1197
1213
  path: '/v1/oauth2/:provider/start',
1198
1214
  handler: (req) => this.postOAuthStart(req),
1199
- auth: { type: 'none', req: 'any' }
1215
+ auth: { type: 'none', req: 'any' },
1216
+ schema: { body: schemas_js_1.oauthStartBodySchema, params: schemas_js_1.oauthProviderParamsSchema }
1200
1217
  }, {
1201
1218
  method: 'post',
1202
1219
  path: '/v1/oauth2/:provider/callback',
1203
1220
  handler: (req) => this.postOAuthCallback(req),
1204
- auth: { type: 'none', req: 'any' }
1221
+ auth: { type: 'none', req: 'any' },
1222
+ schema: { params: schemas_js_1.oauthProviderParamsSchema }
1205
1223
  });
1206
1224
  }
1207
1225
  const oauthStorageSupported = this.hasOAuthStore() &&
@@ -1213,12 +1231,14 @@ class AuthModule extends module_js_1.BaseAuthModule {
1213
1231
  method: 'post',
1214
1232
  path: '/v1/oauth2/authorize',
1215
1233
  handler: (req) => this.postOAuthAuthorize(req),
1216
- auth: { type: 'maybe', req: 'any' }
1234
+ auth: { type: 'maybe', req: 'any' },
1235
+ schema: { body: schemas_js_1.oauthAuthorizeBodySchema }
1217
1236
  }, {
1218
1237
  method: 'post',
1219
1238
  path: '/v1/oauth2/token',
1220
1239
  handler: (req) => this.postOAuthToken(req),
1221
- auth: { type: 'none', req: 'any' }
1240
+ auth: { type: 'none', req: 'any' },
1241
+ schema: { body: schemas_js_1.oauthTokenBodySchema }
1222
1242
  });
1223
1243
  }
1224
1244
  return routes;
@@ -10,7 +10,7 @@ interface CanImpersonateContext<UserEntity> {
10
10
  targetUser: UserEntity;
11
11
  effectiveUserId: AuthIdentifier;
12
12
  }
13
- type AuthRateLimitEndpoint = 'login' | 'passkey-challenge' | 'oauth-token';
13
+ type AuthRateLimitEndpoint = 'login' | 'passkey-challenge' | 'oauth-token' | 'oauth-authorize';
14
14
  interface AuthModuleOptions<UserEntity> {
15
15
  namespace?: string;
16
16
  defaultDomain?: string;
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CompositeAuthAdapter = void 0;
4
4
  const node_crypto_1 = require("node:crypto");
5
- const service_js_1 = require("../passkey/service.js");
5
+ const service_js_1 = require("../passkey/service.cjs");
6
6
  class CompositeAuthAdapter {
7
7
  constructor(options) {
8
8
  this.userStore = options.userStore;
@@ -112,8 +112,8 @@ class CompositeAuthAdapter {
112
112
  if (!this.oauthStore) {
113
113
  return null;
114
114
  }
115
- const consumed = await this.oauthStore.consumeAuthCode(code);
116
- if (!consumed || consumed.clientId !== clientId) {
115
+ const consumed = await this.oauthStore.consumeAuthCode(code, clientId);
116
+ if (!consumed) {
117
117
  return null;
118
118
  }
119
119
  return consumed;
@@ -122,7 +122,7 @@ class CompositeAuthAdapter {
122
122
  if (this.canImpersonateFn) {
123
123
  return !!(await this.canImpersonateFn(params));
124
124
  }
125
- return params.realUserId === params.effectiveUserId;
125
+ return String(params.realUserId) === String(params.effectiveUserId);
126
126
  }
127
127
  }
128
128
  exports.CompositeAuthAdapter = CompositeAuthAdapter;
@@ -1,13 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MemAuthStore = void 0;
4
- const memory_js_1 = require("../oauth/memory.js");
5
- const config_js_1 = require("../passkey/config.js");
6
- const memory_js_2 = require("../passkey/memory.js");
7
- const memory_js_3 = require("../token/memory.js");
8
- const memory_js_4 = require("../user/memory.js");
9
- const compat_auth_storage_js_1 = require("./compat-auth-storage.js");
10
- const user_id_js_1 = require("./user-id.js");
4
+ const memory_js_1 = require("../oauth/memory.cjs");
5
+ const config_js_1 = require("../passkey/config.cjs");
6
+ const memory_js_2 = require("../passkey/memory.cjs");
7
+ const memory_js_3 = require("../token/memory.cjs");
8
+ const memory_js_4 = require("../user/memory.cjs");
9
+ const compat_auth_storage_js_1 = require("./compat-auth-storage.cjs");
10
+ const user_id_js_1 = require("./user-id.cjs");
11
11
  class MemAuthStore {
12
12
  constructor(params = {}) {
13
13
  this.userStore = new memory_js_4.MemoryUserStore({
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.nullAuthModule = exports.BaseAuthModule = void 0;
4
- const api_module_js_1 = require("../api-module.js");
4
+ const api_module_js_1 = require("../api-module.cjs");
5
5
  // Handy base that you can extend when wiring a real auth module. Subclasses
6
6
  // must supply a namespace via the constructor and implement token issuance.
7
7
  class BaseAuthModule extends api_module_js_1.ApiModule {
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ /**
3
+ * JSON Schema definitions for auth module routes.
4
+ * These are the runtime validation source-of-truth; TypeScript interfaces
5
+ * in auth-module.ts remain for handler-internal typing only.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.oauthTokenBodySchema = exports.oauthAuthorizeBodySchema = exports.oauthStartBodySchema = exports.oauthProviderParamsSchema = exports.deleteImpersonationQuerySchema = exports.impersonateBodySchema = exports.passkeyCredentialParamsSchema = exports.passkeyVerifyBodySchema = exports.passkeyChallengeBodySchema = exports.whoamiBodySchema = exports.logoutBodySchema = exports.refreshBodySchema = exports.loginBodySchema = void 0;
9
+ /* ------------------------------------------------------------------ */
10
+ /* Shared fragments */
11
+ /* ------------------------------------------------------------------ */
12
+ const tokenMetadataProperties = {
13
+ domain: { type: 'string' },
14
+ fingerprint: { type: 'string' },
15
+ label: { type: 'string' },
16
+ browser: { type: 'string' },
17
+ device: { type: 'string' },
18
+ ip: { type: 'string' },
19
+ os: { type: 'string' }
20
+ };
21
+ const keepSessionProperty = {
22
+ keepSession: { type: ['boolean', 'number', 'string'] }
23
+ };
24
+ function authIdentifierProperty(name) {
25
+ return { [name]: { type: ['string', 'number'] } };
26
+ }
27
+ /* ------------------------------------------------------------------ */
28
+ /* Auth route schemas */
29
+ /* ------------------------------------------------------------------ */
30
+ exports.loginBodySchema = {
31
+ type: 'object',
32
+ required: ['login', 'password'],
33
+ properties: {
34
+ login: { type: 'string', minLength: 1 },
35
+ password: { type: 'string', minLength: 1 },
36
+ ...tokenMetadataProperties,
37
+ ...keepSessionProperty
38
+ },
39
+ additionalProperties: true
40
+ };
41
+ exports.refreshBodySchema = {
42
+ type: 'object',
43
+ properties: {
44
+ refreshToken: { type: 'string' },
45
+ domain: { type: 'string' },
46
+ fingerprint: { type: 'string' },
47
+ label: { type: 'string' },
48
+ ...keepSessionProperty
49
+ },
50
+ additionalProperties: false
51
+ };
52
+ exports.logoutBodySchema = {
53
+ type: 'object',
54
+ properties: {
55
+ token: { type: 'string' },
56
+ refreshToken: { type: 'string' }
57
+ },
58
+ additionalProperties: false
59
+ };
60
+ exports.whoamiBodySchema = {
61
+ type: 'object',
62
+ properties: {
63
+ refreshToken: { type: 'string' },
64
+ refresh: { type: 'boolean' }
65
+ },
66
+ additionalProperties: false
67
+ };
68
+ /* ------------------------------------------------------------------ */
69
+ /* Passkey schemas */
70
+ /* ------------------------------------------------------------------ */
71
+ exports.passkeyChallengeBodySchema = {
72
+ type: 'object',
73
+ required: ['action'],
74
+ properties: {
75
+ action: { type: 'string', enum: ['register', 'authenticate'] },
76
+ login: { type: 'string' },
77
+ ...authIdentifierProperty('userId')
78
+ },
79
+ additionalProperties: false
80
+ };
81
+ exports.passkeyVerifyBodySchema = {
82
+ type: 'object',
83
+ required: ['expectedChallenge', 'response'],
84
+ properties: {
85
+ expectedChallenge: { type: 'string' },
86
+ response: { type: 'object' },
87
+ login: { type: 'string' },
88
+ ...authIdentifierProperty('userId'),
89
+ userAgent: { type: 'string' },
90
+ ...tokenMetadataProperties,
91
+ ...keepSessionProperty
92
+ },
93
+ additionalProperties: true
94
+ };
95
+ exports.passkeyCredentialParamsSchema = {
96
+ type: 'object',
97
+ required: ['credentialId'],
98
+ properties: {
99
+ credentialId: { type: 'string', minLength: 1 }
100
+ }
101
+ };
102
+ /* ------------------------------------------------------------------ */
103
+ /* Impersonation schemas */
104
+ /* ------------------------------------------------------------------ */
105
+ exports.impersonateBodySchema = {
106
+ type: 'object',
107
+ properties: {
108
+ ...authIdentifierProperty('userId'),
109
+ login: { type: 'string' },
110
+ ...tokenMetadataProperties,
111
+ ...keepSessionProperty,
112
+ clientId: { type: 'string' },
113
+ scope: {},
114
+ loginType: { type: 'string' }
115
+ },
116
+ additionalProperties: true
117
+ };
118
+ exports.deleteImpersonationQuerySchema = {
119
+ type: 'object',
120
+ additionalProperties: true
121
+ };
122
+ /* ------------------------------------------------------------------ */
123
+ /* OAuth schemas */
124
+ /* ------------------------------------------------------------------ */
125
+ exports.oauthProviderParamsSchema = {
126
+ type: 'object',
127
+ required: ['provider'],
128
+ properties: {
129
+ provider: { type: 'string', minLength: 1 }
130
+ }
131
+ };
132
+ exports.oauthStartBodySchema = {
133
+ type: 'object',
134
+ properties: {
135
+ redirectUri: { type: 'string' },
136
+ scope: {},
137
+ state: { type: 'string' },
138
+ extras: { type: 'object' }
139
+ },
140
+ additionalProperties: true
141
+ };
142
+ exports.oauthAuthorizeBodySchema = {
143
+ type: 'object',
144
+ required: ['clientId', 'redirectUri'],
145
+ properties: {
146
+ clientId: { type: 'string', minLength: 1 },
147
+ redirectUri: { type: 'string', minLength: 1 },
148
+ scope: {},
149
+ state: { type: 'string' },
150
+ codeChallenge: { type: 'string' },
151
+ codeChallengeMethod: { type: 'string' },
152
+ login: { type: 'string' },
153
+ password: { type: 'string' }
154
+ },
155
+ additionalProperties: false
156
+ };
157
+ exports.oauthTokenBodySchema = {
158
+ type: 'object',
159
+ required: ['grant_type'],
160
+ properties: {
161
+ grant_type: { type: 'string', enum: ['authorization_code', 'refresh_token'] },
162
+ code: { type: 'string' },
163
+ redirect_uri: { type: 'string' },
164
+ code_verifier: { type: 'string' },
165
+ client_id: { type: 'string' },
166
+ client_secret: { type: 'string' },
167
+ refresh_token: { type: 'string' },
168
+ scope: { type: 'string' }
169
+ },
170
+ additionalProperties: false
171
+ };