@rebasepro/server-core 0.4.0 → 0.6.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 (282) hide show
  1. package/README.md +62 -25
  2. package/dist/{server-core/src/api → api}/errors.d.ts +20 -2
  3. package/dist/{server-core/src/api → api}/rest/api-generator.d.ts +8 -2
  4. package/dist/{server-core/src/api → api}/types.d.ts +2 -0
  5. package/dist/auth/admin-user-ops.d.ts +79 -0
  6. package/dist/{server-core/src/auth → auth}/api-keys/index.d.ts +2 -2
  7. package/dist/{server-core/src/auth → auth}/auth-hooks.d.ts +37 -9
  8. package/dist/{server-core/src/auth → auth}/builtin-auth-adapter.d.ts +4 -4
  9. package/dist/{server-core/src/auth → auth}/index.d.ts +5 -2
  10. package/dist/{server-core/src/auth → auth}/jwt.d.ts +10 -0
  11. package/dist/auth/mfa-routes.d.ts +6 -0
  12. package/dist/auth/reset-password-admin.d.ts +29 -0
  13. package/dist/auth/session-routes.d.ts +25 -0
  14. package/dist/backend-CIxN4FVm.js +15 -0
  15. package/dist/backend-CIxN4FVm.js.map +1 -0
  16. package/dist/chunk-Dze3rakg.js +42 -0
  17. package/dist/{server-core/src/cron → cron}/cron-scheduler.d.ts +1 -1
  18. package/dist/dist-CZKP-Xz4.js +832 -0
  19. package/dist/dist-CZKP-Xz4.js.map +1 -0
  20. package/dist/env.d.ts +102 -0
  21. package/dist/from-VbwD7xRf.js +3849 -0
  22. package/dist/from-VbwD7xRf.js.map +1 -0
  23. package/dist/{server-core/src/index.d.ts → index.d.ts} +1 -0
  24. package/dist/index.es.js +60871 -50424
  25. package/dist/index.es.js.map +1 -1
  26. package/dist/index.umd.js +73151 -51256
  27. package/dist/index.umd.js.map +1 -1
  28. package/dist/init/docs.d.ts +4 -0
  29. package/dist/init/health.d.ts +2 -0
  30. package/dist/init/middlewares.d.ts +10 -0
  31. package/dist/init/shutdown.d.ts +11 -0
  32. package/dist/init/storage.d.ts +5 -0
  33. package/dist/{server-core/src/init.d.ts → init.d.ts} +4 -4
  34. package/dist/jwt-DHcQRGC3.js +4168 -0
  35. package/dist/jwt-DHcQRGC3.js.map +1 -0
  36. package/dist/logger-BYU66ENZ.js +94 -0
  37. package/dist/logger-BYU66ENZ.js.map +1 -0
  38. package/dist/ms-BeBSuOXN.js +125 -0
  39. package/dist/ms-BeBSuOXN.js.map +1 -0
  40. package/dist/multipart-parser-CedBDOeC.js +299 -0
  41. package/dist/multipart-parser-CedBDOeC.js.map +1 -0
  42. package/dist/src-COaj0G3P.js +1182 -0
  43. package/dist/src-COaj0G3P.js.map +1 -0
  44. package/dist/{server-core/src/storage → storage}/image-transform.d.ts +3 -1
  45. package/dist/utils/request-id.d.ts +4 -0
  46. package/package.json +29 -29
  47. package/src/api/collections_for_test/callbacks_test_collection.ts +7 -6
  48. package/src/api/errors.ts +95 -20
  49. package/src/api/graphql/graphql-schema-generator.ts +15 -10
  50. package/src/api/logs-routes.ts +5 -2
  51. package/src/api/openapi-generator.ts +1 -1
  52. package/src/api/rest/api-generator-count.test.ts +21 -10
  53. package/src/api/rest/api-generator.ts +151 -36
  54. package/src/api/rest/query-parser.ts +18 -12
  55. package/src/api/server.ts +13 -2
  56. package/src/api/types.ts +2 -0
  57. package/src/auth/adapter-middleware.ts +17 -10
  58. package/src/auth/admin-user-ops.ts +236 -0
  59. package/src/auth/api-keys/api-key-middleware.ts +15 -9
  60. package/src/auth/api-keys/api-key-permission-guard.ts +1 -1
  61. package/src/auth/api-keys/api-key-routes.ts +3 -3
  62. package/src/auth/api-keys/api-key-store.ts +14 -8
  63. package/src/auth/api-keys/index.ts +2 -2
  64. package/src/auth/apple-oauth.ts +4 -3
  65. package/src/auth/auth-hooks.ts +50 -10
  66. package/src/auth/bitbucket-oauth.ts +5 -4
  67. package/src/auth/builtin-auth-adapter.ts +64 -54
  68. package/src/auth/custom-auth-adapter.ts +4 -5
  69. package/src/auth/discord-oauth.ts +5 -4
  70. package/src/auth/facebook-oauth.ts +5 -4
  71. package/src/auth/github-oauth.ts +6 -5
  72. package/src/auth/gitlab-oauth.ts +5 -4
  73. package/src/auth/google-oauth.ts +2 -1
  74. package/src/auth/index.ts +6 -2
  75. package/src/auth/jwt.ts +15 -4
  76. package/src/auth/linkedin-oauth.ts +4 -3
  77. package/src/auth/mfa-routes.ts +299 -0
  78. package/src/auth/mfa.ts +2 -1
  79. package/src/auth/microsoft-oauth.ts +5 -4
  80. package/src/auth/middleware.ts +5 -4
  81. package/src/auth/rate-limiter.ts +1 -1
  82. package/src/auth/reset-password-admin.ts +144 -0
  83. package/src/auth/rls-scope.ts +1 -1
  84. package/src/auth/routes.ts +52 -600
  85. package/src/auth/session-routes.ts +341 -0
  86. package/src/auth/slack-oauth.ts +6 -5
  87. package/src/auth/spotify-oauth.ts +5 -4
  88. package/src/auth/twitter-oauth.ts +4 -3
  89. package/src/collections/loader.ts +5 -4
  90. package/src/cron/cron-scheduler.test.ts +23 -11
  91. package/src/cron/cron-scheduler.ts +14 -8
  92. package/src/cron/cron-store.ts +1 -1
  93. package/src/email/smtp-email-service.ts +3 -2
  94. package/src/env.ts +11 -10
  95. package/src/index.ts +1 -0
  96. package/src/init/docs.ts +47 -0
  97. package/src/init/health.ts +37 -0
  98. package/src/init/middlewares.ts +61 -0
  99. package/src/init/shutdown.ts +56 -0
  100. package/src/init/storage.ts +57 -0
  101. package/src/init.ts +93 -295
  102. package/src/serve-spa.ts +12 -5
  103. package/src/services/driver-registry.ts +4 -3
  104. package/src/storage/S3StorageController.ts +1 -2
  105. package/src/storage/image-transform.ts +25 -10
  106. package/src/storage/routes.ts +30 -11
  107. package/src/storage/storage-registry.ts +4 -3
  108. package/src/storage/tus-handler.ts +12 -12
  109. package/src/utils/request-id.ts +40 -0
  110. package/src/utils/request-logger.ts +6 -0
  111. package/test/api-generator.test.ts +90 -2
  112. package/test/api-key-permission-guard.test.ts +24 -12
  113. package/test/auth-routes.test.ts +5 -3
  114. package/test/backend-hooks-data.test.ts +99 -30
  115. package/test/custom-auth-adapter.test.ts +16 -15
  116. package/test/email-templates.test.ts +10 -5
  117. package/test/env.test.ts +10 -10
  118. package/test/function-loader.test.ts +7 -4
  119. package/test/graphql-schema-generator.test.ts +554 -0
  120. package/test/jwt-security.test.ts +1 -1
  121. package/test/query-parser.test.ts +0 -1
  122. package/test/reset-password-admin.test.ts +113 -0
  123. package/test/singleton.test.ts +5 -5
  124. package/test/smtp-email-service.test.ts +21 -21
  125. package/test/webhook-service.test.ts +22 -11
  126. package/tsconfig.json +2 -0
  127. package/tsconfig.prod.json +3 -0
  128. package/vite.config.ts +5 -5
  129. package/dist/common/src/collections/CollectionRegistry.d.ts +0 -56
  130. package/dist/common/src/collections/default-collections.d.ts +0 -9
  131. package/dist/common/src/collections/index.d.ts +0 -2
  132. package/dist/common/src/data/buildRebaseData.d.ts +0 -14
  133. package/dist/common/src/data/query_builder.d.ts +0 -55
  134. package/dist/common/src/index.d.ts +0 -4
  135. package/dist/common/src/util/builders.d.ts +0 -57
  136. package/dist/common/src/util/callbacks.d.ts +0 -6
  137. package/dist/common/src/util/collections.d.ts +0 -11
  138. package/dist/common/src/util/common.d.ts +0 -2
  139. package/dist/common/src/util/conditions.d.ts +0 -26
  140. package/dist/common/src/util/entities.d.ts +0 -58
  141. package/dist/common/src/util/enums.d.ts +0 -3
  142. package/dist/common/src/util/index.d.ts +0 -16
  143. package/dist/common/src/util/navigation_from_path.d.ts +0 -34
  144. package/dist/common/src/util/navigation_utils.d.ts +0 -20
  145. package/dist/common/src/util/parent_references_from_path.d.ts +0 -6
  146. package/dist/common/src/util/paths.d.ts +0 -14
  147. package/dist/common/src/util/permissions.d.ts +0 -6
  148. package/dist/common/src/util/references.d.ts +0 -2
  149. package/dist/common/src/util/relations.d.ts +0 -22
  150. package/dist/common/src/util/resolutions.d.ts +0 -72
  151. package/dist/common/src/util/storage.d.ts +0 -24
  152. package/dist/index-Cr1D21av.js +0 -49
  153. package/dist/index-Cr1D21av.js.map +0 -1
  154. package/dist/server-core/src/auth/admin-routes.d.ts +0 -27
  155. package/dist/server-core/src/env.d.ts +0 -137
  156. package/dist/types/src/controllers/analytics_controller.d.ts +0 -7
  157. package/dist/types/src/controllers/auth.d.ts +0 -104
  158. package/dist/types/src/controllers/client.d.ts +0 -168
  159. package/dist/types/src/controllers/collection_registry.d.ts +0 -46
  160. package/dist/types/src/controllers/customization_controller.d.ts +0 -60
  161. package/dist/types/src/controllers/data.d.ts +0 -207
  162. package/dist/types/src/controllers/data_driver.d.ts +0 -218
  163. package/dist/types/src/controllers/database_admin.d.ts +0 -11
  164. package/dist/types/src/controllers/dialogs_controller.d.ts +0 -36
  165. package/dist/types/src/controllers/effective_role.d.ts +0 -4
  166. package/dist/types/src/controllers/email.d.ts +0 -36
  167. package/dist/types/src/controllers/index.d.ts +0 -18
  168. package/dist/types/src/controllers/local_config_persistence.d.ts +0 -20
  169. package/dist/types/src/controllers/navigation.d.ts +0 -225
  170. package/dist/types/src/controllers/registry.d.ts +0 -63
  171. package/dist/types/src/controllers/side_dialogs_controller.d.ts +0 -67
  172. package/dist/types/src/controllers/side_entity_controller.d.ts +0 -97
  173. package/dist/types/src/controllers/snackbar.d.ts +0 -24
  174. package/dist/types/src/controllers/storage.d.ts +0 -171
  175. package/dist/types/src/index.d.ts +0 -4
  176. package/dist/types/src/rebase_context.d.ts +0 -122
  177. package/dist/types/src/types/auth_adapter.d.ts +0 -301
  178. package/dist/types/src/types/backend.d.ts +0 -536
  179. package/dist/types/src/types/backend_hooks.d.ts +0 -172
  180. package/dist/types/src/types/builders.d.ts +0 -15
  181. package/dist/types/src/types/chips.d.ts +0 -5
  182. package/dist/types/src/types/collections.d.ts +0 -941
  183. package/dist/types/src/types/component_ref.d.ts +0 -47
  184. package/dist/types/src/types/cron.d.ts +0 -102
  185. package/dist/types/src/types/data_source.d.ts +0 -64
  186. package/dist/types/src/types/database_adapter.d.ts +0 -94
  187. package/dist/types/src/types/entities.d.ts +0 -145
  188. package/dist/types/src/types/entity_actions.d.ts +0 -104
  189. package/dist/types/src/types/entity_callbacks.d.ts +0 -173
  190. package/dist/types/src/types/entity_link_builder.d.ts +0 -7
  191. package/dist/types/src/types/entity_overrides.d.ts +0 -10
  192. package/dist/types/src/types/entity_views.d.ts +0 -87
  193. package/dist/types/src/types/export_import.d.ts +0 -21
  194. package/dist/types/src/types/formex.d.ts +0 -40
  195. package/dist/types/src/types/index.d.ts +0 -28
  196. package/dist/types/src/types/locales.d.ts +0 -4
  197. package/dist/types/src/types/modify_collections.d.ts +0 -5
  198. package/dist/types/src/types/plugins.d.ts +0 -282
  199. package/dist/types/src/types/properties.d.ts +0 -1181
  200. package/dist/types/src/types/property_config.d.ts +0 -74
  201. package/dist/types/src/types/relations.d.ts +0 -336
  202. package/dist/types/src/types/slots.d.ts +0 -262
  203. package/dist/types/src/types/translations.d.ts +0 -900
  204. package/dist/types/src/types/user_management_delegate.d.ts +0 -86
  205. package/dist/types/src/types/websockets.d.ts +0 -78
  206. package/dist/types/src/users/index.d.ts +0 -1
  207. package/dist/types/src/users/user.d.ts +0 -50
  208. package/src/auth/admin-routes.ts +0 -534
  209. package/test/admin-routes.test.ts +0 -544
  210. package/test/backend-hooks-admin.test.ts +0 -388
  211. package/test.ts +0 -6
  212. /package/dist/{server-core/src/api → api}/ast-schema-editor.d.ts +0 -0
  213. /package/dist/{server-core/src/api → api}/collections_for_test/callbacks_test_collection.d.ts +0 -0
  214. /package/dist/{server-core/src/api → api}/graphql/graphql-schema-generator.d.ts +0 -0
  215. /package/dist/{server-core/src/api → api}/graphql/index.d.ts +0 -0
  216. /package/dist/{server-core/src/api → api}/index.d.ts +0 -0
  217. /package/dist/{server-core/src/api → api}/logs-routes.d.ts +0 -0
  218. /package/dist/{server-core/src/api → api}/openapi-generator.d.ts +0 -0
  219. /package/dist/{server-core/src/api → api}/rest/index.d.ts +0 -0
  220. /package/dist/{server-core/src/api → api}/rest/query-parser.d.ts +0 -0
  221. /package/dist/{server-core/src/api → api}/schema-editor-routes.d.ts +0 -0
  222. /package/dist/{server-core/src/api → api}/server.d.ts +0 -0
  223. /package/dist/{server-core/src/auth → auth}/adapter-middleware.d.ts +0 -0
  224. /package/dist/{server-core/src/auth → auth}/api-keys/api-key-middleware.d.ts +0 -0
  225. /package/dist/{server-core/src/auth → auth}/api-keys/api-key-permission-guard.d.ts +0 -0
  226. /package/dist/{server-core/src/auth → auth}/api-keys/api-key-routes.d.ts +0 -0
  227. /package/dist/{server-core/src/auth → auth}/api-keys/api-key-store.d.ts +0 -0
  228. /package/dist/{server-core/src/auth → auth}/api-keys/api-key-types.d.ts +0 -0
  229. /package/dist/{server-core/src/auth → auth}/apple-oauth.d.ts +0 -0
  230. /package/dist/{server-core/src/auth → auth}/bitbucket-oauth.d.ts +0 -0
  231. /package/dist/{server-core/src/auth → auth}/crypto-utils.d.ts +0 -0
  232. /package/dist/{server-core/src/auth → auth}/custom-auth-adapter.d.ts +0 -0
  233. /package/dist/{server-core/src/auth → auth}/discord-oauth.d.ts +0 -0
  234. /package/dist/{server-core/src/auth → auth}/facebook-oauth.d.ts +0 -0
  235. /package/dist/{server-core/src/auth → auth}/github-oauth.d.ts +0 -0
  236. /package/dist/{server-core/src/auth → auth}/gitlab-oauth.d.ts +0 -0
  237. /package/dist/{server-core/src/auth → auth}/google-oauth.d.ts +0 -0
  238. /package/dist/{server-core/src/auth → auth}/interfaces.d.ts +0 -0
  239. /package/dist/{server-core/src/auth → auth}/linkedin-oauth.d.ts +0 -0
  240. /package/dist/{server-core/src/auth → auth}/mfa.d.ts +0 -0
  241. /package/dist/{server-core/src/auth → auth}/microsoft-oauth.d.ts +0 -0
  242. /package/dist/{server-core/src/auth → auth}/middleware.d.ts +0 -0
  243. /package/dist/{server-core/src/auth → auth}/password.d.ts +0 -0
  244. /package/dist/{server-core/src/auth → auth}/rate-limiter.d.ts +0 -0
  245. /package/dist/{server-core/src/auth → auth}/rls-scope.d.ts +0 -0
  246. /package/dist/{server-core/src/auth → auth}/routes.d.ts +0 -0
  247. /package/dist/{server-core/src/auth → auth}/slack-oauth.d.ts +0 -0
  248. /package/dist/{server-core/src/auth → auth}/spotify-oauth.d.ts +0 -0
  249. /package/dist/{server-core/src/auth → auth}/twitter-oauth.d.ts +0 -0
  250. /package/dist/{server-core/src/collections → collections}/BackendCollectionRegistry.d.ts +0 -0
  251. /package/dist/{server-core/src/collections → collections}/loader.d.ts +0 -0
  252. /package/dist/{server-core/src/cron → cron}/cron-loader.d.ts +0 -0
  253. /package/dist/{server-core/src/cron → cron}/cron-routes.d.ts +0 -0
  254. /package/dist/{server-core/src/cron → cron}/cron-store.d.ts +0 -0
  255. /package/dist/{server-core/src/cron → cron}/index.d.ts +0 -0
  256. /package/dist/{server-core/src/db → db}/interfaces.d.ts +0 -0
  257. /package/dist/{server-core/src/email → email}/index.d.ts +0 -0
  258. /package/dist/{server-core/src/email → email}/smtp-email-service.d.ts +0 -0
  259. /package/dist/{server-core/src/email → email}/templates.d.ts +0 -0
  260. /package/dist/{server-core/src/email → email}/types.d.ts +0 -0
  261. /package/dist/{server-core/src/functions → functions}/function-loader.d.ts +0 -0
  262. /package/dist/{server-core/src/functions → functions}/function-routes.d.ts +0 -0
  263. /package/dist/{server-core/src/functions → functions}/index.d.ts +0 -0
  264. /package/dist/{server-core/src/history → history}/history-routes.d.ts +0 -0
  265. /package/dist/{server-core/src/history → history}/index.d.ts +0 -0
  266. /package/dist/{server-core/src/serve-spa.d.ts → serve-spa.d.ts} +0 -0
  267. /package/dist/{server-core/src/services → services}/driver-registry.d.ts +0 -0
  268. /package/dist/{server-core/src/services → services}/webhook-service.d.ts +0 -0
  269. /package/dist/{server-core/src/singleton.d.ts → singleton.d.ts} +0 -0
  270. /package/dist/{server-core/src/storage → storage}/LocalStorageController.d.ts +0 -0
  271. /package/dist/{server-core/src/storage → storage}/S3StorageController.d.ts +0 -0
  272. /package/dist/{server-core/src/storage → storage}/index.d.ts +0 -0
  273. /package/dist/{server-core/src/storage → storage}/routes.d.ts +0 -0
  274. /package/dist/{server-core/src/storage → storage}/storage-registry.d.ts +0 -0
  275. /package/dist/{server-core/src/storage → storage}/tus-handler.d.ts +0 -0
  276. /package/dist/{server-core/src/storage → storage}/types.d.ts +0 -0
  277. /package/dist/{server-core/src/types → types}/index.d.ts +0 -0
  278. /package/dist/{server-core/src/utils → utils}/dev-port.d.ts +0 -0
  279. /package/dist/{server-core/src/utils → utils}/logger.d.ts +0 -0
  280. /package/dist/{server-core/src/utils → utils}/logging.d.ts +0 -0
  281. /package/dist/{server-core/src/utils → utils}/request-logger.d.ts +0 -0
  282. /package/dist/{server-core/src/utils → utils}/sql.d.ts +0 -0
