itsi 0.1.20 → 0.2.3

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 (323) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -8
  3. data/Cargo.lock +29 -30
  4. data/LICENSE.txt +698 -0
  5. data/README.md +16 -4
  6. data/Rakefile +9 -5
  7. data/crates/itsi_acme/.gitignore +4 -0
  8. data/crates/itsi_acme/Cargo.toml +86 -0
  9. data/crates/itsi_acme/LICENSE-APACHE +201 -0
  10. data/crates/itsi_acme/LICENSE-MIT +23 -0
  11. data/crates/itsi_acme/README.md +9 -0
  12. data/crates/itsi_acme/examples/high_level.rs +63 -0
  13. data/crates/itsi_acme/examples/high_level_warp.rs +52 -0
  14. data/crates/itsi_acme/examples/low_level.rs +87 -0
  15. data/crates/itsi_acme/examples/low_level_axum.rs +66 -0
  16. data/crates/itsi_acme/src/acceptor.rs +81 -0
  17. data/crates/itsi_acme/src/acme.rs +354 -0
  18. data/crates/itsi_acme/src/axum.rs +86 -0
  19. data/crates/itsi_acme/src/cache.rs +39 -0
  20. data/crates/itsi_acme/src/caches/boxed.rs +80 -0
  21. data/crates/itsi_acme/src/caches/composite.rs +69 -0
  22. data/crates/itsi_acme/src/caches/dir.rs +106 -0
  23. data/crates/itsi_acme/src/caches/mod.rs +11 -0
  24. data/crates/itsi_acme/src/caches/no.rs +78 -0
  25. data/crates/itsi_acme/src/caches/test.rs +136 -0
  26. data/crates/itsi_acme/src/config.rs +172 -0
  27. data/crates/itsi_acme/src/https_helper.rs +69 -0
  28. data/crates/itsi_acme/src/incoming.rs +142 -0
  29. data/crates/itsi_acme/src/jose.rs +161 -0
  30. data/crates/itsi_acme/src/lib.rs +142 -0
  31. data/crates/itsi_acme/src/resolver.rs +59 -0
  32. data/crates/itsi_acme/src/state.rs +424 -0
  33. data/crates/itsi_rb_helpers/src/lib.rs +4 -3
  34. data/crates/itsi_scheduler/Cargo.toml +1 -1
  35. data/crates/itsi_scheduler/src/itsi_scheduler.rs +8 -2
  36. data/crates/itsi_scheduler/src/lib.rs +1 -0
  37. data/crates/itsi_server/Cargo.toml +1 -1
  38. data/crates/itsi_server/src/lib.rs +2 -1
  39. data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +18 -1
  40. data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +11 -3
  41. data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +148 -66
  42. data/crates/itsi_server/src/ruby_types/itsi_server.rs +2 -0
  43. data/crates/itsi_server/src/server/binds/bind.rs +3 -0
  44. data/crates/itsi_server/src/server/binds/listener.rs +12 -5
  45. data/crates/itsi_server/src/server/binds/tls.rs +13 -5
  46. data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +12 -5
  47. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +8 -1
  48. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +9 -1
  49. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +48 -43
  50. data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +11 -2
  51. data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +39 -12
  52. data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +36 -27
  53. data/crates/itsi_server/src/server/middleware_stack/middlewares/csp.rs +25 -11
  54. data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +12 -3
  55. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +74 -72
  56. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +15 -1
  57. data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +11 -8
  58. data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +19 -11
  59. data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +5 -5
  60. data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +2 -2
  61. data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +11 -5
  62. data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +17 -20
  63. data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +19 -8
  64. data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +16 -37
  65. data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +22 -12
  66. data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +26 -11
  67. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +7 -1
  68. data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +14 -4
  69. data/crates/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +19 -0
  70. data/crates/itsi_server/src/server/middleware_stack/mod.rs +49 -13
  71. data/crates/itsi_server/src/server/mod.rs +1 -0
  72. data/crates/itsi_server/src/server/redirect_type.rs +26 -0
  73. data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +22 -16
  74. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +49 -12
  75. data/crates/itsi_server/src/server/signal.rs +1 -0
  76. data/crates/itsi_server/src/server/size_limited_incoming.rs +6 -0
  77. data/crates/itsi_server/src/server/thread_worker.rs +5 -1
  78. data/crates/itsi_server/src/services/itsi_http_service.rs +20 -2
  79. data/crates/itsi_server/src/services/rate_limiter.rs +15 -4
  80. data/crates/itsi_server/src/services/static_file_server.rs +33 -19
  81. data/crates/itsi_tracing/src/lib.rs +42 -22
  82. data/docs/content/_index.md +2 -3
  83. data/docs/content/acknowledgements/_index.md +5 -2
  84. data/docs/content/configuration/_index.md +8 -5
  85. data/docs/content/contact/_index.md +8 -1
  86. data/docs/content/directory_listing.jpg +0 -0
  87. data/docs/content/error_page.jpg +0 -0
  88. data/docs/content/faqs/_index.md +5 -3
  89. data/docs/content/features/_index.md +56 -50
  90. data/docs/content/getting_started/_index.md +8 -5
  91. data/docs/content/getting_started/local_development.md +76 -9
  92. data/docs/content/getting_started/logging.md +15 -9
  93. data/docs/content/getting_started/running_itsi_in_production.md +5 -3
  94. data/docs/content/getting_started/signals.md +37 -0
  95. data/docs/content/itsi_scheduler/_index.md +8 -7
  96. data/docs/content/utilities/_index.md +13 -0
  97. data/docs/content/utilities/config_file_testing.md +17 -0
  98. data/docs/content/utilities/passfile_generator.md +41 -0
  99. data/docs/content/utilities/route_testing.md +27 -0
  100. data/docs/content/utilities/secrets_management.md +30 -0
  101. data/docs/hugo.yaml +4 -1
  102. data/fairytale.txt +3 -4
  103. data/gems/scheduler/Cargo.lock +74 -17
  104. data/gems/scheduler/README.md +4 -5
  105. data/gems/scheduler/Rakefile +0 -4
  106. data/gems/scheduler/itsi-scheduler.gemspec +2 -2
  107. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  108. data/gems/scheduler/lib/itsi/scheduler.rb +9 -4
  109. data/gems/scheduler/test/test_active_record.rb +12 -7
  110. data/gems/server/Cargo.lock +28 -29
  111. data/gems/server/Rakefile +0 -4
  112. data/gems/server/exe/itsi +13 -2
  113. data/gems/server/itsi-server.gemspec +2 -2
  114. data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +2 -0
  115. data/gems/server/lib/itsi/http_request.rb +58 -30
  116. data/gems/server/lib/itsi/http_response.rb +10 -7
  117. data/gems/server/lib/itsi/passfile.rb +6 -7
  118. data/gems/server/lib/itsi/server/config/config_helpers.rb +41 -29
  119. data/gems/server/lib/itsi/server/config/dsl.rb +22 -442
  120. data/gems/server/lib/itsi/server/config/known_paths.rb +14 -7
  121. data/gems/server/lib/itsi/server/config/middleware/_index.md +6 -4
  122. data/gems/server/lib/itsi/server/config/middleware/allow_list.md +46 -0
  123. data/gems/server/lib/itsi/server/config/middleware/allow_list.rb +42 -0
  124. data/gems/server/lib/itsi/server/config/middleware/auth_api_key.md +90 -0
  125. data/gems/server/lib/itsi/server/config/middleware/auth_api_key.rb +51 -0
  126. data/gems/server/lib/itsi/server/config/middleware/auth_basic.md +45 -0
  127. data/gems/server/lib/itsi/server/config/middleware/auth_basic.rb +44 -0
  128. data/gems/server/lib/itsi/server/config/middleware/auth_jwt.md +82 -0
  129. data/gems/server/lib/itsi/server/config/middleware/auth_jwt.rb +38 -0
  130. data/gems/server/lib/itsi/server/config/middleware/cache_control.md +78 -0
  131. data/gems/server/lib/itsi/server/config/middleware/cache_control.rb +45 -0
  132. data/gems/server/lib/itsi/server/config/middleware/cidr_to_regex.rb +50 -0
  133. data/gems/server/lib/itsi/server/config/middleware/compression.md +50 -0
  134. data/gems/server/lib/itsi/server/config/middleware/compression.rb +37 -0
  135. data/gems/server/lib/itsi/server/config/middleware/cors.md +93 -0
  136. data/gems/server/lib/itsi/server/config/middleware/cors.rb +32 -0
  137. data/gems/server/lib/itsi/server/config/middleware/csp.md +37 -0
  138. data/gems/server/lib/itsi/server/config/middleware/csp.rb +44 -0
  139. data/gems/server/lib/itsi/server/config/middleware/deny_list.md +45 -0
  140. data/gems/server/lib/itsi/server/config/middleware/deny_list.rb +42 -0
  141. data/gems/server/lib/itsi/server/config/middleware/endpoint/_index.md +159 -0
  142. data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.md +186 -0
  143. data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.rb +33 -0
  144. data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.md +12 -0
  145. data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.rb +42 -0
  146. data/gems/server/lib/itsi/server/config/middleware/endpoint/endpoint.rb +99 -0
  147. data/gems/server/lib/itsi/server/config/middleware/endpoint/get.md +12 -0
  148. data/gems/server/lib/itsi/server/config/middleware/endpoint/get.rb +42 -0
  149. data/gems/server/lib/itsi/server/config/middleware/endpoint/http_request.md +44 -0
  150. data/gems/server/lib/itsi/server/config/middleware/endpoint/http_response.md +39 -0
  151. data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.md +12 -0
  152. data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.rb +42 -0
  153. data/gems/server/lib/itsi/server/config/middleware/endpoint/post.md +12 -0
  154. data/gems/server/lib/itsi/server/config/middleware/endpoint/post.rb +42 -0
  155. data/gems/server/lib/itsi/server/config/middleware/endpoint/put.md +12 -0
  156. data/gems/server/lib/itsi/server/config/middleware/endpoint/put.rb +42 -0
  157. data/gems/server/lib/itsi/server/config/middleware/endpoint/schemas.md +122 -0
  158. data/gems/server/lib/itsi/server/config/middleware/error_response.md +74 -0
  159. data/gems/server/lib/itsi/server/config/middleware/error_response.rb +36 -0
  160. data/gems/server/lib/itsi/server/config/middleware/etag.md +59 -0
  161. data/gems/server/lib/itsi/server/config/middleware/etag.rb +27 -0
  162. data/gems/server/lib/itsi/server/config/middleware/grpc.md +172 -0
  163. data/gems/server/lib/itsi/server/config/middleware/grpc.rb +54 -0
  164. data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.md +124 -0
  165. data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.rb +61 -0
  166. data/gems/server/lib/itsi/server/config/middleware/location.md +107 -0
  167. data/gems/server/lib/itsi/server/config/middleware/location.rb +99 -0
  168. data/gems/server/lib/itsi/server/config/middleware/log_requests.md +13 -11
  169. data/gems/server/lib/itsi/server/config/middleware/log_requests.rb +1 -3
  170. data/gems/server/lib/itsi/server/config/middleware/max_body.md +18 -0
  171. data/gems/server/lib/itsi/server/config/middleware/max_body.rb +21 -0
  172. data/gems/server/lib/itsi/server/config/middleware/proxy.md +62 -0
  173. data/gems/server/lib/itsi/server/config/middleware/proxy.rb +41 -0
  174. data/gems/server/lib/itsi/server/config/middleware/rackup_file.md +54 -0
  175. data/gems/server/lib/itsi/server/config/middleware/rackup_file.rb +44 -0
  176. data/gems/server/lib/itsi/server/config/middleware/rate_limit.md +126 -0
  177. data/gems/server/lib/itsi/server/config/middleware/rate_limit.rb +34 -0
  178. data/gems/server/lib/itsi/server/config/middleware/rate_limit_store.rb +25 -0
  179. data/gems/server/lib/itsi/server/config/middleware/redirect.md +55 -0
  180. data/gems/server/lib/itsi/server/config/middleware/redirect.rb +25 -0
  181. data/gems/server/lib/itsi/server/config/middleware/request_headers.md +34 -0
  182. data/gems/server/lib/itsi/server/config/middleware/request_headers.rb +24 -0
  183. data/gems/server/lib/itsi/server/config/middleware/response_headers.md +33 -0
  184. data/gems/server/lib/itsi/server/config/middleware/response_headers.rb +25 -0
  185. data/gems/server/lib/itsi/server/config/middleware/run.md +60 -0
  186. data/gems/server/lib/itsi/server/config/middleware/run.rb +43 -0
  187. data/gems/server/lib/itsi/server/config/middleware/static_assets.md +113 -0
  188. data/gems/server/lib/itsi/server/config/middleware/static_assets.rb +87 -0
  189. data/gems/server/lib/itsi/server/config/middleware/static_response.md +44 -0
  190. data/gems/server/lib/itsi/server/config/middleware/static_response.rb +29 -0
  191. data/gems/server/lib/itsi/server/config/middleware/string_rewrite.md +67 -0
  192. data/gems/server/lib/itsi/server/config/middleware/token_source.rb +32 -0
  193. data/gems/server/lib/itsi/server/config/middleware.rb +4 -0
  194. data/gems/server/lib/itsi/server/config/option.rb +4 -0
  195. data/gems/server/lib/itsi/server/config/options/_index.md +3 -2
  196. data/gems/server/lib/itsi/server/config/options/auto_reload_config.md +13 -0
  197. data/gems/server/lib/itsi/server/config/options/auto_reload_config.rb +41 -0
  198. data/gems/server/lib/itsi/server/config/options/bind.md +71 -0
  199. data/gems/server/lib/itsi/server/config/options/bind.rb +26 -0
  200. data/gems/server/lib/itsi/server/config/options/certificates.md +65 -0
  201. data/gems/server/lib/itsi/server/config/options/daemonize.md +14 -0
  202. data/gems/server/lib/itsi/server/config/options/daemonize.rb +19 -0
  203. data/gems/server/lib/itsi/server/config/options/fiber_scheduler.md +1 -2
  204. data/gems/server/lib/itsi/server/config/options/fiber_scheduler.rb +6 -3
  205. data/gems/server/lib/itsi/server/config/options/header_read_timeout.md +17 -0
  206. data/gems/server/lib/itsi/server/config/options/header_read_timeout.rb +19 -0
  207. data/gems/server/lib/itsi/server/config/options/hooks/_index.md +11 -0
  208. data/gems/server/lib/itsi/server/config/options/hooks/after_fork.md +13 -0
  209. data/gems/server/lib/itsi/server/config/options/hooks/after_fork.rb +28 -0
  210. data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.md +14 -0
  211. data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.rb +28 -0
  212. data/gems/server/lib/itsi/server/config/options/hooks/after_start.md +12 -0
  213. data/gems/server/lib/itsi/server/config/options/hooks/after_start.rb +28 -0
  214. data/gems/server/lib/itsi/server/config/options/hooks/before_fork.md +13 -0
  215. data/gems/server/lib/itsi/server/config/options/hooks/before_fork.rb +28 -0
  216. data/gems/server/lib/itsi/server/config/options/hooks/before_restart.md +12 -0
  217. data/gems/server/lib/itsi/server/config/options/hooks/before_restart.rb +28 -0
  218. data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.md +12 -0
  219. data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.rb +28 -0
  220. data/gems/server/lib/itsi/server/config/options/include.md +20 -0
  221. data/gems/server/lib/itsi/server/config/options/include.rb +36 -0
  222. data/gems/server/lib/itsi/server/config/options/listen_backlog.md +11 -0
  223. data/gems/server/lib/itsi/server/config/options/listen_backlog.rb +19 -0
  224. data/gems/server/lib/itsi/server/config/options/log_format.md +18 -0
  225. data/gems/server/lib/itsi/server/config/options/log_format.rb +19 -0
  226. data/gems/server/lib/itsi/server/config/options/log_level.md +34 -0
  227. data/gems/server/lib/itsi/server/config/options/log_level.rb +20 -0
  228. data/gems/server/lib/itsi/server/config/options/log_target.md +38 -0
  229. data/gems/server/lib/itsi/server/config/options/log_target.rb +19 -0
  230. data/gems/server/lib/itsi/server/config/options/log_target_filters.md +17 -0
  231. data/gems/server/lib/itsi/server/config/options/log_target_filters.rb +19 -0
  232. data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.md +27 -0
  233. data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.rb +24 -0
  234. data/gems/server/lib/itsi/server/config/options/nodelay.md +16 -0
  235. data/gems/server/lib/itsi/server/config/options/nodelay.rb +19 -0
  236. data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.md +19 -0
  237. data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.rb +18 -0
  238. data/gems/server/lib/itsi/server/config/options/pin_worker_cores.md +17 -0
  239. data/gems/server/lib/itsi/server/config/options/pin_worker_cores.rb +19 -0
  240. data/gems/server/lib/itsi/server/config/options/preload.md +21 -0
  241. data/gems/server/lib/itsi/server/config/options/preload.rb +18 -0
  242. data/gems/server/lib/itsi/server/config/options/recv_buffer_size.md +15 -0
  243. data/gems/server/lib/itsi/server/config/options/recv_buffer_size.rb +19 -0
  244. data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.md +21 -0
  245. data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.rb +30 -0
  246. data/gems/server/lib/itsi/server/config/options/request_timeout.md +23 -0
  247. data/gems/server/lib/itsi/server/config/options/request_timeout.rb +19 -0
  248. data/gems/server/lib/itsi/server/config/options/reuse_address.md +16 -0
  249. data/gems/server/lib/itsi/server/config/options/reuse_address.rb +19 -0
  250. data/gems/server/lib/itsi/server/config/options/reuse_port.md +16 -0
  251. data/gems/server/lib/itsi/server/config/options/reuse_port.rb +19 -0
  252. data/gems/server/lib/itsi/server/config/options/scheduler_threads.md +34 -0
  253. data/gems/server/lib/itsi/server/config/options/scheduler_threads.rb +17 -0
  254. data/gems/server/lib/itsi/server/config/options/shutdown_timeout.md +17 -0
  255. data/gems/server/lib/itsi/server/config/options/shutdown_timeout.rb +19 -0
  256. data/gems/server/lib/itsi/server/config/options/stream_body.md +32 -0
  257. data/gems/server/lib/itsi/server/config/options/stream_body.rb +18 -0
  258. data/gems/server/lib/itsi/server/config/options/threads.md +7 -2
  259. data/gems/server/lib/itsi/server/config/options/threads.rb +1 -1
  260. data/gems/server/lib/itsi/server/config/options/watch.md +16 -0
  261. data/gems/server/lib/itsi/server/config/options/watch.rb +28 -0
  262. data/gems/server/lib/itsi/server/config/options/worker_memory_limit.md +22 -0
  263. data/gems/server/lib/itsi/server/config/options/worker_memory_limit.rb +18 -0
  264. data/gems/server/lib/itsi/server/config/options/workers.md +1 -2
  265. data/gems/server/lib/itsi/server/config/options/workers.rb +1 -1
  266. data/gems/server/lib/itsi/server/config/typed_struct.rb +68 -32
  267. data/gems/server/lib/itsi/server/config.rb +163 -119
  268. data/gems/server/lib/itsi/server/default_app/default_app.rb +1 -1
  269. data/gems/server/lib/itsi/server/default_config/Itsi.rb +3 -3
  270. data/gems/server/lib/itsi/server/grpc/grpc_call.rb +4 -5
  271. data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +10 -4
  272. data/gems/server/lib/itsi/server/rack/handler/itsi.rb +2 -3
  273. data/gems/server/lib/itsi/server/rack_interface.rb +0 -1
  274. data/gems/server/lib/itsi/server/route_tester.rb +61 -9
  275. data/gems/server/lib/itsi/server/signal_trap.rb +1 -1
  276. data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +14 -18
  277. data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +13 -10
  278. data/gems/server/lib/itsi/server/typed_handlers.rb +12 -4
  279. data/gems/server/lib/itsi/server/version.rb +1 -1
  280. data/gems/server/lib/itsi/server.rb +111 -27
  281. data/gems/server/lib/itsi/standard_headers.rb +80 -80
  282. data/gems/server/lib/ruby_lsp/itsi/addon.rb +20 -18
  283. data/gems/server/test/helpers/test_helper.rb +90 -29
  284. data/gems/server/test/middleware/allow_list.rb +128 -0
  285. data/gems/server/test/middleware/auth_api_key.rb +141 -0
  286. data/gems/server/test/middleware/auth_basic.rb +91 -0
  287. data/gems/server/test/middleware/auth_jwt.rb +214 -0
  288. data/gems/server/test/middleware/cache_control.rb +82 -0
  289. data/gems/server/test/middleware/cidr_to_regex.rb +46 -0
  290. data/gems/server/test/middleware/compression.rb +89 -0
  291. data/gems/server/test/middleware/cors.rb +113 -0
  292. data/gems/server/test/middleware/csp.rb +62 -0
  293. data/gems/server/test/middleware/deny_list.rb +131 -0
  294. data/gems/server/test/middleware/endpoint.rb +300 -0
  295. data/gems/server/test/middleware/etag.rb +75 -0
  296. data/gems/server/test/middleware/grpc/grpc.rb +158 -0
  297. data/gems/server/test/middleware/grpc/test_service.proto +32 -0
  298. data/gems/server/test/middleware/grpc/test_service_impl.rb +28 -0
  299. data/gems/server/test/middleware/grpc/test_service_pb.rb +18 -0
  300. data/gems/server/test/middleware/grpc/test_service_services_pb.rb +30 -0
  301. data/gems/server/test/middleware/header_interpolation.rb +35 -0
  302. data/gems/server/test/middleware/intrusion_protection.rb +259 -0
  303. data/gems/server/test/middleware/location.rb +220 -0
  304. data/gems/server/test/middleware/max_body.rb +20 -0
  305. data/gems/server/test/middleware/proxy.rb +415 -0
  306. data/gems/server/test/middleware/rate_limit.rb +211 -0
  307. data/gems/server/test/middleware/redirect.rb +85 -0
  308. data/gems/server/test/middleware/request_headers.rb +50 -0
  309. data/gems/server/test/middleware/response_headers.rb +50 -0
  310. data/gems/server/test/middleware/static_assets.rb +374 -0
  311. data/gems/server/test/middleware/static_response.rb +56 -0
  312. data/gems/server/test/middleware/string_rewrite.rb +112 -0
  313. data/gems/server/test/middleware/test_log_requests.rb +54 -2
  314. data/gems/server/test/options/bind.rb +47 -0
  315. data/gems/server/test/options/header_read_timeout.rb +23 -0
  316. data/gems/server/test/options/test_request_timeout.rb +16 -0
  317. data/gems/server/test/options/test_workers.rb +11 -6
  318. data/gems/server/test/{test_itsi_server.rb → rack/test_rack_server.rb} +2 -2
  319. data/lib/itsi/version.rb +1 -1
  320. data/tasks.txt +15 -72
  321. metadata +209 -10
  322. data/examples/static_assets_example.rb +0 -83
  323. data/gems/server/lib/itsi/server/default_config/Itsi-rackup.rb +0 -119
