@rebasepro/server-core 0.7.0 → 0.9.0

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 (300) hide show
  1. package/README.md +2 -2
  2. package/dist/api/ast-schema-editor.d.ts +21 -0
  3. package/dist/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
  4. package/dist/api/errors.d.ts +53 -0
  5. package/dist/api/index.d.ts +7 -0
  6. package/dist/api/logs-routes.d.ts +37 -0
  7. package/dist/api/openapi-generator.d.ts +16 -0
  8. package/dist/api/rest/api-generator.d.ts +68 -0
  9. package/{src/api/rest/index.ts → dist/api/rest/index.d.ts} +0 -1
  10. package/dist/api/rest/query-parser.d.ts +6 -0
  11. package/dist/api/schema-editor-routes.d.ts +3 -0
  12. package/dist/api/types.d.ts +56 -0
  13. package/dist/auth/adapter-middleware.d.ts +43 -0
  14. package/dist/auth/admin-roles-route.d.ts +18 -0
  15. package/dist/auth/admin-user-ops.d.ts +79 -0
  16. package/dist/auth/admin-users-route.d.ts +28 -0
  17. package/dist/auth/api-keys/api-key-middleware.d.ts +39 -0
  18. package/{src/auth/api-keys/api-key-permission-guard.ts → dist/auth/api-keys/api-key-permission-guard.d.ts} +2 -34
  19. package/dist/auth/api-keys/api-key-routes.d.ts +20 -0
  20. package/dist/auth/api-keys/api-key-store.d.ts +35 -0
  21. package/{src/auth/api-keys/api-key-types.ts → dist/auth/api-keys/api-key-types.d.ts} +0 -6
  22. package/{src/auth/api-keys/index.ts → dist/auth/api-keys/index.d.ts} +2 -22
  23. package/dist/auth/apple-oauth.d.ts +30 -0
  24. package/{src/auth/auth-hooks.ts → dist/auth/auth-hooks.d.ts} +15 -73
  25. package/dist/auth/bitbucket-oauth.d.ts +11 -0
  26. package/dist/auth/builtin-auth-adapter.d.ts +55 -0
  27. package/dist/auth/cookie-utils.d.ts +32 -0
  28. package/{src/auth/crypto-utils.ts → dist/auth/crypto-utils.d.ts} +1 -16
  29. package/dist/auth/custom-auth-adapter.d.ts +39 -0
  30. package/dist/auth/discord-oauth.d.ts +14 -0
  31. package/dist/auth/facebook-oauth.d.ts +14 -0
  32. package/dist/auth/github-oauth.d.ts +15 -0
  33. package/dist/auth/gitlab-oauth.d.ts +13 -0
  34. package/dist/auth/google-oauth.d.ts +47 -0
  35. package/{src/auth/index.ts → dist/auth/index.d.ts} +4 -22
  36. package/{src/auth/interfaces.ts → dist/auth/interfaces.d.ts} +20 -89
  37. package/dist/auth/jwt.d.ts +67 -0
  38. package/dist/auth/linkedin-oauth.d.ts +18 -0
  39. package/dist/auth/magic-link-routes.d.ts +32 -0
  40. package/dist/auth/mfa-crypto.d.ts +23 -0
  41. package/dist/auth/mfa-routes.d.ts +7 -0
  42. package/dist/auth/mfa.d.ts +49 -0
  43. package/dist/auth/microsoft-oauth.d.ts +16 -0
  44. package/dist/auth/middleware.d.ts +158 -0
  45. package/dist/auth/password.d.ts +22 -0
  46. package/dist/auth/rate-limiter.d.ts +50 -0
  47. package/dist/auth/reset-password-admin.d.ts +29 -0
  48. package/dist/auth/rls-scope.d.ts +31 -0
  49. package/dist/auth/routes.d.ts +66 -0
  50. package/dist/auth/session-routes.d.ts +29 -0
  51. package/dist/auth/slack-oauth.d.ts +12 -0
  52. package/dist/auth/spotify-oauth.d.ts +12 -0
  53. package/dist/auth/twitter-oauth.d.ts +18 -0
  54. package/dist/collections/BackendCollectionRegistry.d.ts +13 -0
  55. package/dist/collections/loader.d.ts +5 -0
  56. package/dist/cron/cron-loader.d.ts +18 -0
  57. package/dist/cron/cron-routes.d.ts +14 -0
  58. package/dist/cron/cron-scheduler.d.ts +106 -0
  59. package/dist/cron/cron-store.d.ts +32 -0
  60. package/dist/cron/define-cron.d.ts +28 -0
  61. package/{src/cron/index.ts → dist/cron/index.d.ts} +1 -0
  62. package/dist/db/interfaces.d.ts +18 -0
  63. package/dist/dynamic-import-Dvh-K5fl.js +22 -0
  64. package/dist/dynamic-import-Dvh-K5fl.js.map +1 -0
  65. package/dist/email/index.d.ts +6 -0
  66. package/dist/email/smtp-email-service.d.ts +30 -0
  67. package/dist/email/templates.d.ts +50 -0
  68. package/{src/email/types.ts → dist/email/types.d.ts} +40 -36
  69. package/dist/env.d.ts +102 -0
  70. package/dist/functions/define-function.d.ts +55 -0
  71. package/dist/functions/function-loader.d.ts +18 -0
  72. package/dist/functions/function-routes.d.ts +10 -0
  73. package/{src/functions/index.ts → dist/functions/index.d.ts} +2 -0
  74. package/dist/history/history-routes.d.ts +23 -0
  75. package/{src/history/index.ts → dist/history/index.d.ts} +0 -1
  76. package/dist/index.d.ts +35 -0
  77. package/dist/index.es.js +18184 -0
  78. package/dist/index.es.js.map +1 -0
  79. package/dist/index.umd.js +48629 -0
  80. package/dist/index.umd.js.map +1 -0
  81. package/dist/init/docs.d.ts +4 -0
  82. package/dist/init/health.d.ts +2 -0
  83. package/dist/init/middlewares.d.ts +10 -0
  84. package/dist/init/shutdown.d.ts +65 -0
  85. package/dist/init/storage.d.ts +5 -0
  86. package/dist/init.d.ts +344 -0
  87. package/dist/jwt-BwIn8xmk.js +4200 -0
  88. package/dist/jwt-BwIn8xmk.js.map +1 -0
  89. package/dist/logger-BYU66ENZ.js +94 -0
  90. package/dist/logger-BYU66ENZ.js.map +1 -0
  91. package/dist/ms-DnYXB-Wd.js +162 -0
  92. package/dist/ms-DnYXB-Wd.js.map +1 -0
  93. package/dist/openapi-generator-Z9oYWLf_.js +586 -0
  94. package/dist/openapi-generator-Z9oYWLf_.js.map +1 -0
  95. package/dist/schema-editor-routes-D3ef8zu1.js +219 -0
  96. package/dist/schema-editor-routes-D3ef8zu1.js.map +1 -0
  97. package/dist/serve-spa.d.ts +36 -0
  98. package/dist/services/driver-registry.d.ts +78 -0
  99. package/dist/services/routed-realtime-service.d.ts +43 -0
  100. package/dist/services/webhook-service.d.ts +29 -0
  101. package/dist/singleton.d.ts +51 -0
  102. package/dist/src-B4OLmNVa.js +437 -0
  103. package/dist/src-B4OLmNVa.js.map +1 -0
  104. package/dist/src-B5WkP1Rv.js +24778 -0
  105. package/dist/src-B5WkP1Rv.js.map +1 -0
  106. package/dist/storage/GCSStorageController.d.ts +43 -0
  107. package/dist/storage/LocalStorageController.d.ts +46 -0
  108. package/dist/storage/S3StorageController.d.ts +40 -0
  109. package/dist/storage/image-transform.d.ts +50 -0
  110. package/{src/storage/index.ts → dist/storage/index.d.ts} +6 -21
  111. package/dist/storage/routes.d.ts +70 -0
  112. package/dist/storage/storage-registry.d.ts +78 -0
  113. package/dist/storage/tus-handler.d.ts +53 -0
  114. package/{src/storage/types.ts → dist/storage/types.d.ts} +32 -38
  115. package/dist/types/index.d.ts +11 -0
  116. package/dist/utils/dev-port.d.ts +45 -0
  117. package/dist/utils/dynamic-import.d.ts +25 -0
  118. package/dist/utils/logger.d.ts +31 -0
  119. package/dist/utils/logging.d.ts +9 -0
  120. package/dist/utils/request-id.d.ts +4 -0
  121. package/dist/utils/request-logger.d.ts +19 -0
  122. package/{src/utils/sql.ts → dist/utils/sql.d.ts} +5 -16
  123. package/package.json +49 -22
  124. package/coverage/clover.xml +0 -3739
  125. package/coverage/coverage-final.json +0 -31
  126. package/coverage/lcov-report/base.css +0 -224
  127. package/coverage/lcov-report/block-navigation.js +0 -87
  128. package/coverage/lcov-report/favicon.png +0 -0
  129. package/coverage/lcov-report/index.html +0 -266
  130. package/coverage/lcov-report/prettify.css +0 -1
  131. package/coverage/lcov-report/prettify.js +0 -2
  132. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  133. package/coverage/lcov-report/sorter.js +0 -210
  134. package/coverage/lcov-report/src/api/ast-schema-editor.ts.html +0 -952
  135. package/coverage/lcov-report/src/api/errors.ts.html +0 -472
  136. package/coverage/lcov-report/src/api/graphql/graphql-schema-generator.ts.html +0 -1069
  137. package/coverage/lcov-report/src/api/graphql/index.html +0 -116
  138. package/coverage/lcov-report/src/api/index.html +0 -176
  139. package/coverage/lcov-report/src/api/openapi-generator.ts.html +0 -565
  140. package/coverage/lcov-report/src/api/rest/api-generator.ts.html +0 -994
  141. package/coverage/lcov-report/src/api/rest/index.html +0 -131
  142. package/coverage/lcov-report/src/api/rest/query-parser.ts.html +0 -550
  143. package/coverage/lcov-report/src/api/schema-editor-routes.ts.html +0 -202
  144. package/coverage/lcov-report/src/api/server.ts.html +0 -823
  145. package/coverage/lcov-report/src/auth/admin-routes.ts.html +0 -973
  146. package/coverage/lcov-report/src/auth/index.html +0 -176
  147. package/coverage/lcov-report/src/auth/jwt.ts.html +0 -574
  148. package/coverage/lcov-report/src/auth/middleware.ts.html +0 -745
  149. package/coverage/lcov-report/src/auth/password.ts.html +0 -310
  150. package/coverage/lcov-report/src/auth/services.ts.html +0 -2074
  151. package/coverage/lcov-report/src/collections/index.html +0 -116
  152. package/coverage/lcov-report/src/collections/loader.ts.html +0 -232
  153. package/coverage/lcov-report/src/db/auth-schema.ts.html +0 -523
  154. package/coverage/lcov-report/src/db/data-transformer.ts.html +0 -1753
  155. package/coverage/lcov-report/src/db/entityService.ts.html +0 -700
  156. package/coverage/lcov-report/src/db/index.html +0 -146
  157. package/coverage/lcov-report/src/db/services/EntityFetchService.ts.html +0 -4048
  158. package/coverage/lcov-report/src/db/services/EntityPersistService.ts.html +0 -883
  159. package/coverage/lcov-report/src/db/services/RelationService.ts.html +0 -3121
  160. package/coverage/lcov-report/src/db/services/entity-helpers.ts.html +0 -442
  161. package/coverage/lcov-report/src/db/services/index.html +0 -176
  162. package/coverage/lcov-report/src/db/services/index.ts.html +0 -124
  163. package/coverage/lcov-report/src/generate-drizzle-schema-logic.ts.html +0 -1960
  164. package/coverage/lcov-report/src/index.html +0 -116
  165. package/coverage/lcov-report/src/services/driver-registry.ts.html +0 -631
  166. package/coverage/lcov-report/src/services/index.html +0 -131
  167. package/coverage/lcov-report/src/services/postgresDataDriver.ts.html +0 -3025
  168. package/coverage/lcov-report/src/storage/LocalStorageController.ts.html +0 -1189
  169. package/coverage/lcov-report/src/storage/S3StorageController.ts.html +0 -970
  170. package/coverage/lcov-report/src/storage/index.html +0 -161
  171. package/coverage/lcov-report/src/storage/storage-registry.ts.html +0 -646
  172. package/coverage/lcov-report/src/storage/types.ts.html +0 -451
  173. package/coverage/lcov-report/src/utils/drizzle-conditions.ts.html +0 -3082
  174. package/coverage/lcov-report/src/utils/index.html +0 -116
  175. package/coverage/lcov.info +0 -7179
  176. package/jest.config.cjs +0 -19
  177. package/src/api/ast-schema-editor.ts +0 -289
  178. package/src/api/collections_for_test/callbacks_test_collection.ts +0 -61
  179. package/src/api/errors.ts +0 -278
  180. package/src/api/graphql/graphql-schema-generator.ts +0 -383
  181. package/src/api/graphql/index.ts +0 -2
  182. package/src/api/index.ts +0 -11
  183. package/src/api/logs-routes.ts +0 -132
  184. package/src/api/openapi-generator.ts +0 -727
  185. package/src/api/rest/api-generator-count.test.ts +0 -126
  186. package/src/api/rest/api-generator.ts +0 -747
  187. package/src/api/rest/query-parser.ts +0 -300
  188. package/src/api/schema-editor-routes.ts +0 -41
  189. package/src/api/server.ts +0 -271
  190. package/src/api/types.ts +0 -104
  191. package/src/auth/adapter-middleware.ts +0 -114
  192. package/src/auth/admin-roles-route.ts +0 -36
  193. package/src/auth/admin-user-ops.ts +0 -236
  194. package/src/auth/admin-users-route.ts +0 -302
  195. package/src/auth/api-keys/api-key-middleware.ts +0 -133
  196. package/src/auth/api-keys/api-key-routes.ts +0 -193
  197. package/src/auth/api-keys/api-key-store.ts +0 -340
  198. package/src/auth/apple-oauth.ts +0 -122
  199. package/src/auth/bitbucket-oauth.ts +0 -84
  200. package/src/auth/builtin-auth-adapter.ts +0 -380
  201. package/src/auth/custom-auth-adapter.ts +0 -86
  202. package/src/auth/discord-oauth.ts +0 -85
  203. package/src/auth/facebook-oauth.ts +0 -74
  204. package/src/auth/github-oauth.ts +0 -112
  205. package/src/auth/gitlab-oauth.ts +0 -72
  206. package/src/auth/google-oauth.ts +0 -223
  207. package/src/auth/jwt.ts +0 -208
  208. package/src/auth/linkedin-oauth.ts +0 -83
  209. package/src/auth/magic-link-routes.ts +0 -167
  210. package/src/auth/mfa-crypto.ts +0 -91
  211. package/src/auth/mfa-routes.ts +0 -323
  212. package/src/auth/mfa.ts +0 -161
  213. package/src/auth/microsoft-oauth.ts +0 -90
  214. package/src/auth/middleware.ts +0 -369
  215. package/src/auth/password.ts +0 -77
  216. package/src/auth/rate-limiter.ts +0 -225
  217. package/src/auth/reset-password-admin.ts +0 -160
  218. package/src/auth/rls-scope.ts +0 -58
  219. package/src/auth/routes.ts +0 -802
  220. package/src/auth/session-routes.ts +0 -353
  221. package/src/auth/slack-oauth.ts +0 -73
  222. package/src/auth/spotify-oauth.ts +0 -69
  223. package/src/auth/twitter-oauth.ts +0 -128
  224. package/src/collections/BackendCollectionRegistry.ts +0 -20
  225. package/src/collections/loader.ts +0 -50
  226. package/src/cron/cron-loader.ts +0 -84
  227. package/src/cron/cron-routes.test.ts +0 -265
  228. package/src/cron/cron-routes.ts +0 -85
  229. package/src/cron/cron-scheduler.test.ts +0 -559
  230. package/src/cron/cron-scheduler.ts +0 -581
  231. package/src/cron/cron-store.ts +0 -166
  232. package/src/db/interfaces.ts +0 -60
  233. package/src/email/index.ts +0 -19
  234. package/src/email/smtp-email-service.ts +0 -123
  235. package/src/email/templates.ts +0 -470
  236. package/src/env.ts +0 -227
  237. package/src/functions/function-loader.ts +0 -116
  238. package/src/functions/function-routes.ts +0 -31
  239. package/src/history/history-routes.ts +0 -129
  240. package/src/index.ts +0 -70
  241. package/src/init/docs.ts +0 -47
  242. package/src/init/health.ts +0 -37
  243. package/src/init/middlewares.ts +0 -61
  244. package/src/init/shutdown.ts +0 -56
  245. package/src/init/storage.ts +0 -57
  246. package/src/init.ts +0 -1041
  247. package/src/serve-spa.ts +0 -89
  248. package/src/services/driver-registry.ts +0 -183
  249. package/src/services/routed-realtime-service.ts +0 -113
  250. package/src/services/webhook-service.ts +0 -155
  251. package/src/singleton.ts +0 -70
  252. package/src/storage/LocalStorageController.ts +0 -389
  253. package/src/storage/S3StorageController.ts +0 -300
  254. package/src/storage/image-transform.ts +0 -218
  255. package/src/storage/routes.ts +0 -368
  256. package/src/storage/storage-registry.ts +0 -188
  257. package/src/storage/tus-handler.ts +0 -315
  258. package/src/types/index.ts +0 -27
  259. package/src/utils/dev-port.ts +0 -196
  260. package/src/utils/logger.ts +0 -143
  261. package/src/utils/logging.ts +0 -40
  262. package/src/utils/request-id.ts +0 -40
  263. package/src/utils/request-logger.ts +0 -72
  264. package/test/api-generator.test.ts +0 -588
  265. package/test/api-key-permission-guard.test.ts +0 -144
  266. package/test/ast-schema-editor.test.ts +0 -89
  267. package/test/auth-middleware-hono.test.ts +0 -556
  268. package/test/auth-routes.test.ts +0 -1098
  269. package/test/backend-hooks-data.test.ts +0 -477
  270. package/test/custom-auth-adapter.test.ts +0 -189
  271. package/test/driver-registry.test.ts +0 -282
  272. package/test/email-templates.test.ts +0 -174
  273. package/test/env.test.ts +0 -128
  274. package/test/error-propagation.test.ts +0 -226
  275. package/test/errors-hono.test.ts +0 -133
  276. package/test/errors.test.ts +0 -155
  277. package/test/function-loader.test.ts +0 -127
  278. package/test/graphql-schema-generator.test.ts +0 -554
  279. package/test/jwt-security.test.ts +0 -182
  280. package/test/jwt.test.ts +0 -326
  281. package/test/mfa.test.ts +0 -197
  282. package/test/middleware.test.ts +0 -305
  283. package/test/multi-datasource-routing.test.ts +0 -113
  284. package/test/password.test.ts +0 -165
  285. package/test/query-parser.test.ts +0 -233
  286. package/test/rate-limiter.test.ts +0 -102
  287. package/test/reset-password-admin.test.ts +0 -113
  288. package/test/routed-realtime-service.test.ts +0 -86
  289. package/test/safe-compare.test.ts +0 -66
  290. package/test/singleton.test.ts +0 -59
  291. package/test/smtp-email-service.test.ts +0 -169
  292. package/test/storage-local.test.ts +0 -271
  293. package/test/storage-registry.test.ts +0 -282
  294. package/test/storage-routes.test.ts +0 -222
  295. package/test/storage-s3.test.ts +0 -304
  296. package/test/transform-auth-response.test.ts +0 -305
  297. package/test/webhook-service.test.ts +0 -260
  298. package/tsconfig.json +0 -54
  299. package/tsconfig.prod.json +0 -23
  300. package/vite.config.ts +0 -86
