@fuzdev/fuz_app 0.1.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 (457) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +49 -0
  3. package/dist/actions/action_bridge.d.ts +65 -0
  4. package/dist/actions/action_bridge.d.ts.map +1 -0
  5. package/dist/actions/action_bridge.js +76 -0
  6. package/dist/actions/action_codegen.d.ts +97 -0
  7. package/dist/actions/action_codegen.d.ts.map +1 -0
  8. package/dist/actions/action_codegen.js +280 -0
  9. package/dist/actions/action_registry.d.ts +35 -0
  10. package/dist/actions/action_registry.d.ts.map +1 -0
  11. package/dist/actions/action_registry.js +83 -0
  12. package/dist/actions/action_spec.d.ts +169 -0
  13. package/dist/actions/action_spec.d.ts.map +1 -0
  14. package/dist/actions/action_spec.js +76 -0
  15. package/dist/auth/account_queries.d.ts +96 -0
  16. package/dist/auth/account_queries.d.ts.map +1 -0
  17. package/dist/auth/account_queries.js +172 -0
  18. package/dist/auth/account_routes.d.ts +86 -0
  19. package/dist/auth/account_routes.d.ts.map +1 -0
  20. package/dist/auth/account_routes.js +406 -0
  21. package/dist/auth/account_schema.d.ts +192 -0
  22. package/dist/auth/account_schema.d.ts.map +1 -0
  23. package/dist/auth/account_schema.js +105 -0
  24. package/dist/auth/admin_routes.d.ts +29 -0
  25. package/dist/auth/admin_routes.d.ts.map +1 -0
  26. package/dist/auth/admin_routes.js +193 -0
  27. package/dist/auth/api_token.d.ts +33 -0
  28. package/dist/auth/api_token.d.ts.map +1 -0
  29. package/dist/auth/api_token.js +36 -0
  30. package/dist/auth/api_token_queries.d.ts +80 -0
  31. package/dist/auth/api_token_queries.d.ts.map +1 -0
  32. package/dist/auth/api_token_queries.js +116 -0
  33. package/dist/auth/app_settings_queries.d.ts +33 -0
  34. package/dist/auth/app_settings_queries.d.ts.map +1 -0
  35. package/dist/auth/app_settings_queries.js +51 -0
  36. package/dist/auth/app_settings_routes.d.ts +27 -0
  37. package/dist/auth/app_settings_routes.d.ts.map +1 -0
  38. package/dist/auth/app_settings_routes.js +66 -0
  39. package/dist/auth/app_settings_schema.d.ts +35 -0
  40. package/dist/auth/app_settings_schema.d.ts.map +1 -0
  41. package/dist/auth/app_settings_schema.js +22 -0
  42. package/dist/auth/audit_log_queries.d.ts +90 -0
  43. package/dist/auth/audit_log_queries.d.ts.map +1 -0
  44. package/dist/auth/audit_log_queries.js +205 -0
  45. package/dist/auth/audit_log_routes.d.ts +33 -0
  46. package/dist/auth/audit_log_routes.d.ts.map +1 -0
  47. package/dist/auth/audit_log_routes.js +106 -0
  48. package/dist/auth/audit_log_schema.d.ts +259 -0
  49. package/dist/auth/audit_log_schema.d.ts.map +1 -0
  50. package/dist/auth/audit_log_schema.js +123 -0
  51. package/dist/auth/bearer_auth.d.ts +32 -0
  52. package/dist/auth/bearer_auth.d.ts.map +1 -0
  53. package/dist/auth/bearer_auth.js +90 -0
  54. package/dist/auth/bootstrap_account.d.ts +82 -0
  55. package/dist/auth/bootstrap_account.d.ts.map +1 -0
  56. package/dist/auth/bootstrap_account.js +97 -0
  57. package/dist/auth/bootstrap_routes.d.ts +74 -0
  58. package/dist/auth/bootstrap_routes.d.ts.map +1 -0
  59. package/dist/auth/bootstrap_routes.js +154 -0
  60. package/dist/auth/daemon_token.d.ts +49 -0
  61. package/dist/auth/daemon_token.d.ts.map +1 -0
  62. package/dist/auth/daemon_token.js +49 -0
  63. package/dist/auth/daemon_token_middleware.d.ts +93 -0
  64. package/dist/auth/daemon_token_middleware.d.ts.map +1 -0
  65. package/dist/auth/daemon_token_middleware.js +167 -0
  66. package/dist/auth/ddl.d.ts +27 -0
  67. package/dist/auth/ddl.d.ts.map +1 -0
  68. package/dist/auth/ddl.js +111 -0
  69. package/dist/auth/deps.d.ts +52 -0
  70. package/dist/auth/deps.d.ts.map +1 -0
  71. package/dist/auth/deps.js +10 -0
  72. package/dist/auth/invite_queries.d.ts +68 -0
  73. package/dist/auth/invite_queries.d.ts.map +1 -0
  74. package/dist/auth/invite_queries.js +105 -0
  75. package/dist/auth/invite_routes.d.ts +18 -0
  76. package/dist/auth/invite_routes.d.ts.map +1 -0
  77. package/dist/auth/invite_routes.js +129 -0
  78. package/dist/auth/invite_schema.d.ts +51 -0
  79. package/dist/auth/invite_schema.d.ts.map +1 -0
  80. package/dist/auth/invite_schema.js +25 -0
  81. package/dist/auth/keyring.d.ts +87 -0
  82. package/dist/auth/keyring.d.ts.map +1 -0
  83. package/dist/auth/keyring.js +142 -0
  84. package/dist/auth/middleware.d.ts +40 -0
  85. package/dist/auth/middleware.d.ts.map +1 -0
  86. package/dist/auth/middleware.js +64 -0
  87. package/dist/auth/migrations.d.ts +42 -0
  88. package/dist/auth/migrations.d.ts.map +1 -0
  89. package/dist/auth/migrations.js +79 -0
  90. package/dist/auth/password.d.ts +39 -0
  91. package/dist/auth/password.d.ts.map +1 -0
  92. package/dist/auth/password.js +25 -0
  93. package/dist/auth/password_argon2.d.ts +43 -0
  94. package/dist/auth/password_argon2.d.ts.map +1 -0
  95. package/dist/auth/password_argon2.js +76 -0
  96. package/dist/auth/permit_queries.d.ts +72 -0
  97. package/dist/auth/permit_queries.d.ts.map +1 -0
  98. package/dist/auth/permit_queries.js +116 -0
  99. package/dist/auth/request_context.d.ts +114 -0
  100. package/dist/auth/request_context.d.ts.map +1 -0
  101. package/dist/auth/request_context.js +176 -0
  102. package/dist/auth/require_keeper.d.ts +20 -0
  103. package/dist/auth/require_keeper.d.ts.map +1 -0
  104. package/dist/auth/require_keeper.js +35 -0
  105. package/dist/auth/role_schema.d.ts +69 -0
  106. package/dist/auth/role_schema.d.ts.map +1 -0
  107. package/dist/auth/role_schema.js +70 -0
  108. package/dist/auth/route_guards.d.ts +21 -0
  109. package/dist/auth/route_guards.d.ts.map +1 -0
  110. package/dist/auth/route_guards.js +32 -0
  111. package/dist/auth/session_cookie.d.ts +158 -0
  112. package/dist/auth/session_cookie.d.ts.map +1 -0
  113. package/dist/auth/session_cookie.js +135 -0
  114. package/dist/auth/session_lifecycle.d.ts +35 -0
  115. package/dist/auth/session_lifecycle.d.ts.map +1 -0
  116. package/dist/auth/session_lifecycle.js +27 -0
  117. package/dist/auth/session_middleware.d.ts +33 -0
  118. package/dist/auth/session_middleware.d.ts.map +1 -0
  119. package/dist/auth/session_middleware.js +62 -0
  120. package/dist/auth/session_queries.d.ts +135 -0
  121. package/dist/auth/session_queries.d.ts.map +1 -0
  122. package/dist/auth/session_queries.js +186 -0
  123. package/dist/auth/signup_routes.d.ts +32 -0
  124. package/dist/auth/signup_routes.d.ts.map +1 -0
  125. package/dist/auth/signup_routes.js +150 -0
  126. package/dist/cli/args.d.ts +48 -0
  127. package/dist/cli/args.d.ts.map +1 -0
  128. package/dist/cli/args.js +76 -0
  129. package/dist/cli/config.d.ts +48 -0
  130. package/dist/cli/config.d.ts.map +1 -0
  131. package/dist/cli/config.js +77 -0
  132. package/dist/cli/daemon.d.ts +82 -0
  133. package/dist/cli/daemon.d.ts.map +1 -0
  134. package/dist/cli/daemon.js +149 -0
  135. package/dist/cli/help.d.ts +85 -0
  136. package/dist/cli/help.d.ts.map +1 -0
  137. package/dist/cli/help.js +138 -0
  138. package/dist/cli/logger.d.ts +46 -0
  139. package/dist/cli/logger.d.ts.map +1 -0
  140. package/dist/cli/logger.js +48 -0
  141. package/dist/cli/util.d.ts +36 -0
  142. package/dist/cli/util.d.ts.map +1 -0
  143. package/dist/cli/util.js +50 -0
  144. package/dist/crypto.d.ts +13 -0
  145. package/dist/crypto.d.ts.map +1 -0
  146. package/dist/crypto.js +19 -0
  147. package/dist/db/assert_row.d.ts +18 -0
  148. package/dist/db/assert_row.d.ts.map +1 -0
  149. package/dist/db/assert_row.js +24 -0
  150. package/dist/db/create_db.d.ts +38 -0
  151. package/dist/db/create_db.d.ts.map +1 -0
  152. package/dist/db/create_db.js +57 -0
  153. package/dist/db/db.d.ts +97 -0
  154. package/dist/db/db.d.ts.map +1 -0
  155. package/dist/db/db.js +76 -0
  156. package/dist/db/db_pg.d.ts +21 -0
  157. package/dist/db/db_pg.d.ts.map +1 -0
  158. package/dist/db/db_pg.js +45 -0
  159. package/dist/db/db_pglite.d.ts +21 -0
  160. package/dist/db/db_pglite.d.ts.map +1 -0
  161. package/dist/db/db_pglite.js +28 -0
  162. package/dist/db/migrate.d.ts +67 -0
  163. package/dist/db/migrate.d.ts.map +1 -0
  164. package/dist/db/migrate.js +118 -0
  165. package/dist/db/pg_error.d.ts +16 -0
  166. package/dist/db/pg_error.d.ts.map +1 -0
  167. package/dist/db/pg_error.js +15 -0
  168. package/dist/db/query_deps.d.ts +14 -0
  169. package/dist/db/query_deps.d.ts.map +1 -0
  170. package/dist/db/query_deps.js +9 -0
  171. package/dist/db/sql_identifier.d.ts +27 -0
  172. package/dist/db/sql_identifier.d.ts.map +1 -0
  173. package/dist/db/sql_identifier.js +31 -0
  174. package/dist/db/status.d.ts +62 -0
  175. package/dist/db/status.d.ts.map +1 -0
  176. package/dist/db/status.js +116 -0
  177. package/dist/dev/setup.d.ts +159 -0
  178. package/dist/dev/setup.d.ts.map +1 -0
  179. package/dist/dev/setup.js +265 -0
  180. package/dist/env/dotenv.d.ts +25 -0
  181. package/dist/env/dotenv.d.ts.map +1 -0
  182. package/dist/env/dotenv.js +52 -0
  183. package/dist/env/load.d.ts +52 -0
  184. package/dist/env/load.d.ts.map +1 -0
  185. package/dist/env/load.js +79 -0
  186. package/dist/env/mask.d.ts +19 -0
  187. package/dist/env/mask.d.ts.map +1 -0
  188. package/dist/env/mask.js +26 -0
  189. package/dist/env/resolve.d.ts +126 -0
  190. package/dist/env/resolve.d.ts.map +1 -0
  191. package/dist/env/resolve.js +200 -0
  192. package/dist/hono_context.d.ts +48 -0
  193. package/dist/hono_context.d.ts.map +1 -0
  194. package/dist/hono_context.js +22 -0
  195. package/dist/http/common_routes.d.ts +52 -0
  196. package/dist/http/common_routes.d.ts.map +1 -0
  197. package/dist/http/common_routes.js +65 -0
  198. package/dist/http/db_routes.d.ts +57 -0
  199. package/dist/http/db_routes.d.ts.map +1 -0
  200. package/dist/http/db_routes.js +176 -0
  201. package/dist/http/error_schemas.d.ts +169 -0
  202. package/dist/http/error_schemas.d.ts.map +1 -0
  203. package/dist/http/error_schemas.js +178 -0
  204. package/dist/http/middleware_spec.d.ts +19 -0
  205. package/dist/http/middleware_spec.d.ts.map +1 -0
  206. package/dist/http/middleware_spec.js +9 -0
  207. package/dist/http/origin.d.ts +57 -0
  208. package/dist/http/origin.d.ts.map +1 -0
  209. package/dist/http/origin.js +207 -0
  210. package/dist/http/proxy.d.ts +112 -0
  211. package/dist/http/proxy.d.ts.map +1 -0
  212. package/dist/http/proxy.js +240 -0
  213. package/dist/http/route_spec.d.ts +197 -0
  214. package/dist/http/route_spec.d.ts.map +1 -0
  215. package/dist/http/route_spec.js +243 -0
  216. package/dist/http/schema_helpers.d.ts +64 -0
  217. package/dist/http/schema_helpers.d.ts.map +1 -0
  218. package/dist/http/schema_helpers.js +90 -0
  219. package/dist/http/surface.d.ts +132 -0
  220. package/dist/http/surface.d.ts.map +1 -0
  221. package/dist/http/surface.js +156 -0
  222. package/dist/http/surface_query.d.ts +77 -0
  223. package/dist/http/surface_query.d.ts.map +1 -0
  224. package/dist/http/surface_query.js +86 -0
  225. package/dist/rate_limiter.d.ts +94 -0
  226. package/dist/rate_limiter.d.ts.map +1 -0
  227. package/dist/rate_limiter.js +156 -0
  228. package/dist/realtime/sse.d.ts +80 -0
  229. package/dist/realtime/sse.d.ts.map +1 -0
  230. package/dist/realtime/sse.js +109 -0
  231. package/dist/realtime/sse_auth_guard.d.ts +93 -0
  232. package/dist/realtime/sse_auth_guard.d.ts.map +1 -0
  233. package/dist/realtime/sse_auth_guard.js +111 -0
  234. package/dist/realtime/subscriber_registry.d.ts +85 -0
  235. package/dist/realtime/subscriber_registry.d.ts.map +1 -0
  236. package/dist/realtime/subscriber_registry.js +108 -0
  237. package/dist/runtime/deno.d.ts +21 -0
  238. package/dist/runtime/deno.d.ts.map +1 -0
  239. package/dist/runtime/deno.js +83 -0
  240. package/dist/runtime/deps.d.ts +113 -0
  241. package/dist/runtime/deps.d.ts.map +1 -0
  242. package/dist/runtime/deps.js +10 -0
  243. package/dist/runtime/fs.d.ts +15 -0
  244. package/dist/runtime/fs.d.ts.map +1 -0
  245. package/dist/runtime/fs.js +17 -0
  246. package/dist/runtime/mock.d.ts +81 -0
  247. package/dist/runtime/mock.d.ts.map +1 -0
  248. package/dist/runtime/mock.js +195 -0
  249. package/dist/runtime/node.d.ts +17 -0
  250. package/dist/runtime/node.d.ts.map +1 -0
  251. package/dist/runtime/node.js +117 -0
  252. package/dist/schema_meta.d.ts +16 -0
  253. package/dist/schema_meta.d.ts.map +1 -0
  254. package/dist/schema_meta.js +9 -0
  255. package/dist/sensitivity.d.ts +15 -0
  256. package/dist/sensitivity.d.ts.map +1 -0
  257. package/dist/sensitivity.js +9 -0
  258. package/dist/server/app_backend.d.ts +74 -0
  259. package/dist/server/app_backend.d.ts.map +1 -0
  260. package/dist/server/app_backend.js +39 -0
  261. package/dist/server/app_server.d.ts +201 -0
  262. package/dist/server/app_server.d.ts.map +1 -0
  263. package/dist/server/app_server.js +266 -0
  264. package/dist/server/env.d.ts +68 -0
  265. package/dist/server/env.d.ts.map +1 -0
  266. package/dist/server/env.js +95 -0
  267. package/dist/server/startup.d.ts +22 -0
  268. package/dist/server/startup.d.ts.map +1 -0
  269. package/dist/server/startup.js +48 -0
  270. package/dist/server/static.d.ts +39 -0
  271. package/dist/server/static.d.ts.map +1 -0
  272. package/dist/server/static.js +38 -0
  273. package/dist/server/validate_nginx.d.ts +34 -0
  274. package/dist/server/validate_nginx.d.ts.map +1 -0
  275. package/dist/server/validate_nginx.js +118 -0
  276. package/dist/testing/CLAUDE.md +3 -0
  277. package/dist/testing/admin_integration.d.ts +45 -0
  278. package/dist/testing/admin_integration.d.ts.map +1 -0
  279. package/dist/testing/admin_integration.js +840 -0
  280. package/dist/testing/adversarial_404.d.ts +15 -0
  281. package/dist/testing/adversarial_404.d.ts.map +1 -0
  282. package/dist/testing/adversarial_404.js +118 -0
  283. package/dist/testing/adversarial_headers.d.ts +36 -0
  284. package/dist/testing/adversarial_headers.d.ts.map +1 -0
  285. package/dist/testing/adversarial_headers.js +128 -0
  286. package/dist/testing/adversarial_input.d.ts +56 -0
  287. package/dist/testing/adversarial_input.d.ts.map +1 -0
  288. package/dist/testing/adversarial_input.js +494 -0
  289. package/dist/testing/app_server.d.ts +169 -0
  290. package/dist/testing/app_server.d.ts.map +1 -0
  291. package/dist/testing/app_server.js +240 -0
  292. package/dist/testing/assert_dev_env.d.ts +10 -0
  293. package/dist/testing/assert_dev_env.d.ts.map +1 -0
  294. package/dist/testing/assert_dev_env.js +13 -0
  295. package/dist/testing/assertions.d.ts +61 -0
  296. package/dist/testing/assertions.d.ts.map +1 -0
  297. package/dist/testing/assertions.js +96 -0
  298. package/dist/testing/attack_surface.d.ts +63 -0
  299. package/dist/testing/attack_surface.d.ts.map +1 -0
  300. package/dist/testing/attack_surface.js +224 -0
  301. package/dist/testing/audit_completeness.d.ts +29 -0
  302. package/dist/testing/audit_completeness.d.ts.map +1 -0
  303. package/dist/testing/audit_completeness.js +410 -0
  304. package/dist/testing/auth_apps.d.ts +55 -0
  305. package/dist/testing/auth_apps.d.ts.map +1 -0
  306. package/dist/testing/auth_apps.js +122 -0
  307. package/dist/testing/data_exposure.d.ts +62 -0
  308. package/dist/testing/data_exposure.d.ts.map +1 -0
  309. package/dist/testing/data_exposure.js +297 -0
  310. package/dist/testing/db.d.ts +111 -0
  311. package/dist/testing/db.d.ts.map +1 -0
  312. package/dist/testing/db.js +258 -0
  313. package/dist/testing/entities.d.ts +21 -0
  314. package/dist/testing/entities.d.ts.map +1 -0
  315. package/dist/testing/entities.js +42 -0
  316. package/dist/testing/error_coverage.d.ts +78 -0
  317. package/dist/testing/error_coverage.d.ts.map +1 -0
  318. package/dist/testing/error_coverage.js +135 -0
  319. package/dist/testing/integration.d.ts +37 -0
  320. package/dist/testing/integration.d.ts.map +1 -0
  321. package/dist/testing/integration.js +1139 -0
  322. package/dist/testing/integration_helpers.d.ts +107 -0
  323. package/dist/testing/integration_helpers.d.ts.map +1 -0
  324. package/dist/testing/integration_helpers.js +246 -0
  325. package/dist/testing/middleware.d.ts +125 -0
  326. package/dist/testing/middleware.d.ts.map +1 -0
  327. package/dist/testing/middleware.js +210 -0
  328. package/dist/testing/rate_limiting.d.ts +43 -0
  329. package/dist/testing/rate_limiting.d.ts.map +1 -0
  330. package/dist/testing/rate_limiting.js +216 -0
  331. package/dist/testing/round_trip.d.ts +37 -0
  332. package/dist/testing/round_trip.d.ts.map +1 -0
  333. package/dist/testing/round_trip.js +128 -0
  334. package/dist/testing/schema_generators.d.ts +33 -0
  335. package/dist/testing/schema_generators.d.ts.map +1 -0
  336. package/dist/testing/schema_generators.js +137 -0
  337. package/dist/testing/standard.d.ts +49 -0
  338. package/dist/testing/standard.d.ts.map +1 -0
  339. package/dist/testing/standard.js +16 -0
  340. package/dist/testing/stubs.d.ts +96 -0
  341. package/dist/testing/stubs.d.ts.map +1 -0
  342. package/dist/testing/stubs.js +192 -0
  343. package/dist/testing/surface_invariants.d.ts +189 -0
  344. package/dist/testing/surface_invariants.d.ts.map +1 -0
  345. package/dist/testing/surface_invariants.js +450 -0
  346. package/dist/ui/AccountSessions.svelte +75 -0
  347. package/dist/ui/AccountSessions.svelte.d.ts +19 -0
  348. package/dist/ui/AccountSessions.svelte.d.ts.map +1 -0
  349. package/dist/ui/AdminAccounts.svelte +107 -0
  350. package/dist/ui/AdminAccounts.svelte.d.ts +19 -0
  351. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -0
  352. package/dist/ui/AdminAuditLog.svelte +144 -0
  353. package/dist/ui/AdminAuditLog.svelte.d.ts +4 -0
  354. package/dist/ui/AdminAuditLog.svelte.d.ts.map +1 -0
  355. package/dist/ui/AdminInvites.svelte +142 -0
  356. package/dist/ui/AdminInvites.svelte.d.ts +4 -0
  357. package/dist/ui/AdminInvites.svelte.d.ts.map +1 -0
  358. package/dist/ui/AdminOverview.svelte +337 -0
  359. package/dist/ui/AdminOverview.svelte.d.ts +4 -0
  360. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -0
  361. package/dist/ui/AdminPermitHistory.svelte +61 -0
  362. package/dist/ui/AdminPermitHistory.svelte.d.ts +19 -0
  363. package/dist/ui/AdminPermitHistory.svelte.d.ts.map +1 -0
  364. package/dist/ui/AdminSessions.svelte +85 -0
  365. package/dist/ui/AdminSessions.svelte.d.ts +19 -0
  366. package/dist/ui/AdminSessions.svelte.d.ts.map +1 -0
  367. package/dist/ui/AdminSettings.svelte +32 -0
  368. package/dist/ui/AdminSettings.svelte.d.ts +19 -0
  369. package/dist/ui/AdminSettings.svelte.d.ts.map +1 -0
  370. package/dist/ui/AdminSurface.svelte +42 -0
  371. package/dist/ui/AdminSurface.svelte.d.ts +4 -0
  372. package/dist/ui/AdminSurface.svelte.d.ts.map +1 -0
  373. package/dist/ui/AppShell.svelte +93 -0
  374. package/dist/ui/AppShell.svelte.d.ts +20 -0
  375. package/dist/ui/AppShell.svelte.d.ts.map +1 -0
  376. package/dist/ui/BootstrapForm.svelte +105 -0
  377. package/dist/ui/BootstrapForm.svelte.d.ts +4 -0
  378. package/dist/ui/BootstrapForm.svelte.d.ts.map +1 -0
  379. package/dist/ui/ColumnLayout.svelte +46 -0
  380. package/dist/ui/ColumnLayout.svelte.d.ts +11 -0
  381. package/dist/ui/ColumnLayout.svelte.d.ts.map +1 -0
  382. package/dist/ui/ConfirmButton.svelte +125 -0
  383. package/dist/ui/ConfirmButton.svelte.d.ts +54 -0
  384. package/dist/ui/ConfirmButton.svelte.d.ts.map +1 -0
  385. package/dist/ui/Datatable.svelte +185 -0
  386. package/dist/ui/Datatable.svelte.d.ts +35 -0
  387. package/dist/ui/Datatable.svelte.d.ts.map +1 -0
  388. package/dist/ui/LoginForm.svelte +82 -0
  389. package/dist/ui/LoginForm.svelte.d.ts +8 -0
  390. package/dist/ui/LoginForm.svelte.d.ts.map +1 -0
  391. package/dist/ui/LogoutButton.svelte +36 -0
  392. package/dist/ui/LogoutButton.svelte.d.ts +10 -0
  393. package/dist/ui/LogoutButton.svelte.d.ts.map +1 -0
  394. package/dist/ui/MenuLink.svelte +35 -0
  395. package/dist/ui/MenuLink.svelte.d.ts +12 -0
  396. package/dist/ui/MenuLink.svelte.d.ts.map +1 -0
  397. package/dist/ui/OpenSignupToggle.svelte +36 -0
  398. package/dist/ui/OpenSignupToggle.svelte.d.ts +19 -0
  399. package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -0
  400. package/dist/ui/PopoverButton.svelte +136 -0
  401. package/dist/ui/PopoverButton.svelte.d.ts +63 -0
  402. package/dist/ui/PopoverButton.svelte.d.ts.map +1 -0
  403. package/dist/ui/SignupForm.svelte +117 -0
  404. package/dist/ui/SignupForm.svelte.d.ts +7 -0
  405. package/dist/ui/SignupForm.svelte.d.ts.map +1 -0
  406. package/dist/ui/SurfaceExplorer.svelte +287 -0
  407. package/dist/ui/SurfaceExplorer.svelte.d.ts +8 -0
  408. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -0
  409. package/dist/ui/account_sessions_state.svelte.d.ts +15 -0
  410. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -0
  411. package/dist/ui/account_sessions_state.svelte.js +45 -0
  412. package/dist/ui/admin_accounts_state.svelte.d.ts +19 -0
  413. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -0
  414. package/dist/ui/admin_accounts_state.svelte.js +65 -0
  415. package/dist/ui/admin_invites_state.svelte.d.ts +19 -0
  416. package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -0
  417. package/dist/ui/admin_invites_state.svelte.js +71 -0
  418. package/dist/ui/admin_sessions_state.svelte.d.ts +18 -0
  419. package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -0
  420. package/dist/ui/admin_sessions_state.svelte.js +62 -0
  421. package/dist/ui/app_settings_state.svelte.d.ts +14 -0
  422. package/dist/ui/app_settings_state.svelte.d.ts.map +1 -0
  423. package/dist/ui/app_settings_state.svelte.js +44 -0
  424. package/dist/ui/audit_log_state.svelte.d.ts +40 -0
  425. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -0
  426. package/dist/ui/audit_log_state.svelte.js +153 -0
  427. package/dist/ui/auth_state.svelte.d.ts +85 -0
  428. package/dist/ui/auth_state.svelte.d.ts.map +1 -0
  429. package/dist/ui/auth_state.svelte.js +238 -0
  430. package/dist/ui/datatable.d.ts +25 -0
  431. package/dist/ui/datatable.d.ts.map +1 -0
  432. package/dist/ui/datatable.js +9 -0
  433. package/dist/ui/enter_advance.d.ts +13 -0
  434. package/dist/ui/enter_advance.d.ts.map +1 -0
  435. package/dist/ui/enter_advance.js +30 -0
  436. package/dist/ui/loadable.svelte.d.ts +55 -0
  437. package/dist/ui/loadable.svelte.d.ts.map +1 -0
  438. package/dist/ui/loadable.svelte.js +75 -0
  439. package/dist/ui/popover.svelte.d.ts +137 -0
  440. package/dist/ui/popover.svelte.d.ts.map +1 -0
  441. package/dist/ui/popover.svelte.js +288 -0
  442. package/dist/ui/position_helpers.d.ts +27 -0
  443. package/dist/ui/position_helpers.d.ts.map +1 -0
  444. package/dist/ui/position_helpers.js +81 -0
  445. package/dist/ui/sidebar_state.svelte.d.ts +30 -0
  446. package/dist/ui/sidebar_state.svelte.d.ts.map +1 -0
  447. package/dist/ui/sidebar_state.svelte.js +39 -0
  448. package/dist/ui/table_state.svelte.d.ts +63 -0
  449. package/dist/ui/table_state.svelte.d.ts.map +1 -0
  450. package/dist/ui/table_state.svelte.js +117 -0
  451. package/dist/ui/ui_fetch.d.ts +29 -0
  452. package/dist/ui/ui_fetch.d.ts.map +1 -0
  453. package/dist/ui/ui_fetch.js +37 -0
  454. package/dist/ui/ui_format.d.ts +63 -0
  455. package/dist/ui/ui_format.d.ts.map +1 -0
  456. package/dist/ui/ui_format.js +196 -0
  457. package/package.json +121 -0