@@ -1,86 +1,86 @@
1
1
  module Itsi
2
2
  module StandardHeaders
3
3
  ALL = [
4
- ACCEPT = "accept",
5
- ACCEPT_CHARSET = "accept-charset",
6
- ACCEPT_ENCODING = "accept-encoding",
7
- ACCEPT_LANGUAGE = "accept-language",
8
- ACCEPT_RANGES = "accept-ranges",
9
- ACCESS_CONTROL_ALLOW_CREDENTIALS = "access-control-allow-credentials",
10
- ACCESS_CONTROL_ALLOW_HEADERS = "access-control-allow-headers",
11
- ACCESS_CONTROL_ALLOW_METHODS = "access-control-allow-methods",
12
- ACCESS_CONTROL_ALLOW_ORIGIN = "access-control-allow-origin",
13
- ACCESS_CONTROL_EXPOSE_HEADERS = "access-control-expose-headers",
14
- ACCESS_CONTROL_MAX_AGE = "access-control-max-age",
15
- ACCESS_CONTROL_REQUEST_HEADERS = "access-control-request-headers",
16
- ACCESS_CONTROL_REQUEST_METHOD = "access-control-request-method",
17
- AGE = "age",
18
- ALLOW = "allow",
19
- ALT_SVC = "alt-svc",
20
- AUTHORIZATION = "authorization",
21
- CACHE_CONTROL = "cache-control",
22
- CACHE_STATUS = "cache-status",
23
- CDN_CACHE_CONTROL = "cdn-cache-control",
24
- CONNECTION = "connection",
25
- CONTENT_DISPOSITION = "content-disposition",
26
- CONTENT_ENCODING = "content-encoding",
27
- CONTENT_LANGUAGE = "content-language",
28
- CONTENT_LENGTH = "content-length",
29
- CONTENT_LOCATION = "content-location",
30
- CONTENT_RANGE = "content-range",
31
- CONTENT_SECURITY_POLICY_REPORT_ONLY = "content-security-policy-report-only",
32
- CONTENT_TYPE = "content-type",
33
- COOKIE = "cookie",
34
- DNT = "dnt",
35
- DATE = "date",
36
- ETAG = "etag",
37
- EXPECT = "expect",
38
- EXPIRES = "expires",
39
- FORWARDED = "forwarded",
40
- FROM = "from",
41
- HOST = "host",
42
- IF_MATCH = "if-match",
43
- IF_MODIFIED_SINCE = "if-modified-since",
44
- IF_NONE_MATCH = "if-none-match",
45
- IF_RANGE = "if-range",
46
- IF_UNMODIFIED_SINCE = "if-unmodified-since",
47
- LAST_MODIFIED = "last-modified",
48
- LINK = "link",
49
- LOCATION = "location",
50
- MAX_FORWARDS = "max-forwards",
51
- ORIGIN = "origin",
52
- PRAGMA = "pragma",
53
- PROXY_AUTHENTICATE = "proxy-authenticate",
54
- PROXY_AUTHORIZATION = "proxy-authorization",
55
- PUBLIC_KEY_PINS = "public-key-pins",
56
- PUBLIC_KEY_PINS_REPORT_ONLY = "public-key-pins-report-only",
57
- RANGE = "range",
58
- REFERER = "referer",
59
- REFERRER_POLICY = "referrer-policy",
60
- REFRESH = "refresh",
61
- RETRY_AFTER = "retry-after",
62
- SEC_WEBSOCKET_ACCEPT = "sec-websocket-accept",
63
- SEC_WEBSOCKET_EXTENSIONS = "sec-websocket-extensions",
64
- SEC_WEBSOCKET_KEY = "sec-websocket-key",
65
- SEC_WEBSOCKET_PROTOCOL = "sec-websocket-protocol",
66
- SEC_WEBSOCKET_VERSION = "sec-websocket-version",
67
- SERVER = "server",
68
- SET_COOKIE = "set-cookie",
69
- STRICT_TRANSPORT_SECURITY = "strict-transport-security",
70
- TE = "te",
71
- TRAILER = "trailer",
72
- TRANSFER_ENCODING = "transfer-encoding",
73
- USER_AGENT = "user-agent",
74
- UPGRADE = "upgrade",
75
- UPGRADE_INSECURE_REQUESTS = "upgrade-insecure-requests",
76
- VARY = "vary",
77
- VIA = "via",
78
- WARNING = "warning",
79
- WWW_AUTHENTICATE = "www-authenticate",
80
- X_CONTENT_TYPE_OPTIONS = "x-content-type-options",
81
- X_DNS_PREFETCH_CONTROL = "x-dns-prefetch-control",
82
- X_FRAME_OPTIONS = "x-frame-options",
83
- X_XSS_PROTECTION = "x-xss-protection",
4
+ ACCEPT = "accept".freeze,
5
+ ACCEPT_CHARSET = "accept-charset".freeze,
6
+ ACCEPT_ENCODING = "accept-encoding".freeze,
7
+ ACCEPT_LANGUAGE = "accept-language".freeze,
8
+ ACCEPT_RANGES = "accept-ranges".freeze,
9
+ ACCESS_CONTROL_ALLOW_CREDENTIALS = "access-control-allow-credentials".freeze,
10
+ ACCESS_CONTROL_ALLOW_HEADERS = "access-control-allow-headers".freeze,
11
+ ACCESS_CONTROL_ALLOW_METHODS = "access-control-allow-methods".freeze,
12
+ ACCESS_CONTROL_ALLOW_ORIGIN = "access-control-allow-origin".freeze,
13
+ ACCESS_CONTROL_EXPOSE_HEADERS = "access-control-expose-headers".freeze,
14
+ ACCESS_CONTROL_MAX_AGE = "access-control-max-age".freeze,
15
+ ACCESS_CONTROL_REQUEST_HEADERS = "access-control-request-headers".freeze,
16
+ ACCESS_CONTROL_REQUEST_METHOD = "access-control-request-method".freeze,
17
+ AGE = "age".freeze,
18
+ ALLOW = "allow".freeze,
19
+ ALT_SVC = "alt-svc".freeze,
20
+ AUTHORIZATION = "authorization".freeze,
21
+ CACHE_CONTROL = "cache-control".freeze,
22
+ CACHE_STATUS = "cache-status".freeze,
23
+ CDN_CACHE_CONTROL = "cdn-cache-control".freeze,
24
+ CONNECTION = "connection".freeze,
25
+ CONTENT_DISPOSITION = "content-disposition".freeze,
26
+ CONTENT_ENCODING = "content-encoding".freeze,
27
+ CONTENT_LANGUAGE = "content-language".freeze,
28
+ CONTENT_LENGTH = "content-length".freeze,
29
+ CONTENT_LOCATION = "content-location".freeze,
30
+ CONTENT_RANGE = "content-range".freeze,
31
+ CONTENT_SECURITY_POLICY_REPORT_ONLY = "content-security-policy-report-only".freeze,
32
+ CONTENT_TYPE = "content-type".freeze,
33
+ COOKIE = "cookie".freeze,
34
+ DNT = "dnt".freeze,
35
+ DATE = "date".freeze,
36
+ ETAG = "etag".freeze,
37
+ EXPECT = "expect".freeze,
38
+ EXPIRES = "expires".freeze,
39
+ FORWARDED = "forwarded".freeze,
40
+ FROM = "from".freeze,
41
+ HOST = "host".freeze,
42
+ IF_MATCH = "if-match".freeze,
43
+ IF_MODIFIED_SINCE = "if-modified-since".freeze,
44
+ IF_NONE_MATCH = "if-none-match".freeze,
45
+ IF_RANGE = "if-range".freeze,
46
+ IF_UNMODIFIED_SINCE = "if-unmodified-since".freeze,
47
+ LAST_MODIFIED = "last-modified".freeze,
48
+ LINK = "link".freeze,
49
+ LOCATION = "location".freeze,
50
+ MAX_FORWARDS = "max-forwards".freeze,
51
+ ORIGIN = "origin".freeze,
52
+ PRAGMA = "pragma".freeze,
53
+ PROXY_AUTHENTICATE = "proxy-authenticate".freeze,
54
+ PROXY_AUTHORIZATION = "proxy-authorization".freeze,
55
+ PUBLIC_KEY_PINS = "public-key-pins".freeze,
56
+ PUBLIC_KEY_PINS_REPORT_ONLY = "public-key-pins-report-only".freeze,
57
+ RANGE = "range".freeze,
58
+ REFERER = "referer".freeze,
59
+ REFERRER_POLICY = "referrer-policy".freeze,
60
+ REFRESH = "refresh".freeze,
61
+ RETRY_AFTER = "retry-after".freeze,
62
+ SEC_WEBSOCKET_ACCEPT = "sec-websocket-accept".freeze,
63
+ SEC_WEBSOCKET_EXTENSIONS = "sec-websocket-extensions".freeze,
64
+ SEC_WEBSOCKET_KEY = "sec-websocket-key".freeze,
65
+ SEC_WEBSOCKET_PROTOCOL = "sec-websocket-protocol".freeze,
66
+ SEC_WEBSOCKET_VERSION = "sec-websocket-version".freeze,
67
+ SERVER = "server".freeze,
68
+ SET_COOKIE = "set-cookie".freeze,
69
+ STRICT_TRANSPORT_SECURITY = "strict-transport-security".freeze,
70
+ TE = "te".freeze,
71
+ TRAILER = "trailer".freeze,
72
+ TRANSFER_ENCODING = "transfer-encoding".freeze,
73
+ USER_AGENT = "user-agent".freeze,
74
+ UPGRADE = "upgrade".freeze,
75
+ UPGRADE_INSECURE_REQUESTS = "upgrade-insecure-requests".freeze,
76
+ VARY = "vary".freeze,
77
+ VIA = "via".freeze,
78
+ WARNING = "warning".freeze,
79
+ WWW_AUTHENTICATE = "www-authenticate".freeze,
80
+ X_CONTENT_TYPE_OPTIONS = "x-content-type-options".freeze,
81
+ X_DNS_PREFETCH_CONTROL = "x-dns-prefetch-control".freeze,
82
+ X_FRAME_OPTIONS = "x-frame-options".freeze,
83
+ X_XSS_PROTECTION = "x-xss-protection".freeze
84
84
  ]
