@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/api/server.ts CHANGED
@@ -12,6 +12,8 @@ import { createSchemaEditorRoutes } from "./schema-editor-routes";
12
12
  import { createAuthMiddleware, requireAuth, requireAdmin } from "../auth/middleware";
13
13
  import { errorHandler } from "./errors";
14
14
  import { generateOpenApiSpec } from "./openapi-generator";
15
+ import { requestId } from "../utils/request-id";
16
+ import { logger } from "../utils/logger";
15
17
 
16
18
  /**
17
19
  * Simplified API server that leverages existing Rebase infrastructure
@@ -69,6 +71,9 @@ export class RebaseApiServer {
69
71
  * Setup Hono middleware
70
72
  */
71
73
  private setupMiddleware(): void {
74
+ // Request ID — must be first so all downstream middleware can access it
75
+ this.router.use("/*", requestId());
76
+
72
77
  // Security headers — use same-origin-allow-popups for COOP so that
73
78
  // OAuth popup flows (Google, etc.) can postMessage back to the opener.
74
79
  this.router.use("/*", secureHeaders({
@@ -86,6 +91,12 @@ export class RebaseApiServer {
86
91
  : (origin ?? "*"),
87
92
  credentials: this.config.cors.credentials ?? false
88
93
  }));
94
+ } else if (process.env.NODE_ENV !== "production") {
95
+ logger.warn(
96
+ "⚠️ [RebaseApiServer] No CORS configuration provided. " +
97
+ "The API will accept requests from any origin. " +
98
+ "Pass a `cors` option or configure CORS on your Hono app."
99
+ );
89
100
  }
90
101
 
91
102
  // Auth middleware
@@ -120,7 +131,7 @@ export class RebaseApiServer {
120
131
  singularName: col.singularName,
121
132
  description: col.description,
122
133
  properties: Object.keys(col.properties),
123
- relations: (col as EntityCollection & { relations?: Relation[] }).relations?.map((r: Relation) => ({
134
+ relations: col.relations?.map((r: Relation) => ({
124
135
  relationName: r.relationName,
125
136
  target: typeof r.target === "function" ? r.target().slug : r.target,
126
137
  cardinality: r.cardinality,
@@ -179,7 +190,7 @@ export class RebaseApiServer {
179
190
  // Schema Editor endpoints
180
191
  if (this.config.collectionsDir) {
181
192
  if (process.env.NODE_ENV === "production") {
182
- console.warn("[RebaseApiServer] Schema Editor is disabled in production environments for security.");
193
+ logger.warn("[RebaseApiServer] Schema Editor is disabled in production environments for security.");
183
194
  } else {
184
195
  // Auth middlewares applied to schema-editor via the router prefix
185
196
  // MUST be declared before .route() so they execute first
package/src/api/types.ts CHANGED
@@ -14,6 +14,8 @@ export type HonoEnv = {
14
14
  driver?: DataDriver;
15
15
  /** Set when the request is authenticated via a Service API Key. */
16
16
  apiKey?: ApiKeyMasked;
17
+ /** Unique request correlation ID (generated or propagated from X-Request-ID header). */
18
+ requestId?: string;
17
19
  }
18
20
  };
19
21
 
@@ -21,6 +21,7 @@ import type { HonoEnv } from "../api/types";
21
21
  import type { ApiKeyStore } from "./api-keys/api-key-store";
22
22
  import { scopeDataDriver } from "./rls-scope";
23
23
  import { validateApiKey } from "./api-keys/api-key-middleware";
24
+ import { logger } from "../utils/logger";
24
25
 
25
26
  export interface AdapterAuthMiddlewareOptions {
26
27
  /** The auth adapter to delegate verification to. */
@@ -48,7 +49,8 @@ export function createAdapterAuthMiddleware(options: AdapterAuthMiddlewareOption
48
49
  const authHeader = c.req.header("authorization") || "";
49
50
  const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
50
51
  if (token.startsWith("rk_")) {
51
- const result = await validateApiKey(c, token, { store: apiKeyStore, driver });
52
+ const result = await validateApiKey(c, token, { store: apiKeyStore,
53
+ driver });
52
54
  if (result === true) return next();
53
55
  return result;
54
56
  }
@@ -60,7 +62,8 @@ export function createAdapterAuthMiddleware(options: AdapterAuthMiddlewareOption
60
62
  authenticatedUser = await adapter.verifyRequest(c.req.raw);
61
63
  } catch (error) {
62
64
  // adapter.verifyRequest() threw — reject the request (fail closed)
63
- return c.json({ error: { message: "Unauthorized", code: "UNAUTHORIZED" } }, 401);
65
+ return c.json({ error: { message: "Unauthorized",
66
+ code: "UNAUTHORIZED" } }, 401);
64
67
  }
65
68
 
66
69
  if (authenticatedUser) {
@@ -68,30 +71,34 @@ export function createAdapterAuthMiddleware(options: AdapterAuthMiddlewareOption
68
71
  c.set("user", {
69
72
  userId: authenticatedUser.uid,
70
73
  email: authenticatedUser.email,
71
- roles: authenticatedUser.roles,
74
+ roles: authenticatedUser.roles
72
75
  });
73
76
  try {
74
77
  c.set("driver", await scopeDataDriver(driver, {
75
78
  uid: authenticatedUser.uid,
76
- roles: authenticatedUser.roles,
79
+ roles: authenticatedUser.roles
77
80
  }));
78
81
  } catch (error) {
79
- console.error("[AUTH-ADAPTER] RLS scoping failed for authenticated user:", error);
80
- return c.json({ error: { message: "Internal authentication error", code: "INTERNAL_ERROR" } }, 500);
82
+ logger.error("[AUTH-ADAPTER] RLS scoping failed for authenticated user", { error: error });
83
+ return c.json({ error: { message: "Internal authentication error",
84
+ code: "INTERNAL_ERROR" } }, 500);
81
85
  }
82
86
  } else {
83
87
  // Not authenticated — scope as anon for RLS evaluation
84
88
  try {
85
- c.set("driver", await scopeDataDriver(driver, { uid: "anon", roles: ["anon"] }));
89
+ c.set("driver", await scopeDataDriver(driver, { uid: "anon",
90
+ roles: ["anon"] }));
86
91
  } catch (error) {
87
- console.error("[AUTH-ADAPTER] Failed to create anon-scoped driver:", error);
88
- return c.json({ error: { message: "Server configuration error", code: "INTERNAL_ERROR" } }, 500);
92
+ logger.error("[AUTH-ADAPTER] Failed to create anon-scoped driver", { error: error });
93
+ return c.json({ error: { message: "Server configuration error",
94
+ code: "INTERNAL_ERROR" } }, 500);
89
95
  }
90
96
  }
91
97
 
92
98
  // Enforce auth if required
93
99
  if (enforceAuth && !c.get("user")) {
94
- return c.json({ error: { message: "Unauthorized: Authentication required", code: "UNAUTHORIZED" } }, 401);
100
+ return c.json({ error: { message: "Unauthorized: Authentication required",
101
+ code: "UNAUTHORIZED" } }, 401);
95
102
  }
96
103
 
97
104
  return next();
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Admin User Operations
3
+ *
4
+ * Shared utilities and orchestration for admin-initiated user management
5
+ * (user creation via REST API, password reset via admin panel).
6
+ *
7
+ * Hook resolution order:
8
+ * 1. Collection-level hook (`auth.onCreateUser` on the collection) — closest to the data
9
+ * 2. Backend-level hook (`AuthHooks.onAdminCreateUser`) — global override
10
+ * 3. Built-in default — framework fallback
11
+ */
12
+
13
+ import { randomBytes, createHash, randomInt } from "node:crypto";
14
+ import type { AuthRepository } from "./interfaces";
15
+ import type { EmailService, EmailConfig } from "../email";
16
+ import type { ResolvedAuthHooks } from "./auth-hooks";
17
+ import type { AuthCollectionConfig, AuthCollectionContext } from "@rebasepro/types";
18
+ import { getPasswordResetTemplate } from "../email/templates";
19
+ import { logger } from "../utils/logger";
20
+
21
+ // ─── Shared Crypto Utilities ────────────────────────────────────────────────
22
+
23
+ /**
24
+ * Generate a cryptographically secure random password that meets strength requirements.
25
+ *
26
+ * 16 characters, guaranteed at least one uppercase, one lowercase, one digit.
27
+ * Ambiguous characters (0, O, 1, l, I) are excluded.
28
+ */
29
+ export function generateSecurePassword(): string {
30
+ const upper = "ABCDEFGHJKLMNPQRSTUVWXYZ";
31
+ const lower = "abcdefghjkmnpqrstuvwxyz";
32
+ const digits = "23456789";
33
+ const all = upper + lower + digits;
34
+
35
+ const pick = (chars: string) => chars[randomInt(chars.length)];
36
+ const parts = [pick(upper), pick(lower), pick(digits)];
37
+
38
+ for (let i = parts.length; i < 16; i++) {
39
+ parts.push(pick(all));
40
+ }
41
+
42
+ // Shuffle
43
+ for (let i = parts.length - 1; i > 0; i--) {
44
+ const j = randomInt(i + 1);
45
+ [parts[i], parts[j]] = [parts[j], parts[i]];
46
+ }
47
+ return parts.join("");
48
+ }
49
+
50
+ /**
51
+ * Generate a cryptographically secure random token (80 hex characters).
52
+ */
53
+ export function generateSecureToken(): string {
54
+ return randomBytes(40).toString("hex");
55
+ }
56
+
57
+ /**
58
+ * Hash a token for database storage using SHA-256.
59
+ */
60
+ export function hashToken(token: string): string {
61
+ return createHash("sha256").update(token).digest("hex");
62
+ }
63
+
64
+ // ─── Admin User Creation ────────────────────────────────────────────────────
65
+
66
+ /**
67
+ * Context needed by admin user creation / password reset operations.
68
+ */
69
+ export interface AdminUserContext {
70
+ authRepo: AuthRepository;
71
+ emailService?: EmailService;
72
+ emailConfig?: EmailConfig;
73
+ resolvedHooks: ResolvedAuthHooks;
74
+ /** The parsed auth config from the collection (if `auth` is an object, not just `true`). */
75
+ collectionAuthConfig?: AuthCollectionConfig;
76
+ }
77
+
78
+ /**
79
+ * Result of preparing user values for admin-initiated creation.
80
+ */
81
+ export interface AdminUserPrepareResult {
82
+ /** Values ready for `driver.saveEntity()`. */
83
+ values: Record<string, unknown>;
84
+ /** The cleartext password (for returning to admin or sending via email). */
85
+ clearPassword?: string;
86
+ /** Whether the hook already handled the invitation email. */
87
+ hookHandledEmail: boolean;
88
+ /** Whether an invitation was sent (only relevant when hookHandledEmail is true). */
89
+ invitationSent: boolean;
90
+ }
91
+
92
+ /**
93
+ * Build the `AuthCollectionContext` facade from server internals.
94
+ *
95
+ * This is the simplified context exposed to collection-level auth hooks,
96
+ * keeping them decoupled from internal interfaces like `AuthRepository`.
97
+ */
98
+ function buildCollectionContext(ctx: AdminUserContext): AuthCollectionContext {
99
+ const isEmailConfigured = !!(ctx.emailService && ctx.emailService.isConfigured());
100
+
101
+ return {
102
+ hashPassword: (password: string) => ctx.resolvedHooks.hashPassword(password),
103
+ sendEmail: isEmailConfigured
104
+ ? (options) => ctx.emailService!.send(options)
105
+ : undefined,
106
+ emailConfigured: isEmailConfigured,
107
+ appName: ctx.emailConfig?.appName || "Rebase",
108
+ resetPasswordUrl: ctx.emailConfig?.resetPasswordUrl || ""
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Prepare user values for an admin-initiated user creation.
114
+ *
115
+ * Resolution order:
116
+ * 1. Collection-level `auth.onCreateUser` — closest to the data
117
+ * 2. Backend-level `AuthHooks.onAdminCreateUser` — global override
118
+ * 3. Built-in default — generate password → hash → normalize email
119
+ *
120
+ * The caller is responsible for persisting (via `driver.saveEntity()`).
121
+ */
122
+ export async function prepareAdminUserValues(
123
+ body: Record<string, unknown>,
124
+ ctx: AdminUserContext
125
+ ): Promise<AdminUserPrepareResult> {
126
+ const { resolvedHooks, collectionAuthConfig } = ctx;
127
+
128
+ // 1. Collection-level hook (closest to the data)
129
+ if (collectionAuthConfig?.onCreateUser) {
130
+ const collectionCtx = buildCollectionContext(ctx);
131
+ const hookResult = await collectionAuthConfig.onCreateUser(body, collectionCtx);
132
+ return {
133
+ values: hookResult.values,
134
+ clearPassword: hookResult.temporaryPassword,
135
+ hookHandledEmail: true,
136
+ invitationSent: hookResult.invitationSent ?? false
137
+ };
138
+ }
139
+
140
+ // 2. Backend-level hook (global override)
141
+ if (resolvedHooks.onAdminCreateUser) {
142
+ const hookResult = await resolvedHooks.onAdminCreateUser(body, {
143
+ authRepo: ctx.authRepo,
144
+ emailService: ctx.emailService,
145
+ emailConfig: ctx.emailConfig,
146
+ hashPassword: (password: string) => resolvedHooks.hashPassword(password)
147
+ });
148
+ return {
149
+ values: hookResult.values,
150
+ clearPassword: hookResult.temporaryPassword,
151
+ hookHandledEmail: true,
152
+ invitationSent: hookResult.invitationSent ?? false
153
+ };
154
+ }
155
+
156
+ // 3. Built-in default
157
+ const password = body.password as string | undefined;
158
+ const clearPassword = password || generateSecurePassword();
159
+ const passwordHash = await resolvedHooks.hashPassword(clearPassword);
160
+
161
+ const values = { ...body };
162
+ values.passwordHash = passwordHash;
163
+ if (values.email) {
164
+ values.email = (values.email as string).toLowerCase();
165
+ }
166
+ values.emailVerified = true;
167
+ delete values.password;
168
+
169
+ return {
170
+ values,
171
+ clearPassword: password ? undefined : clearPassword,
172
+ hookHandledEmail: false,
173
+ invitationSent: false
174
+ };
175
+ }
176
+
177
+ /**
178
+ * Handle post-creation work for admin-created users.
179
+ *
180
+ * Sends an invitation email (password-reset link) if email is configured
181
+ * and no explicit password was provided. Falls back to returning the
182
+ * temporary password if email fails or is not configured.
183
+ */
184
+ export async function finalizeAdminUserCreation(
185
+ entity: { id: string; values: Record<string, unknown> },
186
+ clearPassword: string | undefined,
187
+ ctx: AdminUserContext
188
+ ): Promise<{
189
+ temporaryPassword?: string;
190
+ invitationSent: boolean;
191
+ }> {
192
+ // If an explicit password was provided (clearPassword is undefined), nothing to do
193
+ if (!clearPassword) {
194
+ return { invitationSent: false };
195
+ }
196
+
197
+ const isEmailConfigured = !!(ctx.emailService && ctx.emailService.isConfigured());
198
+
199
+ if (isEmailConfigured) {
200
+ try {
201
+ const token = generateSecureToken();
202
+ const tokenHash = hashToken(token);
203
+ const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
204
+
205
+ await ctx.authRepo.createPasswordResetToken(entity.id, tokenHash, expiresAt);
206
+
207
+ const baseUrl = ctx.emailConfig?.resetPasswordUrl || "";
208
+ const setPasswordUrl = `${baseUrl}/reset-password?token=${token}`;
209
+
210
+ const appName = ctx.emailConfig?.appName || "Rebase";
211
+ const templateFn = ctx.emailConfig?.templates?.passwordReset;
212
+ const emailContent = templateFn
213
+ ? templateFn(setPasswordUrl, { email: entity.values.email as string,
214
+ displayName: entity.values.displayName as string })
215
+ : getPasswordResetTemplate(setPasswordUrl, { email: entity.values.email as string,
216
+ displayName: entity.values.displayName as string }, appName);
217
+
218
+ await ctx.emailService!.send({
219
+ to: entity.values.email as string,
220
+ subject: emailContent.subject,
221
+ html: emailContent.html,
222
+ text: emailContent.text
223
+ });
224
+ return { invitationSent: true };
225
+ } catch (emailError: unknown) {
226
+ logger.error("Failed to send reset email", { error: emailError instanceof Error ? emailError.message : emailError });
227
+ // Fall back to returning the temporary password
228
+ return { temporaryPassword: clearPassword,
229
+ invitationSent: false };
230
+ }
231
+ }
232
+
233
+ // No email service — return the temporary password
234
+ return { temporaryPassword: clearPassword,
235
+ invitationSent: false };
236
+ }
@@ -20,6 +20,7 @@ import type { HonoEnv } from "../../api/types";
20
20
  import type { ApiKeyStore } from "./api-key-store";
21
21
  import type { ApiKeyMasked } from "./api-key-types";
22
22
  import { scopeDataDriver } from "../rls-scope";
23
+ import { logger } from "../../utils/logger";
23
24
 
24
25
  /**
25
26
  * Check whether a token looks like a Rebase API key.
@@ -55,7 +56,7 @@ export interface ApiKeyAuthOptions {
55
56
  export async function validateApiKey(
56
57
  c: Context<HonoEnv>,
57
58
  token: string,
58
- options: ApiKeyAuthOptions,
59
+ options: ApiKeyAuthOptions
59
60
  ): Promise<Response | true> {
60
61
  const { store, driver } = options;
61
62
 
@@ -64,27 +65,31 @@ export async function validateApiKey(
64
65
 
65
66
  if (!apiKey) {
66
67
  return c.json({
67
- error: { message: "Invalid API key", code: "UNAUTHORIZED" },
68
+ error: { message: "Invalid API key",
69
+ code: "UNAUTHORIZED" }
68
70
  }, 401);
69
71
  }
70
72
 
71
73
  // Check revocation
72
74
  if (apiKey.revoked_at) {
73
75
  return c.json({
74
- error: { message: "API key has been revoked", code: "UNAUTHORIZED" },
76
+ error: { message: "API key has been revoked",
77
+ code: "UNAUTHORIZED" }
75
78
  }, 401);
76
79
  }
77
80
 
78
81
  // Check expiration
79
82
  if (apiKey.expires_at && new Date(apiKey.expires_at) < new Date()) {
80
83
  return c.json({
81
- error: { message: "API key has expired", code: "UNAUTHORIZED" },
84
+ error: { message: "API key has expired",
85
+ code: "UNAUTHORIZED" }
82
86
  }, 401);
83
87
  }
84
88
 
85
89
  // Set user identity — API keys represent service accounts
86
90
  const userId = `api-key:${apiKey.id}`;
87
- c.set("user", { userId, roles: [] });
91
+ c.set("user", { userId,
92
+ roles: [] });
88
93
 
89
94
  // Expose masked key metadata for downstream permission checks
90
95
  const masked: ApiKeyMasked = {
@@ -98,7 +103,7 @@ export async function validateApiKey(
98
103
  updated_at: apiKey.updated_at,
99
104
  last_used_at: apiKey.last_used_at,
100
105
  expires_at: apiKey.expires_at,
101
- revoked_at: apiKey.revoked_at,
106
+ revoked_at: apiKey.revoked_at
102
107
  };
103
108
  // Store apiKey in the context for permission checking in api-generator
104
109
  c.set("apiKey", masked);
@@ -107,13 +112,14 @@ export async function validateApiKey(
107
112
  try {
108
113
  const scopedDriver = await scopeDataDriver(driver, {
109
114
  uid: userId,
110
- roles: ["service"],
115
+ roles: ["service"]
111
116
  });
112
117
  c.set("driver", scopedDriver);
113
118
  } catch (error) {
114
- console.error("[AUTH] RLS scoping failed for API key:", error);
119
+ logger.error("[AUTH] RLS scoping failed for API key", { error: error });
115
120
  return c.json({
116
- error: { message: "Internal authentication error", code: "INTERNAL_ERROR" },
121
+ error: { message: "Internal authentication error",
122
+ code: "INTERNAL_ERROR" }
117
123
  }, 500);
118
124
  }
119
125
 
@@ -52,7 +52,7 @@ export function httpMethodToOperation(method: string): ApiKeyOperation {
52
52
  export function isOperationAllowed(
53
53
  permissions: ApiKeyPermission[],
54
54
  collection: string,
55
- operation: ApiKeyOperation,
55
+ operation: ApiKeyOperation
56
56
  ): boolean {
57
57
  for (const perm of permissions) {
58
58
  const collectionMatch = perm.collection === "*" || perm.collection === collection;
@@ -68,7 +68,7 @@ export function createApiKeyRoutes(options: ApiKeyRouteOptions): Hono<HonoEnv> {
68
68
  if (!validatePermissions(permissions)) {
69
69
  throw ApiError.badRequest(
70
70
  "Permissions must be an array of { collection: string, operations: ('read' | 'write' | 'delete')[] }",
71
- "INVALID_INPUT",
71
+ "INVALID_INPUT"
72
72
  );
73
73
  }
74
74
 
@@ -97,7 +97,7 @@ export function createApiKeyRoutes(options: ApiKeyRouteOptions): Hono<HonoEnv> {
97
97
  name: name.trim(),
98
98
  permissions: permissions as ApiKeyPermission[],
99
99
  rate_limit: (rate_limit as number | null) ?? null,
100
- expires_at: (expires_at as string | null) ?? null,
100
+ expires_at: (expires_at as string | null) ?? null
101
101
  };
102
102
 
103
103
  const keyWithSecret = await store.createApiKey(request, createdBy);
@@ -134,7 +134,7 @@ export function createApiKeyRoutes(options: ApiKeyRouteOptions): Hono<HonoEnv> {
134
134
  if (!validatePermissions(permissions)) {
135
135
  throw ApiError.badRequest(
136
136
  "Permissions must be an array of { collection: string, operations: ('read' | 'write' | 'delete')[] }",
137
- "INVALID_INPUT",
137
+ "INVALID_INPUT"
138
138
  );
139
139
  }
140
140
  }
@@ -15,9 +15,10 @@ import { logger } from "../../utils/logger";
15
15
  import type {
16
16
  ApiKey,
17
17
  ApiKeyMasked,
18
+ ApiKeyPermission,
18
19
  ApiKeyWithSecret,
19
20
  CreateApiKeyRequest,
20
- UpdateApiKeyRequest,
21
+ UpdateApiKeyRequest
21
22
  } from "./api-key-types";
22
23
 
23
24
  const TABLE = "rebase.api_keys";
@@ -64,7 +65,7 @@ function toMasked(row: ApiKey): ApiKeyMasked {
64
65
  updated_at: row.updated_at,
65
66
  last_used_at: row.last_used_at,
66
67
  expires_at: row.expires_at,
67
- revoked_at: row.revoked_at,
68
+ revoked_at: row.revoked_at
68
69
  };
69
70
  }
70
71
 
@@ -72,9 +73,14 @@ function toMasked(row: ApiKey): ApiKeyMasked {
72
73
  * Parse a raw DB row into the typed `ApiKey` shape.
73
74
  */
74
75
  function rowToApiKey(row: Record<string, unknown>): ApiKey {
75
- const permissions = typeof row.permissions === "string"
76
- ? JSON.parse(row.permissions)
77
- : (row.permissions ?? []);
76
+ let permissions = (row.permissions ?? []) as ApiKeyPermission[];
77
+ if (typeof row.permissions === "string") {
78
+ try {
79
+ permissions = JSON.parse(row.permissions) as ApiKeyPermission[];
80
+ } catch {
81
+ permissions = [];
82
+ }
83
+ }
78
84
 
79
85
  return {
80
86
  id: row.id as string,
@@ -90,7 +96,7 @@ function rowToApiKey(row: Record<string, unknown>): ApiKey {
90
96
  updated_at: new Date(row.updated_at as string).toISOString(),
91
97
  last_used_at: row.last_used_at ? new Date(row.last_used_at as string).toISOString() : null,
92
98
  expires_at: row.expires_at ? new Date(row.expires_at as string).toISOString() : null,
93
- revoked_at: row.revoked_at ? new Date(row.revoked_at as string).toISOString() : null,
99
+ revoked_at: row.revoked_at ? new Date(row.revoked_at as string).toISOString() : null
94
100
  };
95
101
  }
96
102
 
@@ -212,7 +218,7 @@ export function createApiKeyStore(driver: DataDriver): ApiKeyStore | undefined {
212
218
  const apiKey = rowToApiKey(rows[0]);
213
219
  return {
214
220
  ...toMasked(apiKey),
215
- key: plaintext,
221
+ key: plaintext
216
222
  };
217
223
  },
218
224
 
@@ -312,6 +318,6 @@ export function createApiKeyStore(driver: DataDriver): ApiKeyStore | undefined {
312
318
  // Non-blocking — don't fail requests because of a usage timestamp update
313
319
  logger.error("[api-key-store] Failed to update last_used_at", { error: err });
314
320
  }
315
- },
321
+ }
316
322
  };
317
323
  }
@@ -14,7 +14,7 @@ export type {
14
14
  ApiKeyPermission,
15
15
  ApiKeyWithSecret,
16
16
  CreateApiKeyRequest,
17
- UpdateApiKeyRequest,
17
+ UpdateApiKeyRequest
18
18
  } from "./api-key-types";
19
19
 
20
20
  // Store
@@ -28,7 +28,7 @@ export type { ApiKeyAuthOptions } from "./api-key-middleware";
28
28
  // Permission guard
29
29
  export {
30
30
  httpMethodToOperation,
31
- isOperationAllowed,
31
+ isOperationAllowed
32
32
  } from "./api-key-permission-guard";
33
33
  export type { ApiKeyOperation } from "./api-key-permission-guard";
34
34
 
@@ -1,6 +1,7 @@
1
1
  import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
2
2
  import { z } from "zod";
3
3
  import jwt from "jsonwebtoken";
4
+ import { logger } from "../utils/logger";
4
5
  /**
5
6
  * Creates an Apple Sign In OAuth Provider integration.
6
7
  *
@@ -75,7 +76,7 @@ export function createAppleProvider(config: {
75
76
  });
76
77
 
77
78
  if (!tokenResponse.ok) {
78
- console.error("Failed to get Apple access token:", await tokenResponse.text());
79
+ logger.error("Failed to get Apple access token", { detail: await tokenResponse.text() });
79
80
  return null;
80
81
  }
81
82
 
@@ -95,7 +96,7 @@ export function createAppleProvider(config: {
95
96
  // the user object from the first auth for us to capture the name.
96
97
  const email = decoded.email || payload.user?.email;
97
98
  if (!email) {
98
- console.error("Apple user has no email");
99
+ logger.error("Apple user has no email");
99
100
  return null;
100
101
  }
101
102
 
@@ -112,7 +113,7 @@ export function createAppleProvider(config: {
112
113
  photoUrl: null // Apple does not provide a profile photo
113
114
  };
114
115
  } catch (error) {
115
- console.error("Apple OAuth error:", error);
116
+ logger.error("Apple OAuth error", { error: error });
116
117
  return null;
117
118
  }
118
119
  }