@rebasepro/server-core 0.0.1-canary.09e5ec5

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/LICENSE +6 -0
  2. package/README.md +40 -0
  3. package/build-errors.txt +52 -0
  4. package/coverage/clover.xml +3739 -0
  5. package/coverage/coverage-final.json +31 -0
  6. package/coverage/lcov-report/base.css +224 -0
  7. package/coverage/lcov-report/block-navigation.js +87 -0
  8. package/coverage/lcov-report/favicon.png +0 -0
  9. package/coverage/lcov-report/index.html +266 -0
  10. package/coverage/lcov-report/prettify.css +1 -0
  11. package/coverage/lcov-report/prettify.js +2 -0
  12. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  13. package/coverage/lcov-report/sorter.js +210 -0
  14. package/coverage/lcov-report/src/api/ast-schema-editor.ts.html +952 -0
  15. package/coverage/lcov-report/src/api/errors.ts.html +472 -0
  16. package/coverage/lcov-report/src/api/graphql/graphql-schema-generator.ts.html +1069 -0
  17. package/coverage/lcov-report/src/api/graphql/index.html +116 -0
  18. package/coverage/lcov-report/src/api/index.html +176 -0
  19. package/coverage/lcov-report/src/api/openapi-generator.ts.html +565 -0
  20. package/coverage/lcov-report/src/api/rest/api-generator.ts.html +994 -0
  21. package/coverage/lcov-report/src/api/rest/index.html +131 -0
  22. package/coverage/lcov-report/src/api/rest/query-parser.ts.html +550 -0
  23. package/coverage/lcov-report/src/api/schema-editor-routes.ts.html +202 -0
  24. package/coverage/lcov-report/src/api/server.ts.html +823 -0
  25. package/coverage/lcov-report/src/auth/admin-routes.ts.html +973 -0
  26. package/coverage/lcov-report/src/auth/index.html +176 -0
  27. package/coverage/lcov-report/src/auth/jwt.ts.html +574 -0
  28. package/coverage/lcov-report/src/auth/middleware.ts.html +745 -0
  29. package/coverage/lcov-report/src/auth/password.ts.html +310 -0
  30. package/coverage/lcov-report/src/auth/services.ts.html +2074 -0
  31. package/coverage/lcov-report/src/collections/index.html +116 -0
  32. package/coverage/lcov-report/src/collections/loader.ts.html +232 -0
  33. package/coverage/lcov-report/src/db/auth-schema.ts.html +523 -0
  34. package/coverage/lcov-report/src/db/data-transformer.ts.html +1753 -0
  35. package/coverage/lcov-report/src/db/entityService.ts.html +700 -0
  36. package/coverage/lcov-report/src/db/index.html +146 -0
  37. package/coverage/lcov-report/src/db/services/EntityFetchService.ts.html +4048 -0
  38. package/coverage/lcov-report/src/db/services/EntityPersistService.ts.html +883 -0
  39. package/coverage/lcov-report/src/db/services/RelationService.ts.html +3121 -0
  40. package/coverage/lcov-report/src/db/services/entity-helpers.ts.html +442 -0
  41. package/coverage/lcov-report/src/db/services/index.html +176 -0
  42. package/coverage/lcov-report/src/db/services/index.ts.html +124 -0
  43. package/coverage/lcov-report/src/generate-drizzle-schema-logic.ts.html +1960 -0
  44. package/coverage/lcov-report/src/index.html +116 -0
  45. package/coverage/lcov-report/src/services/driver-registry.ts.html +631 -0
  46. package/coverage/lcov-report/src/services/index.html +131 -0
  47. package/coverage/lcov-report/src/services/postgresDataDriver.ts.html +3025 -0
  48. package/coverage/lcov-report/src/storage/LocalStorageController.ts.html +1189 -0
  49. package/coverage/lcov-report/src/storage/S3StorageController.ts.html +970 -0
  50. package/coverage/lcov-report/src/storage/index.html +161 -0
  51. package/coverage/lcov-report/src/storage/storage-registry.ts.html +646 -0
  52. package/coverage/lcov-report/src/storage/types.ts.html +451 -0
  53. package/coverage/lcov-report/src/utils/drizzle-conditions.ts.html +3082 -0
  54. package/coverage/lcov-report/src/utils/index.html +116 -0
  55. package/coverage/lcov.info +7179 -0
  56. package/dist/common/src/collections/CollectionRegistry.d.ts +56 -0
  57. package/dist/common/src/collections/index.d.ts +1 -0
  58. package/dist/common/src/data/buildRebaseData.d.ts +14 -0
  59. package/dist/common/src/index.d.ts +3 -0
  60. package/dist/common/src/util/builders.d.ts +57 -0
  61. package/dist/common/src/util/callbacks.d.ts +6 -0
  62. package/dist/common/src/util/collections.d.ts +11 -0
  63. package/dist/common/src/util/common.d.ts +2 -0
  64. package/dist/common/src/util/conditions.d.ts +26 -0
  65. package/dist/common/src/util/entities.d.ts +58 -0
  66. package/dist/common/src/util/enums.d.ts +3 -0
  67. package/dist/common/src/util/index.d.ts +16 -0
  68. package/dist/common/src/util/navigation_from_path.d.ts +34 -0
  69. package/dist/common/src/util/navigation_utils.d.ts +20 -0
  70. package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
  71. package/dist/common/src/util/paths.d.ts +14 -0
  72. package/dist/common/src/util/permissions.d.ts +5 -0
  73. package/dist/common/src/util/references.d.ts +2 -0
  74. package/dist/common/src/util/relations.d.ts +22 -0
  75. package/dist/common/src/util/resolutions.d.ts +72 -0
  76. package/dist/common/src/util/storage.d.ts +24 -0
  77. package/dist/index-DXVBFp5V.js +37 -0
  78. package/dist/index-DXVBFp5V.js.map +1 -0
  79. package/dist/index.es.js +49934 -0
  80. package/dist/index.es.js.map +1 -0
  81. package/dist/index.umd.js +49968 -0
  82. package/dist/index.umd.js.map +1 -0
  83. package/dist/server-core/src/api/ast-schema-editor.d.ts +21 -0
  84. package/dist/server-core/src/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
  85. package/dist/server-core/src/api/errors.d.ts +35 -0
  86. package/dist/server-core/src/api/graphql/graphql-schema-generator.d.ts +35 -0
  87. package/dist/server-core/src/api/graphql/index.d.ts +1 -0
  88. package/dist/server-core/src/api/index.d.ts +9 -0
  89. package/dist/server-core/src/api/openapi-generator.d.ts +16 -0
  90. package/dist/server-core/src/api/rest/api-generator.d.ts +64 -0
  91. package/dist/server-core/src/api/rest/index.d.ts +1 -0
  92. package/dist/server-core/src/api/rest/query-parser.d.ts +9 -0
  93. package/dist/server-core/src/api/schema-editor-routes.d.ts +3 -0
  94. package/dist/server-core/src/api/server.d.ts +40 -0
  95. package/dist/server-core/src/api/types.d.ts +90 -0
  96. package/dist/server-core/src/auth/admin-routes.d.ts +16 -0
  97. package/dist/server-core/src/auth/apple-oauth.d.ts +30 -0
  98. package/dist/server-core/src/auth/bitbucket-oauth.d.ts +11 -0
  99. package/dist/server-core/src/auth/discord-oauth.d.ts +14 -0
  100. package/dist/server-core/src/auth/facebook-oauth.d.ts +14 -0
  101. package/dist/server-core/src/auth/github-oauth.d.ts +15 -0
  102. package/dist/server-core/src/auth/gitlab-oauth.d.ts +13 -0
  103. package/dist/server-core/src/auth/google-oauth.d.ts +14 -0
  104. package/dist/server-core/src/auth/index.d.ts +23 -0
  105. package/dist/server-core/src/auth/interfaces.d.ts +309 -0
  106. package/dist/server-core/src/auth/jwt.d.ts +43 -0
  107. package/dist/server-core/src/auth/linkedin-oauth.d.ts +18 -0
  108. package/dist/server-core/src/auth/microsoft-oauth.d.ts +16 -0
  109. package/dist/server-core/src/auth/middleware.d.ts +81 -0
  110. package/dist/server-core/src/auth/password.d.ts +22 -0
  111. package/dist/server-core/src/auth/rate-limiter.d.ts +31 -0
  112. package/dist/server-core/src/auth/routes.d.ts +27 -0
  113. package/dist/server-core/src/auth/slack-oauth.d.ts +12 -0
  114. package/dist/server-core/src/auth/spotify-oauth.d.ts +12 -0
  115. package/dist/server-core/src/auth/twitter-oauth.d.ts +18 -0
  116. package/dist/server-core/src/bootstrappers/index.d.ts +0 -0
  117. package/dist/server-core/src/collections/BackendCollectionRegistry.d.ts +13 -0
  118. package/dist/server-core/src/collections/loader.d.ts +5 -0
  119. package/dist/server-core/src/cron/cron-loader.d.ts +17 -0
  120. package/dist/server-core/src/cron/cron-routes.d.ts +14 -0
  121. package/dist/server-core/src/cron/cron-scheduler.d.ts +61 -0
  122. package/dist/server-core/src/cron/cron-store.d.ts +32 -0
  123. package/dist/server-core/src/cron/index.d.ts +6 -0
  124. package/dist/server-core/src/db/interfaces.d.ts +18 -0
  125. package/dist/server-core/src/email/index.d.ts +6 -0
  126. package/dist/server-core/src/email/smtp-email-service.d.ts +25 -0
  127. package/dist/server-core/src/email/templates.d.ts +42 -0
  128. package/dist/server-core/src/email/types.d.ts +107 -0
  129. package/dist/server-core/src/functions/function-loader.d.ts +17 -0
  130. package/dist/server-core/src/functions/function-routes.d.ts +10 -0
  131. package/dist/server-core/src/functions/index.d.ts +3 -0
  132. package/dist/server-core/src/history/history-routes.d.ts +23 -0
  133. package/dist/server-core/src/history/index.d.ts +1 -0
  134. package/dist/server-core/src/index.d.ts +29 -0
  135. package/dist/server-core/src/init.d.ts +159 -0
  136. package/dist/server-core/src/serve-spa.d.ts +30 -0
  137. package/dist/server-core/src/services/driver-registry.d.ts +78 -0
  138. package/dist/server-core/src/singleton.d.ts +35 -0
  139. package/dist/server-core/src/storage/LocalStorageController.d.ts +46 -0
  140. package/dist/server-core/src/storage/S3StorageController.d.ts +36 -0
  141. package/dist/server-core/src/storage/index.d.ts +25 -0
  142. package/dist/server-core/src/storage/routes.d.ts +38 -0
  143. package/dist/server-core/src/storage/storage-registry.d.ts +78 -0
  144. package/dist/server-core/src/storage/types.d.ts +103 -0
  145. package/dist/server-core/src/types/index.d.ts +11 -0
  146. package/dist/server-core/src/utils/dev-port.d.ts +35 -0
  147. package/dist/server-core/src/utils/logger.d.ts +31 -0
  148. package/dist/server-core/src/utils/logging.d.ts +9 -0
  149. package/dist/server-core/src/utils/request-logger.d.ts +19 -0
  150. package/dist/server-core/src/utils/sql.d.ts +27 -0
  151. package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
  152. package/dist/types/src/controllers/auth.d.ts +119 -0
  153. package/dist/types/src/controllers/client.d.ts +170 -0
  154. package/dist/types/src/controllers/collection_registry.d.ts +45 -0
  155. package/dist/types/src/controllers/customization_controller.d.ts +60 -0
  156. package/dist/types/src/controllers/data.d.ts +168 -0
  157. package/dist/types/src/controllers/data_driver.d.ts +160 -0
  158. package/dist/types/src/controllers/database_admin.d.ts +11 -0
  159. package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
  160. package/dist/types/src/controllers/effective_role.d.ts +4 -0
  161. package/dist/types/src/controllers/email.d.ts +34 -0
  162. package/dist/types/src/controllers/index.d.ts +18 -0
  163. package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
  164. package/dist/types/src/controllers/navigation.d.ts +213 -0
  165. package/dist/types/src/controllers/registry.d.ts +54 -0
  166. package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
  167. package/dist/types/src/controllers/side_entity_controller.d.ts +90 -0
  168. package/dist/types/src/controllers/snackbar.d.ts +24 -0
  169. package/dist/types/src/controllers/storage.d.ts +171 -0
  170. package/dist/types/src/index.d.ts +4 -0
  171. package/dist/types/src/rebase_context.d.ts +105 -0
  172. package/dist/types/src/types/backend.d.ts +536 -0
  173. package/dist/types/src/types/builders.d.ts +15 -0
  174. package/dist/types/src/types/chips.d.ts +5 -0
  175. package/dist/types/src/types/collections.d.ts +856 -0
  176. package/dist/types/src/types/cron.d.ts +102 -0
  177. package/dist/types/src/types/data_source.d.ts +64 -0
  178. package/dist/types/src/types/entities.d.ts +145 -0
  179. package/dist/types/src/types/entity_actions.d.ts +98 -0
  180. package/dist/types/src/types/entity_callbacks.d.ts +173 -0
  181. package/dist/types/src/types/entity_link_builder.d.ts +7 -0
  182. package/dist/types/src/types/entity_overrides.d.ts +10 -0
  183. package/dist/types/src/types/entity_views.d.ts +61 -0
  184. package/dist/types/src/types/export_import.d.ts +21 -0
  185. package/dist/types/src/types/index.d.ts +23 -0
  186. package/dist/types/src/types/locales.d.ts +4 -0
  187. package/dist/types/src/types/modify_collections.d.ts +5 -0
  188. package/dist/types/src/types/plugins.d.ts +279 -0
  189. package/dist/types/src/types/properties.d.ts +1176 -0
  190. package/dist/types/src/types/property_config.d.ts +70 -0
  191. package/dist/types/src/types/relations.d.ts +336 -0
  192. package/dist/types/src/types/slots.d.ts +252 -0
  193. package/dist/types/src/types/translations.d.ts +870 -0
  194. package/dist/types/src/types/user_management_delegate.d.ts +121 -0
  195. package/dist/types/src/types/websockets.d.ts +78 -0
  196. package/dist/types/src/users/index.d.ts +2 -0
  197. package/dist/types/src/users/roles.d.ts +22 -0
  198. package/dist/types/src/users/user.d.ts +46 -0
  199. package/history_diff.log +385 -0
  200. package/jest.config.cjs +16 -0
  201. package/package.json +86 -0
  202. package/scratch.ts +9 -0
  203. package/src/api/ast-schema-editor.ts +289 -0
  204. package/src/api/collections_for_test/callbacks_test_collection.ts +60 -0
  205. package/src/api/errors.ts +179 -0
  206. package/src/api/graphql/graphql-schema-generator.ts +336 -0
  207. package/src/api/graphql/index.ts +2 -0
  208. package/src/api/index.ts +11 -0
  209. package/src/api/openapi-generator.ts +715 -0
  210. package/src/api/rest/api-generator.ts +472 -0
  211. package/src/api/rest/index.ts +2 -0
  212. package/src/api/rest/query-parser.ts +155 -0
  213. package/src/api/schema-editor-routes.ts +41 -0
  214. package/src/api/server.ts +248 -0
  215. package/src/api/types.ts +90 -0
  216. package/src/auth/admin-routes.ts +529 -0
  217. package/src/auth/apple-oauth.ts +130 -0
  218. package/src/auth/bitbucket-oauth.ts +82 -0
  219. package/src/auth/discord-oauth.ts +83 -0
  220. package/src/auth/facebook-oauth.ts +72 -0
  221. package/src/auth/github-oauth.ts +110 -0
  222. package/src/auth/gitlab-oauth.ts +70 -0
  223. package/src/auth/google-oauth.ts +48 -0
  224. package/src/auth/index.ts +34 -0
  225. package/src/auth/interfaces.ts +363 -0
  226. package/src/auth/jwt.ts +181 -0
  227. package/src/auth/linkedin-oauth.ts +81 -0
  228. package/src/auth/microsoft-oauth.ts +88 -0
  229. package/src/auth/middleware.ts +384 -0
  230. package/src/auth/password.ts +77 -0
  231. package/src/auth/rate-limiter.ts +129 -0
  232. package/src/auth/routes.ts +788 -0
  233. package/src/auth/slack-oauth.ts +71 -0
  234. package/src/auth/spotify-oauth.ts +67 -0
  235. package/src/auth/twitter-oauth.ts +120 -0
  236. package/src/bootstrappers/index.ts +1 -0
  237. package/src/collections/BackendCollectionRegistry.ts +20 -0
  238. package/src/collections/loader.ts +49 -0
  239. package/src/cron/cron-loader.ts +89 -0
  240. package/src/cron/cron-routes.test.ts +265 -0
  241. package/src/cron/cron-routes.ts +85 -0
  242. package/src/cron/cron-scheduler.test.ts +421 -0
  243. package/src/cron/cron-scheduler.ts +413 -0
  244. package/src/cron/cron-store.ts +163 -0
  245. package/src/cron/index.ts +6 -0
  246. package/src/db/interfaces.ts +60 -0
  247. package/src/email/index.ts +18 -0
  248. package/src/email/smtp-email-service.ts +91 -0
  249. package/src/email/templates.ts +388 -0
  250. package/src/email/types.ts +105 -0
  251. package/src/functions/function-loader.ts +119 -0
  252. package/src/functions/function-routes.ts +31 -0
  253. package/src/functions/index.ts +3 -0
  254. package/src/history/history-routes.ts +129 -0
  255. package/src/history/index.ts +2 -0
  256. package/src/index.ts +66 -0
  257. package/src/init.ts +727 -0
  258. package/src/serve-spa.ts +81 -0
  259. package/src/services/driver-registry.ts +182 -0
  260. package/src/singleton.test.ts +28 -0
  261. package/src/singleton.ts +70 -0
  262. package/src/storage/LocalStorageController.ts +365 -0
  263. package/src/storage/S3StorageController.ts +298 -0
  264. package/src/storage/index.ts +43 -0
  265. package/src/storage/routes.ts +264 -0
  266. package/src/storage/storage-registry.ts +187 -0
  267. package/src/storage/types.ts +134 -0
  268. package/src/types/index.ts +27 -0
  269. package/src/utils/dev-port.ts +176 -0
  270. package/src/utils/logger.ts +143 -0
  271. package/src/utils/logging.ts +38 -0
  272. package/src/utils/request-logger.ts +66 -0
  273. package/src/utils/sql.ts +38 -0
  274. package/test/admin-routes.test.ts +640 -0
  275. package/test/api-generator.test.ts +501 -0
  276. package/test/ast-schema-editor.test.ts +63 -0
  277. package/test/auth-middleware-hono.test.ts +556 -0
  278. package/test/auth-routes.test.ts +1047 -0
  279. package/test/driver-registry.test.ts +282 -0
  280. package/test/error-propagation.test.ts +226 -0
  281. package/test/errors-hono.test.ts +133 -0
  282. package/test/errors.test.ts +155 -0
  283. package/test/jwt-security.test.ts +182 -0
  284. package/test/jwt.test.ts +324 -0
  285. package/test/middleware.test.ts +300 -0
  286. package/test/password.test.ts +165 -0
  287. package/test/query-parser.test.ts +263 -0
  288. package/test/rate-limiter.test.ts +102 -0
  289. package/test/safe-compare.test.ts +66 -0
  290. package/test/singleton.test.ts +59 -0
  291. package/test/storage-local.test.ts +271 -0
  292. package/test/storage-registry.test.ts +282 -0
  293. package/test/storage-routes.test.ts +222 -0
  294. package/test/storage-s3.test.ts +304 -0
  295. package/test-ast.ts +28 -0
  296. package/test.ts +6 -0
  297. package/test_output.txt +1133 -0
  298. package/tsconfig.json +49 -0
  299. package/tsconfig.prod.json +20 -0
  300. package/vite.config.ts +80 -0