85
85
  end
86
86
  end
@@ -100,24 +100,26 @@ module RubyLsp
100
100
  end
101
101
 
102
102
  ::Itsi::Server::Config::Middleware.subclasses.each do |middleware|
103
- completion_item = Interface::CompletionItem.new(
104
- label: middleware.middleware_name,
105
- kind: Constant::CompletionItemKind::METHOD,
106
- label_details: Interface::CompletionItemLabelDetails.new(
107
- detail: middleware.detail,
108
- description: middleware.documentation
109
- ),
110
- documentation: Interface::MarkupContent.new(
111
- kind: Constant::MarkupKind::MARKDOWN,
112
- value: middleware.documentation
113
- ),
114
- insert_text: middleware.insert_text,
115
- insert_text_format: Constant::InsertTextFormat::SNIPPET,
116
- data: {
117
- delegateCompletion: true
118
- }
119
- )
120
- @response_builder << completion_item
103
+ Array(middleware.insert_text).zip(Array(middleware.detail)).each do |insert_text, detail|
104
+ completion_item = Interface::CompletionItem.new(
105
+ label: middleware.middleware_name,
106
+ kind: Constant::CompletionItemKind::METHOD,
107
+ label_details: Interface::CompletionItemLabelDetails.new(
108
+ detail: detail,
109
+ description: middleware.documentation
110
+ ),
111
+ documentation: Interface::MarkupContent.new(
112
+ kind: Constant::MarkupKind::MARKDOWN,
113
+ value: middleware.documentation
114
+ ),
115
+ insert_text: insert_text,
116
+ insert_text_format: Constant::InsertTextFormat::SNIPPET,
117
+ data: {
118
+ delegateCompletion: true
119
+ }
120
+ )
121
+ @response_builder << completion_item
122
+ end
121
123
  end
