itsi 0.1.20 → 0.2.2

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 (319) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -8
  3. data/Cargo.lock +2 -2
  4. data/LICENSE.txt +698 -0
  5. data/README.md +15 -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 +122 -63
  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 +1 -2
  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/faqs/_index.md +5 -3
  87. data/docs/content/features/_index.md +56 -50
  88. data/docs/content/getting_started/_index.md +8 -5
  89. data/docs/content/getting_started/local_development.md +68 -8
  90. data/docs/content/getting_started/logging.md +16 -9
  91. data/docs/content/getting_started/running_itsi_in_production.md +5 -3
  92. data/docs/content/getting_started/signals.md +38 -0
  93. data/docs/content/itsi_scheduler/_index.md +8 -7
  94. data/docs/content/utilities/_index.md +13 -0
  95. data/docs/content/utilities/config_file_testing.md +17 -0
  96. data/docs/content/utilities/passfile_generator.md +41 -0
  97. data/docs/content/utilities/route_testing.md +27 -0
  98. data/docs/content/utilities/secrets_management.md +30 -0
  99. data/docs/hugo.yaml +1 -1
  100. data/fairytale.txt +3 -4
  101. data/gems/scheduler/Cargo.lock +1 -1
  102. data/gems/scheduler/README.md +4 -5
  103. data/gems/scheduler/Rakefile +0 -4
  104. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  105. data/gems/scheduler/lib/itsi/scheduler.rb +9 -4
  106. data/gems/scheduler/test/test_active_record.rb +12 -7
  107. data/gems/server/Cargo.lock +1 -1
  108. data/gems/server/Rakefile +0 -4
  109. data/gems/server/exe/itsi +13 -2
  110. data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +2 -0
  111. data/gems/server/lib/itsi/http_request.rb +40 -9
  112. data/gems/server/lib/itsi/http_response.rb +2 -1
  113. data/gems/server/lib/itsi/passfile.rb +0 -1
  114. data/gems/server/lib/itsi/server/config/config_helpers.rb +20 -8
  115. data/gems/server/lib/itsi/server/config/dsl.rb +20 -435
  116. data/gems/server/lib/itsi/server/config/known_paths.rb +4 -1
  117. data/gems/server/lib/itsi/server/config/middleware/_index.md +6 -4
  118. data/gems/server/lib/itsi/server/config/middleware/allow_list.md +46 -0
  119. data/gems/server/lib/itsi/server/config/middleware/allow_list.rb +42 -0
  120. data/gems/server/lib/itsi/server/config/middleware/auth_api_key.md +90 -0
  121. data/gems/server/lib/itsi/server/config/middleware/auth_api_key.rb +51 -0
  122. data/gems/server/lib/itsi/server/config/middleware/auth_basic.md +45 -0
  123. data/gems/server/lib/itsi/server/config/middleware/auth_basic.rb +44 -0
  124. data/gems/server/lib/itsi/server/config/middleware/auth_jwt.md +82 -0
  125. data/gems/server/lib/itsi/server/config/middleware/auth_jwt.rb +38 -0
  126. data/gems/server/lib/itsi/server/config/middleware/cache_control.md +78 -0
  127. data/gems/server/lib/itsi/server/config/middleware/cache_control.rb +45 -0
  128. data/gems/server/lib/itsi/server/config/middleware/cidr_to_regex.rb +50 -0
  129. data/gems/server/lib/itsi/server/config/middleware/compression.md +50 -0
  130. data/gems/server/lib/itsi/server/config/middleware/compression.rb +37 -0
  131. data/gems/server/lib/itsi/server/config/middleware/cors.md +93 -0
  132. data/gems/server/lib/itsi/server/config/middleware/cors.rb +32 -0
  133. data/gems/server/lib/itsi/server/config/middleware/csp.md +37 -0
  134. data/gems/server/lib/itsi/server/config/middleware/csp.rb +44 -0
  135. data/gems/server/lib/itsi/server/config/middleware/deny_list.md +45 -0
  136. data/gems/server/lib/itsi/server/config/middleware/deny_list.rb +42 -0
  137. data/gems/server/lib/itsi/server/config/middleware/endpoint/_index.md +159 -0
  138. data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.md +186 -0
  139. data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.rb +33 -0
  140. data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.md +12 -0
  141. data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.rb +42 -0
  142. data/gems/server/lib/itsi/server/config/middleware/endpoint/endpoint.rb +99 -0
  143. data/gems/server/lib/itsi/server/config/middleware/endpoint/get.md +12 -0
  144. data/gems/server/lib/itsi/server/config/middleware/endpoint/get.rb +42 -0
  145. data/gems/server/lib/itsi/server/config/middleware/endpoint/http_request.md +44 -0
  146. data/gems/server/lib/itsi/server/config/middleware/endpoint/http_response.md +39 -0
  147. data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.md +12 -0
  148. data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.rb +42 -0
  149. data/gems/server/lib/itsi/server/config/middleware/endpoint/post.md +12 -0
  150. data/gems/server/lib/itsi/server/config/middleware/endpoint/post.rb +42 -0
  151. data/gems/server/lib/itsi/server/config/middleware/endpoint/put.md +12 -0
  152. data/gems/server/lib/itsi/server/config/middleware/endpoint/put.rb +42 -0
  153. data/gems/server/lib/itsi/server/config/middleware/endpoint/schemas.md +122 -0
  154. data/gems/server/lib/itsi/server/config/middleware/error_response.md +61 -0
  155. data/gems/server/lib/itsi/server/config/middleware/error_response.rb +36 -0
  156. data/gems/server/lib/itsi/server/config/middleware/etag.md +59 -0
  157. data/gems/server/lib/itsi/server/config/middleware/etag.rb +27 -0
  158. data/gems/server/lib/itsi/server/config/middleware/grpc.md +172 -0
  159. data/gems/server/lib/itsi/server/config/middleware/grpc.rb +54 -0
  160. data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.md +124 -0
  161. data/gems/server/lib/itsi/server/config/middleware/intrusion_protection.rb +61 -0
  162. data/gems/server/lib/itsi/server/config/middleware/location.md +107 -0
  163. data/gems/server/lib/itsi/server/config/middleware/location.rb +99 -0
  164. data/gems/server/lib/itsi/server/config/middleware/log_requests.md +13 -11
  165. data/gems/server/lib/itsi/server/config/middleware/log_requests.rb +1 -3
  166. data/gems/server/lib/itsi/server/config/middleware/max_body.md +18 -0
  167. data/gems/server/lib/itsi/server/config/middleware/max_body.rb +21 -0
  168. data/gems/server/lib/itsi/server/config/middleware/proxy.md +62 -0
  169. data/gems/server/lib/itsi/server/config/middleware/proxy.rb +41 -0
  170. data/gems/server/lib/itsi/server/config/middleware/rackup_file.md +54 -0
  171. data/gems/server/lib/itsi/server/config/middleware/rackup_file.rb +44 -0
  172. data/gems/server/lib/itsi/server/config/middleware/rate_limit.md +126 -0
  173. data/gems/server/lib/itsi/server/config/middleware/rate_limit.rb +34 -0
  174. data/gems/server/lib/itsi/server/config/middleware/rate_limit_store.rb +25 -0
  175. data/gems/server/lib/itsi/server/config/middleware/redirect.md +55 -0
  176. data/gems/server/lib/itsi/server/config/middleware/redirect.rb +25 -0
  177. data/gems/server/lib/itsi/server/config/middleware/request_headers.md +34 -0
  178. data/gems/server/lib/itsi/server/config/middleware/request_headers.rb +24 -0
  179. data/gems/server/lib/itsi/server/config/middleware/response_headers.md +33 -0
  180. data/gems/server/lib/itsi/server/config/middleware/response_headers.rb +25 -0
  181. data/gems/server/lib/itsi/server/config/middleware/run.md +60 -0
  182. data/gems/server/lib/itsi/server/config/middleware/run.rb +43 -0
  183. data/gems/server/lib/itsi/server/config/middleware/static_assets.md +73 -0
  184. data/gems/server/lib/itsi/server/config/middleware/static_assets.rb +87 -0
  185. data/gems/server/lib/itsi/server/config/middleware/static_response.md +44 -0
  186. data/gems/server/lib/itsi/server/config/middleware/static_response.rb +29 -0
  187. data/gems/server/lib/itsi/server/config/middleware/string_rewrite.md +67 -0
  188. data/gems/server/lib/itsi/server/config/middleware/token_source.rb +32 -0
  189. data/gems/server/lib/itsi/server/config/middleware.rb +4 -0
  190. data/gems/server/lib/itsi/server/config/option.rb +5 -0
  191. data/gems/server/lib/itsi/server/config/options/_index.md +3 -2
  192. data/gems/server/lib/itsi/server/config/options/auto_reload_config.md +13 -0
  193. data/gems/server/lib/itsi/server/config/options/auto_reload_config.rb +41 -0
  194. data/gems/server/lib/itsi/server/config/options/bind.md +71 -0
  195. data/gems/server/lib/itsi/server/config/options/bind.rb +26 -0
  196. data/gems/server/lib/itsi/server/config/options/certificates.md +65 -0
  197. data/gems/server/lib/itsi/server/config/options/daemonize.md +14 -0
  198. data/gems/server/lib/itsi/server/config/options/daemonize.rb +19 -0
  199. data/gems/server/lib/itsi/server/config/options/fiber_scheduler.md +1 -2
  200. data/gems/server/lib/itsi/server/config/options/fiber_scheduler.rb +6 -3
  201. data/gems/server/lib/itsi/server/config/options/header_read_timeout.md +17 -0
  202. data/gems/server/lib/itsi/server/config/options/header_read_timeout.rb +19 -0
  203. data/gems/server/lib/itsi/server/config/options/hooks/_index.md +11 -0
  204. data/gems/server/lib/itsi/server/config/options/hooks/after_fork.md +13 -0
  205. data/gems/server/lib/itsi/server/config/options/hooks/after_fork.rb +28 -0
  206. data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.md +14 -0
  207. data/gems/server/lib/itsi/server/config/options/hooks/after_memory_limit_reached.rb +28 -0
  208. data/gems/server/lib/itsi/server/config/options/hooks/after_start.md +12 -0
  209. data/gems/server/lib/itsi/server/config/options/hooks/after_start.rb +28 -0
  210. data/gems/server/lib/itsi/server/config/options/hooks/before_fork.md +13 -0
  211. data/gems/server/lib/itsi/server/config/options/hooks/before_fork.rb +28 -0
  212. data/gems/server/lib/itsi/server/config/options/hooks/before_restart.md +12 -0
  213. data/gems/server/lib/itsi/server/config/options/hooks/before_restart.rb +28 -0
  214. data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.md +12 -0
  215. data/gems/server/lib/itsi/server/config/options/hooks/before_shutdown.rb +28 -0
  216. data/gems/server/lib/itsi/server/config/options/include.md +20 -0
  217. data/gems/server/lib/itsi/server/config/options/include.rb +36 -0
  218. data/gems/server/lib/itsi/server/config/options/listen_backlog.md +11 -0
  219. data/gems/server/lib/itsi/server/config/options/listen_backlog.rb +19 -0
  220. data/gems/server/lib/itsi/server/config/options/log_format.md +18 -0
  221. data/gems/server/lib/itsi/server/config/options/log_format.rb +19 -0
  222. data/gems/server/lib/itsi/server/config/options/log_level.md +34 -0
  223. data/gems/server/lib/itsi/server/config/options/log_level.rb +20 -0
  224. data/gems/server/lib/itsi/server/config/options/log_target.md +38 -0
  225. data/gems/server/lib/itsi/server/config/options/log_target.rb +19 -0
  226. data/gems/server/lib/itsi/server/config/options/log_target_filters.md +17 -0
  227. data/gems/server/lib/itsi/server/config/options/log_target_filters.rb +19 -0
  228. data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.md +27 -0
  229. data/gems/server/lib/itsi/server/config/options/multithreaded_reactor.rb +24 -0
  230. data/gems/server/lib/itsi/server/config/options/nodelay.md +16 -0
  231. data/gems/server/lib/itsi/server/config/options/nodelay.rb +19 -0
  232. data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.md +19 -0
  233. data/gems/server/lib/itsi/server/config/options/oob_gc_responses_threshold.rb +18 -0
  234. data/gems/server/lib/itsi/server/config/options/pin_worker_cores.md +17 -0
  235. data/gems/server/lib/itsi/server/config/options/pin_worker_cores.rb +19 -0
  236. data/gems/server/lib/itsi/server/config/options/preload.md +21 -0
  237. data/gems/server/lib/itsi/server/config/options/preload.rb +18 -0
  238. data/gems/server/lib/itsi/server/config/options/recv_buffer_size.md +15 -0
  239. data/gems/server/lib/itsi/server/config/options/recv_buffer_size.rb +19 -0
  240. data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.md +21 -0
  241. data/gems/server/lib/itsi/server/config/options/redirect_http_to_https.rb +30 -0
  242. data/gems/server/lib/itsi/server/config/options/request_timeout.md +23 -0
  243. data/gems/server/lib/itsi/server/config/options/request_timeout.rb +19 -0
  244. data/gems/server/lib/itsi/server/config/options/reuse_address.md +16 -0
  245. data/gems/server/lib/itsi/server/config/options/reuse_address.rb +19 -0
  246. data/gems/server/lib/itsi/server/config/options/reuse_port.md +16 -0
  247. data/gems/server/lib/itsi/server/config/options/reuse_port.rb +19 -0
  248. data/gems/server/lib/itsi/server/config/options/scheduler_threads.md +34 -0
  249. data/gems/server/lib/itsi/server/config/options/scheduler_threads.rb +17 -0
  250. data/gems/server/lib/itsi/server/config/options/shutdown_timeout.md +17 -0
  251. data/gems/server/lib/itsi/server/config/options/shutdown_timeout.rb +19 -0
  252. data/gems/server/lib/itsi/server/config/options/stream_body.md +32 -0
  253. data/gems/server/lib/itsi/server/config/options/stream_body.rb +18 -0
  254. data/gems/server/lib/itsi/server/config/options/threads.md +7 -2
  255. data/gems/server/lib/itsi/server/config/options/threads.rb +1 -1
  256. data/gems/server/lib/itsi/server/config/options/watch.md +16 -0
  257. data/gems/server/lib/itsi/server/config/options/watch.rb +28 -0
  258. data/gems/server/lib/itsi/server/config/options/worker_memory_limit.md +22 -0
  259. data/gems/server/lib/itsi/server/config/options/worker_memory_limit.rb +18 -0
  260. data/gems/server/lib/itsi/server/config/options/workers.md +1 -2
  261. data/gems/server/lib/itsi/server/config/options/workers.rb +1 -1
  262. data/gems/server/lib/itsi/server/config/typed_struct.rb +59 -20
  263. data/gems/server/lib/itsi/server/config.rb +77 -48
  264. data/gems/server/lib/itsi/server/default_config/Itsi.rb +3 -3
  265. data/gems/server/lib/itsi/server/grpc/grpc_call.rb +1 -1
  266. data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +11 -4
  267. data/gems/server/lib/itsi/server/rack/handler/itsi.rb +3 -3
  268. data/gems/server/lib/itsi/server/route_tester.rb +58 -8
  269. data/gems/server/lib/itsi/server/signal_trap.rb +1 -1
  270. data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +14 -18
  271. data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +5 -4
  272. data/gems/server/lib/itsi/server/typed_handlers.rb +12 -4
  273. data/gems/server/lib/itsi/server/version.rb +1 -1
  274. data/gems/server/lib/itsi/server.rb +98 -14
  275. data/gems/server/lib/ruby_lsp/itsi/addon.rb +20 -18
  276. data/gems/server/test/helpers/test_helper.rb +89 -29
  277. data/gems/server/test/middleware/allow_list.rb +128 -0
  278. data/gems/server/test/middleware/auth_api_key.rb +141 -0
  279. data/gems/server/test/middleware/auth_basic.rb +91 -0
  280. data/gems/server/test/middleware/auth_jwt.rb +214 -0
  281. data/gems/server/test/middleware/cache_control.rb +82 -0
  282. data/gems/server/test/middleware/cidr_to_regex.rb +46 -0
  283. data/gems/server/test/middleware/compression.rb +89 -0
  284. data/gems/server/test/middleware/cors.rb +113 -0
  285. data/gems/server/test/middleware/csp.rb +62 -0
  286. data/gems/server/test/middleware/deny_list.rb +131 -0
  287. data/gems/server/test/middleware/endpoint.rb +300 -0
  288. data/gems/server/test/middleware/etag.rb +75 -0
  289. data/gems/server/test/middleware/grpc/grpc.rb +158 -0
  290. data/gems/server/test/middleware/grpc/test_service.proto +32 -0
  291. data/gems/server/test/middleware/grpc/test_service_impl.rb +28 -0
  292. data/gems/server/test/middleware/grpc/test_service_pb.rb +18 -0
  293. data/gems/server/test/middleware/grpc/test_service_services_pb.rb +30 -0
  294. data/gems/server/test/middleware/header_interpolation.rb +35 -0
  295. data/gems/server/test/middleware/intrusion_protection.rb +259 -0
  296. data/gems/server/test/middleware/location.rb +220 -0
  297. data/gems/server/test/middleware/max_body.rb +20 -0
  298. data/gems/server/test/middleware/proxy.rb +415 -0
  299. data/gems/server/test/middleware/rate_limit.rb +211 -0
  300. data/gems/server/test/middleware/redirect.rb +85 -0
  301. data/gems/server/test/middleware/request_headers.rb +50 -0
  302. data/gems/server/test/middleware/response_headers.rb +50 -0
  303. data/gems/server/test/middleware/static_assets.rb +374 -0
  304. data/gems/server/test/middleware/static_response.rb +56 -0
  305. data/gems/server/test/middleware/string_rewrite.rb +112 -0
  306. data/gems/server/test/options/bind.rb +47 -0
  307. data/gems/server/test/options/header_read_timeout.rb +23 -0
  308. data/gems/server/test/options/test_request_timeout.rb +16 -0
  309. data/gems/server/test/options/test_workers.rb +2 -4
  310. data/gems/server/test/{test_itsi_server.rb → rack/test_rack_server.rb} +2 -2
  311. data/grpc_test/Itsi.rb +11 -0
  312. data/grpc_test/echo.proto +14 -0
  313. data/grpc_test/echo_pb.rb +16 -0
  314. data/grpc_test/echo_service_impl.rb +8 -0
  315. data/grpc_test/echo_services_pb.rb +22 -0
  316. data/lib/itsi/version.rb +1 -1
  317. data/tasks.txt +15 -72
  318. metadata +210 -7
  319. data/gems/server/lib/itsi/server/default_config/Itsi-rackup.rb +0 -119