package/src/auth/index.ts CHANGED
@@ -7,9 +7,12 @@ export type { JwtConfig, AccessTokenPayload } from "./jwt";
7
7
  export { hashPassword, verifyPassword, validatePasswordStrength } from "./password";
8
8
  export type { PasswordValidationResult } from "./password";
9
9
 
10
- export type { AuthHooks, AuthMethod, ResolvedAuthOperations } from "./auth-hooks";
10
+ export type { AuthHooks, AuthMethod, ResolvedAuthHooks } from "./auth-hooks";
11
11
  export { resolveAuthHooks } from "./auth-hooks";
12
12
 
13
+ export { generateSecurePassword, generateSecureToken, hashToken, prepareAdminUserValues, finalizeAdminUserCreation } from "./admin-user-ops";
14
+ export type { AdminUserContext, AdminUserPrepareResult } from "./admin-user-ops";
15
+
13
16
  // OAuth Providers
14
17
  export { createGoogleProvider } from "./google-oauth";
15
18
  export type { GoogleProviderConfig } from "./google-oauth";
@@ -32,7 +35,8 @@ export type { AuthMiddlewareOptions, AuthResult } from "./middleware";
32
35
  export { createAuthRoutes } from "./routes";
33
36
  export type { AuthModuleConfig } from "./routes";