122
124
  end
123
125
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  ENV["ITSI_LOG"] = "off"
3
4
 
4
5
  require "minitest/reporters"
@@ -8,17 +9,22 @@ require "socket"
8
9
  require "net/http"
9
10
  require "minitest/autorun"
10
11
 
11
-
12
12
  Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
13
13
 
14
- def free_bind(protocol)
15
- server = TCPServer.new("0.0.0.0", 0)
16
- port = server.addr[1]
17
- server.close
18
- "#{protocol}://0.0.0.0:#{port}"
14
+ def free_bind(protocol = "http", unix_socket: false)
15
+ if unix_socket
16
+ socket_path = "/tmp/itsi_socket_#{Process.pid}_#{rand(1000)}.sock"
17
+ UNIXServer.new(socket_path).close
18
+ protocol == "https" ? "tls://#{socket_path}" : "unix://#{socket_path}"
19
+ else
20
+ server = TCPServer.new("0.0.0.0", 0)
21
+ port = server.addr[1]
22
+ server.close
23
+ "#{protocol}://0.0.0.0:#{port}"
24
+ end
19
25
  end
20
26
 
21
- def server(app: nil, protocol: "http", bind: free_bind(protocol), itsi_rb: nil, &blk)
27
+ def server(app: nil, protocol: "http", bind: free_bind(protocol), itsi_rb: nil, cleanup: true, timeout: 5, &blk)
22
28
  itsi_rb ||= lambda do