@@ -1,802 +0,0 @@
1
- import { Hono } from "hono";
2
- import { ApiError, errorHandler } from "../api/errors";
3
- import { randomBytes } from "crypto";
4
- import { generateSecureToken, hashToken } from "./admin-user-ops";
5
- import type { AuthRepository, OAuthProvider, CreateUserData } from "./interfaces";
6
- import { generateAccessToken, generateRefreshToken, hashRefreshToken, getRefreshTokenExpiry, getAccessTokenExpiry } from "./jwt";
7
- import type { AuthHooks } from "./auth-hooks";
8
- import { resolveAuthHooks } from "./auth-hooks";
9
- import { requireAuth } from "./middleware";
10
- import { EmailService, EmailConfig } from "../email";
11
- import { getPasswordResetTemplate, getEmailVerificationTemplate, getWelcomeEmailTemplate } from "../email/templates";
12
- import { HonoEnv } from "../api/types";
13
- import { defaultAuthLimiter, strictAuthLimiter } from "./rate-limiter";
14
- import { z } from "zod";
15
- import { logger } from "../utils/logger";
16
- import { mountMfaRoutes } from "./mfa-routes";
17
- import { mountSessionRoutes } from "./session-routes";
18
- import { mountMagicLinkRoutes } from "./magic-link-routes";
19
- import type { AuthResponsePayload, TransformAuthResponseContext } from "@rebasepro/types";
20
-
21
- /**
22
- * Shared configuration for auth and admin route factories.
23
- */
24
- export interface AuthModuleConfig {
25
- authRepo: AuthRepository;
26
- emailService?: EmailService;
27
- emailConfig?: EmailConfig;
28
- /** Allow new user registration (default: false). */
29
- allowRegistration?: boolean;
30
- /** Default role ID to assign to new users (default: none). Must NOT be "admin". */
31
- defaultRole?: string;
32
- /** Optional array of OAuth providers */
33
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
- oauthProviders?: OAuthProvider<any>[];
35
- /** When true, blocks all self-registration regardless of `allowRegistration`. */
36
- disableSelfRegistration?: boolean;
37
- /**
38
- * Auth hooks for customizing password hashing, credential
39
- * verification, lifecycle hooks, etc.
40
- */
41
- authHooks?: AuthHooks;
42
- /**
43
- * Callback that checks if bootstrap has already been completed.
44
- * Used by GET /auth/config to report `needsSetup` status.
45
- * When not provided, falls back to checking if any users exist.
46
- */
47
- isBootstrapCompleted?: () => Promise<boolean>;
48
- /** Enable magic link (passwordless email) login. Requires email service. */
49
- enableMagicLink?: boolean;
50
- }
51
-
52
- /**
53
- * Helper to build standard auth response output
54
- */
55
- function buildAuthResponse(
56
- user: { id: string; email: string; displayName?: string | null; photoUrl?: string | null; metadata?: Record<string, unknown> | null },
57
- roleIds: string[],
58
- accessToken: string,
59
- refreshToken: string
60
- ): AuthResponsePayload {
61
- return {
62
- user: {
63
- uid: user.id,
64
- email: user.email,
65
- displayName: user.displayName ?? null,
66
- photoURL: user.photoUrl ?? null,
67
- roles: roleIds,
68
- metadata: user.metadata ?? {}
69
- },
70
- tokens: {
71
- accessToken,
72
- refreshToken,
73
- accessTokenExpiresAt: getAccessTokenExpiry()
74
- }
75
- };
76
- }
77
-
78
-
79
- /**
80
- * Get password reset token expiry (1 hour from now)
81
- */
82
- function getPasswordResetExpiry(): Date {
83
- return new Date(Date.now() + 60 * 60 * 1000); // 1 hour
84
- }
85
-
86
- export function createAuthRoutes(config: AuthModuleConfig): Hono<HonoEnv> {
87
- if (config.defaultRole === "admin") {
88
- throw new Error("CRITICAL SECURITY ERROR: defaultRole cannot be 'admin'. Administrative privilege escalation via registration is strictly forbidden. Use the POST /admin/bootstrap endpoint to promote the initial administrator.");
89
- }
90
-
91
- const router = new Hono<HonoEnv>();
92
-
93
- // Attach Rebase error handler to ensure ApiError exceptions are correctly
94
- // formatted instead of caught by Hono's default error handler.
95
- // Hono's onError does NOT propagate from parent to child routers.
96
- router.onError(errorHandler);
97
-
98
- const authRepo = config.authRepo;
99
- const { emailService, emailConfig, allowRegistration = false } = config;
100
- const ops = resolveAuthHooks(config.authHooks);
101
-
102
- /**
103
- * Apply the `transformAuthResponse` hook if provided.
104
- *
105
- * Errors are caught and logged — the untransformed response is returned
106
- * as a graceful fallback so auth never breaks due to a hook failure.
107
- */
108
- async function applyTransformHook(
109
- response: AuthResponsePayload,
110
- method: TransformAuthResponseContext["method"],
111
- request: Request,
112
- userId: string
113
- ): Promise<AuthResponsePayload> {
114
- if (!ops.transformAuthResponse) return response;
115
- try {
116
- return await ops.transformAuthResponse(response, { userId, method, request });
117
- } catch (err) {
118
- logger.error("[AuthHooks] transformAuthResponse error", {
119
- error: err instanceof Error ? err.message : err
120
- });
121
- return response;
122
- }
123
- }
124
-
125
- // ── Zod input schemas ──────────────────────────────────────────────
126
- const registerSchema = z.object({
127
- email: z.string().email("Invalid email address").max(255),
128
- password: z.string().min(1, "Password is required").max(128),
129
- displayName: z.string().max(255).optional()
130
- });
131
- const loginSchema = z.object({
132
- email: z.string().email("Invalid email address").max(255),
133
- password: z.string().min(1, "Password is required").max(128)
134
- });
135
- const forgotPasswordSchema = z.object({
136
- email: z.string().email("Invalid email address").max(255)
137
- });
138
- const resetPasswordSchema = z.object({
139
- token: z.string().min(1, "Token is required"),
140
- password: z.string().min(1, "Password is required").max(128)
141
- });
142
- const changePasswordSchema = z.object({
143
- oldPassword: z.string().min(1, "Old password is required").max(128),
144
- newPassword: z.string().min(1, "New password is required").max(128)
145
- });
146
- const refreshSchema = z.object({
147
- refreshToken: z.string().min(1, "Refresh token is required")
148
- });
149
- const logoutSchema = z.object({
150
- refreshToken: z.string().optional()
151
- });
152
- const updateProfileSchema = z.object({
153
- displayName: z.string().max(255).optional(),
154
- photoURL: z.string().url().max(2048).optional()
155
- });
156
-
157
- /** Parse a Zod schema against the request body, throwing ApiError on failure */
158
- function parseBody<T>(schema: z.ZodSchema<T>, body: unknown): T {
159
- const result = schema.safeParse(body);
160
- if (!result.success) {
161
- const messages = result.error.issues.map(e => `${e.path.join(".")}: ${e.message}`).join(". ");
162
- throw ApiError.badRequest(messages, "INVALID_INPUT");
163
- }
164
- return result.data;
165
- }
166
-
167
- /**
168
- * Check if email service is configured
169
- */
170
- function isEmailConfigured(): boolean {
171
- return !!(emailService && emailService.isConfigured());
172
- }
173
-
174
- /**
175
- * Check if registration is allowed.
176
- * Registration is only allowed when explicitly enabled via `allowRegistration`.
177
- * First-user bootstrap must use POST /admin/bootstrap instead.
178
- */
179
- function isRegistrationAllowed(): boolean {
180
- if (config.disableSelfRegistration) return false;
181
- return !!allowRegistration;
182
- }
183
-
184
- /**
185
- * Send welcome email to a newly registered user (fire-and-forget).
186
- */
187
- function sendWelcomeEmail(user: { email: string; displayName?: string | null }) {
188
- if (!isEmailConfigured()) return;
189
- const appName = emailConfig?.appName || "Rebase";
190
- const loginUrl = emailConfig?.resetPasswordUrl || ""; // reuse base URL → the login / app page
191
- const templateFn = emailConfig?.templates?.welcomeEmail;
192
- const emailContent = templateFn
193
- ? templateFn(user, appName)
194
- : getWelcomeEmailTemplate(user, appName, loginUrl ? `${loginUrl}/app` : undefined);
195
-
196
- emailService!.send({
197
- to: user.email,
198
- subject: emailContent.subject,
199
- html: emailContent.html,
200
- text: emailContent.text
201
- }).catch(err => {
202
- logger.error("Failed to send welcome email", { error: err instanceof Error ? err.message : err });
203
- });
204
- }
205
-
206
- /**
207
- * Helper to generate and store session tokens
208
- */
209
- async function createSessionAndTokens(userId: string, userAgent: string, ipAddress: string) {
210
- const roles = await authRepo.getUserRoles(userId);
211
- const roleIds = roles.map(r => r.id);
212
-
213
- // Allow customization of access token claims via hook
214
- let customClaims: Record<string, unknown> | undefined;
215
- if (ops.customizeAccessToken) {
216
- const user = await authRepo.getUserById(userId);
217
- if (user) {
218
- const defaultClaims: Record<string, unknown> = { userId,
219
- roles: roleIds,
220
- aal: "aal1" };
221
- customClaims = await ops.customizeAccessToken(defaultClaims, user);
222
- }
223
- }
224
-
225
- const accessToken = generateAccessToken(userId, roleIds, "aal1", customClaims);
226
- const refreshToken = generateRefreshToken();
227
-
228
- await authRepo.createRefreshToken(
229
- userId,
230
- hashRefreshToken(refreshToken),
231
- getRefreshTokenExpiry(),
232
- userAgent,
233
- ipAddress
234
- );
235
-
236
- return { roleIds,
237
- accessToken,
238
- refreshToken };
239
- }
240
-
241
- /**
242
- * POST /auth/register
243
- * Create a new account with email/password
244
- */
245
- router.post("/register", defaultAuthLimiter, async (c) => {
246
- const { email, password, displayName } = parseBody(registerSchema, await c.req.json());
247
-
248
- // Hard kill switch — blocks registration regardless of allowRegistration
249
- if (config.disableSelfRegistration) {
250
- throw ApiError.forbidden("Registration is disabled", "REGISTRATION_DISABLED");
251
- }
252
-
253
- // Check if registration is allowed (no bypass for empty databases)
254
- if (!isRegistrationAllowed()) {
255
- throw ApiError.forbidden("Registration is disabled", "REGISTRATION_DISABLED");
256
- }
257
-
258
- // Validate password strength
259
- const passwordValidation = ops.validatePasswordStrength(password);
260
- if (!passwordValidation.valid) {
261
- throw ApiError.badRequest(passwordValidation.errors.join(". "), "WEAK_PASSWORD");
262
- }
263
-
264
- // Check if email already exists
265
- const existingUser = await authRepo.getUserByEmail(email);
266
- if (existingUser) {
267
- throw ApiError.conflict("Email already registered", "EMAIL_EXISTS");
268
- }
269
-
270
- // Create user
271
- const passwordHash = await ops.hashPassword(password);
272
- let createData: import("./interfaces").CreateUserData = {
273
- email: email.toLowerCase(),
274
- passwordHash,
275
- displayName: displayName || undefined
276
- };
277
- if (ops.beforeUserCreate) {
278
- createData = await ops.beforeUserCreate(createData);
279
- }
280
- const user = await authRepo.createUser(createData);
281
-
282
- // Auto-bootstrap: if this is the very first user in the system, promote to admin.
283
- // This avoids the chicken-and-egg problem where the first user has no permissions
284
- // and no way to access the bootstrap endpoint from the UI.
285
- const existingUsers = await authRepo.listUsers();
286
- const isFirstUser = existingUsers.length === 1 && existingUsers[0].id === user.id;
287
-
288
- if (isFirstUser) {
289
- await authRepo.setUserRoles(user.id, ["admin"]);
290
- } else if (config.defaultRole) {
291
- // Assign configured default role (never auto-assign admin via registration)
292
- await authRepo.assignDefaultRole(user.id, config.defaultRole);
293
- }
294
-
295
- const { roleIds, accessToken, refreshToken } = await createSessionAndTokens(
296
- user.id,
297
- c.req.header("user-agent") || "unknown",
298
- c.req.header("x-forwarded-for") || "unknown"
299
- );
300
-
301
- // Send welcome email (fire-and-forget, don't block registration)
302
- sendWelcomeEmail({ email: user.email,
303
- displayName: user.displayName });
304
-
305
- // Fire afterUserCreate hook
306
- if (ops.afterUserCreate) {
307
- try {
308
- await ops.afterUserCreate(user);
309
- } catch (err) {
310
- logger.error("[AuthHooks] afterUserCreate error", { error: err instanceof Error ? err.message : err });
311
- }
312
- }
313
-
314
- // Fire onAuthenticated hook (fire-and-forget)
315
- if (ops.onAuthenticated) {
316
- ops.onAuthenticated(user, "register").catch(err => {
317
- logger.error("[AuthHooks] onAuthenticated error", { error: err instanceof Error ? err.message : err });
318
- });
319
- }
320
-
321
- const authResponse = buildAuthResponse(user, roleIds, accessToken, refreshToken);
322
- const finalResponse = await applyTransformHook(authResponse, "register", c.req.raw, user.id);
323
- return c.json(finalResponse, 201);
324
- });
325
-
326
- /**
327
- * POST /auth/login
328
- * Login with email/password
329
- */
330
- router.post("/login", defaultAuthLimiter, async (c) => {
331
- const { email, password } = parseBody(loginSchema, await c.req.json());
332
-
333
- // Call beforeLogin hook if provided (throw to reject)
334
- if (ops.beforeLogin) {
335
- await ops.beforeLogin(email, "login");
336
- }
337
-
338
- let user;
339
-
340
- if (ops.verifyCredentials) {
341
- // Full credential verification override
342
- user = await ops.verifyCredentials(email, password, authRepo);
343
- if (!user) {
344
- throw ApiError.unauthorized("Invalid email or password", "INVALID_CREDENTIALS");
345
- }
346
- } else {
347
- // Default: email lookup + password hash verification
348
- user = await authRepo.getUserByEmail(email);
349
- if (!user) {
350
- throw ApiError.unauthorized("Invalid email or password", "INVALID_CREDENTIALS");
351
- }
352
-
353
- if (!user.passwordHash) {
354
- throw ApiError.unauthorized("Invalid email or password", "INVALID_CREDENTIALS");
355
- }
356
-
357
- const isValidPassword = await ops.verifyPassword(password, user.passwordHash);
358
- if (!isValidPassword) {
359
- logger.warn("[Security Audit] Auth login failure", {
360
- eventType: "auth.login.failure",
361
- email,
362
- userId: user.id
363
- });
364
- throw ApiError.unauthorized("Invalid email or password", "INVALID_CREDENTIALS");
365
- }
366
- }
367
-
368
- const { roleIds, accessToken, refreshToken } = await createSessionAndTokens(
369
- user.id,
370
- c.req.header("user-agent") || "unknown",
371
- c.req.header("x-forwarded-for") || "unknown"
372
- );
373
-
374
- // Fire onAuthenticated hook (fire-and-forget)
375
- if (ops.onAuthenticated) {
376
- ops.onAuthenticated(user, "login").catch(err => {
377
- logger.error("[AuthHooks] onAuthenticated error", { error: err instanceof Error ? err.message : err });
378
- });
379
- }
380
-
381
- logger.info("[Security Audit] Auth login success", {
382
- eventType: "auth.login.success",
383
- userId: user.id,
384
- email
385
- });
386
-
387
- const authResponse = buildAuthResponse(user, roleIds, accessToken, refreshToken);
388
- const finalResponse = await applyTransformHook(authResponse, "login", c.req.raw, user.id);
389
- return c.json(finalResponse);
390
- });
391
-
392
- /**
393
- * Dynamically mount OAuth provider routes
394
- */
395
- if (config.oauthProviders && config.oauthProviders.length > 0) {
396
- for (const provider of config.oauthProviders) {
397
- router.post(`/${provider.id}`, defaultAuthLimiter, async (c) => {
398
- const payload = parseBody(provider.schema, await c.req.json());
399
-
400
- let externalUser;
401
- try {
402
- externalUser = await provider.verify(payload);
403
- } catch (err: unknown) {
404
- const msg = err instanceof Error ? err.message : String(err);
405
- throw ApiError.unauthorized(`${provider.id} login failed: ${msg}`, "OAUTH_ERROR");
406
- }
407
- if (!externalUser) {
408
- throw ApiError.unauthorized(`Invalid ${provider.id} credentials`, "INVALID_TOKEN");
409
- }
410
-
411
- // Find or create user
412
- let user = await authRepo.getUserByIdentity(provider.id, externalUser.providerId);
413
-
414
- if (!user) {
415
- // Check if email exists (link accounts)
416
- user = await authRepo.getUserByEmail(externalUser.email);
417
-
418
- if (user) {
419
- // Only auto-link if the OAuth provider confirmed the email is verified
420
- if (!externalUser.emailVerified) {
421
- throw ApiError.forbidden(
422
- "Cannot auto-link account: email not verified by the OAuth provider. Please log in with your password first and link the provider from your profile.",
423
- "EMAIL_NOT_VERIFIED"
424
- );
425
- }
426
- // Link Provider to existing account
427
- await authRepo.linkUserIdentity(user.id, provider.id, externalUser.providerId, { email: externalUser.email });
428
-
429
- // Optional: Update profile info from external provider if empty
430
- await authRepo.updateUser(user.id, {
431
- displayName: user.displayName || externalUser.displayName || undefined,
432
- photoUrl: user.photoUrl || externalUser.photoUrl || undefined
433
- });
434
- } else {
435
- // Create new user
436
- user = await authRepo.createUser({
437
- email: externalUser.email.toLowerCase(),
438
- displayName: externalUser.displayName || undefined,
439
- photoUrl: externalUser.photoUrl || undefined
440
- });
441
-
442
- await authRepo.linkUserIdentity(user.id, provider.id, externalUser.providerId, { email: externalUser.email });
443
-
444
- // Fire afterUserCreate hook
445
- if (ops.afterUserCreate) {
446
- try {
447
- await ops.afterUserCreate(user);
448
- } catch (err) {
449
- logger.error("[AuthHooks] afterUserCreate error", { error: err instanceof Error ? err.message : err });
450
- }
451
- }
452
-
453
- // Auto-bootstrap: first user in the system gets admin
454
- const allUsers = await authRepo.listUsers();
455
- const isFirstUser = allUsers.length === 1 && allUsers[0].id === user.id;
456
-
457
- if (isFirstUser) {
458
- await authRepo.setUserRoles(user.id, ["admin"]);
459
- } else if (config.defaultRole) {
460
- // Assign configured default role (never auto-assign admin via registration)
461
- await authRepo.assignDefaultRole(user.id, config.defaultRole);
462
- }
463
-
464
- // Send welcome email for new OAuth users (fire-and-forget)
465
- sendWelcomeEmail({ email: user.email,
466
- displayName: user.displayName });
467
- }
468
- } else {
469
- // Update profile info from external provider
470
- await authRepo.updateUser(user.id, {
471
- displayName: externalUser.displayName || user.displayName || undefined,
472
- photoUrl: externalUser.photoUrl || user.photoUrl || undefined
473
- });
474
- }
475
-
476
- const { roleIds, accessToken, refreshToken } = await createSessionAndTokens(
477
- user.id,
478
- c.req.header("user-agent") || "unknown",
479
- c.req.header("x-forwarded-for") || "unknown"
480
- );
481
-
482
- const authResponse = buildAuthResponse(user, roleIds, accessToken, refreshToken);
483
- const finalResponse = await applyTransformHook(authResponse, "oauth", c.req.raw, user.id);
484
- return c.json(finalResponse);
485
- });
486
- }
487
- }
488
-
489
- /**
490
- * POST /auth/forgot-password
491
- * Request password reset email
492
- */
493
- router.post("/forgot-password", strictAuthLimiter, async (c) => {
494
- const { email } = parseBody(forgotPasswordSchema, await c.req.json());
495
-
496
- // Check if email service is configured
497
- if (!isEmailConfigured()) {
498
- throw ApiError.serviceUnavailable("Email service not configured. Password reset is not available.", "EMAIL_NOT_CONFIGURED");
499
- }
500
-
501
- // Always return success (security: don't reveal if email exists)
502
- // But only send email if user exists
503
- const user = await authRepo.getUserByEmail(email);
504
-
505
- if (user) {
506
- // Generate reset token
507
- const token = generateSecureToken();
508
- const tokenHash = hashToken(token);
509
- const expiresAt = getPasswordResetExpiry();
510
-
511
- await authRepo.createPasswordResetToken(user.id, tokenHash, expiresAt);
512
-
513
- // Build reset URL
514
- const baseUrl = emailConfig?.resetPasswordUrl || "";
515
- const resetUrl = `${baseUrl}/reset-password?token=${token}`;
516
-
517
- // Get email template
518
- const appName = emailConfig?.appName || "Rebase";
519
- const templateFn = emailConfig?.templates?.passwordReset;
520
- const emailContent = templateFn
521
- ? templateFn(resetUrl, { email: user.email,
522
- displayName: user.displayName })
523
- : getPasswordResetTemplate(resetUrl, { email: user.email,
524
- displayName: user.displayName }, appName);
525
-
526
- // Send email
527
- try {
528
- await emailService!.send({
529
- to: user.email,
530
- subject: emailContent.subject,
531
- html: emailContent.html,
532
- text: emailContent.text
533
- });
534
- } catch (emailError: unknown) {
535
- logger.error("Failed to send password reset email", { error: emailError instanceof Error ? emailError.message : emailError });
536
- // Don't reveal email sending failure to client
537
- }
538
- }
539
-
540
- // Always return success
541
- return c.json({
542
- success: true,
543
- message: "If an account with that email exists, a password reset link has been sent."
544
- });
545
- });
546
-
547
- /**
548
- * POST /auth/reset-password
549
- * Reset password using token
550
- */
551
- router.post("/reset-password", strictAuthLimiter, async (c) => {
552
- const { token, password } = parseBody(resetPasswordSchema, await c.req.json());
553
-
554
- // Validate password strength
555
- const passwordValidation = ops.validatePasswordStrength(password);
556
- if (!passwordValidation.valid) {
557
- throw ApiError.badRequest(passwordValidation.errors.join(". "), "WEAK_PASSWORD");
558
- }
559
-
560
- // Find valid token
561
- const tokenHash = hashToken(token);
562
- const storedToken = await authRepo.findValidPasswordResetToken(tokenHash);
563
-
564
- if (!storedToken) {
565
- throw ApiError.badRequest("Invalid or expired reset token", "INVALID_TOKEN");
566
- }
567
-
568
- // Update password
569
- const passwordHash = await ops.hashPassword(password);
570
- await authRepo.updatePassword(storedToken.userId, passwordHash);
571
-
572
- // Mark token as used
573
- await authRepo.markPasswordResetTokenUsed(tokenHash);
574
-
575
- // Invalidate all refresh tokens (security: log out all sessions)
576
- await authRepo.deleteAllRefreshTokensForUser(storedToken.userId);
577
-
578
- // Fire onPasswordReset hook (fire-and-forget)
579
- if (ops.onPasswordReset) {
580
- ops.onPasswordReset(storedToken.userId).catch(err => {
581
- logger.error("[AuthHooks] onPasswordReset error", { error: err instanceof Error ? err.message : err });
582
- });
583
- }
584
-
585
- return c.json({ success: true,
586
- message: "Password has been reset successfully" });
587
- });
588
-
589
- /**
590
- * POST /auth/change-password
591
- * Change password for authenticated user
592
- */
593
- router.post("/change-password", requireAuth, async (c) => {
594
- const userCtx = c.get("user") as { userId: string; roles?: string[] } | undefined;
595
- if (!userCtx) {
596
- throw ApiError.unauthorized("Not authenticated");
597
- }
598
-
599
- const { oldPassword, newPassword } = parseBody(changePasswordSchema, await c.req.json());
600
-
601
- // Get user
602
- const user = await authRepo.getUserById(userCtx.userId);
603
- if (!user || !user.passwordHash) {
604
- throw ApiError.badRequest("Cannot change password for this account", "INVALID_ACCOUNT");
605
- }
606
-
607
- // Verify old password
608
- const isValidOldPassword = await ops.verifyPassword(oldPassword, user.passwordHash);
609
- if (!isValidOldPassword) {
610
- throw ApiError.unauthorized("Current password is incorrect", "INVALID_CREDENTIALS");
611
- }
612
-
613
- // Validate new password strength
614
- const passwordValidation = ops.validatePasswordStrength(newPassword);
615
- if (!passwordValidation.valid) {
616
- throw ApiError.badRequest(passwordValidation.errors.join(". "), "WEAK_PASSWORD");
617
- }
618
-
619
- // Update password
620
- const passwordHash = await ops.hashPassword(newPassword);
621
- await authRepo.updatePassword(user.id, passwordHash);
622
-
623
- // Invalidate all refresh tokens (security: log out all sessions)
624
- await authRepo.deleteAllRefreshTokensForUser(user.id);
625
-
626
- return c.json({ success: true,
627
- message: "Password has been changed successfully" });
628
- });
629
-
630
- /**
631
- * POST /auth/send-verification
632
- * Send email verification link (authenticated)
633
- */
634
- router.post("/send-verification", requireAuth, async (c) => {
635
- const userCtx = c.get("user") as { userId: string; roles?: string[] } | undefined;
636
- if (!userCtx) {
637
- throw ApiError.unauthorized("Not authenticated");
638
- }
639
-
640
- // Check if email service is configured
641
- if (!isEmailConfigured()) {
642
- throw ApiError.serviceUnavailable("Email service not configured. Email verification is not available.", "EMAIL_NOT_CONFIGURED");
643
- }
644
-
645
- const user = await authRepo.getUserById(userCtx.userId);
646
- if (!user) {
647
- throw ApiError.notFound("User not found");
648
- }
649
-
650
- if (user.emailVerified) {
651
- throw ApiError.badRequest("Email is already verified", "ALREADY_VERIFIED");
652
- }
653
-
654
- // Generate verification token
655
- const token = generateSecureToken();
656
-
657
- // Store hashed token in user record (raw token goes in the email URL)
658
- await authRepo.setVerificationToken(user.id, hashToken(token));
659
-
660
- // Build verification URL
661
- const baseUrl = emailConfig?.verifyEmailUrl || "";
662
- const verifyUrl = `${baseUrl}/verify-email?token=${token}`;
663
-
664
- // Get email template
665
- const appName = emailConfig?.appName || "Rebase";
666
- const templateFn = emailConfig?.templates?.emailVerification;
667
- const emailContent = templateFn
668
- ? templateFn(verifyUrl, { email: user.email,
669
- displayName: user.displayName })
670
- : getEmailVerificationTemplate(verifyUrl, { email: user.email,
671
- displayName: user.displayName }, appName);
672
-
673
- // Send email
674
- await emailService!.send({
675
- to: user.email,
676
- subject: emailContent.subject,
677
- html: emailContent.html,
678
- text: emailContent.text
679
- });
680
-
681
- return c.json({ success: true,
682
- message: "Verification email sent" });
683
- });
684
-
685
- /**
686
- * GET /auth/verify-email
687
- * Verify email address using token
688
- */
689
- router.get("/verify-email", async (c) => {
690
- const token = c.req.query("token");
691
-
692
- if (!token) {
693
- throw ApiError.badRequest("Verification token is required", "INVALID_INPUT");
694
- }
695
-
696
- // Find user by hashed verification token
697
- const user = await authRepo.getUserByVerificationToken(hashToken(token));
698
- if (!user) {
699
- throw ApiError.badRequest("Invalid or expired verification token", "INVALID_TOKEN");
700
- }
701
-
702
- // Mark email as verified
703
- await authRepo.setEmailVerified(user.id, true);
704
-
705
- return c.json({ success: true,
706
- message: "Email verified successfully" });
707
- });
708
-
709
- /**
710
- * POST /auth/refresh
711
- * Refresh access token using refresh token
712
- */
713
- router.post("/refresh", async (c) => {
714
- const { refreshToken } = parseBody(refreshSchema, await c.req.json());
715
-
716
- const tokenHash = hashRefreshToken(refreshToken);
717
- const storedToken = await authRepo.findRefreshTokenByHash(tokenHash);
718
-
719
- if (!storedToken) {
720
- throw ApiError.unauthorized("Invalid refresh token", "INVALID_TOKEN");
721
- }
722
-
723
- if (new Date() > storedToken.expiresAt) {
724
- await authRepo.deleteRefreshToken(tokenHash);
725
- throw ApiError.unauthorized("Refresh token expired", "TOKEN_EXPIRED");
726
- }
727
-
728
- // Generate new tokens
729
- const roles = await authRepo.getUserRoles(storedToken.userId);
730
- const roleIds = roles.map(r => r.id);
731
-
732
- // Allow customization of access token claims via hook
733
- let customClaims: Record<string, unknown> | undefined;
734
- if (ops.customizeAccessToken) {
735
- const user = await authRepo.getUserById(storedToken.userId);
736
- if (user) {
737
- const defaultClaims: Record<string, unknown> = { userId: storedToken.userId,
738
- roles: roleIds,
739
- aal: "aal1" };
740
- customClaims = await ops.customizeAccessToken(defaultClaims, user);
741
- }
742
- }
743
-
744
- const newAccessToken = generateAccessToken(storedToken.userId, roleIds, "aal1", customClaims);
745
- const newRefreshToken = generateRefreshToken();
746
-
747
- // Rotate refresh token (delete old, create new)
748
- const userAgent = c.req.header("user-agent") || "unknown";
749
- const ipAddress = c.req.header("x-forwarded-for") || "unknown";
750
-
751
- await authRepo.deleteRefreshToken(tokenHash);
752
- await authRepo.createRefreshToken(
753
- storedToken.userId,
754
- hashRefreshToken(newRefreshToken),
755
- getRefreshTokenExpiry(),
756
- userAgent,
757
- ipAddress
758
- );
759
-
760
- const refreshResponse: AuthResponsePayload = {
761
- tokens: {
762
- accessToken: newAccessToken,
763
- refreshToken: newRefreshToken,
764
- accessTokenExpiresAt: getAccessTokenExpiry()
765
- }
766
- };
767
- const finalResponse = await applyTransformHook(refreshResponse, "refresh", c.req.raw, storedToken.userId);
768
- return c.json(finalResponse);
769
- });
770
-
771
- mountSessionRoutes({
772
- router,
773
- config,
774
- ops,
775
- parseBody,
776
- buildAuthResponse,
777
- createSessionAndTokens,
778
- applyTransformHook
779
- });
780
-
781
- // ═══════════════════════════════════════════════════════════════════════
782
- // MFA / TOTP
783
- // ═══════════════════════════════════════════════════════════════════════
784
- mountMfaRoutes(router, config, ops, parseBody, applyTransformHook);
785
-
786
- // ═══════════════════════════════════════════════════════════════════════
787
- // Magic Link (passwordless email login)
788
- // ═══════════════════════════════════════════════════════════════════════
789
- if (config.enableMagicLink) {
790
- mountMagicLinkRoutes({
791
- router,
792
- config,
793
- ops,
794
- parseBody,
795
- buildAuthResponse,
796
- createSessionAndTokens,
797
- applyTransformHook
798
- });
799
- }
800
-
801
- return router;
802
- }