34
37
 
35
- export { createAdminRoutes } from "./admin-routes";
38
+ export { createResetPasswordRoute } from "./reset-password-admin";
39
+ export type { ResetPasswordRouteConfig } from "./reset-password-admin";
36
40
 
37
41
 
38
42
  export { createRateLimiter, defaultAuthLimiter, strictAuthLimiter, createApiKeyRateLimiter, apiKeyKeyGenerator } from "./rate-limiter";
package/src/auth/jwt.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import jwt from "jsonwebtoken";
2
2
  import { createHash, randomBytes } from "crypto";
3
+ import { logger } from "../utils/logger";
3
4
 
4
5
  export interface JwtConfig {
5
6
  secret: string;
@@ -13,6 +14,16 @@ export interface AccessTokenPayload {
13
14
  uid?: string;
14
15
  /** Authentication Assurance Level: aal1 = password/oauth, aal2 = MFA verified */
15
16
  aal?: "aal1" | "aal2";
17
+ /** Email claim from the JWT, if present */
18
+ email?: string;
19
+ /** Display name claim from the JWT, if present */
20
+ displayName?: string;
21
+ /** Photo URL claim from the JWT, if present */
22
+ photoURL?: string;
23
+ /** Whether MFA has been verified for this session */
24
+ mfa_verified?: boolean;
25
+ /** Authentication Methods Reference — list of methods used (e.g. 'pwd', 'otp') */
26
+ amr?: string[];
16
27
  }
17
28
 
18
29
  let jwtConfig: JwtConfig = {
@@ -52,14 +63,14 @@ export function configureJwt(config: JwtConfig): void {
52
63
  if (!config.secret || config.secret.length < 32) {
53
64
  throw new Error(
54
65
  "JWT secret is too short. Must be at least 32 characters. " +
55
- "Generate one with: node -e \"console.log(require('crypto').randomBytes(48).toString('base64'))\""
66
+ "Generate one with: node -e \"logger.info(require('crypto').randomBytes(48).toString('base64'))\""
56
67
  );
57
68
  }
58
69
 
59
70
  if (weakSecrets.has(config.secret.toLowerCase())) {
60
71
  throw new Error(
61
72
  "JWT secret is a known default/weak value. Please use a strong, randomly generated secret. " +
62
- "Generate one with: node -e \"console.log(require('crypto').randomBytes(48).toString('base64'))\""
73
+ "Generate one with: node -e \"logger.info(require('crypto').randomBytes(48).toString('base64'))\""
63
74
  );
64
75
  }
65
76
 
@@ -138,7 +149,7 @@ export function verifyAccessToken(token: string): AccessTokenPayload | null {
138
149
  const decoded = jwt.verify(token, jwtConfig.secret, { algorithms: ["HS256"] }) as { userId?: string; uid?: string; sub?: string; roles?: string[]; aal?: string };
139
150
  const id = decoded.userId || decoded.uid || decoded.sub;
140
151
  if (!id) {
141
- console.error("[JWT] Verification failed: missing id in payload", decoded);
152
+ logger.error("[JWT] Verification failed: missing id in payload", { detail: decoded });
142
153
  return null;
143
154
  }
144
155
 
@@ -150,7 +161,7 @@ export function verifyAccessToken(token: string): AccessTokenPayload | null {
150
161
  aal
151
162
  };
152
163
  } catch (error) {
153
- console.error("[JWT] Verification failed:", error, "Token start:", token.substring(0, 15));
164
+ logger.error("[JWT] Verification failed", { error: error, detail: token.substring(0, 15) });
154
165
  return null;
155
166
  }
156
167
  }
@@ -1,5 +1,6 @@
1
1
  import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
2
2
  import { z } from "zod";
3
+ import { logger } from "../utils/logger";
3
4
 
4
5
  export interface LinkedinUserInfo {
5
6
  linkedinId: string;
@@ -38,7 +39,7 @@ export function createLinkedinProvider(config: { clientId: string, clientSecret:
38
39
 
39
40
  if (!tokenResponse.ok) {
40
41
  const errBody = await tokenResponse.text();
41
- console.error("Failed to get LinkedIn access token:", errBody);
42
+ logger.error("Failed to get LinkedIn access token", { detail: errBody });
42
43
  return null;
43
44
  }
44
45
 
@@ -54,7 +55,7 @@ export function createLinkedinProvider(config: { clientId: string, clientSecret:
54
55
 
55
56
  if (!profileResponse.ok) {
56
57
  const errBody = await profileResponse.text();
57
- console.error("Failed to get LinkedIn user info:", errBody);
58
+ logger.error("Failed to get LinkedIn user info", { detail: errBody });
58
59
  return null;
59
60
  }
60
61
 
@@ -73,7 +74,7 @@ export function createLinkedinProvider(config: { clientId: string, clientSecret:
73
74
  photoUrl: profileData.picture || null
74
75
  };
75
76
  } catch (error) {
76
- console.error("LinkedIn OAuth error:", error);
77
+ logger.error("LinkedIn OAuth error", { error: error });
77
78
  return null;
78
79
  }
79
80
  }
@@ -0,0 +1,299 @@
1
+ import { Hono } from "hono";
2
+ import { z } from "zod";
3
+ import { ApiError } from "../api/errors";
4
+ import { HonoEnv } from "../api/types";
5
+ import { requireAuth } from "./middleware";
6
+ import { logger } from "../utils/logger";
7
+ import {
8
+ generateTotpSecret,
9
+ verifyTotp,
10
+ base32Decode,
11
+ generateRecoveryCodes,
12
+ hashRecoveryCode
13
+ } from "./mfa";
14
+ import {
15
+ generateAccessToken,
16
+ generateRefreshToken,
17
+ hashRefreshToken,
18
+ getRefreshTokenExpiry,
19
+ getAccessTokenExpiry
20
+ } from "./jwt";
21
+ import type { AuthModuleConfig } from "./routes";
22
+ import { resolveAuthHooks } from "./auth-hooks";
23
+
24
+ export function mountMfaRoutes(
25
+ router: Hono<HonoEnv>,
26
+ config: AuthModuleConfig,
27
+ ops: ReturnType<typeof resolveAuthHooks>,
28
+ parseBody: <T>(schema: z.ZodSchema<T>, body: unknown) => T
29
+ ): void {
30
+ const authRepo = config.authRepo;
31
+ const emailConfig = config.emailConfig;
32
+
33
+ /**
34
+ * POST /auth/mfa/enroll
35
+ * Start MFA enrollment: generate TOTP secret and recovery codes
36
+ */
37
+ router.post("/mfa/enroll", requireAuth, async (c) => {
38
+ const userCtx = c.get("user") as { userId: string; roles?: string[] } | undefined;
39
+ if (!userCtx) {
40
+ throw ApiError.unauthorized("Not authenticated");
41
+ }
42
+
43
+ const body = (await c.req.json().catch(() => ({}))) as Record<string, unknown>;
44
+ const friendlyName = typeof body.friendlyName === "string" ? body.friendlyName : undefined;
45
+ const issuer = typeof body.issuer === "string" ? body.issuer : emailConfig?.appName || "Rebase";
46
+
47
+ // Get user for account name
48
+ const user = await authRepo.getUserById(userCtx.userId);
49
+ if (!user) {
50
+ throw ApiError.notFound("User not found");
51
+ }
52
+
53
+ // Generate TOTP secret
54
+ const { secret, uri } = generateTotpSecret(issuer, user.email);
55
+
56
+ // Store the factor (unverified until user confirms with a valid code)
57
+ const factor = await authRepo.createMfaFactor(
58
+ user.id,
59
+ "totp",
60
+ secret, // In production, encrypt this before storage
61
+ friendlyName
62
+ );
63
+
64
+ // Generate recovery codes
65
+ const codes = generateRecoveryCodes(10);
66
+ const codeHashes = codes.map(hashRecoveryCode);
67
+ await authRepo.createRecoveryCodes(user.id, codeHashes);
68
+
69
+ return c.json(
70
+ {
71
+ factor: {
72
+ id: factor.id,
73
+ factorType: factor.factorType,
74
+ friendlyName: factor.friendlyName
75
+ },
76
+ totp: {
77
+ secret,
78
+ uri,
79
+ qrUri: uri
80
+ },
81
+ recoveryCodes: codes
82
+ },
83
+ 201
84
+ );
85
+ });
86
+
87
+ /**
88
+ * POST /auth/mfa/verify
89
+ * Verify TOTP code to complete MFA enrollment
90
+ */
91
+ router.post("/mfa/verify", requireAuth, async (c) => {
92
+ const userCtx = c.get("user") as { userId: string; roles?: string[] } | undefined;
93
+ if (!userCtx) {
94
+ throw ApiError.unauthorized("Not authenticated");
95
+ }
96
+
97
+ const verifySchema = z.object({
98
+ factorId: z.string().min(1, "Factor ID is required"),
99
+ code: z.string().length(6, "Code must be 6 digits")
100
+ });
101
+ const { factorId, code } = parseBody(verifySchema, await c.req.json());
102
+
103
+ // Get the factor
104
+ const factor = await authRepo.getMfaFactorById(factorId);
105
+ if (!factor || factor.userId !== userCtx.userId) {
106
+ throw ApiError.notFound("MFA factor not found");
107
+ }
108
+
109
+ if (factor.verified) {
110
+ throw ApiError.badRequest("Factor is already verified", "ALREADY_VERIFIED");
111
+ }
112
+
113
+ // Verify the TOTP code
114
+ const secretBuffer = base32Decode(factor.secretEncrypted);
115
+ const isValid = verifyTotp(secretBuffer, code);
116
+
117
+ if (!isValid) {
118
+ throw ApiError.unauthorized("Invalid TOTP code", "INVALID_CODE");
119
+ }
120
+
121
+ // Mark factor as verified
122
+ await authRepo.verifyMfaFactor(factorId);
123
+
124
+ return c.json({
125
+ success: true,
126
+ message: "MFA factor verified and enrolled"
127
+ });
128
+ });
129
+
130
+ /**
131
+ * POST /auth/mfa/challenge
132
+ * Create an MFA challenge during login (user has MFA enrolled)
133
+ */
134
+ router.post("/mfa/challenge", requireAuth, async (c) => {
135
+ const userCtx = c.get("user") as { userId: string; roles?: string[] } | undefined;
136
+ if (!userCtx) {
137
+ throw ApiError.unauthorized("Not authenticated");
138
+ }
139
+
140
+ const challengeSchema = z.object({
141
+ factorId: z.string().min(1, "Factor ID is required")
142
+ });
143
+ const { factorId } = parseBody(challengeSchema, await c.req.json());
144
+
145
+ // Verify the factor belongs to this user and is verified
146
+ const factor = await authRepo.getMfaFactorById(factorId);
147
+ if (!factor || factor.userId !== userCtx.userId) {
148
+ throw ApiError.notFound("MFA factor not found");
149
+ }
150
+
151
+ if (!factor.verified) {
152
+ throw ApiError.badRequest("MFA factor is not yet verified", "FACTOR_NOT_VERIFIED");
153
+ }
154
+
155
+ const ipAddress = c.req.header("x-forwarded-for") || "unknown";
156
+ const challenge = await authRepo.createMfaChallenge(factorId, ipAddress);
157
+
158
+ return c.json({
159
+ challengeId: challenge.id,
160
+ factorId: challenge.factorId,
161
+ expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString()
162
+ });
163
+ });
164
+
165
+ /**
166
+ * POST /auth/mfa/challenge/verify
167
+ * Verify a TOTP code for an active challenge, upgrade aal1 → aal2
168
+ */
169
+ router.post("/mfa/challenge/verify", requireAuth, async (c) => {
170
+ const userCtx = c.get("user") as { userId: string; roles?: string[] } | undefined;
171
+ if (!userCtx) {
172
+ throw ApiError.unauthorized("Not authenticated");
173
+ }
174
+
175
+ const challengeVerifySchema = z.object({
176
+ challengeId: z.string().min(1, "Challenge ID is required"),
177
+ code: z.string().min(1, "Code is required")
178
+ });
179
+ const { challengeId, code } = parseBody(challengeVerifySchema, await c.req.json());
180
+
181
+ // Find the challenge
182
+ const challenge = await authRepo.getMfaChallengeById(challengeId);
183
+ if (!challenge) {
184
+ throw ApiError.badRequest("Invalid or expired challenge", "INVALID_CHALLENGE");
185
+ }
186
+
187
+ // Get the factor and verify ownership
188
+ const factor = await authRepo.getMfaFactorById(challenge.factorId);
189
+ if (!factor || factor.userId !== userCtx.userId) {
190
+ throw ApiError.notFound("MFA factor not found");
191
+ }
192
+
193
+ // Try TOTP verification first (standard 6-digit codes)
194
+ const secretBuffer = base32Decode(factor.secretEncrypted);
195
+ let isValid = verifyTotp(secretBuffer, code);
196
+
197
+ // Fall back to recovery code verification if TOTP didn't match
198
+ if (!isValid) {
199
+ const codeHash = hashRecoveryCode(code);
200
+ isValid = await authRepo.useRecoveryCode(userCtx.userId, codeHash);
201
+ }
202
+
203
+ if (!isValid) {
204
+ throw ApiError.unauthorized("Invalid verification code", "INVALID_CODE");
205
+ }
206
+
207
+ // Mark challenge as verified
208
+ await authRepo.verifyMfaChallenge(challengeId);
209
+
210
+ // Generate new access token with aal2
211
+ const roles = await authRepo.getUserRoles(userCtx.userId);
212
+ const roleIds = roles.map((r) => r.id);
213
+ const accessToken = generateAccessToken(userCtx.userId, roleIds, "aal2");
214
+ const refreshToken = generateRefreshToken();
215
+
216
+ // Create new refresh token
217
+ await authRepo.createRefreshToken(
218
+ userCtx.userId,
219
+ hashRefreshToken(refreshToken),
220
+ getRefreshTokenExpiry(),
221
+ c.req.header("user-agent") || "unknown",
222
+ c.req.header("x-forwarded-for") || "unknown"
223
+ );
224
+
225
+ // Fire onMfaVerified hook
226
+ if (ops.onMfaVerified) {
227
+ ops.onMfaVerified(userCtx.userId, factor.id).catch((err) => {
228
+ logger.error("[AuthHooks] onMfaVerified error", {
229
+ error: err instanceof Error ? err.message : err
230
+ });
231
+ });
232
+ }
233
+
234
+ return c.json({
235
+ tokens: {
236
+ accessToken,
237
+ refreshToken,
238
+ accessTokenExpiresAt: getAccessTokenExpiry()
239
+ }
240
+ });
241
+ });
242
+
243
+ /**
244
+ * GET /auth/mfa/factors
245
+ * List enrolled MFA factors for the current user
246
+ */
247
+ router.get("/mfa/factors", requireAuth, async (c) => {
248
+ const userCtx = c.get("user") as { userId: string; roles?: string[] } | undefined;
249
+ if (!userCtx) {
250
+ throw ApiError.unauthorized("Not authenticated");
251
+ }
252
+
253
+ const factors = await authRepo.getMfaFactors(userCtx.userId);
254
+ return c.json({
255
+ factors: factors.map((f) => ({
256
+ id: f.id,
257
+ factorType: f.factorType,
258
+ friendlyName: f.friendlyName,
259
+ verified: f.verified,
260
+ createdAt: f.createdAt
261
+ }))
262
+ });
263
+ });
264
+
265
+ /**
266
+ * DELETE /auth/mfa/unenroll
267
+ * Remove an MFA factor
268
+ */
269
+ router.delete("/mfa/unenroll", requireAuth, async (c) => {
270
+ const userCtx = c.get("user") as { userId: string; roles?: string[] } | undefined;
271
+ if (!userCtx) {
272
+ throw ApiError.unauthorized("Not authenticated");
273
+ }
274
+
275
+ const unenrollSchema = z.object({
276
+ factorId: z.string().min(1, "Factor ID is required")
277
+ });
278
+ const { factorId } = parseBody(unenrollSchema, await c.req.json());
279
+
280
+ // Verify ownership
281
+ const factor = await authRepo.getMfaFactorById(factorId);
282
+ if (!factor || factor.userId !== userCtx.userId) {
283
+ throw ApiError.notFound("MFA factor not found");
284
+ }
285
+
286
+ await authRepo.deleteMfaFactor(factorId, userCtx.userId);
287
+
288
+ // If no more verified factors, clean up recovery codes
289
+ const hasFactors = await authRepo.hasVerifiedMfaFactors(userCtx.userId);
290
+ if (!hasFactors) {
291
+ await authRepo.deleteAllRecoveryCodes(userCtx.userId);
292
+ }
293
+
294
+ return c.json({
295
+ success: true,
296
+ message: "MFA factor removed"
297
+ });
298
+ });
299
+ }
package/src/auth/mfa.ts CHANGED
@@ -133,7 +133,8 @@ export function generateTotpSecret(issuer: string, accountName: string): {
133
133
  const encodedAccount = encodeURIComponent(accountName);
134
134
  const uri = `otpauth://totp/${encodedIssuer}:${encodedAccount}?secret=${secret}&issuer=${encodedIssuer}&algorithm=SHA1&digits=6&period=30`;
135
135
 
136
- return { secret, uri };
136
+ return { secret,
137
+ uri };
137
138
  }
138
139
 
139
140
  // ─── Recovery Codes ──────────────────────────────────────────────────────────
@@ -1,5 +1,6 @@
1
1
  import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
2
2
  import { z } from "zod";
3
+ import { logger } from "../utils/logger";
3
4
 
4
5
  /**
5
6
  * Creates a Microsoft / Entra ID (Azure AD) OAuth Provider integration.
@@ -41,7 +42,7 @@ export function createMicrosoftProvider(config: {
41
42
  );
42
43
 
43
44
  if (!tokenResponse.ok) {
44
- console.error("Failed to get Microsoft access token:", await tokenResponse.text());
45
+ logger.error("Failed to get Microsoft access token", { detail: await tokenResponse.text() });
45
46
  return null;
46
47
  }
47
48
 
@@ -54,7 +55,7 @@ export function createMicrosoftProvider(config: {
54
55
  });
55
56
 
56
57
  if (!profileResponse.ok) {
57
- console.error("Failed to get Microsoft user info:", await profileResponse.text());
58
+ logger.error("Failed to get Microsoft user info", { detail: await profileResponse.text() });
58
59
  return null;
59
60
  }
60
61
 
@@ -67,7 +68,7 @@ export function createMicrosoftProvider(config: {
67
68
 
68
69
  const email = profileData.mail || profileData.userPrincipalName;
69
70
  if (!email) {
70
- console.error("Microsoft user has no email");
71
+ logger.error("Microsoft user has no email");
71
72
  return null;
72
73
  }
73
74
 
@@ -80,7 +81,7 @@ export function createMicrosoftProvider(config: {
80
81
  photoUrl: null
81
82
  };
82
83
  } catch (error) {
83
- console.error("Microsoft OAuth error:", error);
84
+ logger.error("Microsoft OAuth error", { error: error });
84
85
  return null;
85
86
  }
86
87
  }
@@ -6,6 +6,7 @@ import { scopeDataDriver } from "./rls-scope";
6
6
  import { safeCompare } from "./crypto-utils";
7
7
  import { isApiKeyToken, validateApiKey } from "./api-keys/api-key-middleware";
8
8
  import type { ApiKeyStore } from "./api-keys/api-key-store";
9
+ import { logger } from "../utils/logger";
9
10
 
10
11
  /**
11
12
  * Result from a custom auth validator.
@@ -295,7 +296,7 @@ code: "UNAUTHORIZED" } }, 401);
295
296
  roles: ["admin"]
296
297
  }));
297
298
  } catch (error) {
298
- console.error("[AUTH] RLS scoping failed for service key:", error);
299
+ logger.error("[AUTH] RLS scoping failed for service key", { error: error });
299
300
  return c.json({ error: { message: "Internal authentication error",
300
301
  code: "INTERNAL_ERROR" } }, 500);
301
302
  }
@@ -305,7 +306,7 @@ code: "INTERNAL_ERROR" } }, 500);
305
306
  // api_keys table instead of JWT verification.
306
307
  const result = await validateApiKey(c, token, {
307
308
  store: apiKeyStore,
308
- driver,
309
+ driver
309
310
  });
310
311
  if (result !== true) {
311
312
  return result;
@@ -322,7 +323,7 @@ roles: payload.roles };
322
323
  c.set("driver", await scopeDataDriver(driver, user));
323
324
  } catch (error) {
324
325
  // withAuth() failed for a valid token — reject (fail closed)
325
- console.error("[AUTH] RLS scoping failed for authenticated user:", error);
326
+ logger.error("[AUTH] RLS scoping failed for authenticated user", { error: error });
326
327
  return c.json({ error: { message: "Internal authentication error",
327
328
  code: "INTERNAL_ERROR" } }, 500);
328
329
  }
@@ -342,7 +343,7 @@ code: "UNAUTHORIZED" } }, 401);
342
343
  c.set("driver", await scopeDataDriver(driver, { uid: "anon",
343
344
  roles: ["anon"] }));
344
345
  } catch (error) {
345
- console.error("[AUTH] Failed to create anon-scoped driver:", error);
346
+ logger.error("[AUTH] Failed to create anon-scoped driver", { error: error });
346
347
  return c.json({ error: { message: "Server configuration error",
347
348
  code: "INTERNAL_ERROR" } }, 500);
348
349
  }
@@ -159,7 +159,7 @@ export function apiKeyKeyGenerator(c: Parameters<MiddlewareHandler<HonoEnv>>[0])
159
159
  */
160
160
  export function createApiKeyRateLimiter(
161
161
  defaultLimit = 1000,
162
- windowMs = 15 * 60 * 1000,
162
+ windowMs = 15 * 60 * 1000
163
163
  ): MiddlewareHandler<HonoEnv> {
164
164
  // We maintain a single shared store keyed by API key ID.
165
165
  // The actual limit is resolved per-request from the key's metadata.
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Standalone admin endpoint for resetting a user's password.
3
+ *
4
+ * Hook resolution order:
5
+ * 1. Collection-level hook (`auth.onResetPassword` on the collection)
6
+ * 2. Backend-level hook (`AuthHooks.onAdminResetPassword`)
7
+ * 3. Built-in default (send reset email, or generate temp password)
8
+ */
9
+
10
+ import { Hono } from "hono";
11
+ import { ApiError, errorHandler } from "../api/errors";
12
+ import type { AuthRepository } from "./interfaces";
13
+ import { createRequireAuth, requireAdmin } from "./middleware";
14
+ import type { AuthHooks } from "./auth-hooks";
15
+ import { resolveAuthHooks } from "./auth-hooks";
16
+ import { generateSecurePassword, generateSecureToken, hashToken } from "./admin-user-ops";
17
+ import { getPasswordResetTemplate } from "../email/templates";
18
+ import type { EmailService, EmailConfig } from "../email";
19
+ import type { HonoEnv } from "../api/types";
20
+ import type { AuthCollectionConfig } from "@rebasepro/types";
21
+ import { logger } from "../utils/logger";
22
+
23
+ export interface ResetPasswordRouteConfig {
24
+ authRepo: AuthRepository;
25
+ emailService?: EmailService;
26
+ emailConfig?: EmailConfig;
27
+ serviceKey?: string;
28
+ authHooks?: AuthHooks;
29
+ /** The parsed auth config from the collection, if available. */
30
+ collectionAuthConfig?: AuthCollectionConfig;
31
+ }
32
+
33
+ /**
34
+ * Create a standalone admin route for resetting user passwords.
35
+ *
36
+ * Mounts: POST /users/:userId/reset-password
37
+ */
38
+ export function createResetPasswordRoute(config: ResetPasswordRouteConfig): Hono<HonoEnv> {
39
+ const router = new Hono<HonoEnv>();
40
+ const authRepo = config.authRepo;
41
+ const { emailService, emailConfig, collectionAuthConfig } = config;
42
+ const ops = resolveAuthHooks(config.authHooks);
43
+
44
+ router.onError(errorHandler);
45
+ router.use("/*", createRequireAuth({ serviceKey: config.serviceKey }));
46
+
47
+ router.post("/users/:userId/reset-password", requireAdmin, async (c) => {
48
+ const userId = c.req.param("userId");
49
+ const existing = await authRepo.getUserById(userId);
50
+ if (!existing) {
51
+ throw ApiError.notFound("User not found");
52
+ }
53
+
54
+ let invitationSent = false;
55
+ let temporaryPassword: string | undefined;
56
+
57
+ // 1. Collection-level hook (closest to the data)
58
+ if (collectionAuthConfig?.onResetPassword) {
59
+ const isEmailConfigured = !!(emailService && emailService.isConfigured());
60
+ const hookResult = await collectionAuthConfig.onResetPassword(existing.id, {
61
+ hashPassword: (password: string) => ops.hashPassword(password),
62
+ sendEmail: isEmailConfigured
63
+ ? (options) => emailService!.send(options)
64
+ : undefined,
65
+ emailConfigured: isEmailConfigured,
66
+ appName: emailConfig?.appName || "Rebase",
67
+ resetPasswordUrl: emailConfig?.resetPasswordUrl || ""
68
+ });
69
+ temporaryPassword = hookResult.temporaryPassword;
70
+ invitationSent = hookResult.invitationSent ?? false;
71
+ }
72
+ // 2. Backend-level hook (global override)
73
+ else if (ops.onAdminResetPassword) {
74
+ const hookResult = await ops.onAdminResetPassword(existing.id, {
75
+ authRepo,
76
+ emailService,
77
+ emailConfig
78
+ });
79
+ temporaryPassword = hookResult.temporaryPassword;
80
+ invitationSent = hookResult.invitationSent ?? false;
81
+ }
82
+ // 3. Built-in default
83
+ else {
84
+ const isEmailConfigured = !!(emailService && emailService.isConfigured());
85
+
86
+ if (isEmailConfigured) {
87
+ try {
88
+ const token = generateSecureToken();
89
+ const tokenHash = hashToken(token);
90
+ const expiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
91
+
92
+ await authRepo.createPasswordResetToken(existing.id, tokenHash, expiresAt);
93
+
94
+ const baseUrl = emailConfig?.resetPasswordUrl || "";
95
+ const setPasswordUrl = `${baseUrl}/reset-password?token=${token}`;
96
+
97
+ const appName = emailConfig?.appName || "Rebase";
98
+ const templateFn = emailConfig?.templates?.passwordReset;
99
+ const emailContent = templateFn
100
+ ? templateFn(setPasswordUrl, { email: existing.email,
101
+ displayName: existing.displayName })
102
+ : getPasswordResetTemplate(setPasswordUrl, { email: existing.email,
103
+ displayName: existing.displayName }, appName);
104
+
105
+ await emailService!.send({
106
+ to: existing.email,
107
+ subject: emailContent.subject,
108
+ html: emailContent.html,
109
+ text: emailContent.text
110
+ });
111
+ invitationSent = true;
112
+ } catch (emailError: unknown) {
113
+ logger.error("Failed to send reset email", { error: emailError instanceof Error ? emailError.message : emailError });
114
+ // Fall back to returning the temporary password
115
+ const clearPassword = generateSecurePassword();
116
+ const passwordHash = await ops.hashPassword(clearPassword);
117
+ await authRepo.updatePassword(existing.id, passwordHash);
118
+ temporaryPassword = clearPassword;
119
+ }
120
+ } else {
121
+ // No email service — generate password, set it, and return one-time
122
+ const clearPassword = generateSecurePassword();
123
+ const passwordHash = await ops.hashPassword(clearPassword);
124
+ await authRepo.updatePassword(existing.id, passwordHash);
125
+ temporaryPassword = clearPassword;
126
+ }
127
+ }
128
+
129
+ const userRoles = await authRepo.getUserRoleIds(existing.id);
130
+
131
+ return c.json({
132
+ user: {
133
+ uid: existing.id,
134
+ email: existing.email,
135
+ displayName: existing.displayName,
136
+ roles: userRoles
137
+ },
138
+ invitationSent,
139
+ ...(temporaryPassword ? { temporaryPassword } : {})
140
+ }, 200);
141
+ });
142
+
143
+ return router;
144
+ }
@@ -47,7 +47,7 @@ function isRLSScopedDriver(driver: DataDriver): driver is RLSScopedDriver {
47
47
  */
48
48
  export async function scopeDataDriver(
49
49
  driver: DataDriver,
50
- user: { uid: string; roles?: string[] },
50
+ user: { uid: string; roles?: string[] }
51
51
  ): Promise<DataDriver> {
52
52
  if (isRLSScopedDriver(driver)) {
53
53
  // Fail closed — do NOT catch and swallow errors here.