@@ -0,0 +1,91 @@
1
+ require_relative "../helpers/test_helper"
2
+
3
+ class TestAuthBasic < Minitest::Test
4
+
5
+
6
+ # 1. Inline credentials success
7
+ def test_inline_success
8
+ server(
9
+ itsi_rb: lambda do
10
+ auth_basic realm: "Admin", credential_pairs: { "alice" => Itsi.create_password_hash("wonderland", "sha256") }
11
+ get("/secure") {|r| r.ok "hello" }
12
+ end
13
+ ) do
14
+ hdr = { "Authorization" => "Basic #{["alice:wonderland"].pack("m0")}" }
15
+ res = get_resp("/secure", hdr)
16
+ assert_equal "200", res.code
17
+ assert_equal "hello", res.body
18
+ end
19
+ end
20
+
21
+ # 2. Missing credentials → 401 + WWW-Authenticate header
22
+ def test_missing_credentials
23
+ server(
24
+ itsi_rb: lambda do
25
+ auth_basic realm: "Admin", credential_pairs: { "alice" => Itsi.create_password_hash("wonderland", "sha256") }
26
+ get("/secure") {|r| r.ok "never" }
27
+ end
28
+ ) do
29
+ res = get_resp("/secure")
30
+ assert_equal "401", res.code
31
+ assert_match /Basic realm="Admin"/, res["WWW-Authenticate"]
32
+ end
33
+ end
34
+
35
+ # 3. Invalid credentials → 401
36
+ def test_invalid_credentials
37
+ server(
38
+ itsi_rb: lambda do
39
+ auth_basic realm: "Area", credential_pairs: { "alice" => Itsi.create_password_hash("wonderland", "sha256") }
40
+ get("/secure") {|r| r.ok "never" }
41
+ end
42
+ ) do
43
+ bad = ["bob:wrong"].pack("m0")
44
+ res = get_resp("/secure", { "Authorization" => "Basic #{bad}" })
45
+ assert_equal "401", res.code
46
+ end
47
+ end
48
+
49
+ # 4. Load from credentials_file
50
+ def test_credentials_file
51
+ Dir.mktmpdir do |dir|
52
+ Dir.chdir("#{dir}") do
53
+ path = File.join(dir, ".itsi-credentials")
54
+ File.write(path, "alice:#{Itsi.create_password_hash("wonderland", "sha256")}\n")
55
+ server(
56
+ itsi_rb: lambda do
57
+ auth_basic # default will load .itsi-credentials
58
+ get("/a") {|r| r.ok "ok" }
59
+ end
60
+ ) do
61
+ hdr = { "Authorization" => "Basic #{["alice:wonderland"].pack("m0")}" }
62
+ res = get_resp("/a", hdr)
63
+ assert_equal "200", res.code
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ # 5. Apply to sub-path only
70
+ def test_scoped_to_location
71
+ server(
72
+ itsi_rb: lambda do
73
+ location "/admin/*" do
74
+ auth_basic realm: "Adm", credential_pairs: { "alice" => Itsi.create_password_hash("wonderland", "sha256") }
75
+ end
76
+ get("/admin/yes") {|r| r.ok "y"}
77
+ get("/no") {|r| r.ok "n"}
78
+ end
79
+ ) do
80
+ # outside
81
+ assert_equal "n", get_resp("/no").body
82
+ # inside, need auth
83
+ res1 = get_resp("/admin/yes")
84
+ assert_equal "401", res1.code
85
+ # then with creds
86
+ hdr = { "Authorization" => "Basic #{["alice:wonderland"].pack("m0")}" }
87
+ res2 = get_resp("/admin/yes", hdr)
88
+ assert_equal "200", res2.code
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,214 @@
1
+ require_relative "../helpers/test_helper"
2
+ require "jwt"
3
+ require "openssl"
4
+ require "securerandom"
5
+ require "base64"
6
+
7
+ class TestAuthJwt < Minitest::Test
8
+ ALGORITHMS = {
9
+ "HS256" => -> {
10
+ secret = Base64.strict_encode64(SecureRandom.random_bytes(32))
11
+ verifier = secret
12
+ signer = Base64.decode64(secret)
13
+ [verifier, signer]
14
+ },
15
+ "HS384" => -> {
16
+ secret = Base64.strict_encode64(SecureRandom.random_bytes(48))
17
+ [secret, Base64.decode64(secret)]
18
+ },
19
+ "HS512" => -> {
20
+ secret = Base64.strict_encode64(SecureRandom.random_bytes(64))
21
+ [secret, Base64.decode64(secret)]
22
+ },
23
+ "RS256" => -> {
24
+ rsa = OpenSSL::PKey::RSA.new(2048)
25
+ [rsa.public_key.to_pem, rsa]
26
+ },
27
+ "RS384" => -> {
28
+ rsa = OpenSSL::PKey::RSA.new(2048)
29
+ [rsa.public_key.to_pem, rsa]
30
+ },
31
+ "RS512" => -> {
32
+ rsa = OpenSSL::PKey::RSA.new(2048)
33
+ [rsa.public_key.to_pem, rsa]
34
+ },
35
+ "PS256" => -> {
36
+ rsa = OpenSSL::PKey::RSA.new(2048)
37
+ [rsa.public_key.to_pem, rsa]
38
+ },
39
+ "PS384" => -> {
40
+ rsa = OpenSSL::PKey::RSA.new(2048)
41
+ [rsa.public_key.to_pem, rsa]
42
+ },
43
+ "PS512" => -> {
44
+ rsa = OpenSSL::PKey::RSA.new(2048)
45
+ [rsa.public_key.to_pem, rsa]
46
+ },
47
+ "ES256" => -> {
48
+ private_key = OpenSSL::PKey::EC.generate("prime256v1")
49
+ asn1 = OpenSSL::ASN1::Sequence(
50
+ [
51
+ OpenSSL::ASN1::Sequence(
52
+ [
53
+ OpenSSL::ASN1::ObjectId("id-ecPublicKey"),
54
+ OpenSSL::ASN1::ObjectId(private_key.public_key.group.curve_name)
55
+ ]
56
+ ),
57
+ OpenSSL::ASN1::BitString(private_key.public_key.to_octet_string(:uncompressed))
58
+ ]
59
+ )
60
+ public_key = OpenSSL::PKey::EC.new(asn1.to_der)
61
+ [public_key.to_pem, private_key]
62
+ },
63
+ "ES384" => -> {
64
+ private_key = OpenSSL::PKey::EC.generate("secp384r1")
65
+ asn1 = OpenSSL::ASN1::Sequence(
66
+ [
67
+ OpenSSL::ASN1::Sequence(
68
+ [
69
+ OpenSSL::ASN1::ObjectId("id-ecPublicKey"),
70
+ OpenSSL::ASN1::ObjectId(private_key.public_key.group.curve_name)
71
+ ]
72
+ ),
73
+ OpenSSL::ASN1::BitString(private_key.public_key.to_octet_string(:uncompressed))
74
+ ]
75
+ )
76
+ public_key = OpenSSL::PKey::EC.new(asn1.to_der)
77
+ [public_key.to_pem, private_key]
78
+ }
79
+ }
80
+
81
+ # 1. Missing token → 401
82
+ def test_missing_token
83
+ server(
84
+ itsi_rb: lambda do
85
+ auth_jwt verifiers: { "HS256" => ["Zm9v"] }
86
+ get("/j") { |r| r.ok "j" }
87
+ end
88
+ ) do
89
+ res = get_resp("/j")
90
+ assert_equal "401", res.code
91
+ end
92
+ end
93
+
94
+ # 2. Invalid token → 401
95
+ def test_invalid_token
96
+ server(
97
+ itsi_rb: lambda do
98
+ auth_jwt verifiers: { "HS256" => ["Zm9v"] }
99
+ get("/j") { |r| r.ok "j" }
100
+ end
101
+ ) do
102
+ res = get_resp("/j", { "Authorization" => "Bearer invalid.token" })
103
+ assert_equal "401", res.code
104
+ end
105
+
106
+ end
107
+
108
+ # Dynamically define one test per algorithm
109
+ ALGORITHMS.each do |alg, gen|
110
+ define_method("test_#{alg.downcase}_success") do
111
+ verifier, signer = gen.call
112
+ # build a minimal payload
113
+ payload = { "sub" => "user", "aud" => "aud1", "iss" => "iss1", "iat" => Time.now.to_i, exp: Time.now.to_i + 10 }
114
+ token = JWT.encode(payload, signer, alg)
115
+ server(
116
+ itsi_rb: lambda do
117
+ auth_jwt verifiers: { alg => [verifier] }
118
+ get("/#{alg.downcase}") { |r| r.ok alg }
119
+ end
120
+ ) do
121
+ res = get_resp("/#{alg.downcase}", { "Authorization" => "Bearer #{token}" })
122
+ assert_equal "200", res.code, "#{alg} should verify successfully"
123
+ assert_equal alg, res.body
124
+ end
125
+ end
126
+ end
127
+
128
+ # 5. Audience restriction
129
+ def test_audience_restriction
130
+ verifier, signer = ALGORITHMS["HS256"].call
131
+ payload = { "aud" => "good", exp: Time.now.to_i + 10 }
132
+ token = JWT.encode(payload, signer, "HS256")
133
+ server(
134
+ itsi_rb: lambda do
135
+ auth_jwt verifiers: { "HS256" => [verifier] }, audiences: ["good"]
136
+ get("/a") { |r| r.ok "ok" }
137
+ end
138
+ ) do
139
+ res1 = get_resp("/a", { "Authorization" => "Bearer #{token}" })
140
+ assert_equal "200", res1.code
141
+ # wrong audience
142
+ bad = JWT.encode({ "aud" => "bad" }, signer, "HS256")
143
+ res2 = get_resp("/a", { "Authorization" => "Bearer #{bad}" })
144
+ assert_equal "401", res2.code
145
+ end
146
+ end
147
+
148
+ # 6. Subject & issuer restrictions
149
+ def test_subject_and_issuer
150
+ verifier, signer = ALGORITHMS["HS256"].call
151
+ payload = { "sub" => "mysub", "iss" => "myiss", exp: Time.now.to_i + 10 }
152
+ token = JWT.encode(payload, signer, "HS256")
153
+ server(
154
+ itsi_rb: lambda do
155
+ auth_jwt verifiers: { "HS256" => [verifier] },
156
+ subjects: ["mysub"], issuers: ["myiss"]
157
+ get("/si") { |r| r.ok "si" }
158
+ end
159
+ ) do
160
+ res = get_resp("/si", { "Authorization" => "Bearer #{token}" })
161
+ assert_equal "200", res.code
162
+ end
163
+ end
164
+
165
+ # 7. Custom token_source query
166
+ def test_custom_token_source_query
167
+ verifier, signer = ALGORITHMS["HS256"].call
168
+ token = JWT.encode({exp: Time.now.to_i + 10}, signer, "HS256")
169
+ server(
170
+ itsi_rb: lambda do
171
+ auth_jwt verifiers: { "HS256" => [verifier] }, token_source: { query: "jwt" }
172
+ get("/") { |r| r.ok "root" }
173
+ end
174
+ ) do
175
+ res = get_resp("/?jwt=#{token}")
176
+ assert_equal "200", res.code
177
+ end
178
+ end
179
+
180
+ # 8. Leeway works for 'exp' skew
181
+ def test_leeway_exp
182
+ verifier, signer = ALGORITHMS["HS256"].call
183
+ expired_iat = Time.now.to_i - 120
184
+ token = JWT.encode({ "exp" => expired_iat }, signer, "HS256")
185
+ server(
186
+ itsi_rb: lambda do
187
+ auth_jwt verifiers: { "HS256" => [verifier] }, leeway: 300
188
+ get("/") { |r| r.ok "ok" }
189
+ end
190
+ ) do
191
+ res = get_resp("/", { "Authorization" => "Bearer #{token}" })
192
+ assert_equal "200", res.code
193
+ end
194
+ end
195
+
196
+ # 9. Scoped to location
197
+ def test_scoped_to_location
198
+ verifier, signer = ALGORITHMS["HS256"].call
199
+ token = JWT.encode({exp: Time.now.to_i + 10}, signer, "HS256")
200
+ server(
201
+ itsi_rb: lambda do
202
+ location "/api/*" do
203
+ auth_jwt verifiers: { "HS256" => [verifier] }
204
+ get("x") { |r| r.ok "x" }
205
+ end
206
+ get("/public") { |r| r.ok "p" }
207
+ end
208
+ ) do
209
+ assert_equal "p", get_resp("/public").body
210
+ assert_equal "401", get_resp("/api/x").code
211
+ assert_equal "x", get_resp("/api/x", { "Authorization" => "Bearer #{token}" }).body
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,82 @@
1
+ require_relative "../helpers/test_helper"
2
+
3
+ require_relative "../helpers/test_helper"
4
+
5
+ class TestCacheControl < Minitest::Test
6
+ def test_cache_control_headers_are_set
7
+ server(
8
+ itsi_rb: lambda do
9
+ cache_control \
10
+ max_age: 3600,
11
+ public: true,
12
+ vary: ["Accept-Encoding"],
13
+ additional_headers: { "X-Custom-Header" => "HIT" }
14
+ get("/foo") { |r| r.ok "content" }
15
+ end
16
+ ) do
17
+ require 'debug'
18
+ res = get_resp("/foo")
19
+ # Check that the Cache-Control header is present and contains required directives
20
+ assert_includes res["Cache-Control"], "public"
21
+ assert_includes res["Cache-Control"], "max-age=3600"
22
+
23
+ # Check that the Expires header exists and is a valid HTTP date string
24
+ assert res.key?("Expires"), "Expires header should be present"
25
+ # A basic check: the Expires header should include a comma and a GMT designation
26
+ assert_match /GMT/, res["Expires"]
27
+
28
+ # Check that the Vary header is set correctly
29
+ assert_equal "Accept-Encoding", res["Vary"]
30
+
31
+ # Check that additional custom header is set
32
+ assert_equal "HIT", res["X-Custom-Header"]
33
+ end
34
+ end
35
+
36
+ def test_cache_control_not_set_for_error_statuses
37
+ server(
38
+ itsi_rb: lambda do
39
+ cache_control max_age: 3600, public: true
40
+ get("/foo") { |r| r.respond("error", 500) }
41
+ end
42
+ ) do
43
+ res = get_resp("/foo")
44
+ refute res.key?("Cache-Control"), "Cache-Control should not be set for 500 errors"
45
+ refute res.key?("Expires"), "Expires should not be set for 500 errors"
46
+ end
47
+ end
48
+
49
+ def test_cache_control_s_max_age_and_stale_directives
50
+ server(
51
+ itsi_rb: lambda do
52
+ cache_control \
53
+ max_age: 3600,
54
+ s_max_age: 1800,
55
+ stale_while_revalidate: 30,
56
+ stale_if_error: 60,
57
+ private: true
58
+ get("/foo") { |r| r.ok "some content" }
59
+ end
60
+ ) do
61
+ res = get_resp("/foo")
62
+ cc = res["Cache-Control"]
63
+ assert_includes cc, "private"
64
+ assert_includes cc, "s-maxage=1800"
65
+ assert_includes cc, "stale-while-revalidate=30"
66
+ assert_includes cc, "stale-if-error=60"
67
+ end
68
+ end
69
+
70
+ def test_cache_control_set_without_optional_fields
71
+ server(
72
+ itsi_rb: lambda do
73
+ cache_control public: true
74
+ get("/foo") { |r| r.ok "minimal" }
75
+ end
76
+ ) do
77
+ res = get_resp("/foo")
78
+ cc = res["Cache-Control"]
79
+ assert_includes cc, "public"
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,46 @@
1
+ require 'minitest/autorun'
2
+ require 'ipaddr'
3
+
4
+ class TestCidrToRegex < Minitest::Test
5
+ include Itsi::Server::CidrToRegex
6
+
7
+ def test_simple_cidr
8
+ regex = cidr_to_regex("192.168.1.0/24")
9
+ assert_match regex, "192.168.1.0"
10
+ assert_match regex, "192.168.1.255"
11
+ refute_match regex, "192.168.2.0"
12
+ end
13
+
14
+ def test_class_b_cidr
15
+ regex = cidr_to_regex("10.1.0.0/16")
16
+ assert_match regex, "10.1.0.1"
17
+ assert_match regex, "10.1.255.255"
18
+ refute_match regex, "10.2.0.0"
19
+ end
20
+
21
+ def test_class_a_cidr
22
+ regex = cidr_to_regex("10.0.0.0/8")
23
+ assert_match regex, "10.255.255.255"
24
+ refute_match regex, "11.0.0.0"
25
+ end
26
+
27
+ def test_tiny_range
28
+ regex = cidr_to_regex("127.0.0.0/30")
29
+ assert_match regex, "127.0.0.0"
30
+ assert_match regex, "127.0.0.3"
31
+ refute_match regex, "127.0.0.4"
32
+ end
33
+
34
+ def test_single_ip
35
+ regex = cidr_to_regex("8.8.8.8/32")
36
+ assert_match regex, "8.8.8.8"
37
+ refute_match regex, "8.8.8.9"
38
+ end
39
+
40
+ def test_edge_case_lower_bound
41
+ regex = cidr_to_regex("0.0.0.0/8")
42
+ assert_match regex, "0.0.0.0"
43
+ assert_match regex, "0.255.255.255"
44
+ refute_match regex, "1.0.0.0"
45
+ end
46
+ end
@@ -0,0 +1,89 @@
1
+ require_relative "../helpers/test_helper"
2
+ class TestCompression < Minitest::Test
3
+
4
+ def test_supports_compression
5
+ require 'zlib'
6
+ require 'stringio'
7
+
8
+ payload = "I will be gzip compressed"
9
+ server(
10
+ itsi_rb: lambda do
11
+ compress min_size: 0
12
+ get("/foo"){|r| r.ok payload}
13
+ end) do
14
+ string_io = StringIO.new(get("/foo", {"Accept-Encoding" => "gzip"}))
15
+ gzip_reader = Zlib::GzipReader.new(string_io)
16
+ decompressed = gzip_reader.read
17
+ gzip_reader.close
18
+ assert_equal payload, decompressed
19
+ end
20
+ end
21
+
22
+ def test_supports_compression_size_limit
23
+ require 'zlib'
24
+ require 'stringio'
25
+
26
+ server(
27
+ itsi_rb: lambda do
28
+ compress min_size: 100
29
+ get("/foo") do |r|
30
+ r.ok "data" * r.query_params["repeat"].to_i
31
+ end
32
+ end) do
33
+
34
+ string_io = StringIO.new(get("/foo?repeat=100", {"Accept-Encoding" => "gzip"}))
35
+ gzip_reader = Zlib::GzipReader.new(string_io)
36
+ decompressed = gzip_reader.read
37
+ gzip_reader.close
38
+ assert_equal "data" * 100, decompressed
39
+
40
+ assert_equal get("/foo?repeat=1"), "data"
41
+ end
42
+ end
43
+
44
+ def test_supports_compression_mime_type_conditions
45
+ require 'zlib'
46
+ require 'stringio'
47
+
48
+ server(
49
+ itsi_rb: lambda do
50
+ compress mime_types: %w[image], min_size: 0
51
+ get("/foo") do |r|
52
+ r.respond("data", 200, {"Content-Type" => r.query_params["type"]})
53
+ end
54
+ end) do
55
+
56
+ string_io = StringIO.new(get("/foo?type=image/png", {"Accept-Encoding" => "gzip"}))
57
+ gzip_reader = Zlib::GzipReader.new(string_io)
58
+ decompressed = gzip_reader.read
59
+ gzip_reader.close
60
+ assert_equal "data", decompressed
61
+ assert_equal get("/foo?type=application/octet-stream"), "data"
62
+ end
63
+ end
64
+
65
+ def test_supports_compression_streaming_compression
66
+ require 'zlib'
67
+ require 'stringio'
68
+
69
+ server(
70
+ itsi_rb: lambda do
71
+ compress mime_types: %w[all], min_size: 0, compress_streams: true
72
+ get("/foo") do |req|
73
+ r = req.response
74
+ r << "one\n"
75
+ r << "two\n"
76
+ r << "three\n"
77
+ r.close
78
+ end
79
+ end) do
80
+
81
+ string_io = StringIO.new(get("/foo", {"Accept-Encoding" => "gzip"}))
82
+ gzip_reader = Zlib::GzipReader.new(string_io)
83
+ decompressed = gzip_reader.read
84
+ gzip_reader.close
85
+ assert_equal "one\ntwo\nthree\n", decompressed
86
+ end
87
+ end
88
+
89
+ end
@@ -0,0 +1,113 @@
1
+ require_relative "../helpers/test_helper"
2
+
3
+ class TestCORS < Minitest::Test
4
+
5
+ def test_cors_supports_origin_and_methods
6
+ server(
7
+ itsi_rb: lambda do
8
+ cors \
9
+ allow_origins: ["https://itsi.fyi"],
10
+ allow_methods: %w[GET POST],
11
+ allow_headers: %w[Content-Type Authorization],
12
+ allow_credentials: true,
13
+ expose_headers: ["X-Special-Header"],
14
+ max_age: 3600
15
+
16
+ get("/foo") { |r| r.ok "ok" }
17
+ end
18
+ ) do
19
+ res = options("/foo", {
20
+ "Origin" => "https://itsi.fyi",
21
+ "Access-Control-Request-Method" => "POST",
22
+ "Access-Control-Request-Headers" => "Content-Type"
23
+ })
24
+
25
+ assert_includes res["Access-Control-Allow-Origin"], "https://itsi.fyi"
26
+ assert_includes res["Access-Control-Allow-Methods"], "POST"
27
+ assert_includes res["Access-Control-Allow-Headers"], "Content-Type"
28
+ assert_equal "true", res["Access-Control-Allow-Credentials"]
29
+ assert_equal "3600", res["Access-Control-Max-Age"]
30
+ end
31
+ end
32
+
33
+ def test_cors_exposes_headers_and_allows_credentials
34
+ server(
35
+ itsi_rb: lambda do
36
+ cors \
37
+ allow_origins: ["https://itsi.fyi"],
38
+ allow_methods: %w[GET],
39
+ allow_headers: %w[Content-Type],
40
+ allow_credentials: true,
41
+ expose_headers: ["X-Special-Header"],
42
+ max_age: 1000
43
+
44
+ get("/foo") do |r|
45
+ r.respond("ok", 200, {
46
+ "X-Special-Header" => "42"
47
+ })
48
+ end
49
+ end
50
+ ) do
51
+ res = get("/foo", {
52
+ "Origin" => "https://itsi.fyi"
53
+ })
54
+
55
+ assert_equal "https://itsi.fyi", res["Access-Control-Allow-Origin"]
56
+ assert_equal "true", res["Access-Control-Allow-Credentials"]
57
+ assert_equal "X-Special-Header", res["Access-Control-Expose-Headers"]
58
+ end
59
+ end
60
+
61
+ def test_cors_blocks_unauthorized_origin
62
+ server(
63
+ itsi_rb: lambda do
64
+ cors \
65
+ allow_origins: ["https://itsi.fyi"],
66
+ allow_methods: %w[GET],
67
+ allow_headers: %w[Content-Type],
68
+ allow_credentials: false,
69
+ expose_headers: [],
70
+ max_age: 600
71
+
72
+ get("/foo") { |r| r.ok "ok" }
73
+ end
74
+ ) do
75
+ res = options("/foo", {
76
+ "Origin" => "https://evil.com",
77
+ "Access-Control-Request-Method" => "GET"
78
+ })
79
+
80
+ refute res.key?("Access-Control-Allow-Origin")
81
+ refute res.key?("Access-Control-Allow-Headers")
82
+ refute res.key?("Access-Control-Allow-Credentials")
83
+ end
84
+ end
85
+
86
+ def test_cors_exposes_headers_and_allows_credentials
87
+ server(
88
+ itsi_rb: lambda do
89
+ cors \
90
+ allow_origins: ["https://itsi.fyi"],
91
+ allow_methods: %w[GET],
92
+ allow_headers: %w[Content-Type],
93
+ allow_credentials: true,
94
+ expose_headers: ["X-Special-Header"],
95
+ max_age: 1000
96
+
97
+ get("/foo") do |r|
98
+ r.respond("ok", 200, {
99
+ "X-Special-Header" => "42"
100
+ })
101
+ end
102
+ end
103
+ ) do
104
+ res = get_resp("/foo", {
105
+ "Origin" => "https://itsi.fyi"
106
+ })
107
+ assert_equal "https://itsi.fyi", res["Access-Control-Allow-Origin"]
108
+ assert_equal "true", res["Access-Control-Allow-Credentials"]
109
+ assert_equal "X-Special-Header", res["Access-Control-Expose-Headers"]
110
+ end
111
+ end
112
+
113
+ end
@@ -0,0 +1,62 @@
1
+ require_relative "../helpers/test_helper"
2
+ require "json"
3
+
4
+ class TestCsp < Minitest::Test
5
+ def test_header_is_added
6
+ server(
7
+ itsi_rb: lambda do
8
+ csp \
9
+ policy: {
10
+ default_src: ["'self'"],
11
+ script_src: ["example.com"]
12
+ }
13
+ get("/") { |r| r.ok "hello" }
14
+ end
15
+ ) do
16
+ res = get_resp("/")
17
+ header = res["Content-Security-Policy"]
18
+ assert header.include?("default-src 'self'")
19
+ assert header.include?("script-src example.com")
20
+ end
21
+ end
22
+
23
+ def test_reports_get_logged
24
+ Tempfile.create("csp-log") do |f|
25
+ server(
26
+ itsi_rb: lambda do
27
+ csp \
28
+ policy: {
29
+ default_src: ["'none'"],
30
+ report_uri: ["/csp-report"]
31
+ },
32
+ reporting_enabled: true,
33
+ report_file: f.path,
34
+ report_endpoint: "/csp-report",
35
+ flush_interval: 0.1
36
+
37
+ get("/") { |r| r.ok "ok" }
38
+ end
39
+ ) do
40
+ report = {
41
+ "csp-report" => {
42
+ "document-uri" => "http://example.com/",
43
+ "referrer" => "",
44
+ "violated-directive" => "script-src",
45
+ "original-policy" => "default-src 'none';",
46
+ "blocked-uri" => "inline"
47
+ }
48
+ }
49
+
50
+ post("/csp-report", JSON.dump(report), {
51
+ "Content-Type" => "application/csp-report"
52
+ })
53
+
54
+ sleep 0.15
55
+
56
+ content = File.read(f.path)
57
+ assert_includes content, "violated-directive"
58
+ assert_includes content, "blocked-uri"
59
+ end
60
+ end
61
+ end
62
+ end