itsi 0.1.13 → 0.1.18

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +113 -593
  3. data/Cargo.toml +6 -0
  4. data/crates/itsi_error/Cargo.toml +1 -0
  5. data/crates/itsi_error/src/lib.rs +100 -10
  6. data/crates/itsi_scheduler/src/itsi_scheduler.rs +1 -1
  7. data/crates/itsi_server/Cargo.toml +9 -11
  8. data/crates/itsi_server/src/default_responses/html/401.html +68 -0
  9. data/crates/itsi_server/src/default_responses/html/403.html +68 -0
  10. data/crates/itsi_server/src/default_responses/html/404.html +68 -0
  11. data/crates/itsi_server/src/default_responses/html/413.html +71 -0
  12. data/crates/itsi_server/src/default_responses/html/429.html +68 -0
  13. data/crates/itsi_server/src/default_responses/html/500.html +71 -0
  14. data/crates/itsi_server/src/default_responses/html/502.html +71 -0
  15. data/crates/itsi_server/src/default_responses/html/503.html +68 -0
  16. data/crates/itsi_server/src/default_responses/html/504.html +69 -0
  17. data/crates/itsi_server/src/default_responses/html/index.html +238 -0
  18. data/crates/itsi_server/src/default_responses/json/401.json +6 -0
  19. data/crates/itsi_server/src/default_responses/json/403.json +6 -0
  20. data/crates/itsi_server/src/default_responses/json/404.json +6 -0
  21. data/crates/itsi_server/src/default_responses/json/413.json +6 -0
  22. data/crates/itsi_server/src/default_responses/json/429.json +6 -0
  23. data/crates/itsi_server/src/default_responses/json/500.json +6 -0
  24. data/crates/itsi_server/src/default_responses/json/502.json +6 -0
  25. data/crates/itsi_server/src/default_responses/json/503.json +6 -0
  26. data/crates/itsi_server/src/default_responses/json/504.json +6 -0
  27. data/crates/itsi_server/src/default_responses/mod.rs +11 -0
  28. data/crates/itsi_server/src/lib.rs +58 -26
  29. data/crates/itsi_server/src/prelude.rs +2 -0
  30. data/crates/itsi_server/src/ruby_types/README.md +21 -0
  31. data/crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +8 -6
  32. data/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
  33. data/crates/itsi_server/src/ruby_types/{itsi_grpc_stream → itsi_grpc_response_stream}/mod.rs +121 -73
  34. data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +103 -40
  35. data/crates/itsi_server/src/ruby_types/itsi_http_response.rs +8 -5
  36. data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +4 -4
  37. data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +37 -17
  38. data/crates/itsi_server/src/ruby_types/itsi_server.rs +4 -3
  39. data/crates/itsi_server/src/ruby_types/mod.rs +6 -13
  40. data/crates/itsi_server/src/server/{bind.rs → binds/bind.rs} +23 -4
  41. data/crates/itsi_server/src/server/{listener.rs → binds/listener.rs} +24 -10
  42. data/crates/itsi_server/src/server/binds/mod.rs +4 -0
  43. data/crates/itsi_server/src/server/{tls.rs → binds/tls.rs} +9 -4
  44. data/crates/itsi_server/src/server/http_message_types.rs +97 -0
  45. data/crates/itsi_server/src/server/io_stream.rs +2 -1
  46. data/crates/itsi_server/src/server/middleware_stack/middleware.rs +28 -16
  47. data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +17 -8
  48. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +47 -18
  49. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +13 -9
  50. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +145 -181
  51. data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +5 -2
  52. data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +37 -48
  53. data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +25 -20
  54. data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +14 -7
  55. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +190 -0
  56. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +125 -95
  57. data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +9 -5
  58. data/crates/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +1 -4
  59. data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +25 -19
  60. data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +4 -4
  61. data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
  62. data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +9 -4
  63. data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +260 -62
  64. data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +29 -22
  65. data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +6 -6
  66. data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +6 -5
  67. data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +4 -2
  68. data/crates/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +51 -18
  69. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +31 -13
  70. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
  71. data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +13 -8
  72. data/crates/itsi_server/src/server/middleware_stack/mod.rs +101 -69
  73. data/crates/itsi_server/src/server/mod.rs +3 -9
  74. data/crates/itsi_server/src/server/process_worker.rs +21 -3
  75. data/crates/itsi_server/src/server/request_job.rs +2 -2
  76. data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +8 -3
  77. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +26 -26
  78. data/crates/itsi_server/src/server/signal.rs +24 -41
  79. data/crates/itsi_server/src/server/size_limited_incoming.rs +101 -0
  80. data/crates/itsi_server/src/server/thread_worker.rs +59 -28
  81. data/crates/itsi_server/src/services/itsi_http_service.rs +239 -0
  82. data/crates/itsi_server/src/services/mime_types.rs +1416 -0
  83. data/crates/itsi_server/src/services/mod.rs +6 -0
  84. data/crates/itsi_server/src/services/password_hasher.rs +83 -0
  85. data/crates/itsi_server/src/{server → services}/rate_limiter.rs +35 -31
  86. data/crates/itsi_server/src/{server → services}/static_file_server.rs +521 -181
  87. data/crates/itsi_tracing/src/lib.rs +145 -55
  88. data/{Itsi.rb → foo/Itsi.rb} +6 -9
  89. data/gems/scheduler/Cargo.lock +7 -0
  90. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  91. data/gems/scheduler/test/helpers/test_helper.rb +0 -1
  92. data/gems/scheduler/test/test_address_resolve.rb +0 -1
  93. data/gems/scheduler/test/test_network_io.rb +1 -1
  94. data/gems/scheduler/test/test_process_wait.rb +0 -1
  95. data/gems/server/Cargo.lock +113 -593
  96. data/gems/server/exe/itsi +65 -19
  97. data/gems/server/itsi-server.gemspec +4 -3
  98. data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +74 -0
  99. data/gems/server/lib/itsi/http_request.rb +116 -17
  100. data/gems/server/lib/itsi/http_response.rb +2 -0
  101. data/gems/server/lib/itsi/passfile.rb +109 -0
  102. data/gems/server/lib/itsi/server/config/dsl.rb +160 -101
  103. data/gems/server/lib/itsi/server/config.rb +58 -23
  104. data/gems/server/lib/itsi/server/default_app/default_app.rb +25 -29
  105. data/gems/server/lib/itsi/server/default_app/index.html +113 -89
  106. data/gems/server/lib/itsi/server/{Itsi.rb → default_config/Itsi-rackup.rb} +1 -1
  107. data/gems/server/lib/itsi/server/default_config/Itsi.rb +107 -0
  108. data/gems/server/lib/itsi/server/grpc/grpc_call.rb +246 -0
  109. data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +100 -0
  110. data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_pb.rb +26 -0
  111. data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_services_pb.rb +122 -0
  112. data/gems/server/lib/itsi/server/route_tester.rb +107 -0
  113. data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +200 -0
  114. data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +55 -0
  115. data/gems/server/lib/itsi/server/typed_handlers.rb +17 -0
  116. data/gems/server/lib/itsi/server/version.rb +1 -1
  117. data/gems/server/lib/itsi/server.rb +82 -12
  118. data/gems/server/lib/ruby_lsp/itsi/addon.rb +111 -0
  119. data/gems/server/lib/shell_completions/completions.rb +26 -0
  120. data/gems/server/test/helpers/test_helper.rb +2 -1
  121. data/lib/itsi/version.rb +1 -1
  122. data/sandbox/README.md +5 -0
  123. data/sandbox/itsi_file/Gemfile +4 -2
  124. data/sandbox/itsi_file/Gemfile.lock +48 -6
  125. data/sandbox/itsi_file/Itsi.rb +326 -129
  126. data/sandbox/itsi_file/call.json +1 -0
  127. data/sandbox/itsi_file/echo_client/Gemfile +10 -0
  128. data/sandbox/itsi_file/echo_client/Gemfile.lock +27 -0
  129. data/sandbox/itsi_file/echo_client/README.md +95 -0
  130. data/sandbox/itsi_file/echo_client/echo_client.rb +164 -0
  131. data/sandbox/itsi_file/echo_client/gen_proto.sh +17 -0
  132. data/sandbox/itsi_file/echo_client/lib/echo_pb.rb +16 -0
  133. data/sandbox/itsi_file/echo_client/lib/echo_services_pb.rb +29 -0
  134. data/sandbox/itsi_file/echo_client/run_client.rb +64 -0
  135. data/sandbox/itsi_file/echo_client/test_compressions.sh +20 -0
  136. data/sandbox/itsi_file/echo_service_nonitsi/Gemfile +10 -0
  137. data/sandbox/itsi_file/echo_service_nonitsi/Gemfile.lock +79 -0
  138. data/sandbox/itsi_file/echo_service_nonitsi/echo.proto +26 -0
  139. data/sandbox/itsi_file/echo_service_nonitsi/echo_pb.rb +16 -0
  140. data/sandbox/itsi_file/echo_service_nonitsi/echo_services_pb.rb +29 -0
  141. data/sandbox/itsi_file/echo_service_nonitsi/server.rb +52 -0
  142. data/sandbox/itsi_sandbox_async/config.ru +0 -1
  143. data/sandbox/itsi_sandbox_rack/Gemfile.lock +2 -2
  144. data/sandbox/itsi_sandbox_rails/Gemfile +2 -2
  145. data/sandbox/itsi_sandbox_rails/Gemfile.lock +76 -2
  146. data/sandbox/itsi_sandbox_rails/app/controllers/home_controller.rb +15 -0
  147. data/sandbox/itsi_sandbox_rails/config/environments/development.rb +1 -0
  148. data/sandbox/itsi_sandbox_rails/config/environments/production.rb +1 -0
  149. data/sandbox/itsi_sandbox_rails/config/routes.rb +2 -0
  150. data/sandbox/itsi_sinatra/app.rb +0 -1
  151. data/sandbox/static_files/.env +1 -0
  152. data/sandbox/static_files/404.html +25 -0
  153. data/sandbox/static_files/_DSC0102.NEF.jpg +0 -0
  154. data/sandbox/static_files/about.html +68 -0
  155. data/sandbox/static_files/tiny.html +1 -0
  156. data/sandbox/static_files/writebook.zip +0 -0
  157. data/tasks.txt +28 -32
  158. metadata +87 -26
  159. data/crates/itsi_error/src/from.rs +0 -68
  160. data/crates/itsi_server/src/ruby_types/itsi_grpc_request.rs +0 -147
  161. data/crates/itsi_server/src/ruby_types/itsi_grpc_response.rs +0 -19
  162. data/crates/itsi_server/src/server/itsi_service.rs +0 -172
  163. data/crates/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +0 -72
  164. data/crates/itsi_server/src/server/types.rs +0 -43
  165. data/gems/server/lib/itsi/server/grpc_interface.rb +0 -213
  166. data/sandbox/itsi_file/public/assets/index.html +0 -1
  167. /data/crates/itsi_server/src/server/{bind_protocol.rs → binds/bind_protocol.rs} +0 -0
  168. /data/crates/itsi_server/src/server/{tls → binds/tls}/locked_dir_cache.rs +0 -0
  169. /data/crates/itsi_server/src/{server → services}/cache_store.rs +0 -0