@@ -0,0 +1,556 @@
1
+ import { describe, it, expect, beforeAll, jest } from "@jest/globals";
2
+ import { Hono, Context } from "hono";
3
+ import { configureJwt, generateAccessToken } from "../src/auth/jwt";
4
+ import { requireAuth, optionalAuth, requireAdmin, createAuthMiddleware, createRequireAuth } from "../src/auth/middleware";
5
+ import type { HonoEnv } from "../src/api/types";
6
+ import type { DataDriver } from "../../types/src/controllers/data_driver";
7
+
8
+ const TEST_SECRET = "test-secret-key-for-hono-middleware-testing-1234567890";
9
+
10
+ describe("Auth Middleware (Hono)", () => {
11
+
12
+ beforeAll(() => {
13
+ configureJwt({ secret: TEST_SECRET,
14
+ accessExpiresIn: "1h" });
15
+ });
16
+
17
+ // ── requireAuth ─────────────────────────────────────────
18
+ describe("requireAuth", () => {
19
+ function createApp() {
20
+ const app = new Hono<HonoEnv>();
21
+ app.use("/protected/*", requireAuth);
22
+ app.get("/protected/resource", (c: Context<HonoEnv>) => {
23
+ const user = c.get("user");
24
+ return c.json({ user });
25
+ });
26
+ return app;
27
+ }
28
+
29
+ it("passes with valid Bearer token", async () => {
30
+ const app = createApp();
31
+ const token = generateAccessToken("user-1", ["admin"]);
32
+ const res = await app.request("/protected/resource", {
33
+ headers: { Authorization: `Bearer ${token}` }
34
+ });
35
+ expect(res.status).toBe(200);
36
+ const body = await res.json() as any;
37
+ expect(body.user.userId).toBe("user-1");
38
+ expect(body.user.roles).toEqual(["admin"]);
39
+ });
40
+
41
+ it("returns 401 for missing Authorization header", async () => {
42
+ const app = createApp();
43
+ const res = await app.request("/protected/resource");
44
+ expect(res.status).toBe(401);
45
+ const body = await res.json() as any;
46
+ expect(body.error.code).toBe("UNAUTHORIZED");
47
+ });
48
+
49
+ it("returns 401 for non-Bearer prefix", async () => {
50
+ const app = createApp();
51
+ const res = await app.request("/protected/resource", {
52
+ headers: { Authorization: "Basic abc123" }
53
+ });
54
+ expect(res.status).toBe(401);
55
+ });
56
+
57
+ it("returns 401 for invalid/expired token", async () => {
58
+ const app = createApp();
59
+ const res = await app.request("/protected/resource", {
60
+ headers: { Authorization: "Bearer invalid.token.here" }
61
+ });
62
+ expect(res.status).toBe(401);
63
+ const body = await res.json() as any;
64
+ expect(body.error.code).toBe("UNAUTHORIZED");
65
+ });
66
+
67
+ it("accepts token via query parameter", async () => {
68
+ const app = createApp();
69
+ const token = generateAccessToken("user-2", ["editor"]);
70
+ const res = await app.request(`/protected/resource?token=${token}`);
71
+ expect(res.status).toBe(200);
72
+ const body = await res.json() as any;
73
+ expect(body.user.userId).toBe("user-2");
74
+ });
75
+
76
+ it("prefers Bearer token over query parameter", async () => {
77
+ const app = createApp();
78
+ const bearerToken = generateAccessToken("bearer-user", ["admin"]);
79
+ const queryToken = generateAccessToken("query-user", ["viewer"]);
80
+ const res = await app.request(`/protected/resource?token=${queryToken}`, {
81
+ headers: { Authorization: `Bearer ${bearerToken}` }
82
+ });
83
+ expect(res.status).toBe(200);
84
+ const body = await res.json() as any;
85
+ expect(body.user.userId).toBe("bearer-user");
86
+ });
87
+ });
88
+
89
+ // ── requireAdmin ────────────────────────────────────────
90
+ describe("requireAdmin", () => {
91
+ function createApp() {
92
+ const app = new Hono<HonoEnv>();
93
+ app.use("/admin/*", requireAuth, requireAdmin);
94
+ app.get("/admin/dashboard", (c: Context<HonoEnv>) => c.json({ ok: true }));
95
+ return app;
96
+ }
97
+
98
+ it("allows admin users", async () => {
99
+ const app = createApp();
100
+ const token = generateAccessToken("admin-1", ["admin"]);
101
+ const res = await app.request("/admin/dashboard", {
102
+ headers: { Authorization: `Bearer ${token}` }
103
+ });
104
+ expect(res.status).toBe(200);
105
+ });
106
+
107
+ it("allows schema-admin users", async () => {
108
+ const app = createApp();
109
+ const token = generateAccessToken("schema-admin-1", ["schema-admin"]);
110
+ const res = await app.request("/admin/dashboard", {
111
+ headers: { Authorization: `Bearer ${token}` }
112
+ });
113
+ expect(res.status).toBe(200);
114
+ });
115
+
116
+ it("returns 403 for non-admin users", async () => {
117
+ const app = createApp();
118
+ const token = generateAccessToken("user-1", ["editor"]);
119
+ const res = await app.request("/admin/dashboard", {
120
+ headers: { Authorization: `Bearer ${token}` }
121
+ });
122
+ expect(res.status).toBe(403);
123
+ const body = await res.json() as any;
124
+ expect(body.error.code).toBe("FORBIDDEN");
125
+ });
126
+
127
+ it("returns 401 when requireAdmin is used without requireAuth", async () => {
128
+ const app = new Hono<HonoEnv>();
129
+ app.use("/admin/*", requireAdmin);
130
+ app.get("/admin/dashboard", (c: Context<HonoEnv>) => c.json({ ok: true }));
131
+
132
+ const res = await app.request("/admin/dashboard");
133
+ expect(res.status).toBe(401);
134
+ });
135
+ });
136
+
137
+ // ── optionalAuth ────────────────────────────────────────
138
+ describe("optionalAuth", () => {
139
+ function createApp() {
140
+ const app = new Hono<HonoEnv>();
141
+ app.use("/public/*", optionalAuth);
142
+ app.get("/public/feed", (c: Context<HonoEnv>) => {
143
+ const user = c.get("user");
144
+ return c.json({ authenticated: !!user,
145
+ user: user ?? null });
146
+ });
147
+ return app;
148
+ }
149
+
150
+ it("sets user when valid token is present", async () => {
151
+ const app = createApp();
152
+ const token = generateAccessToken("opt-user", ["viewer"]);
153
+ const res = await app.request("/public/feed", {
154
+ headers: { Authorization: `Bearer ${token}` }
155
+ });
156
+ const body = await res.json() as any;
157
+ expect(res.status).toBe(200);
158
+ expect(body.authenticated).toBe(true);
159
+ expect(body.user.userId).toBe("opt-user");
160
+ });
161
+
162
+ it("proceeds without user when no token", async () => {
163
+ const app = createApp();
164
+ const res = await app.request("/public/feed");
165
+ const body = await res.json() as any;
166
+ expect(res.status).toBe(200);
167
+ expect(body.authenticated).toBe(false);
168
+ expect(body.user).toBeNull();
169
+ });
170
+
171
+ it("proceeds without user when token is invalid", async () => {
172
+ const app = createApp();
173
+ const res = await app.request("/public/feed", {
174
+ headers: { Authorization: "Bearer garbage.token.value" }
175
+ });
176
+ const body = await res.json() as any;
177
+ expect(res.status).toBe(200);
178
+ expect(body.authenticated).toBe(false);
179
+ });
180
+ });
181
+
182
+ // ── createAuthMiddleware ────────────────────────────────
183
+ describe("createAuthMiddleware", () => {
184
+ const mockDriver: DataDriver = {
185
+ fetchCollection: jest.fn() as any,
186
+ fetchEntity: jest.fn() as any,
187
+ saveEntity: jest.fn() as any,
188
+ deleteEntity: jest.fn() as any
189
+ };
190
+
191
+ it("sets driver in context (requireAuth: false)", async () => {
192
+ const app = new Hono<HonoEnv>();
193
+ app.use("/*", createAuthMiddleware({ driver: mockDriver,
194
+ requireAuth: false }));
195
+ app.get("/test", (c) => {
196
+ const driver = c.get("driver");
197
+ return c.json({ hasDriver: !!driver });
198
+ });
199
+
200
+ const res = await app.request("/test");
201
+ expect(res.status).toBe(200);
202
+ const body = await res.json() as any;
203
+ expect(body.hasDriver).toBe(true);
204
+ });
205
+
206
+ it("enforces auth by default (requireAuth defaults to true)", async () => {
207
+ const app = new Hono<HonoEnv>();
208
+ app.use("/*", createAuthMiddleware({ driver: mockDriver }));
209
+ app.get("/test", (c) => c.json({ ok: true }));
210
+
211
+ const res = await app.request("/test");
212
+ expect(res.status).toBe(401);
213
+ });
214
+
215
+ it("enforces auth when requireAuth is true", async () => {
216
+ const app = new Hono<HonoEnv>();
217
+ app.use("/*", createAuthMiddleware({ driver: mockDriver,
218
+ requireAuth: true }));
219
+ app.get("/test", (c) => c.json({ ok: true }));
220
+
221
+ const res = await app.request("/test");
222
+ expect(res.status).toBe(401);
223
+ });
224
+
225
+ it("allows anonymous when requireAuth is false", async () => {
226
+ const app = new Hono<HonoEnv>();
227
+ app.use("/*", createAuthMiddleware({ driver: mockDriver,
228
+ requireAuth: false }));
229
+ app.get("/test", (c) => c.json({ ok: true }));
230
+
231
+ const res = await app.request("/test");
232
+ expect(res.status).toBe(200);
233
+ });
234
+
235
+ it("extracts JWT and sets user in context", async () => {
236
+ const app = new Hono<HonoEnv>();
237
+ // Default requireAuth=true, but providing a valid token satisfies it
238
+ app.use("/*", createAuthMiddleware({ driver: mockDriver }));
239
+ app.get("/test", (c) => {
240
+ const user = c.get("user");
241
+ return c.json({ user });
242
+ });
243
+
244
+ const token = generateAccessToken("jwt-user", ["admin"]);
245
+ const res = await app.request("/test", {
246
+ headers: { Authorization: `Bearer ${token}` }
247
+ });
248
+ const body = await res.json() as any;
249
+ expect(body.user.userId).toBe("jwt-user");
250
+ expect(body.user.roles).toEqual(["admin"]);
251
+ });
252
+
253
+ it("rejects invalid tokens even with requireAuth: false (fail closed)", async () => {
254
+ const app = new Hono<HonoEnv>();
255
+ app.use("/*", createAuthMiddleware({ driver: mockDriver,
256
+ requireAuth: false }));
257
+ app.get("/test", (c) => c.json({ ok: true }));
258
+
259
+ const res = await app.request("/test", {
260
+ headers: { Authorization: "Bearer invalid.garbage.token" }
261
+ });
262
+ expect(res.status).toBe(401);
263
+ const body = await res.json() as any;
264
+ expect(body.error.code).toBe("UNAUTHORIZED");
265
+ });
266
+
267
+ it("uses custom validator when provided", async () => {
268
+ const app = new Hono<HonoEnv>();
269
+ app.use("/*", createAuthMiddleware({
270
+ driver: mockDriver,
271
+ validator: async (c: Context<HonoEnv>) => {
272
+ const apiKey = c.req.header("x-api-key");
273
+ if (apiKey === "valid-key") {
274
+ return { userId: "api-user",
275
+ roles: ["api"] };
276
+ }
277
+ return false;
278
+ }
279
+ }));
280
+ app.get("/test", (c) => {
281
+ const user = c.get("user");
282
+ return c.json({ user: user ?? null });
283
+ });
284
+
285
+ // Valid API key
286
+ const res = await app.request("/test", {
287
+ headers: { "x-api-key": "valid-key" }
288
+ });
289
+ const body = await res.json() as any;
290
+ expect(body.user.userId).toBe("api-user");
291
+
292
+ // Invalid API key — requireAuth not set, defaults to true,
293
+ // so unauthenticated requests are rejected
294
+ const res2 = await app.request("/test", {
295
+ headers: { "x-api-key": "bad-key" }
296
+ });
297
+ expect(res2.status).toBe(401); // Secure by default
298
+ });
299
+
300
+ it("returns 401 when custom validator throws", async () => {
301
+ const app = new Hono<HonoEnv>();
302
+ app.use("/*", createAuthMiddleware({
303
+ driver: mockDriver,
304
+ validator: async () => { throw new Error("auth failed"); }
305
+ }));
306
+ app.get("/test", (c) => c.json({ ok: true }));
307
+
308
+ const res = await app.request("/test");
309
+ expect(res.status).toBe(401);
310
+ });
311
+
312
+ it("calls withAuth on driver when available", async () => {
313
+ const scopedDriver = { ...mockDriver,
314
+ isScopedDriver: true };
315
+ const driverWithAuth = {
316
+ ...mockDriver,
317
+ withAuth: jest.fn().mockResolvedValue(scopedDriver)
318
+ };
319
+
320
+ const app = new Hono<HonoEnv>();
321
+ app.use("/*", createAuthMiddleware({ driver: driverWithAuth as any }));
322
+ app.get("/test", (c) => {
323
+ const driver = c.get("driver") as any;
324
+ return c.json({ scoped: !!driver?.isScopedDriver });
325
+ });
326
+
327
+ const token = generateAccessToken("rls-user", ["editor"]);
328
+ const res = await app.request("/test", {
329
+ headers: { Authorization: `Bearer ${token}` }
330
+ });
331
+ const body = await res.json() as any;
332
+ expect(body.scoped).toBe(true);
333
+ expect(driverWithAuth.withAuth).toHaveBeenCalledWith(
334
+ expect.objectContaining({ uid: "rls-user",
335
+ roles: ["editor"] })
336
+ );
337
+ });
338
+
339
+ it("handles validator returning true (default user)", async () => {
340
+ const app = new Hono<HonoEnv>();
341
+ app.use("/*", createAuthMiddleware({
342
+ driver: mockDriver,
343
+ requireAuth: false, // Explicit opt-out for this test
344
+ validator: async () => true
345
+ }));
346
+ app.get("/test", (c) => {
347
+ const user = c.get("user");
348
+ return c.json({ user });
349
+ });
350
+
351
+ const res = await app.request("/test");
352
+ const body = await res.json() as any;
353
+ expect(body.user.userId).toBe("default");
354
+ });
355
+ });
356
+
357
+ describe("Service Key Authentication", () => {
358
+ const SERVICE_KEY = "a-very-secure-service-key-that-is-at-least-32-characters-long";
359
+ const mockDriver: DataDriver = {
360
+ fetchCollection: jest.fn() as any,
361
+ fetchEntity: jest.fn() as any,
362
+ saveEntity: jest.fn() as any,
363
+ deleteEntity: jest.fn() as any
364
+ };
365
+
366
+ it("grants admin access when Bearer token matches the service key", async () => {
367
+ const app = new Hono<HonoEnv>();
368
+ app.use("/*", createAuthMiddleware({
369
+ driver: mockDriver,
370
+ requireAuth: true,
371
+ serviceKey: SERVICE_KEY
372
+ }));
373
+ app.get("/test", (c) => {
374
+ const user = c.get("user");
375
+ return c.json({ user });
376
+ });
377
+
378
+ const res = await app.request("/test", {
379
+ headers: { Authorization: `Bearer ${SERVICE_KEY}` }
380
+ });
381
+ expect(res.status).toBe(200);
382
+ const body = await res.json() as any;
383
+ expect(body.user.userId).toBe("service");
384
+ expect(body.user.roles).toEqual(["admin"]);
385
+ });
386
+
387
+ it("rejects when Bearer token does not match the service key", async () => {
388
+ const app = new Hono<HonoEnv>();
389
+ app.use("/*", createAuthMiddleware({
390
+ driver: mockDriver,
391
+ requireAuth: true,
392
+ serviceKey: SERVICE_KEY
393
+ }));
394
+ app.get("/test", (c) => c.json({ ok: true }));
395
+
396
+ const res = await app.request("/test", {
397
+ headers: { Authorization: "Bearer wrong-key-that-is-definitely-not-correct" }
398
+ });
399
+ // Wrong key is not a valid service key, and also not a valid JWT → 401
400
+ expect(res.status).toBe(401);
401
+ });
402
+
403
+ it("still accepts valid JWT tokens when service key is configured", async () => {
404
+ const token = generateAccessToken("jwt-user", ["editor"]);
405
+ const app = new Hono<HonoEnv>();
406
+ app.use("/*", createAuthMiddleware({
407
+ driver: mockDriver,
408
+ requireAuth: true,
409
+ serviceKey: SERVICE_KEY
410
+ }));
411
+ app.get("/test", (c) => {
412
+ const user = c.get("user");
413
+ return c.json({ user });
414
+ });
415
+
416
+ const res = await app.request("/test", {
417
+ headers: { Authorization: `Bearer ${token}` }
418
+ });
419
+ expect(res.status).toBe(200);
420
+ const body = await res.json() as any;
421
+ expect(body.user.userId).toBe("jwt-user");
422
+ expect(body.user.roles).toEqual(["editor"]);
423
+ });
424
+
425
+ it("scopes driver via withAuth with service identity", async () => {
426
+ const scopedDriver = { scoped: true } as unknown as DataDriver;
427
+ const driverWithAuth = {
428
+ withAuth: jest.fn().mockResolvedValue(scopedDriver)
429
+ } as unknown as DataDriver;
430
+
431
+ const app = new Hono<HonoEnv>();
432
+ app.use("/*", createAuthMiddleware({
433
+ driver: driverWithAuth,
434
+ requireAuth: true,
435
+ serviceKey: SERVICE_KEY
436
+ }));
437
+ app.get("/test", (c) => {
438
+ const d = c.get("driver") as any;
439
+ return c.json({ scoped: d.scoped });
440
+ });
441
+
442
+ const res = await app.request("/test", {
443
+ headers: { Authorization: `Bearer ${SERVICE_KEY}` }
444
+ });
445
+ const body = await res.json() as any;
446
+ expect(body.scoped).toBe(true);
447
+ expect((driverWithAuth as any).withAuth).toHaveBeenCalledWith(
448
+ expect.objectContaining({ uid: "service",
449
+ roles: ["admin"] })
450
+ );
451
+ });
452
+
453
+ it("does not activate service key path when no serviceKey is configured", async () => {
454
+ const app = new Hono<HonoEnv>();
455
+ app.use("/*", createAuthMiddleware({
456
+ driver: mockDriver,
457
+ requireAuth: true
458
+ // No serviceKey configured
459
+ }));
460
+ app.get("/test", (c) => c.json({ ok: true }));
461
+
462
+ // Sending a random string should fail as invalid JWT
463
+ const res = await app.request("/test", {
464
+ headers: { Authorization: `Bearer ${SERVICE_KEY}` }
465
+ });
466
+ expect(res.status).toBe(401);
467
+ });
468
+ });
469
+
470
+ // ── createRequireAuth (standalone factory) ──────────────
471
+ describe("createRequireAuth", () => {
472
+ const SERVICE_KEY = "a-very-secure-service-key-that-is-at-least-32-characters-long";
473
+ it("returns the default requireAuth when no serviceKey is provided", async () => {
474
+ const middleware = createRequireAuth();
475
+ // The returned function should be the original requireAuth
476
+ expect(middleware).toBe(requireAuth);
477
+ });
478
+
479
+ it("returns the default requireAuth when serviceKey is undefined", async () => {
480
+ const middleware = createRequireAuth({ serviceKey: undefined });
481
+ expect(middleware).toBe(requireAuth);
482
+ });
483
+
484
+ it("authenticates with a valid service key", async () => {
485
+ const app = new Hono<HonoEnv>();
486
+ app.use("/*", createRequireAuth({ serviceKey: SERVICE_KEY }));
487
+ app.get("/admin/users", (c) => {
488
+ const user = c.get("user");
489
+ return c.json({ userId: user?.userId,
490
+ roles: user?.roles });
491
+ });
492
+
493
+ const res = await app.request("/admin/users", {
494
+ headers: { Authorization: `Bearer ${SERVICE_KEY}` }
495
+ });
496
+ expect(res.status).toBe(200);
497
+ const body = await res.json() as any;
498
+ expect(body.userId).toBe("service");
499
+ expect(body.roles).toEqual(["admin"]);
500
+ });
501
+
502
+ it("rejects an incorrect service key and falls back to JWT (which also fails)", async () => {
503
+ const app = new Hono<HonoEnv>();
504
+ app.use("/*", createRequireAuth({ serviceKey: SERVICE_KEY }));
505
+ app.get("/admin/users", (c) => c.json({ ok: true }));
506
+
507
+ const res = await app.request("/admin/users", {
508
+ headers: { Authorization: "Bearer wrong-key" }
509
+ });
510
+ expect(res.status).toBe(401);
511
+ });
512
+
513
+ it("falls back to valid JWT when service key doesn't match", async () => {
514
+ const token = generateAccessToken("user-123", ["admin"]);
515
+ const app = new Hono<HonoEnv>();
516
+ app.use("/*", createRequireAuth({ serviceKey: SERVICE_KEY }));
517
+ app.get("/admin/users", (c) => {
518
+ const user = c.get("user");
519
+ return c.json({ userId: user?.userId,
520
+ roles: user?.roles });
521
+ });
522
+
523
+ const res = await app.request("/admin/users", {
524
+ headers: { Authorization: `Bearer ${token}` }
525
+ });
526
+ expect(res.status).toBe(200);
527
+ const body = await res.json() as any;
528
+ expect(body.userId).toBe("user-123");
529
+ expect(body.roles).toEqual(["admin"]);
530
+ });
531
+
532
+ it("returns 401 when no authorization header is provided", async () => {
533
+ const app = new Hono<HonoEnv>();
534
+ app.use("/*", createRequireAuth({ serviceKey: SERVICE_KEY }));
535
+ app.get("/admin/users", (c) => c.json({ ok: true }));
536
+
537
+ const res = await app.request("/admin/users");
538
+ expect(res.status).toBe(401);
539
+ });
540
+
541
+ it("works with requireAdmin for service key (admin role is granted)", async () => {
542
+ const app = new Hono<HonoEnv>();
543
+ app.use("/*", createRequireAuth({ serviceKey: SERVICE_KEY }));
544
+ app.get("/admin/users", requireAdmin, (c) => {
545
+ return c.json({ authorized: true });
546
+ });
547
+
548
+ const res = await app.request("/admin/users", {
549
+ headers: { Authorization: `Bearer ${SERVICE_KEY}` }
550
+ });
551
+ expect(res.status).toBe(200);
552
+ const body = await res.json() as any;
553
+ expect(body.authorized).toBe(true);
554
+ });
555
+ });
556
+ });