@@ -0,0 +1,210 @@
1
+ import './assert_dev_env.js';
2
+ /**
3
+ * Table-driven middleware test helpers.
4
+ *
5
+ * Provides mock builders for bearer auth middleware dependencies,
6
+ * a generic test runner that iterates case tables, and a reusable
7
+ * middleware stack factory for integration testing.
8
+ *
9
+ * @module
10
+ */
11
+ import { vi, test, assert, describe } from 'vitest';
12
+ import { Hono } from 'hono';
13
+ import { Logger } from '@fuzdev/fuz_util/log.js';
14
+ import { create_bearer_auth_middleware } from '../auth/bearer_auth.js';
15
+ import { query_validate_api_token } from '../auth/api_token_queries.js';
16
+ import { query_account_by_id, query_actor_by_account } from '../auth/account_queries.js';
17
+ import { query_permit_find_active_for_actor } from '../auth/permit_queries.js';
18
+ import { create_proxy_middleware, get_client_ip } from '../http/proxy.js';
19
+ import { verify_request_source, parse_allowed_origins } from '../http/origin.js';
20
+ import { REQUEST_CONTEXT_KEY } from '../auth/request_context.js';
21
+ import { CREDENTIAL_TYPE_KEY } from '../hono_context.js';
22
+ import { ApiError } from '../http/error_schemas.js';
23
+ // Mock the query modules so test cases can control return values.
24
+ // vi.mock() is hoisted by vitest, so these run before any imports resolve.
25
+ vi.mock('../auth/api_token_queries.js', () => ({
26
+ query_validate_api_token: vi.fn(),
27
+ }));
28
+ vi.mock('../auth/account_queries.js', () => ({
29
+ query_account_by_id: vi.fn(),
30
+ query_actor_by_account: vi.fn(),
31
+ }));
32
+ vi.mock('../auth/permit_queries.js', () => ({
33
+ query_permit_find_active_for_actor: vi.fn(),
34
+ }));
35
+ /** Stub `QueryDeps` for bearer auth tests (no real DB needed). */
36
+ const STUB_DEPS = { db: {} };
37
+ /**
38
+ * Create mock dependencies for `create_bearer_auth_middleware`, configured per test case.
39
+ *
40
+ * Configures the module-level mocks for `query_validate_api_token`,
41
+ * `query_account_by_id`, `query_actor_by_account`, and `query_permit_find_active_for_actor`
42
+ * so each test case controls return values independently.
43
+ *
44
+ * @param tc - the test config providing mock return values
45
+ * @returns mocks bundle with spy references
46
+ */
47
+ export const create_bearer_auth_mocks = (tc) => {
48
+ const mock_validate = vi.mocked(query_validate_api_token);
49
+ const mock_find_by_id = vi.mocked(query_account_by_id);
50
+ const mock_find_by_account = vi.mocked(query_actor_by_account);
51
+ const mock_find_active_for_actor = vi.mocked(query_permit_find_active_for_actor);
52
+ mock_validate
53
+ .mockReset()
54
+ .mockImplementation(() => Promise.resolve(tc.mock_validate_result));
55
+ mock_find_by_id
56
+ .mockReset()
57
+ .mockImplementation(() => Promise.resolve(tc.mock_find_by_id_result));
58
+ mock_find_by_account
59
+ .mockReset()
60
+ .mockImplementation(() => Promise.resolve(tc.mock_find_by_account_result));
61
+ mock_find_active_for_actor
62
+ .mockReset()
63
+ .mockImplementation(() => Promise.resolve(tc.mock_permits_result ?? []));
64
+ return { mock_validate, mock_find_by_id, mock_find_by_account, mock_find_active_for_actor };
65
+ };
66
+ /** Default client IP set by the proxy stub in test apps. */
67
+ export const TEST_CLIENT_IP = '127.0.0.1';
68
+ /**
69
+ * Create a Hono app wired with `create_bearer_auth_middleware` using mocked deps.
70
+ *
71
+ * The route handler at `/api/test` returns the resolved context in the response body,
72
+ * enabling assertions on `REQUEST_CONTEXT_KEY` and `CREDENTIAL_TYPE_KEY`.
73
+ *
74
+ * @param tc - the test config providing mock behavior
75
+ * @param ip_rate_limiter - optional rate limiter (null to disable)
76
+ * @returns the app and mocks bundle
77
+ */
78
+ export const create_bearer_auth_test_app = (tc, ip_rate_limiter = null) => {
79
+ const mocks = create_bearer_auth_mocks(tc);
80
+ const bearer_middleware = create_bearer_auth_middleware(STUB_DEPS, ip_rate_limiter, new Logger('test', { level: 'off' }));
81
+ const app = new Hono();
82
+ // inject pre-existing request context if the test case specifies one
83
+ if (tc.pre_context) {
84
+ app.use('*', async (c, next) => {
85
+ c.set(REQUEST_CONTEXT_KEY, tc.pre_context);
86
+ c.set(CREDENTIAL_TYPE_KEY, 'session');
87
+ await next();
88
+ });
89
+ }
90
+ // proxy middleware stub — sets a known client_ip
91
+ app.use('*', async (c, next) => {
92
+ c.set('client_ip', TEST_CLIENT_IP);
93
+ await next();
94
+ });
95
+ app.use('/api/*', bearer_middleware);
96
+ // route handler echoes full context state for assertions
97
+ app.get('/api/test', (c) => {
98
+ const ctx = c.get(REQUEST_CONTEXT_KEY);
99
+ const cred = c.get(CREDENTIAL_TYPE_KEY);
100
+ return c.json({
101
+ ok: true,
102
+ has_context: ctx != null,
103
+ credential_type: cred ?? null,
104
+ account_id: ctx?.account.id ?? null,
105
+ actor_id: ctx?.actor.id ?? null,
106
+ permit_count: ctx?.permits.length ?? 0,
107
+ });
108
+ });
109
+ return { app, mocks };
110
+ };
111
+ // --- Table-driven test runner ---
112
+ /**
113
+ * Run a table of bearer auth middleware test cases.
114
+ *
115
+ * Generates one `test()` per case inside a `describe()` block.
116
+ *
117
+ * @param suite_name - the describe block name
118
+ * @param cases - the test case table
119
+ * @param ip_rate_limiter - optional rate limiter shared across cases
120
+ */
121
+ export const describe_bearer_auth_cases = (suite_name, cases, ip_rate_limiter = null) => {
122
+ describe(suite_name, () => {
123
+ for (const tc of cases) {
124
+ test(tc.name, async () => {
125
+ const { app, mocks } = create_bearer_auth_test_app(tc, ip_rate_limiter);
126
+ const res = await app.request('/api/test', {
127
+ method: 'GET',
128
+ headers: tc.headers,
129
+ });
130
+ const body = await res.json();
131
+ if (tc.expected_status === 'next') {
132
+ assert.strictEqual(res.status, 200, `expected next() but got ${res.status}`);
133
+ }
134
+ else {
135
+ assert.strictEqual(res.status, tc.expected_status);
136
+ if (tc.expected_error) {
137
+ assert.strictEqual(body.error, tc.expected_error);
138
+ const error_schema = tc.expected_error_schema ?? ApiError;
139
+ error_schema.parse(body);
140
+ }
141
+ }
142
+ if (tc.validate_expectation === 'not_called') {
143
+ assert.strictEqual(mocks.mock_validate.mock.calls.length, 0, 'validate should not have been called');
144
+ }
145
+ else {
146
+ assert.ok(mocks.mock_validate.mock.calls.length > 0, 'validate should have been called');
147
+ }
148
+ if (tc.assert_context_set) {
149
+ assert.strictEqual(body.has_context, true, 'REQUEST_CONTEXT_KEY should be set');
150
+ assert.strictEqual(body.credential_type, 'api_token', 'CREDENTIAL_TYPE_KEY should be api_token');
151
+ }
152
+ if (tc.assert_context_preserved) {
153
+ assert.strictEqual(body.has_context, true, 'original context should be preserved');
154
+ assert.strictEqual(body.credential_type, 'session', 'credential type should remain session');
155
+ }
156
+ if (tc.assert_mocks) {
157
+ tc.assert_mocks(mocks);
158
+ }
159
+ });
160
+ }
161
+ });
162
+ };
163
+ // --- Middleware stack test factory ---
164
+ /** Path used by the echo route in `create_test_middleware_stack_app`. */
165
+ export const TEST_MIDDLEWARE_PATH = '/api/test';
166
+ /**
167
+ * Create a Hono app with real proxy + origin + bearer middleware for integration testing.
168
+ *
169
+ * All DB queries return undefined (no real database needed).
170
+ * The echo route at `TEST_MIDDLEWARE_PATH` returns `{ok, client_ip, has_context}`.
171
+ *
172
+ * @param options - middleware stack configuration
173
+ * @returns the app and mock spies (reconfigure via `mockImplementation` for valid-token paths)
174
+ */
175
+ export const create_test_middleware_stack_app = (options) => {
176
+ const trusted_proxies = options?.trusted_proxies ?? ['10.0.0.1'];
177
+ const allowed_origins_str = options?.allowed_origins ?? 'https://app.example.com';
178
+ const mock_validate = vi.mocked(query_validate_api_token);
179
+ const mock_find_by_id = vi.mocked(query_account_by_id);
180
+ const mock_find_by_account = vi.mocked(query_actor_by_account);
181
+ const mock_find_active_for_actor = vi.mocked(query_permit_find_active_for_actor);
182
+ mock_validate.mockReset().mockImplementation(() => Promise.resolve(undefined));
183
+ mock_find_by_id.mockReset().mockImplementation(() => Promise.resolve(undefined));
184
+ mock_find_by_account.mockReset().mockImplementation(() => Promise.resolve(undefined));
185
+ mock_find_active_for_actor.mockReset().mockImplementation(() => Promise.resolve([]));
186
+ const get_connection_ip = typeof options?.connection_ip === 'function'
187
+ ? options.connection_ip
188
+ : () => options?.connection_ip ?? trusted_proxies[0];
189
+ const proxy_mw = create_proxy_middleware({
190
+ trusted_proxies,
191
+ get_connection_ip: get_connection_ip,
192
+ });
193
+ const allowed_patterns = parse_allowed_origins(allowed_origins_str);
194
+ const origin_mw = verify_request_source(allowed_patterns);
195
+ const bearer_mw = create_bearer_auth_middleware(STUB_DEPS, options?.ip_rate_limiter ?? null, new Logger('test', { level: 'off' }));
196
+ const app = new Hono();
197
+ app.use('*', proxy_mw);
198
+ app.use('/api/*', origin_mw);
199
+ app.use('/api/*', bearer_mw);
200
+ // echo route for assertions
201
+ app.get(TEST_MIDDLEWARE_PATH, (c) => {
202
+ const ctx = c.get(REQUEST_CONTEXT_KEY);
203
+ return c.json({
204
+ ok: true,
205
+ client_ip: get_client_ip(c),
206
+ has_context: ctx != null,
207
+ });
208
+ });
209
+ return { app, mock_validate, mock_find_by_id, mock_find_by_account, mock_find_active_for_actor };
210
+ };
@@ -0,0 +1,43 @@
1
+ import './assert_dev_env.js';
2
+ import type { SessionOptions } from '../auth/session_cookie.js';
3
+ import type { AppServerContext, AppServerOptions } from '../server/app_server.js';
4
+ import type { RouteSpec } from '../http/route_spec.js';
5
+ import { type DbFactory } from './db.js';
6
+ /**
7
+ * Configuration for `describe_rate_limiting_tests`.
8
+ */
9
+ export interface RateLimitingTestOptions {
10
+ /** Session config for cookie-based auth. */
11
+ session_options: SessionOptions<string>;
12
+ /** Route spec factory — same one used in production. */
13
+ create_route_specs: (ctx: AppServerContext) => Array<RouteSpec>;
14
+ /** Optional overrides for `AppServerOptions`. */
15
+ app_options?: Partial<Omit<AppServerOptions, 'backend' | 'session_options' | 'create_route_specs'>>;
16
+ /**
17
+ * Database factories to run tests against. Default: pglite only.
18
+ */
19
+ db_factories?: Array<DbFactory>;
20
+ /**
21
+ * Maximum attempts before rate limiting kicks in.
22
+ * Default: `2` (tight limit for fast tests).
23
+ */
24
+ max_attempts?: number;
25
+ }
26
+ /**
27
+ * Standard rate limiting integration test suite.
28
+ *
29
+ * Creates 3 test groups:
30
+ * 1. IP rate limiting on login — fires `max_attempts + 1` login requests,
31
+ * verifies the last returns 429 with a valid `RateLimitError` body.
32
+ * 2. Per-account rate limiting on login — fires `max_attempts + 1` login
33
+ * requests with the same username, verifies the last returns 429.
34
+ * 3. Bearer auth IP rate limiting — fires `max_attempts + 1` bearer requests
35
+ * with an invalid token, verifies the last returns 429.
36
+ *
37
+ * Each test group asserts that required routes exist, failing with a descriptive
38
+ * message if the consumer's route specs are misconfigured.
39
+ *
40
+ * @param options - session config and route factory
41
+ */
42
+ export declare const describe_rate_limiting_tests: (options: RateLimitingTestOptions) => void;
43
+ //# sourceMappingURL=rate_limiting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate_limiting.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rate_limiting.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAiB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAKrD,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAKjB;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF;;OAEG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,4BAA4B,GAAI,SAAS,uBAAuB,KAAG,IA+N/E,CAAC"}
@@ -0,0 +1,216 @@
1
+ import './assert_dev_env.js';
2
+ /**
3
+ * Rate limiting integration test suite.
4
+ *
5
+ * Verifies that sensitive routes (login, bootstrap, token creation) enforce
6
+ * rate limits when rate limiters are enabled. Tests create a tight rate limiter
7
+ * (2 attempts / 1 minute) and fire requests until 429 is returned.
8
+ *
9
+ * Consumers call `describe_rate_limiting_tests` with their route factory and
10
+ * session config — rate limit enforcement tests come for free.
11
+ *
12
+ * @module
13
+ */
14
+ import { describe, test, assert } from 'vitest';
15
+ import { RateLimiter } from '../rate_limiter.js';
16
+ import { RateLimitError } from '../http/error_schemas.js';
17
+ import { AUTH_MIGRATION_NS } from '../auth/migrations.js';
18
+ import { create_test_app } from './app_server.js';
19
+ import { create_pglite_factory, create_describe_db, AUTH_INTEGRATION_TRUNCATE_TABLES, } from './db.js';
20
+ import { find_auth_route, assert_rate_limit_retry_after_header } from './integration_helpers.js';
21
+ import { run_migrations } from '../db/migrate.js';
22
+ /**
23
+ * Standard rate limiting integration test suite.
24
+ *
25
+ * Creates 3 test groups:
26
+ * 1. IP rate limiting on login — fires `max_attempts + 1` login requests,
27
+ * verifies the last returns 429 with a valid `RateLimitError` body.
28
+ * 2. Per-account rate limiting on login — fires `max_attempts + 1` login
29
+ * requests with the same username, verifies the last returns 429.
30
+ * 3. Bearer auth IP rate limiting — fires `max_attempts + 1` bearer requests
31
+ * with an invalid token, verifies the last returns 429.
32
+ *
33
+ * Each test group asserts that required routes exist, failing with a descriptive
34
+ * message if the consumer's route specs are misconfigured.
35
+ *
36
+ * @param options - session config and route factory
37
+ */
38
+ export const describe_rate_limiting_tests = (options) => {
39
+ const max_attempts = options.max_attempts ?? 2;
40
+ const init_schema = async (db) => {
41
+ await run_migrations(db, [AUTH_MIGRATION_NS]);
42
+ };
43
+ const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
44
+ const describe_db = create_describe_db(factories, AUTH_INTEGRATION_TRUNCATE_TABLES);
45
+ /** Create a tight rate limiter for testing — low attempt count, long window. */
46
+ const create_test_rate_limiter = () => new RateLimiter({ max_attempts, window_ms: 60_000, cleanup_interval_ms: 0 });
47
+ describe_db('rate_limiting', (get_db) => {
48
+ // --- 1. IP rate limiting on login ---
49
+ describe('IP rate limiting on login', () => {
50
+ test(`login is blocked after ${max_attempts} failed attempts`, async () => {
51
+ const ip_rate_limiter = create_test_rate_limiter();
52
+ try {
53
+ const test_app = await create_test_app({
54
+ session_options: options.session_options,
55
+ create_route_specs: options.create_route_specs,
56
+ db: get_db(),
57
+ app_options: {
58
+ ...options.app_options,
59
+ ip_rate_limiter,
60
+ login_account_rate_limiter: null,
61
+ bearer_ip_rate_limiter: null,
62
+ },
63
+ });
64
+ const login_route = find_auth_route(test_app.route_specs, '/login', 'POST');
65
+ assert.ok(login_route, 'Expected POST /login route — ensure create_route_specs includes account routes');
66
+ // Fire max_attempts failed login requests (sequential — must exhaust the window)
67
+ /* eslint-disable no-await-in-loop */
68
+ for (let i = 0; i < max_attempts; i++) {
69
+ const res = await test_app.app.request(login_route.path, {
70
+ method: 'POST',
71
+ headers: {
72
+ host: 'localhost',
73
+ origin: 'http://localhost:5173',
74
+ 'content-type': 'application/json',
75
+ },
76
+ body: JSON.stringify({ username: 'nonexistent', password: 'wrong' }),
77
+ });
78
+ assert.notStrictEqual(res.status, 429, `Request ${i + 1}/${max_attempts} should not be rate limited`);
79
+ }
80
+ /* eslint-enable no-await-in-loop */
81
+ // The next request should be rate limited
82
+ const blocked_res = await test_app.app.request(login_route.path, {
83
+ method: 'POST',
84
+ headers: {
85
+ host: 'localhost',
86
+ origin: 'http://localhost:5173',
87
+ 'content-type': 'application/json',
88
+ },
89
+ body: JSON.stringify({ username: 'nonexistent', password: 'wrong' }),
90
+ });
91
+ assert.strictEqual(blocked_res.status, 429);
92
+ const body = await blocked_res.json();
93
+ RateLimitError.parse(body);
94
+ assert.ok(typeof body.retry_after === 'number' && body.retry_after > 0, 'Expected positive retry_after');
95
+ assert_rate_limit_retry_after_header(blocked_res, body);
96
+ }
97
+ finally {
98
+ ip_rate_limiter.dispose();
99
+ }
100
+ });
101
+ });
102
+ // --- 2. Per-account rate limiting on login ---
103
+ describe('per-account rate limiting on login', () => {
104
+ test(`login is blocked after ${max_attempts} failed attempts for the same username`, async () => {
105
+ const login_account_rate_limiter = create_test_rate_limiter();
106
+ try {
107
+ const test_app = await create_test_app({
108
+ session_options: options.session_options,
109
+ create_route_specs: options.create_route_specs,
110
+ db: get_db(),
111
+ app_options: {
112
+ ...options.app_options,
113
+ ip_rate_limiter: null,
114
+ login_account_rate_limiter,
115
+ bearer_ip_rate_limiter: null,
116
+ },
117
+ });
118
+ const login_route = find_auth_route(test_app.route_specs, '/login', 'POST');
119
+ assert.ok(login_route, 'Expected POST /login route — ensure create_route_specs includes account routes');
120
+ const target_username = 'rate_limit_target';
121
+ // Fire max_attempts failed login requests for the same username
122
+ /* eslint-disable no-await-in-loop */
123
+ for (let i = 0; i < max_attempts; i++) {
124
+ const res = await test_app.app.request(login_route.path, {
125
+ method: 'POST',
126
+ headers: {
127
+ host: 'localhost',
128
+ origin: 'http://localhost:5173',
129
+ 'content-type': 'application/json',
130
+ },
131
+ body: JSON.stringify({ username: target_username, password: 'wrong' }),
132
+ });
133
+ assert.notStrictEqual(res.status, 429, `Request ${i + 1}/${max_attempts} should not be rate limited`);
134
+ }
135
+ /* eslint-enable no-await-in-loop */
136
+ // The next request for the same username should be rate limited
137
+ const blocked_res = await test_app.app.request(login_route.path, {
138
+ method: 'POST',
139
+ headers: {
140
+ host: 'localhost',
141
+ origin: 'http://localhost:5173',
142
+ 'content-type': 'application/json',
143
+ },
144
+ body: JSON.stringify({ username: target_username, password: 'wrong' }),
145
+ });
146
+ assert.strictEqual(blocked_res.status, 429);
147
+ const body = await blocked_res.json();
148
+ RateLimitError.parse(body);
149
+ assert.ok(typeof body.retry_after === 'number' && body.retry_after > 0, 'Expected positive retry_after');
150
+ assert_rate_limit_retry_after_header(blocked_res, body);
151
+ // A different username should NOT be rate limited
152
+ const other_res = await test_app.app.request(login_route.path, {
153
+ method: 'POST',
154
+ headers: {
155
+ host: 'localhost',
156
+ origin: 'http://localhost:5173',
157
+ 'content-type': 'application/json',
158
+ },
159
+ body: JSON.stringify({ username: 'different_user', password: 'wrong' }),
160
+ });
161
+ assert.notStrictEqual(other_res.status, 429, 'Different username should not be rate limited');
162
+ }
163
+ finally {
164
+ login_account_rate_limiter.dispose();
165
+ }
166
+ });
167
+ });
168
+ // --- 3. Bearer auth IP rate limiting ---
169
+ describe('bearer auth IP rate limiting', () => {
170
+ test(`bearer auth is blocked after ${max_attempts} invalid token attempts`, async () => {
171
+ const bearer_ip_rate_limiter = create_test_rate_limiter();
172
+ try {
173
+ const test_app = await create_test_app({
174
+ session_options: options.session_options,
175
+ create_route_specs: options.create_route_specs,
176
+ db: get_db(),
177
+ app_options: {
178
+ ...options.app_options,
179
+ ip_rate_limiter: null,
180
+ login_account_rate_limiter: null,
181
+ bearer_ip_rate_limiter,
182
+ },
183
+ });
184
+ const verify_route = find_auth_route(test_app.route_specs, '/verify', 'GET');
185
+ assert.ok(verify_route, 'Expected GET /verify route — ensure create_route_specs includes account routes');
186
+ // Fire max_attempts invalid bearer requests (sequential — must exhaust the window)
187
+ /* eslint-disable no-await-in-loop */
188
+ for (let i = 0; i < max_attempts; i++) {
189
+ const res = await test_app.app.request(verify_route.path, {
190
+ headers: {
191
+ host: 'localhost',
192
+ authorization: 'Bearer secret_fuz_token_invalid',
193
+ },
194
+ });
195
+ assert.notStrictEqual(res.status, 429, `Request ${i + 1}/${max_attempts} should not be rate limited`);
196
+ }
197
+ /* eslint-enable no-await-in-loop */
198
+ // The next request should be rate limited
199
+ const blocked_res = await test_app.app.request(verify_route.path, {
200
+ headers: {
201
+ host: 'localhost',
202
+ authorization: 'Bearer secret_fuz_token_invalid',
203
+ },
204
+ });
205
+ assert.strictEqual(blocked_res.status, 429);
206
+ const body = await blocked_res.json();
207
+ RateLimitError.parse(body);
208
+ assert_rate_limit_retry_after_header(blocked_res, body);
209
+ }
210
+ finally {
211
+ bearer_ip_rate_limiter.dispose();
212
+ }
213
+ });
214
+ });
215
+ });
216
+ };
@@ -0,0 +1,37 @@
1
+ import './assert_dev_env.js';
2
+ import type { RouteSpec } from '../http/route_spec.js';
3
+ import type { AppServerContext, AppServerOptions } from '../server/app_server.js';
4
+ import type { SessionOptions } from '../auth/session_cookie.js';
5
+ import { type DbFactory } from './db.js';
6
+ /** Options for `describe_round_trip_validation`. */
7
+ export interface RoundTripTestOptions {
8
+ /** Session config for cookie-based auth. */
9
+ session_options: SessionOptions<string>;
10
+ /** Route spec factory — same one used in production. */
11
+ create_route_specs: (ctx: AppServerContext) => Array<RouteSpec>;
12
+ /** Optional overrides for `AppServerOptions`. */
13
+ app_options?: Partial<Omit<AppServerOptions, 'backend' | 'session_options' | 'create_route_specs'>>;
14
+ /** Database factories to run tests against. Default: pglite only. */
15
+ db_factories?: Array<DbFactory>;
16
+ /** Routes to skip, in `'METHOD /path'` format. */
17
+ skip_routes?: Array<string>;
18
+ /** Override generated bodies for specific routes (`'METHOD /path'` → body). */
19
+ input_overrides?: Map<string, Record<string, unknown>>;
20
+ }
21
+ /**
22
+ * Run schema-driven round-trip validation tests.
23
+ *
24
+ * For each route:
25
+ * 1. Resolve URL with valid params
26
+ * 2. Generate a valid request body (or use override)
27
+ * 3. Pick auth headers matching the route's auth requirement
28
+ * 4. Fire the request and validate the response against declared schemas
29
+ *
30
+ * SSE routes are skipped (Content-Type `text/event-stream`).
31
+ * Routes returning non-2xx with valid input are still validated against
32
+ * their declared error schemas.
33
+ *
34
+ * @param options - round-trip test configuration
35
+ */
36
+ export declare const describe_round_trip_validation: (options: RoundTripTestOptions) => void;
37
+ //# sourceMappingURL=round_trip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAc7B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAG9D,OAAO,EAAwB,KAAK,SAAS,EAAC,MAAM,SAAS,CAAC;AAO9D,oDAAoD;AACpD,MAAM,WAAW,oBAAoB;IACpC,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF,qEAAqE;IACrE,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC,kDAAkD;IAClD,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,+EAA+E;IAC/E,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACvD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,8BAA8B,GAAI,SAAS,oBAAoB,KAAG,IAsF9E,CAAC"}
@@ -0,0 +1,128 @@
1
+ import './assert_dev_env.js';
2
+ /**
3
+ * Schema-driven round-trip validation test suite.
4
+ *
5
+ * For every route spec, generates a valid request (auth, params, body)
6
+ * and validates the response against declared output or error schemas.
7
+ * DB-backed via `create_test_app` — exercises the full middleware stack.
8
+ *
9
+ * @module
10
+ */
11
+ import { describe, test, beforeAll, afterAll } from 'vitest';
12
+ import { ROLE_ADMIN } from '../auth/role_schema.js';
13
+ import { create_test_app } from './app_server.js';
14
+ import { create_pglite_factory } from './db.js';
15
+ import { assert_response_matches_spec } from './integration_helpers.js';
16
+ import { resolve_valid_path, generate_valid_body } from './schema_generators.js';
17
+ import { run_migrations } from '../db/migrate.js';
18
+ import { AUTH_MIGRATION_NS } from '../auth/migrations.js';
19
+ /**
20
+ * Run schema-driven round-trip validation tests.
21
+ *
22
+ * For each route:
23
+ * 1. Resolve URL with valid params
24
+ * 2. Generate a valid request body (or use override)
25
+ * 3. Pick auth headers matching the route's auth requirement
26
+ * 4. Fire the request and validate the response against declared schemas
27
+ *
28
+ * SSE routes are skipped (Content-Type `text/event-stream`).
29
+ * Routes returning non-2xx with valid input are still validated against
30
+ * their declared error schemas.
31
+ *
32
+ * @param options - round-trip test configuration
33
+ */
34
+ export const describe_round_trip_validation = (options) => {
35
+ const skip_set = new Set(options.skip_routes);
36
+ const init_schema = async (db) => {
37
+ await run_migrations(db, [AUTH_MIGRATION_NS]);
38
+ };
39
+ const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
40
+ for (const factory of factories) {
41
+ describe(`round-trip validation (${factory.name})`, () => {
42
+ if (factory.skip)
43
+ return;
44
+ let test_app;
45
+ let authed_account;
46
+ let admin_account;
47
+ let db;
48
+ beforeAll(async () => {
49
+ db = await factory.create();
50
+ test_app = await create_test_app({
51
+ session_options: options.session_options,
52
+ create_route_specs: options.create_route_specs,
53
+ db,
54
+ app_options: options.app_options,
55
+ });
56
+ // Create accounts at each auth level
57
+ authed_account = await test_app.create_account({
58
+ username: 'round_trip_authed',
59
+ roles: [],
60
+ });
61
+ admin_account = await test_app.create_account({
62
+ username: 'round_trip_admin',
63
+ roles: [ROLE_ADMIN],
64
+ });
65
+ });
66
+ afterAll(async () => {
67
+ await test_app.cleanup();
68
+ await factory.close(db);
69
+ });
70
+ test('all routes produce schema-valid responses', async () => {
71
+ for (const spec of test_app.route_specs) {
72
+ const route_key = `${spec.method} ${spec.path}`;
73
+ if (skip_set.has(route_key))
74
+ continue;
75
+ // Resolve URL with valid param values
76
+ const url = resolve_valid_path(spec.path, spec.params);
77
+ // Generate or override request body
78
+ const override = options.input_overrides?.get(route_key);
79
+ const body = override ?? generate_valid_body(spec.input);
80
+ // Pick auth headers based on route auth requirement
81
+ const headers = pick_auth_headers(spec, test_app, authed_account, admin_account);
82
+ // Fire request
83
+ const request_init = {
84
+ method: spec.method,
85
+ headers: {
86
+ ...headers,
87
+ ...(body ? { 'content-type': 'application/json' } : {}),
88
+ },
89
+ ...(body ? { body: JSON.stringify(body) } : {}),
90
+ };
91
+ const res = await test_app.app.request(url, request_init); // eslint-disable-line no-await-in-loop
92
+ // Skip SSE responses — streaming bodies can't be parsed as JSON
93
+ if (res.headers.get('Content-Type')?.includes('text/event-stream')) {
94
+ await res.body?.cancel(); // eslint-disable-line no-await-in-loop
95
+ continue;
96
+ }
97
+ // Validate response against declared schemas
98
+ try {
99
+ await assert_response_matches_spec(test_app.route_specs, spec.method, url, res); // eslint-disable-line no-await-in-loop
100
+ }
101
+ catch (e) {
102
+ // Re-throw with route context for easier debugging
103
+ throw new Error(`Round-trip validation failed for ${route_key} (status ${res.status}): ${e.message}`);
104
+ }
105
+ }
106
+ });
107
+ });
108
+ }
109
+ };
110
+ /**
111
+ * Pick auth headers matching a route spec's auth requirement.
112
+ */
113
+ const pick_auth_headers = (spec, test_app, authed_account, admin_account) => {
114
+ switch (spec.auth.type) {
115
+ case 'none':
116
+ return { host: 'localhost', origin: 'http://localhost:5173' };
117
+ case 'authenticated':
118
+ return authed_account.create_session_headers();
119
+ case 'role':
120
+ if (spec.auth.role === ROLE_ADMIN) {
121
+ return admin_account.create_session_headers();
122
+ }
123
+ // Keeper role uses the bootstrapped account (which has keeper role)
124
+ return test_app.create_session_headers();
125
+ case 'keeper':
126
+ return test_app.create_bearer_headers();
127
+ }
128
+ };