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
@@ -1,42 +1,46 @@
1
1
  use super::{error_response::ErrorResponse, token_source::TokenSource, FromValue, MiddlewareLayer};
2
- use crate::server::{
3
- itsi_service::RequestContext,
4
- types::{HttpRequest, HttpResponse, RequestExt},
2
+ use crate::{
3
+ server::http_message_types::{HttpRequest, HttpResponse, RequestExt},
4
+ services::itsi_http_service::HttpRequestContext,
5
5
  };
6
+
6
7
  use async_trait::async_trait;
7
8
  use base64::{engine::general_purpose, Engine};
9
+ use derive_more::Debug;
8
10
  use either::Either;
9
11
  use itsi_error::ItsiError;
10
- use jwt_simple::{
11
- claims::{self, JWTClaims, NoCustomClaims},
12
- prelude::{
13
- ECDSAP256PublicKeyLike, ECDSAP384PublicKeyLike, ES256PublicKey, ES384PublicKey, HS256Key,
14
- HS384Key, HS512Key, MACLike, PS256PublicKey, PS384PublicKey, PS512PublicKey,
15
- RS256PublicKey, RS384PublicKey, RS512PublicKey, RSAPublicKeyLike,
16
- },
17
- token::Token,
12
+ use jsonwebtoken::{
13
+ decode, decode_header, Algorithm as JwtAlg, DecodingKey, TokenData, Validation,
18
14
  };
19
15
  use magnus::error::Result;
20
16
  use serde::Deserialize;
21
- use std::str;
22
17
  use std::{
23
18
  collections::{HashMap, HashSet},
24
19
  sync::OnceLock,
25
20
  };
21
+ use tracing::error;
26
22
 
27
23
  #[derive(Debug, Clone, Deserialize)]
28
24
  pub struct AuthJwt {
29
25
  pub token_source: TokenSource,
26
+ // The verifiers map still holds base64-encoded key strings keyed by algorithm.
30
27
  pub verifiers: HashMap<JwtAlgorithm, Vec<String>>,
28
+ // We now store jsonwebtoken’s DecodingKey in our OnceLock.
31
29
  #[serde(skip_deserializing)]
32
- pub keys: OnceLock<HashMap<JwtAlgorithm, Vec<JwtKey>>>,
30
+ #[debug(skip)]
31
+ pub keys: OnceLock<HashMap<JwtAlgorithm, Vec<DecodingKey>>>,
33
32
  pub audiences: Option<HashSet<String>>,
34
33
  pub subjects: Option<HashSet<String>>,
35
34
  pub issuers: Option<HashSet<String>>,
36
35
  pub leeway: Option<u64>,
36
+ #[serde(default = "unauthorized_error_response")]
37
37
  pub error_response: ErrorResponse,
38
38
  }
39
39
 
40
+ fn unauthorized_error_response() -> ErrorResponse {
41
+ ErrorResponse::unauthorized()
42
+ }
43
+
40
44
  #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Hash)]
41
45
  pub enum JwtAlgorithm {
42
46
  #[serde(rename(deserialize = "hs256"))]
@@ -63,169 +67,100 @@ pub enum JwtAlgorithm {
63
67
  Ps512,
64
68
  }
65
69
 
66
- impl JwtAlgorithm {
67
- pub fn key_from(&self, base64: &str) -> Result<JwtKey> {
68
- let bytes = general_purpose::STANDARD
69
- .decode(base64)
70
- .map_err(ItsiError::default)?;
71
-
72
- match self {
73
- JwtAlgorithm::Hs256 => Ok(JwtKey::Hs256(HS256Key::from_bytes(&bytes))),
74
- JwtAlgorithm::Hs384 => Ok(JwtKey::Hs384(HS384Key::from_bytes(&bytes))),
75
- JwtAlgorithm::Hs512 => Ok(JwtKey::Hs512(HS512Key::from_bytes(&bytes))),
76
- JwtAlgorithm::Rs256 => Ok(RS256PublicKey::from_der(&bytes)
77
- .or_else(|_| {
78
- RS256PublicKey::from_pem(
79
- &String::from_utf8(bytes.clone()).map_err(ItsiError::default)?,
80
- )
81
- })
82
- .map(JwtKey::Rs256)
83
- .map_err(ItsiError::default)?),
84
- JwtAlgorithm::Rs384 => Ok(RS384PublicKey::from_der(&bytes)
85
- .or_else(|_| {
86
- RS384PublicKey::from_pem(
87
- &String::from_utf8(bytes.clone()).map_err(ItsiError::default)?,
88
- )
89
- })
90
- .map(JwtKey::Rs384)
91
- .map_err(ItsiError::default)?),
92
- JwtAlgorithm::Rs512 => Ok(RS512PublicKey::from_der(&bytes)
93
- .or_else(|_| {
94
- RS512PublicKey::from_pem(
95
- &String::from_utf8(bytes.clone()).map_err(ItsiError::default)?,
96
- )
97
- })
98
- .map(JwtKey::Rs512)
99
- .map_err(ItsiError::default)?),
100
- JwtAlgorithm::Es256 => Ok(ES256PublicKey::from_der(&bytes)
101
- .or_else(|_| {
102
- ES256PublicKey::from_pem(
103
- &String::from_utf8(bytes.clone()).map_err(ItsiError::default)?,
104
- )
105
- })
106
- .map(JwtKey::Es256)
107
- .map_err(ItsiError::default)?),
108
- JwtAlgorithm::Es384 => Ok(ES384PublicKey::from_der(&bytes)
109
- .or_else(|_| {
110
- ES384PublicKey::from_pem(
111
- &String::from_utf8(bytes.clone()).map_err(ItsiError::default)?,
112
- )
113
- })
114
- .map(JwtKey::Es384)
115
- .map_err(ItsiError::default)?),
116
- JwtAlgorithm::Ps256 => Ok(PS256PublicKey::from_der(&bytes)
117
- .or_else(|_| {
118
- PS256PublicKey::from_pem(
119
- &String::from_utf8(bytes.clone()).map_err(ItsiError::default)?,
120
- )
121
- })
122
- .map(JwtKey::Ps256)
123
- .map_err(ItsiError::default)?),
124
- JwtAlgorithm::Ps384 => Ok(PS384PublicKey::from_der(&bytes)
125
- .or_else(|_| {
126
- PS384PublicKey::from_pem(
127
- &String::from_utf8(bytes.clone()).map_err(ItsiError::default)?,
128
- )
129
- })
130
- .map(JwtKey::Ps384)
131
- .map_err(ItsiError::default)?),
132
- JwtAlgorithm::Ps512 => Ok(PS512PublicKey::from_der(&bytes)
133
- .or_else(|_| {
134
- PS512PublicKey::from_pem(
135
- &String::from_utf8(bytes.clone()).map_err(ItsiError::default)?,
136
- )
137
- })
138
- .map(JwtKey::Ps512)
139
- .map_err(ItsiError::default)?),
70
+ // Allow conversion from jsonwebtoken’s Algorithm to our JwtAlgorithm.
71
+ impl From<JwtAlg> for JwtAlgorithm {
72
+ fn from(alg: JwtAlg) -> Self {
73
+ match alg {
74
+ JwtAlg::HS256 => JwtAlgorithm::Hs256,
75
+ JwtAlg::HS384 => JwtAlgorithm::Hs384,
76
+ JwtAlg::HS512 => JwtAlgorithm::Hs512,
77
+ JwtAlg::RS256 => JwtAlgorithm::Rs256,
78
+ JwtAlg::RS384 => JwtAlgorithm::Rs384,
79
+ JwtAlg::RS512 => JwtAlgorithm::Rs512,
80
+ JwtAlg::ES256 => JwtAlgorithm::Es256,
81
+ JwtAlg::ES384 => JwtAlgorithm::Es384,
82
+ JwtAlg::PS256 => JwtAlgorithm::Ps256,
83
+ JwtAlg::PS384 => JwtAlgorithm::Ps384,
84
+ JwtAlg::PS512 => JwtAlgorithm::Ps512,
85
+ _ => panic!("Unsupported algorithm"),
140
86
  }
141
87
  }
142
88
  }
143
89
 
144
- #[derive(Debug, Clone)]
145
- pub enum JwtKey {
146
- Hs256(HS256Key),
147
- Hs384(HS384Key),
148
- Hs512(HS512Key),
149
- Rs256(RS256PublicKey),
150
- Rs384(RS384PublicKey),
151
- Rs512(RS512PublicKey),
152
- Es256(ES256PublicKey),
153
- Es384(ES384PublicKey),
154
- Ps256(PS256PublicKey),
155
- Ps384(PS384PublicKey),
156
- Ps512(PS512PublicKey),
157
- }
158
-
159
- impl TryFrom<&str> for JwtAlgorithm {
160
- type Error = itsi_error::ItsiError;
161
-
162
- fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
163
- match value.to_ascii_lowercase().as_str() {
164
- "hs256" => Ok(JwtAlgorithm::Hs256),
165
- "hs384" => Ok(JwtAlgorithm::Hs384),
166
- "hs512" => Ok(JwtAlgorithm::Hs512),
167
- "rs256" => Ok(JwtAlgorithm::Rs256),
168
- "rs384" => Ok(JwtAlgorithm::Rs384),
169
- "rs512" => Ok(JwtAlgorithm::Rs512),
170
- "es256" => Ok(JwtAlgorithm::Es256),
171
- "es384" => Ok(JwtAlgorithm::Es384),
172
- "ps256" => Ok(JwtAlgorithm::Ps256),
173
- "ps384" => Ok(JwtAlgorithm::Ps384),
174
- "ps512" => Ok(JwtAlgorithm::Ps512),
175
- _ => Err(itsi_error::ItsiError::UnsupportedProtocol(
176
- "Unsupported JWT Algorithm".to_string(),
177
- )),
90
+ impl JwtAlgorithm {
91
+ /// Given a base64-encoded key string, decode and construct a jsonwebtoken::DecodingKey.
92
+ pub fn key_from(&self, base64: &str) -> itsi_error::Result<DecodingKey> {
93
+ match self {
94
+ // For HMAC algorithms, use the secret directly.
95
+ JwtAlgorithm::Hs256 | JwtAlgorithm::Hs384 | JwtAlgorithm::Hs512 => {
96
+ Ok(DecodingKey::from_secret(
97
+ &general_purpose::STANDARD
98
+ .decode(base64)
99
+ .map_err(ItsiError::new)?,
100
+ ))
101
+ }
102
+ // For RSA (and PS) algorithms, expect a PEM-formatted key.
103
+ JwtAlgorithm::Rs256
104
+ | JwtAlgorithm::Rs384
105
+ | JwtAlgorithm::Rs512
106
+ | JwtAlgorithm::Ps256
107
+ | JwtAlgorithm::Ps384
108
+ | JwtAlgorithm::Ps512 => DecodingKey::from_rsa_pem(base64.trim_ascii().as_bytes())
109
+ .map_err(|e| ItsiError::new(e.to_string())),
110
+ // For ECDSA algorithms, expect a PEM-formatted key.
111
+ JwtAlgorithm::Es256 | JwtAlgorithm::Es384 => {
112
+ DecodingKey::from_ec_pem(base64.trim_ascii().as_bytes())
113
+ .map_err(|e| ItsiError::new(e.to_string()))
114
+ }
178
115
  }
179
116
  }
180
117
  }
181
118
 
182
- impl JwtKey {
183
- pub fn verify(
184
- &self,
185
- token: &str,
186
- ) -> std::result::Result<JWTClaims<claims::NoCustomClaims>, jwt_simple::Error> {
187
- match self {
188
- JwtKey::Hs256(key) => key.verify_token::<NoCustomClaims>(token, None),
189
- JwtKey::Hs384(key) => key.verify_token::<NoCustomClaims>(token, None),
190
- JwtKey::Hs512(key) => key.verify_token::<NoCustomClaims>(token, None),
191
- JwtKey::Rs256(key) => key.verify_token::<NoCustomClaims>(token, None),
192
- JwtKey::Rs384(key) => key.verify_token::<NoCustomClaims>(token, None),
193
- JwtKey::Rs512(key) => key.verify_token::<NoCustomClaims>(token, None),
194
- JwtKey::Es256(key) => key.verify_token::<NoCustomClaims>(token, None),
195
- JwtKey::Es384(key) => key.verify_token::<NoCustomClaims>(token, None),
196
- JwtKey::Ps256(key) => key.verify_token::<NoCustomClaims>(token, None),
197
- JwtKey::Ps384(key) => key.verify_token::<NoCustomClaims>(token, None),
198
- JwtKey::Ps512(key) => key.verify_token::<NoCustomClaims>(token, None),
199
- }
200
- }
119
+ #[derive(Debug, Deserialize)]
120
+ #[serde(untagged)]
121
+ enum Audience {
122
+ Single(String),
123
+ Multiple(Vec<String>),
124
+ }
125
+
126
+ #[derive(Debug, Deserialize)]
127
+ struct Claims {
128
+ // Here we assume the token includes an expiration.
129
+ #[allow(dead_code)]
130
+ exp: usize,
131
+ // The audience claim may be a single string or an array.
132
+ aud: Option<Audience>,
133
+ sub: Option<String>,
134
+ iss: Option<String>,
201
135
  }
202
136
 
203
137
  #[async_trait]
204
138
  impl MiddlewareLayer for AuthJwt {
205
139
  async fn initialize(&self) -> Result<()> {
206
- let keys: HashMap<JwtAlgorithm, Vec<JwtKey>> = self
140
+ let keys: HashMap<JwtAlgorithm, Vec<DecodingKey>> = self
207
141
  .verifiers
208
142
  .iter()
209
143
  .map(|(algorithm, key_strings)| {
210
144
  let algo = algorithm.clone();
211
- let keys: Result<Vec<JwtKey>> = key_strings
145
+ let keys: itsi_error::Result<Vec<DecodingKey>> = key_strings
212
146
  .iter()
213
147
  .map(|key_string| algorithm.key_from(key_string))
214
148
  .collect();
215
149
  keys.map(|keys| (algo, keys))
216
150
  })
217
- .collect::<Result<HashMap<JwtAlgorithm, Vec<JwtKey>>>>()?;
151
+ .collect::<itsi_error::Result<HashMap<JwtAlgorithm, Vec<DecodingKey>>>>()?;
218
152
  self.keys
219
153
  .set(keys)
220
- .map_err(|e| ItsiError::default(format!("Failed to set keys: {:?}", e)))?;
154
+ .map_err(|_| ItsiError::new("Failed to set keys"))?;
221
155
  Ok(())
222
156
  }
223
157
 
224
158
  async fn before(
225
159
  &self,
226
160
  req: HttpRequest,
227
- _context: &mut RequestContext,
161
+ _context: &mut HttpRequestContext,
228
162
  ) -> Result<Either<HttpRequest, HttpResponse>> {
163
+ // Retrieve the JWT token from either a header or a query parameter.
229
164
  let token_str = match &self.token_source {
230
165
  TokenSource::Header { name, prefix } => {
231
166
  if let Some(header) = req.header(name) {
@@ -243,72 +178,101 @@ impl MiddlewareLayer for AuthJwt {
243
178
 
244
179
  if token_str.is_none() {
245
180
  return Ok(Either::Right(
246
- self.error_response.to_http_response(&req).await,
181
+ self.error_response
182
+ .to_http_response(req.accept().into())
183
+ .await,
247
184
  ));
248
185
  }
249
-
250
186
  let token_str = token_str.unwrap();
251
- let token_meta = Token::decode_metadata(token_str);
187
+ let header =
188
+ decode_header(token_str).map_err(|_| ItsiError::new("Invalid token header"))?;
189
+ let alg: JwtAlgorithm = header.alg.into();
252
190
 
253
- if token_meta.is_err() {
254
- return Ok(Either::Right(
255
- self.error_response.to_http_response(&req).await,
256
- ));
257
- }
258
- let token_meta: std::result::Result<JwtAlgorithm, ItsiError> =
259
- token_meta.unwrap().algorithm().try_into();
260
- if token_meta.is_err() {
191
+ if !self.verifiers.contains_key(&alg) {
261
192
  return Ok(Either::Right(
262
- self.error_response.to_http_response(&req).await,
193
+ self.error_response
194
+ .to_http_response(req.accept().into())
195
+ .await,
263
196
  ));
264
197
  }
265
- let algorithm = token_meta.unwrap();
198
+ let keys = self.keys.get().unwrap().get(&alg).unwrap();
266
199
 
267
- if !self.verifiers.contains_key(&algorithm) {
268
- return Ok(Either::Right(
269
- self.error_response.to_http_response(&req).await,
270
- ));
271
- }
200
+ // Build validation based on the algorithm and optional leeway.
201
+ let mut validation = Validation::new(match alg {
202
+ JwtAlgorithm::Hs256 => JwtAlg::HS256,
203
+ JwtAlgorithm::Hs384 => JwtAlg::HS384,
204
+ JwtAlgorithm::Hs512 => JwtAlg::HS512,
205
+ JwtAlgorithm::Rs256 => JwtAlg::RS256,
206
+ JwtAlgorithm::Rs384 => JwtAlg::RS384,
207
+ JwtAlgorithm::Rs512 => JwtAlg::RS512,
208
+ JwtAlgorithm::Es256 => JwtAlg::ES256,
209
+ JwtAlgorithm::Es384 => JwtAlg::ES384,
210
+ JwtAlgorithm::Ps256 => JwtAlg::PS256,
211
+ JwtAlgorithm::Ps384 => JwtAlg::PS384,
212
+ JwtAlgorithm::Ps512 => JwtAlg::PS512,
213
+ });
272
214
 
273
- let keys = self.keys.get().unwrap().get(&algorithm).unwrap();
215
+ if let Some(leeway) = self.leeway {
216
+ validation.leeway = leeway;
217
+ }
274
218
 
275
- let verified_claims = keys.iter().find_map(|key| key.verify(token_str).ok());
276
- if verified_claims.is_none() {
219
+ let token_data: Option<TokenData<Claims>> =
220
+ keys.iter()
221
+ .find_map(|key| match decode::<Claims>(token_str, key, &validation) {
222
+ Ok(data) => Some(data),
223
+ Err(e) => {
224
+ error!("Token validation failed: {:?}", e);
225
+ None
226
+ }
227
+ });
228
+ let token_data = if let Some(data) = token_data {
229
+ data
230
+ } else {
277
231
  return Ok(Either::Right(
278
- self.error_response.to_http_response(&req).await,
232
+ self.error_response
233
+ .to_http_response(req.accept().into())
234
+ .await,
279
235
  ));
280
- }
236
+ };
281
237
 
282
- let claims = verified_claims.unwrap();
238
+ let claims = token_data.claims;
283
239
 
284
240
  if let Some(expected_audiences) = &self.audiences {
285
- // The aud claim may be a string or an array.
286
- if let Some(audiences) = &claims.audiences {
287
- if !audiences.contains(expected_audiences) {
241
+ if let Some(aud) = &claims.aud {
242
+ let token_auds: HashSet<String> = match aud {
243
+ Audience::Single(s) => [s.clone()].into_iter().collect(),
244
+ Audience::Multiple(v) => v.iter().cloned().collect(),
245
+ };
246
+ if expected_audiences.is_disjoint(&token_auds) {
288
247
  return Ok(Either::Right(
289
- self.error_response.to_http_response(&req).await,
248
+ self.error_response
249
+ .to_http_response(req.accept().into())
250
+ .await,
290
251
  ));
291
252
  }
292
253
  }
293
254
  }
294
255
 
295
256
  if let Some(expected_subjects) = &self.subjects {
296
- // The aud claim may be a string or an array.
297
- if let Some(subject) = &claims.subject {
298
- if !expected_subjects.contains(subject) {
257
+ if let Some(sub) = &claims.sub {
258
+ if !expected_subjects.contains(sub) {
299
259
  return Ok(Either::Right(
300
- self.error_response.to_http_response(&req).await,
260
+ self.error_response
261
+ .to_http_response(req.accept().into())
262
+ .await,
301
263
  ));
302
264
  }
303
265
  }
304
266
  }
305
267
 
268
+ // Verify expected issuer.
306
269
  if let Some(expected_issuers) = &self.issuers {
307
- // The aud claim may be a string or an array.
308
- if let Some(issuer) = &claims.issuer {
309
- if !expected_issuers.contains(issuer) {
270
+ if let Some(iss) = &claims.iss {
271
+ if !expected_issuers.contains(iss) {
310
272
  return Ok(Either::Right(
311
- self.error_response.to_http_response(&req).await,
273
+ self.error_response
274
+ .to_http_response(req.accept().into())
275
+ .await,
312
276
  ));
313
277
  }
314
278
  }
@@ -1,5 +1,8 @@
1
+ use crate::{
2
+ server::http_message_types::HttpResponse, services::itsi_http_service::HttpRequestContext,
3
+ };
4
+
1
5
  use super::{FromValue, MiddlewareLayer};
2
- use crate::server::{itsi_service::RequestContext, types::HttpResponse};
3
6
  use async_trait::async_trait;
4
7
  use http::{HeaderName, HeaderValue};
5
8
  use magnus::error::Result;
@@ -90,7 +93,7 @@ impl MiddlewareLayer for CacheControl {
90
93
  Ok(())
91
94
  }
92
95
 
93
- async fn after(&self, mut resp: HttpResponse, _: &mut RequestContext) -> HttpResponse {
96
+ async fn after(&self, mut resp: HttpResponse, _: &mut HttpRequestContext) -> HttpResponse {
94
97
  // Skip for statuses where caching doesn't make sense
95
98
  let status = resp.status().as_u16();
96
99
  if matches!(status, 401 | 403 | 500..=599) {
@@ -1,26 +1,25 @@
1
+ use crate::{
2
+ server::http_message_types::HttpResponse, services::itsi_http_service::HttpRequestContext,
3
+ };
4
+
1
5
  use super::{
2
6
  header_interpretation::{find_first_supported, header_contains},
3
7
  FromValue, MiddlewareLayer,
4
8
  };
5
- use crate::server::{
6
- itsi_service::RequestContext,
7
- types::{HttpRequest, HttpResponse},
8
- };
9
+
9
10
  use async_compression::{
10
11
  tokio::bufread::{BrotliEncoder, DeflateEncoder, GzipEncoder, ZstdEncoder},
11
12
  Level,
12
13
  };
13
14
  use async_trait::async_trait;
14
15
  use bytes::{Bytes, BytesMut};
15
- use either::Either;
16
16
  use futures::TryStreamExt;
17
17
  use http::{
18
- header::{GetAll, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE},
18
+ header::{GetAll, CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE},
19
19
  HeaderValue, Response,
20
20
  };
21
21
  use http_body_util::{combinators::BoxBody, BodyExt, Full, StreamBody};
22
22
  use hyper::body::{Body, Frame};
23
- use magnus::error::Result;
24
23
  use serde::{Deserialize, Serialize};
25
24
  use std::convert::Infallible;
26
25
  use tokio::io::{AsyncRead, AsyncReadExt, BufReader};
@@ -148,55 +147,16 @@ fn update_content_encoding(parts: &mut http::response::Parts, new_encoding: Head
148
147
 
149
148
  #[async_trait]
150
149
  impl MiddlewareLayer for Compression {
151
- /// A the request comes in, take note of the accepted content encodings,
152
- /// so that we can apply compression on the response, where appropriate.
153
- ///
154
- /// We store the temporary state inside the RequestContext.
155
- async fn before(
156
- &self,
157
- req: HttpRequest,
158
- context: &mut RequestContext,
159
- ) -> Result<Either<HttpRequest, HttpResponse>> {
160
- let algo = match find_first_supported(
161
- &req.headers().get_all(ACCEPT_ENCODING),
162
- self.algorithms.iter().map(|algo| algo.as_str()),
163
- ) {
164
- Some("gzip") => CompressionAlgorithm::Gzip,
165
- Some("br") => CompressionAlgorithm::Brotli,
166
- Some("deflate") => CompressionAlgorithm::Deflate,
167
- Some("zstd") => CompressionAlgorithm::Zstd,
168
- _ => CompressionAlgorithm::None,
169
- };
170
-
171
- if matches!(algo, CompressionAlgorithm::None) {
172
- return Ok(Either::Left(req));
173
- }
174
-
175
- context.set_compression_method(algo);
176
-
177
- Ok(Either::Left(req))
178
- }
179
-
180
150
  /// We'll apply compression on the response, where appropriate.
181
151
  /// This is if:
182
152
  /// * The response body is larger than the minimum size.
183
153
  /// * The response content type is supported.
184
154
  /// * The client supports the compression algorithm.
185
- async fn after(&self, resp: HttpResponse, context: &mut RequestContext) -> HttpResponse {
186
- let compression_method;
187
- if let Some(method) = context.compression_method.get() {
188
- compression_method = method.clone();
189
- } else {
190
- return resp;
191
- }
192
-
193
- if matches!(compression_method, CompressionAlgorithm::None) {
194
- return resp;
195
- }
196
-
155
+ async fn after(&self, resp: HttpResponse, context: &mut HttpRequestContext) -> HttpResponse {
197
156
  let body_size = resp.size_hint().exact();
198
157
  let resp = resp;
199
158
 
159
+ // Don't compress if it's not an explicitly listed compressable type
200
160
  if !self
201
161
  .mime_types
202
162
  .iter()
@@ -205,14 +165,43 @@ impl MiddlewareLayer for Compression {
205
165
  return resp;
206
166
  }
207
167
 
168
+ // Don't compress streams unless compress streams is enabled.
208
169
  if body_size.is_none() && !self.compress_streams {
209
170
  return resp;
210
171
  }
211
172
 
173
+ // Don't compress too small bodies
212
174
  if body_size.is_some_and(|s| s < self.min_size as u64) {
213
175
  return resp;
214
176
  }
215
177
 
178
+ // Don't recompress if we're already compressed in a supported format
179
+ for existing_encoding in resp.headers().get_all(CONTENT_ENCODING) {
180
+ if let Ok(encodings) = existing_encoding.to_str() {
181
+ for encoding in encodings.split(',').map(str::trim) {
182
+ let encoding = encoding.split(';').next().unwrap_or(encoding).trim();
183
+ if self.algorithms.iter().any(|algo| algo.as_str() == encoding) {
184
+ return resp;
185
+ }
186
+ }
187
+ }
188
+ }
189
+
190
+ let compression_method = match find_first_supported(
191
+ &context.supported_encoding_set,
192
+ self.algorithms.iter().map(|algo| algo.as_str()),
193
+ ) {
194
+ Some("gzip") => CompressionAlgorithm::Gzip,
195
+ Some("br") => CompressionAlgorithm::Brotli,
196
+ Some("deflate") => CompressionAlgorithm::Deflate,
197
+ Some("zstd") => CompressionAlgorithm::Zstd,
198
+ _ => CompressionAlgorithm::None,
199
+ };
200
+
201
+ if matches!(compression_method, CompressionAlgorithm::None) {
202
+ return resp;
203
+ }
204
+
216
205
  let (mut parts, body) = resp.into_parts();
217
206
 
218
207
  let new_body = if let Some(_size) = body_size {