@@ -0,0 +1,4 @@
1
+ pub mod bind;
2
+ pub mod bind_protocol;
3
+ pub mod listener;
4
+ pub mod tls;
@@ -3,7 +3,7 @@ use itsi_error::Result;
3
3
  use itsi_tracing::info;
4
4
  use locked_dir_cache::LockedDirCache;
5
5
  use rcgen::{
6
- generate_simple_self_signed, CertificateParams, CertifiedKey, DnType, KeyPair, SanType,
6
+ BasicConstraints, CertificateParams, DistinguishedName, DnType, IsCa, KeyPair, SanType,
7
7
  };
8
8
  use rustls::{
9
9
  pki_types::{CertificateDer, PrivateKeyDer},
@@ -24,6 +24,7 @@ use crate::env::{
24
24
  ITSI_ACME_CACHE_DIR, ITSI_ACME_CA_PEM_PATH, ITSI_ACME_CONTACT_EMAIL, ITSI_ACME_DIRECTORY_URL,
25
25
  ITSI_LOCAL_CA_DIR,
26
26
  };
27
+
27
28
  mod locked_dir_cache;
28
29
 
29
30
  #[derive(Clone)]
@@ -253,9 +254,13 @@ fn get_or_create_local_dev_ca() -> Result<(String, String)> {
253
254
  Ok((key_pem, cert_pem))
254
255
  } else {
255
256
  let subject_alt_names = vec!["dev.itsi.fyi".to_string(), "localhost".to_string()];
256
-
257
- let CertifiedKey { cert, key_pair } =
258
- generate_simple_self_signed(subject_alt_names).unwrap();
257
+ let mut params = CertificateParams::new(subject_alt_names)?;
258
+ let mut distinguished_name = DistinguishedName::new();
259
+ distinguished_name.push(DnType::CommonName, "Itsi Development CA");
260
+ params.distinguished_name = distinguished_name;
261
+ params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
262
+ let key_pair = KeyPair::generate()?;
263
+ let cert = params.self_signed(&key_pair)?;
259
264
 
260
265
  fs::write(&key_path, key_pair.serialize_pem())?;
261
266
  fs::write(&cert_path, cert.pem())?;
@@ -0,0 +1,97 @@
1
+ use std::convert::Infallible;
2
+
3
+ use bytes::Bytes;
4
+ use http::{Request, Response};
5
+ use http_body_util::combinators::BoxBody;
6
+ use hyper::body::Incoming;
7
+
8
+ use super::size_limited_incoming::SizeLimitedIncoming;
9
+
10
+ pub type HttpResponse = Response<BoxBody<Bytes, Infallible>>;
11
+ pub type HttpRequest = Request<SizeLimitedIncoming<Incoming>>;
12
+
13
+ pub trait ConversionExt {
14
+ fn limit(self) -> HttpRequest;
15
+ }
16
+
17
+ impl ConversionExt for Request<Incoming> {
18
+ fn limit(self) -> HttpRequest {
19
+ let (parts, body) = self.into_parts();
20
+ Request::from_parts(parts, SizeLimitedIncoming::new(body))
21
+ }
22
+ }
23
+
24
+ pub trait RequestExt {
25
+ fn content_type(&self) -> Option<&str>;
26
+ fn accept(&self) -> Option<&str>;
27
+ fn header(&self, header_name: &str) -> Option<&str>;
28
+ fn query_param(&self, query_name: &str) -> Option<&str>;
29
+ }
30
+
31
+ pub trait PathExt {
32
+ fn no_trailing_slash(&self) -> &str;
33
+ }
34
+
35
+ #[derive(Debug, Clone)]
36
+ pub enum ResponseFormat {
37
+ JSON,
38
+ HTML,
39
+ TEXT,
40
+ UNKNOWN,
41
+ }
42
+
43
+ #[derive(Debug, Clone, Default)]
44
+ pub struct SupportedEncodingSet {
45
+ pub zstd: bool,
46
+ pub br: bool,
47
+ pub deflate: bool,
48
+ pub gzip: bool,
49
+ }
50
+
51
+ impl From<Option<&str>> for ResponseFormat {
52
+ fn from(value: Option<&str>) -> Self {
53
+ match value {
54
+ Some("application/json") => ResponseFormat::JSON,
55
+ Some("text/html") => ResponseFormat::HTML,
56
+ Some("text/plain") => ResponseFormat::TEXT,
57
+ _ => ResponseFormat::UNKNOWN,
58
+ }
59
+ }
60
+ }
61
+
62
+ impl PathExt for str {
63
+ fn no_trailing_slash(&self) -> &str {
64
+ if self == "/" {
65
+ self
66
+ } else {
67
+ self.trim_end_matches("/")
68
+ }
69
+ }
70
+ }
71
+
72
+ impl RequestExt for HttpRequest {
73
+ fn content_type(&self) -> Option<&str> {
74
+ self.headers()
75
+ .get("content-type")
76
+ .map(|hv| hv.to_str().unwrap_or(""))
77
+ }
78
+
79
+ fn accept(&self) -> Option<&str> {
80
+ self.headers()
81
+ .get("accept")
82
+ .map(|hv| hv.to_str().unwrap_or(""))
83
+ }
84
+
85
+ fn header(&self, header_name: &str) -> Option<&str> {
86
+ self.headers()
87
+ .get(header_name)
88
+ .map(|hv| hv.to_str().unwrap_or(""))
89
+ }
90
+
91
+ fn query_param(&self, query_name: &str) -> Option<&str> {
92
+ self.uri()
93
+ .query()
94
+ .and_then(|query| query.split('&').find(|param| param.starts_with(query_name)))
95
+ .map(|param| param.split('=').nth(1).unwrap_or(""))
96
+ }
97
+ }
@@ -1,4 +1,3 @@
1
- use super::listener::SockAddr;
2
1
  use pin_project::pin_project;
3
2
  use tokio::net::{TcpStream, UnixStream};
4
3
  use tokio_rustls::server::TlsStream;
@@ -8,6 +7,8 @@ use std::pin::Pin;
8
7
  use std::task::{Context, Poll};
9
8
  use tokio::io::{AsyncRead, AsyncWrite};
10
9
 
10
+ use super::binds::listener::SockAddr;
11
+
11
12
  #[pin_project(project = IoStreamEnumProj)]
12
13
  pub enum IoStream {
13
14
  Tcp {
@@ -1,8 +1,10 @@
1
- use super::middlewares::*;
2
- use crate::server::{
3
- itsi_service::RequestContext,
4
- types::{HttpRequest, HttpResponse},
1
+ use crate::{
2
+ server::http_message_types::{HttpRequest, HttpResponse},
3
+ services::itsi_http_service::HttpRequestContext,
5
4
  };
5
+
6
+ use super::middlewares::*;
7
+
6
8
  use async_trait::async_trait;
7
9
  use either::Either;
8
10
  use magnus::error::Result;
@@ -21,6 +23,7 @@ pub enum Middleware {
21
23
  ETag(ETag),
22
24
  IntrusionProtection(IntrusionProtection),
23
25
  LogRequests(LogRequests),
26
+ MaxBody(MaxBody),
24
27
  Proxy(Proxy),
25
28
  RateLimit(RateLimit),
26
29
  Redirect(Redirect),
@@ -28,6 +31,7 @@ pub enum Middleware {
28
31
  ResponseHeaders(ResponseHeaders),
29
32
  RubyApp(RubyApp),
30
33
  StaticAssets(StaticAssets),
34
+ StaticResponse(StaticResponse),
31
35
  }
32
36
 
33
37
  #[async_trait]
@@ -41,6 +45,7 @@ impl MiddlewareLayer for Middleware {
41
45
  Middleware::AuthJwt(filter) => filter.initialize().await,
42
46
  Middleware::AuthAPIKey(filter) => filter.initialize().await,
43
47
  Middleware::IntrusionProtection(filter) => filter.initialize().await,
48
+ Middleware::MaxBody(filter) => filter.initialize().await,
44
49
  Middleware::RateLimit(filter) => filter.initialize().await,
45
50
  Middleware::RequestHeaders(filter) => filter.initialize().await,
46
51
  Middleware::ResponseHeaders(filter) => filter.initialize().await,
@@ -48,6 +53,7 @@ impl MiddlewareLayer for Middleware {
48
53
  Middleware::Cors(filter) => filter.initialize().await,
49
54
  Middleware::ETag(filter) => filter.initialize().await,
50
55
  Middleware::StaticAssets(filter) => filter.initialize().await,
56
+ Middleware::StaticResponse(filter) => filter.initialize().await,
51
57
  Middleware::Compression(filter) => filter.initialize().await,
52
58
  Middleware::LogRequests(filter) => filter.initialize().await,
53
59
  Middleware::Redirect(filter) => filter.initialize().await,
@@ -59,7 +65,7 @@ impl MiddlewareLayer for Middleware {
59
65
  async fn before(
60
66
  &self,
61
67
  req: HttpRequest,
62
- context: &mut RequestContext,
68
+ context: &mut HttpRequestContext,
63
69
  ) -> Result<Either<HttpRequest, HttpResponse>> {
64
70
  match self {
65
71
  Middleware::DenyList(filter) => filter.before(req, context).await,
@@ -68,6 +74,7 @@ impl MiddlewareLayer for Middleware {
68
74
  Middleware::AuthJwt(filter) => filter.before(req, context).await,
69
75
  Middleware::AuthAPIKey(filter) => filter.before(req, context).await,
70
76
  Middleware::IntrusionProtection(filter) => filter.before(req, context).await,
77
+ Middleware::MaxBody(filter) => filter.before(req, context).await,
71
78
  Middleware::RequestHeaders(filter) => filter.before(req, context).await,
72
79
  Middleware::ResponseHeaders(filter) => filter.before(req, context).await,
73
80
  Middleware::RateLimit(filter) => filter.before(req, context).await,
@@ -75,6 +82,7 @@ impl MiddlewareLayer for Middleware {
75
82
  Middleware::Cors(filter) => filter.before(req, context).await,
76
83
  Middleware::ETag(filter) => filter.before(req, context).await,
77
84
  Middleware::StaticAssets(filter) => filter.before(req, context).await,
85
+ Middleware::StaticResponse(filter) => filter.before(req, context).await,
78
86
  Middleware::Compression(filter) => filter.before(req, context).await,
79
87
  Middleware::LogRequests(filter) => filter.before(req, context).await,
80
88
  Middleware::Redirect(filter) => filter.before(req, context).await,
@@ -83,7 +91,7 @@ impl MiddlewareLayer for Middleware {
83
91
  }
84
92
  }
85
93
 
86
- async fn after(&self, res: HttpResponse, context: &mut RequestContext) -> HttpResponse {
94
+ async fn after(&self, res: HttpResponse, context: &mut HttpRequestContext) -> HttpResponse {
87
95
  match self {
88
96
  Middleware::DenyList(filter) => filter.after(res, context).await,
89
97
  Middleware::AllowList(filter) => filter.after(res, context).await,
@@ -91,6 +99,7 @@ impl MiddlewareLayer for Middleware {
91
99
  Middleware::AuthJwt(filter) => filter.after(res, context).await,
92
100
  Middleware::AuthAPIKey(filter) => filter.after(res, context).await,
93
101
  Middleware::IntrusionProtection(filter) => filter.after(res, context).await,
102
+ Middleware::MaxBody(filter) => filter.after(res, context).await,
94
103
  Middleware::RateLimit(filter) => filter.after(res, context).await,
95
104
  Middleware::RequestHeaders(filter) => filter.after(res, context).await,
96
105
  Middleware::ResponseHeaders(filter) => filter.after(res, context).await,
@@ -98,6 +107,7 @@ impl MiddlewareLayer for Middleware {
98
107
  Middleware::Cors(filter) => filter.after(res, context).await,
99
108
  Middleware::ETag(filter) => filter.after(res, context).await,
100
109
  Middleware::StaticAssets(filter) => filter.after(res, context).await,
110
+ Middleware::StaticResponse(filter) => filter.after(res, context).await,
101
111
  Middleware::Compression(filter) => filter.after(res, context).await,
102
112
  Middleware::LogRequests(filter) => filter.after(res, context).await,
103
113
  Middleware::Redirect(filter) => filter.after(res, context).await,
@@ -118,16 +128,18 @@ impl Middleware {
118
128
  Middleware::CacheControl(_) => 5,
119
129
  Middleware::RequestHeaders(_) => 6,
120
130
  Middleware::ResponseHeaders(_) => 7,
121
- Middleware::AuthBasic(_) => 8,
122
- Middleware::AuthJwt(_) => 9,
123
- Middleware::AuthAPIKey(_) => 10,
124
- Middleware::RateLimit(_) => 11,
125
- Middleware::ETag(_) => 12,
126
- Middleware::Compression(_) => 13,
127
- Middleware::Proxy(_) => 14,
128
- Middleware::Cors(_) => 15,
129
- Middleware::StaticAssets(_) => 16,
130
- Middleware::RubyApp(_) => 17,
131
+ Middleware::MaxBody(_) => 8,
132
+ Middleware::AuthBasic(_) => 9,
133
+ Middleware::AuthJwt(_) => 10,
134
+ Middleware::AuthAPIKey(_) => 11,
135
+ Middleware::RateLimit(_) => 12,
136
+ Middleware::ETag(_) => 13,
137
+ Middleware::Compression(_) => 14,
138
+ Middleware::Proxy(_) => 15,
139
+ Middleware::Cors(_) => 16,
140
+ Middleware::StaticResponse(_) => 17,
141
+ Middleware::StaticAssets(_) => 18,
142
+ Middleware::RubyApp(_) => 19,
131
143
  }
132
144
  }
133
145
  }
@@ -1,8 +1,10 @@
1
- use super::{ErrorResponse, FromValue, MiddlewareLayer};
2
- use crate::server::{
3
- itsi_service::RequestContext,
4
- types::{HttpRequest, HttpResponse},
1
+ use crate::{
2
+ server::http_message_types::{HttpRequest, HttpResponse, RequestExt},
3
+ services::itsi_http_service::HttpRequestContext,
5
4
  };
5
+
6
+ use super::{ErrorResponse, FromValue, MiddlewareLayer};
7
+
6
8
  use async_trait::async_trait;
7
9
  use either::Either;
8
10
  use itsi_error::ItsiError;
@@ -16,28 +18,35 @@ pub struct AllowList {
16
18
  #[serde(skip_deserializing)]
17
19
  pub allowed_ips: OnceLock<RegexSet>,
18
20
  pub allowed_patterns: Vec<String>,
21
+ #[serde(default = "forbidden_error_response")]
19
22
  pub error_response: ErrorResponse,
20
23
  }
21
24
 
25
+ fn forbidden_error_response() -> ErrorResponse {
26
+ ErrorResponse::forbidden()
27
+ }
28
+
22
29
  #[async_trait]
23
30
  impl MiddlewareLayer for AllowList {
24
31
  async fn initialize(&self) -> Result<()> {
25
- let allowed_ips = RegexSet::new(&self.allowed_patterns).map_err(ItsiError::default)?;
32
+ let allowed_ips = RegexSet::new(&self.allowed_patterns).map_err(ItsiError::new)?;
26
33
  self.allowed_ips
27
34
  .set(allowed_ips)
28
- .map_err(|e| ItsiError::default(format!("Failed to set allowed IPs: {:?}", e)))?;
35
+ .map_err(|e| ItsiError::new(format!("Failed to set allowed IPs: {:?}", e)))?;
29
36
  Ok(())
30
37
  }
31
38
 
32
39
  async fn before(
33
40
  &self,
34
41
  req: HttpRequest,
35
- context: &mut RequestContext,
42
+ context: &mut HttpRequestContext,
36
43
  ) -> Result<Either<HttpRequest, HttpResponse>> {
37
44
  if let Some(allowed_ips) = self.allowed_ips.get() {
38
45
  if !allowed_ips.is_match(&context.addr) {
39
46
  return Ok(Either::Right(
40
- self.error_response.to_http_response(&req).await,
47
+ self.error_response
48
+ .to_http_response(req.accept().into())
49
+ .await,
41
50
  ));
42
51
  }
43
52
  }
@@ -1,6 +1,8 @@
1
- use crate::server::{
2
- itsi_service::RequestContext,
3
- types::{HttpRequest, HttpResponse, RequestExt},
1
+ use std::collections::HashMap;
2
+
3
+ use crate::{
4
+ server::http_message_types::{HttpRequest, HttpResponse, RequestExt},
5
+ services::{itsi_http_service::HttpRequestContext, password_hasher},
4
6
  };
5
7
 
6
8
  use super::{error_response::ErrorResponse, token_source::TokenSource, FromValue, MiddlewareLayer};
@@ -10,25 +12,32 @@ use either::Either;
10
12
  use magnus::error::Result;
11
13
  use serde::Deserialize;
12
14
 
15
+ type PasswordHash = String;
16
+
13
17
  /// A simple API key filter.
14
18
  /// The API key can be given inside the header or a query string
15
19
  /// Keys are validated against a list of allowed key values (Changing these requires a restart)
16
- ///
17
20
  #[derive(Debug, Clone, Deserialize)]
18
21
  pub struct AuthAPIKey {
19
- pub valid_keys: Vec<String>,
22
+ pub valid_keys: HashMap<String, PasswordHash>,
23
+ pub key_id_source: Option<TokenSource>,
20
24
  pub token_source: TokenSource,
25
+ #[serde(default = "unauthorized_error_response")]
21
26
  pub error_response: ErrorResponse,
22
27
  }
23
28
 
29
+ fn unauthorized_error_response() -> ErrorResponse {
30
+ ErrorResponse::unauthorized()
31
+ }
32
+
24
33
  #[async_trait]
25
34
  impl MiddlewareLayer for AuthAPIKey {
26
35
  async fn before(
27
36
  &self,
28
37
  req: HttpRequest,
29
- _context: &mut RequestContext,
38
+ _context: &mut HttpRequestContext,
30
39
  ) -> Result<Either<HttpRequest, HttpResponse>> {
31
- let submitted_value = match &self.token_source {
40
+ if let Some(submitted_key) = match &self.token_source {
32
41
  TokenSource::Header { name, prefix } => {
33
42
  if let Some(header) = req.header(name) {
34
43
  if let Some(prefix) = prefix {
@@ -41,18 +50,38 @@ impl MiddlewareLayer for AuthAPIKey {
41
50
  }
42
51
  }
43
52
  TokenSource::Query(query_name) => req.query_param(query_name),
44
- };
45
- if !self
46
- .valid_keys
47
- .iter()
48
- .any(|key| submitted_value.is_some_and(|sv| sv == key))
49
- {
50
- Ok(Either::Right(
51
- self.error_response.to_http_response(&req).await,
52
- ))
53
- } else {
54
- Ok(Either::Left(req))
53
+ } {
54
+ if let Some(key_id) = self.key_id_source.as_ref() {
55
+ let key_id = match &key_id {
56
+ TokenSource::Header { name, prefix } => {
57
+ if let Some(header) = req.header(name) {
58
+ if let Some(prefix) = prefix {
59
+ Some(header.strip_prefix(prefix).unwrap_or("").trim_ascii())
60
+ } else {
61
+ Some(header.trim_ascii())
62
+ }
63
+ } else {
64
+ None
65
+ }
66
+ }
67
+ TokenSource::Query(query_name) => req.query_param(query_name),
68
+ };
69
+ if let Some(hash) = key_id.and_then(|kid| self.valid_keys.get(kid)) {
70
+ if password_hasher::verify_password_hash(submitted_key, hash).is_ok_and(|v| v) {
71
+ return Ok(Either::Left(req));
72
+ }
73
+ }
74
+ } else if self.valid_keys.iter().any(|(_key_id, key)| {
75
+ password_hasher::verify_password_hash(submitted_key, key).is_ok_and(|v| v)
76
+ }) {
77
+ return Ok(Either::Left(req));
78
+ }
55
79
  }
80
+ Ok(Either::Right(
81
+ self.error_response
82
+ .to_http_response(req.accept().into())
83
+ .await,
84
+ ))
56
85
  }
57
86
  }
58
87
  impl FromValue for AuthAPIKey {}
@@ -9,18 +9,20 @@ use serde::{Deserialize, Serialize};
9
9
  use std::collections::HashMap;
10
10
  use std::str;
11
11
 
12
- use crate::server::{
13
- itsi_service::RequestContext,
14
- types::{HttpRequest, HttpResponse, RequestExt},
12
+ use crate::{
13
+ server::http_message_types::{HttpRequest, HttpResponse, RequestExt},
14
+ services::{itsi_http_service::HttpRequestContext, password_hasher::verify_password_hash},
15
15
  };
16
16
 
17
17
  use super::{FromValue, MiddlewareLayer};
18
18
 
19
+ type PasswordHash = String;
20
+
19
21
  #[derive(Debug, Clone, Serialize, Deserialize)]
20
22
  pub struct AuthBasic {
21
23
  pub realm: String,
22
24
  /// Maps usernames to passwords.
23
- pub credential_pairs: HashMap<String, String>,
25
+ pub credential_pairs: HashMap<String, PasswordHash>,
24
26
  }
25
27
 
26
28
  impl AuthBasic {
@@ -40,7 +42,7 @@ impl MiddlewareLayer for AuthBasic {
40
42
  async fn before(
41
43
  &self,
42
44
  req: HttpRequest,
43
- _context: &mut RequestContext,
45
+ _context: &mut HttpRequestContext,
44
46
  ) -> Result<Either<HttpRequest, HttpResponse>> {
45
47
  // Retrieve the Authorization header.
46
48
  let auth_header = req.header("Authorization");
@@ -69,12 +71,14 @@ impl MiddlewareLayer for AuthBasic {
69
71
  let mut parts = decoded_str.splitn(2, ':');
70
72
  let username = parts.next().unwrap_or("");
71
73
  let password = parts.next().unwrap_or("");
72
-
73
74
  match self.credential_pairs.get(username) {
74
- Some(expected_password) if expected_password == password => Ok(Either::Left(req)),
75
- _ => {
76
- return Ok(Either::Right(self.basic_auth_failed_response()));
75
+ Some(expected_password_hash) => {
76
+ match verify_password_hash(password, expected_password_hash) {
77
+ Ok(true) => Ok(Either::Left(req)),
78
+ _ => Ok(Either::Right(self.basic_auth_failed_response())),
79
+ }
77
80
  }
81
+ None => Ok(Either::Right(self.basic_auth_failed_response())),
78
82
  }
79
83
  }
80
84
  }