23
29
  # Inline Itsi.rb
24
30
  bind bind
@@ -31,17 +37,32 @@ def server(app: nil, protocol: "http", bind: free_bind(protocol), itsi_rb: nil,
31
37
  cli_params = {}
32
38
  cli_params[:binds] = [bind] if bind
33
39
 
40
+ sync = Queue.new
41
+ cli_params[:hooks] ||= {}
42
+ cli_params[:hooks]["after_start"] = lambda do
43
+ sync.push(true)
44
+ end
45
+
34
46
  Itsi::Server.start_in_background_thread(cli_params, &itsi_rb)
47
+
48
+ sync.pop
35
49
  uri = URI(bind)
50
+ # Timeout.timeout(timeout) do
36
51
  RequestContext.new(uri, self).instance_exec(uri, &blk)
52
+ # end
37
53
  rescue StandardError => e
38
54
  puts e
39
- puts e.message
40
- puts e.backtrace.join("\n")
55
+ # puts e.message
56
+ # puts e.backtrace.join("\n")
57
+ raise
41
58
  ensure
42
- Itsi::Server.stop_background_thread
59
+ Itsi::Server.stop_background_threads if cleanup
43
60
  end
44
61
 
62
+ require "net/http"
63
+ require "net_http_unix"
64
+ require "uri"
65
+
45
66
  class RequestContext
46
67
  def initialize(uri, binding)
47
68
  @uri = uri
@@ -52,39 +73,79 @@ class RequestContext
52
73
  @binding.send(method_name, *args, &block)
53
74
  end
54
75
 
55
- def post(path, data)
56
- Net::HTTP.post(@uri+path, data)
76
+ def post(path, data = "", headers = {})
77
+ client.post(uri_for(path), data, headers)
57
78
  end
58
79
 
59
- def get(path)
60
- Net::HTTP.get(@uri+path)
80
+ def get(path, headers = {})
81
+ request = Net::HTTP::Get.new(uri_for(path))
82
+ headers.each { |k, v| request[k] = v }
83
+ client.request(request).body
61
84
  end
62
85
 
63
- def get_resp(path)
64
- Net::HTTP.get_response(@uri+path)
86
+ def get_resp(path, headers = {})
87
+ request = Net::HTTP::Get.new(uri_for(path))
88
+ headers.each { |k, v| request[k] = v }
89
+ client.request(request)
65
90
  end
66
91
 
67
92
  def head(path)
68
- Net::HTTP.start(@uri.host, @uri.port) {|http|
69
- http.head(path)
70
- }
93
+ request = Net::HTTP::Head.new(uri_for(path))
94
+ client.request(request)
71
95
  end
72
96
 
73
- def options(path)
74
- Net::HTTP.start(@uri.host, @uri.port) {|http|
75
- http.options(path)
76
- }
97
+ def options(path, headers = {})
98
+ request = Net::HTTP::Options.new(uri_for(path))
99
+ headers.each { |k, v| request[k] = v }
100
+ client.request(request)
101
+ end
102
+
103
+ def put(path, data = "", headers = {})
104
+ request = Net::HTTP::Put.new(uri_for(path))
105
+ request.body = data
106
+ headers.each { |k, v| request[k] = v }
107
+ client.request(request)
77
108
  end
78
109
 
79
- def put(path, data)
80
- Net::HTTP.put(@uri+path, data)
110
+ def delete(path, headers = {})
111
+ request = Net::HTTP::Delete.new(uri_for(path))
112
+ headers.each { |k, v| request[k] = v }
113
+ client.request(request)
81
114
  end
82
115
 
83
- def delete(path)
84
- Net::HTTP.delete(@uri+path)
116
+ def patch(path, data = "", headers = {})
117
+ request = Net::HTTP::Patch.new(uri_for(path))
118
+ request.body = data
119
+ client.request(request)
120
+ end
121
+
122
+ private
123
+
124
+ def client
125
+ opts = {
126
+ read_timeout: 1,
127
+ open_timeout: 1
128
+ }
129
+ if @uri.scheme == "unix"
130
+ NetX::HTTPUnix.new(
131
+ @uri.to_s,
132
+ **opts
133
+ )
134
+ else
135
+ Net::HTTP.start(
136
+ @uri.host,
137
+ @uri.port,
138
+ use_ssl: @uri.scheme == "https",
139
+ **opts
140
+ )
141
+ end
85
142
  end
86
143
 
87
- def patch(path, data)
88
- Net::HTTP.patch(@uri+path, data)
144
+ def uri_for(path)
145
+ if @uri.scheme == "unix"
146
+ URI::HTTP.build(path: path, host: "localhost")
147
+ else
148
+ URI.join(@uri.to_s, path)
149
+ end
89
150
  end
90
151
  end
@@ -0,0 +1,128 @@
1
+ # test_allow_list.rb
2
+ require_relative "../helpers/test_helper"
3
+
4
+ class TestAllowList < Minitest::Test
5
+ # 1. Single‐pattern: only localhost is allowed
6
+ def test_single_pattern_allows_and_denies
7
+ server(
8
+ itsi_rb: lambda do
9
+ allow_list allowed_patterns: ["^127\\.0\\.0\\.1$"]
10
+ get("/foo") { |r| r.ok "allowed" }
11
+ end
12
+ ) do
13
+ # Our test client always comes from 127.0.0.1
14
+ res1 = get_resp("/foo")
15
+ assert_equal "200", res1.code
16
+ assert_equal "allowed", res1.body
17
+
18
+ # If we change the pattern so it no longer matches, 127.0.0.1 is now forbidden
19
+ server(
20
+ itsi_rb: lambda do
21
+ allow_list allowed_patterns: ["^10\\.0\\."]
22
+ get("/foo") { |r| r.ok "never" }
23
+ end
24
+ ) do
25
+ res2 = get_resp("/foo")
26
+ assert_equal "403", res2.code
27
+ end
28
+ end
29
+ end
30
+
31
+ # 2. Multiple‐pattern: localhost or 192.168.x.x
32
+ def test_multiple_patterns
33
+ server(
34
+ itsi_rb: lambda do
35
+ allow_list allowed_patterns: ["^127\\.0\\.0\\.1$", "^192\\.168\\."]
36
+ get("/ping") { |r| r.ok "pong" }
37
+ end
38
+ ) do
39
+ # localhost matches first pattern
40
+ res1 = get_resp("/ping")
41
+ assert_equal "200", res1.code
42
+ assert_equal "pong", res1.body
43
+
44
+ # If we restrict to only 192.168.*, localhost becomes forbidden
45
+ server(
46
+ itsi_rb: lambda do
47
+ allow_list allowed_patterns: ["^192\\.168\\."]
48
+ get("/ping") { |r| r.ok "never" }
49
+ end
50
+ ) do
51
+ res2 = get_resp("/ping")
52
+ assert_equal "403", res2.code
53
+ end
54
+ end
55
+ end
56
+
57
+ # 3. Custom error_response
58
+ def test_custom_error_response
59
+ server(
60
+ itsi_rb: lambda do
61
+ allow_list \
62
+ allowed_patterns: ["^192\\.168\\."], # localhost no longer matches
63
+ error_response: {
64
+ code: 403,
65
+ plaintext: { inline: "No access" },
66
+ default: "plaintext"
67
+ }
68
+ get("/x") { |r| r.ok "never" }
69
+ end
70
+ ) do
71
+ res = get_resp("/x")
72
+ assert_equal "403", res.code
73
+ assert_equal "No access", res.body
74
+ end
75
+ end
76
+
77
+ # 4. Trusted proxies: extract client IP from header if proxy is trusted
78
+ def test_trusted_proxy_allows_based_on_header
79
+ server(
80
+ itsi_rb: lambda do
81
+ allow_list \
82
+ allowed_patterns: ["^203\\.0\\.113\\.7$"], # only allow this client IP
83
+ trusted_proxies: {
84
+ "127.0.0.1" => { header: { name: "X-Forwarded-For" } }
85
+ }
86
+ get("/trusted") { |r| r.ok "trusted" }
87
+ end
88
+ ) do
89
+ res = get_resp("/trusted", { "X-Forwarded-For" => "203.0.113.7" })
90
+ assert_equal "200", res.code
91
+ assert_equal "trusted", res.body
92
+ end
93
+ end
94
+
95
+ def test_trusted_proxy_denies_if_forwarded_ip_does_not_match
96
+ server(
97
+ itsi_rb: lambda do
98
+ allow_list \
99
+ allowed_patterns: ["^203\\.0\\.113\\.7$"], # only allow this
100
+ trusted_proxies: {
101
+ "127.0.0.1" => { header: { name: "X-Forwarded-For" } }
102
+ }
103
+ get("/trusted") { |r| r.ok "nope" }
104
+ end
105
+ ) do
106
+ # Send a forwarded IP that doesn't match the allow list
107
+ res = get_resp("/trusted", { "X-Forwarded-For" => "192.0.2.55" })
108
+ assert_equal "403", res.code
109
+ end
110
+ end
111
+
112
+ def test_untrusted_proxy_ignores_forwarded_ip
113
+ server(
114
+ itsi_rb: lambda do
115
+ allow_list \
116
+ allowed_patterns: ["^203\\.0\\.113\\.7$"], # client IP matches, but header is ignored
117
+ trusted_proxies: {
118
+ "10.0.0.1" => { header: { name: "X-Forwarded-For" } } # current proxy (127.0.0.1) is not trusted
119
+ }
120
+ get("/trusted") { |r| r.ok "never" }
121
+ end
122
+ ) do
123
+ res = get_resp("/trusted", { "X-Forwarded-For" => "203.0.113.7" })
124
+ # Since proxy is untrusted, header is ignored, and 127.0.0.1 is checked (not allowed)
125
+ assert_equal "403", res.code
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,141 @@
1
+ require_relative "../helpers/test_helper"
2
+
3
+ class TestAuthApiKey < Minitest::Test
4
+
5
+ # 1. Anonymous inline keys: valid secret in Authorization header
6
+ def test_anonymous_inline_success
7
+ server(
8
+ itsi_rb: lambda do
9
+ auth_api_key valid_keys: [
10
+ Itsi.create_password_hash("supersecret", "sha256")
11
+ ]
12
+ get("/foo") {|r| r.ok "ok" }
13
+ end
14
+ ) do
15
+ res = get_resp("/foo", { "Authorization" => "Bearer supersecret" })
16
+
17
+ assert_equal "200", res.code
18
+ assert_equal "ok", res.body
19
+ end
20
+ end
21
+
22
+ # 2. Anonymous inline: missing token → 401
23
+ def test_anonymous_inline_missing
24
+ server(
25
+ itsi_rb: lambda do
26
+ auth_api_key valid_keys: [Itsi.create_password_hash("supersecret", "sha256")]
27
+ get("/foo") {|r| r.ok "never" }
28
+ end
29
+ ) do
30
+ res = get_resp("/foo")
31
+ assert_equal "401", res.code
32
+ end
33
+ end
34
+
35
+ # 3. Identified inline keys: need both ID header and Bearer token
36
+ def test_identified_inline_success
37
+ key_id = "Key-1"
38
+ server(
39
+ itsi_rb: lambda do
40
+ auth_api_key valid_keys: { "#{key_id}" => Itsi.create_password_hash("supersecret", "sha256") }
41
+ get("/bar") {|r| r.ok "bar OK" }
42
+ end
43
+ ) do
44
+ headers = {
45
+ "X-Api-Key-Id" => key_id,
46
+ "Authorization" => "Bearer supersecret"
47
+ }
48
+ res = get_resp("/bar", headers)
49
+ assert_equal "200", res.code
50
+ assert_equal "bar OK", res.body
51
+ end
52
+ end
53
+
54
+ # 4. Identified inline: wrong ID → 401
55
+ def test_identified_inline_wrong_id
56
+ key_id = "Key-1"
57
+ server(
58
+ itsi_rb: lambda do
59
+ auth_api_key valid_keys: { "#{key_id}" => Itsi.create_password_hash("supersecret", "sha256") }
60
+ get("/bar") {|r| r.ok "never" }
61
+ end
62
+ ) do
63
+ headers = { "X-Api-Key-Id" => "bad", "Authorization" => "Bearer supersecret" }
64
+ res = get_resp("/bar", headers)
65
+ assert_equal "401", res.code
66
+ end
67
+ end
68
+
69
+ # 5. Custom token_source/query param
70
+ def test_custom_token_source_query
71
+ server(
72
+ itsi_rb: lambda do
73
+ auth_api_key \
74
+ valid_keys: [Itsi.create_password_hash("supersecret", "sha256")],
75
+ token_source: { query: "api_key" }
76
+ get("/q") {|r| r.ok "qok" }
77
+ end
78
+ ) do
79
+ res = get_resp("/q?api_key=supersecret")
80
+ assert_equal "200", res.code
81
+ assert_equal "qok", res.body
82
+ end
83
+ end
84
+
85
+ # 6. Custom key_id_source/query param
86
+ def test_custom_key_id_source_query
87
+ server(
88
+ itsi_rb: lambda do
89
+ auth_api_key \
90
+ valid_keys: { "#{@id1}" => Itsi.create_password_hash("supersecret", "sha256") },
91
+ key_id_source: { query: "kid" }
92
+ get("/q") {|r| r.ok "qok" }
93
+ end
94
+ ) do
95
+ res = get_resp("/q?kid=#{@id1}", { "Authorization" => "Bearer supersecret" })
96
+ assert_equal "200", res.code
97
+ assert_equal "qok", res.body
98
+ end
99
+ end
100
+
101
+ # 7. Custom error_response override
102
+ def test_custom_error_response
103
+ server(
104
+ itsi_rb: lambda do
105
+ auth_api_key \
106
+ valid_keys: [Itsi.create_password_hash("supersecret", "sha256") ],
107
+ error_response: { code: 403,
108
+ plaintext: { inline: "nope" },
109
+ default: "plaintext" }
110
+ get("/x") {|r| r.ok "never" }
111
+ end
112
+ ) do
113
+ res = get_resp("/x")
114
+ assert_equal "403", res.code
115
+ assert_equal "nope", res.body
116
+ end
117
+ end
118
+
119
+ # 8. Scoped to a location path
120
+ def test_scoped_to_location
121
+ server(
122
+ itsi_rb: lambda do
123
+ location "/admin/*" do
124
+ auth_api_key valid_keys: [Itsi.create_password_hash("supersecret", "sha256") ]
125
+ end
126
+ get("/admin/secret") {|r| r.ok "adm ok" }
127
+ get("/public") {|r| r.ok "pub ok" }
128
+ end
129
+ ) do
130
+ # Unprotected
131
+ r1 = get_resp("/public")
132
+ assert_equal "200", r1.code
133
+ # Protected
134
+ r2 = get_resp("/admin/secret")
135
+ assert_equal "401", r2.code
136
+ # Then with key
137
+ r3 = get_resp("/admin/secret", { "Authorization" => "Bearer supersecret" })
138
+ assert_equal "200", r3.code
139
+ end
140
+ end
141
+ end