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.
- checksums.yaml +4 -4
- data/Cargo.lock +113 -593
- data/Cargo.toml +6 -0
- data/crates/itsi_error/Cargo.toml +1 -0
- data/crates/itsi_error/src/lib.rs +100 -10
- data/crates/itsi_scheduler/src/itsi_scheduler.rs +1 -1
- data/crates/itsi_server/Cargo.toml +9 -11
- data/crates/itsi_server/src/default_responses/html/401.html +68 -0
- data/crates/itsi_server/src/default_responses/html/403.html +68 -0
- data/crates/itsi_server/src/default_responses/html/404.html +68 -0
- data/crates/itsi_server/src/default_responses/html/413.html +71 -0
- data/crates/itsi_server/src/default_responses/html/429.html +68 -0
- data/crates/itsi_server/src/default_responses/html/500.html +71 -0
- data/crates/itsi_server/src/default_responses/html/502.html +71 -0
- data/crates/itsi_server/src/default_responses/html/503.html +68 -0
- data/crates/itsi_server/src/default_responses/html/504.html +69 -0
- data/crates/itsi_server/src/default_responses/html/index.html +238 -0
- data/crates/itsi_server/src/default_responses/json/401.json +6 -0
- data/crates/itsi_server/src/default_responses/json/403.json +6 -0
- data/crates/itsi_server/src/default_responses/json/404.json +6 -0
- data/crates/itsi_server/src/default_responses/json/413.json +6 -0
- data/crates/itsi_server/src/default_responses/json/429.json +6 -0
- data/crates/itsi_server/src/default_responses/json/500.json +6 -0
- data/crates/itsi_server/src/default_responses/json/502.json +6 -0
- data/crates/itsi_server/src/default_responses/json/503.json +6 -0
- data/crates/itsi_server/src/default_responses/json/504.json +6 -0
- data/crates/itsi_server/src/default_responses/mod.rs +11 -0
- data/crates/itsi_server/src/lib.rs +58 -26
- data/crates/itsi_server/src/prelude.rs +2 -0
- data/crates/itsi_server/src/ruby_types/README.md +21 -0
- data/crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +8 -6
- data/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
- data/crates/itsi_server/src/ruby_types/{itsi_grpc_stream → itsi_grpc_response_stream}/mod.rs +121 -73
- data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +103 -40
- data/crates/itsi_server/src/ruby_types/itsi_http_response.rs +8 -5
- data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +4 -4
- data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +37 -17
- data/crates/itsi_server/src/ruby_types/itsi_server.rs +4 -3
- data/crates/itsi_server/src/ruby_types/mod.rs +6 -13
- data/crates/itsi_server/src/server/{bind.rs → binds/bind.rs} +23 -4
- data/crates/itsi_server/src/server/{listener.rs → binds/listener.rs} +24 -10
- data/crates/itsi_server/src/server/binds/mod.rs +4 -0
- data/crates/itsi_server/src/server/{tls.rs → binds/tls.rs} +9 -4
- data/crates/itsi_server/src/server/http_message_types.rs +97 -0
- data/crates/itsi_server/src/server/io_stream.rs +2 -1
- data/crates/itsi_server/src/server/middleware_stack/middleware.rs +28 -16
- data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +17 -8
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +47 -18
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +13 -9
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +145 -181
- data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +5 -2
- data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +37 -48
- data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +25 -20
- data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +14 -7
- data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +190 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +125 -95
- data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +9 -5
- data/crates/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +1 -4
- data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +25 -19
- data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +4 -4
- data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +9 -4
- data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +260 -62
- data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +29 -22
- data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +6 -6
- data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +6 -5
- data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +4 -2
- data/crates/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +51 -18
- data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +31 -13
- data/crates/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +13 -8
- data/crates/itsi_server/src/server/middleware_stack/mod.rs +101 -69
- data/crates/itsi_server/src/server/mod.rs +3 -9
- data/crates/itsi_server/src/server/process_worker.rs +21 -3
- data/crates/itsi_server/src/server/request_job.rs +2 -2
- data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +8 -3
- data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +26 -26
- data/crates/itsi_server/src/server/signal.rs +24 -41
- data/crates/itsi_server/src/server/size_limited_incoming.rs +101 -0
- data/crates/itsi_server/src/server/thread_worker.rs +59 -28
- data/crates/itsi_server/src/services/itsi_http_service.rs +239 -0
- data/crates/itsi_server/src/services/mime_types.rs +1416 -0
- data/crates/itsi_server/src/services/mod.rs +6 -0
- data/crates/itsi_server/src/services/password_hasher.rs +83 -0
- data/crates/itsi_server/src/{server → services}/rate_limiter.rs +35 -31
- data/crates/itsi_server/src/{server → services}/static_file_server.rs +521 -181
- data/crates/itsi_tracing/src/lib.rs +145 -55
- data/{Itsi.rb → foo/Itsi.rb} +6 -9
- data/gems/scheduler/Cargo.lock +7 -0
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/scheduler/test/helpers/test_helper.rb +0 -1
- data/gems/scheduler/test/test_address_resolve.rb +0 -1
- data/gems/scheduler/test/test_network_io.rb +1 -1
- data/gems/scheduler/test/test_process_wait.rb +0 -1
- data/gems/server/Cargo.lock +113 -593
- data/gems/server/exe/itsi +65 -19
- data/gems/server/itsi-server.gemspec +4 -3
- data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +74 -0
- data/gems/server/lib/itsi/http_request.rb +116 -17
- data/gems/server/lib/itsi/http_response.rb +2 -0
- data/gems/server/lib/itsi/passfile.rb +109 -0
- data/gems/server/lib/itsi/server/config/dsl.rb +160 -101
- data/gems/server/lib/itsi/server/config.rb +58 -23
- data/gems/server/lib/itsi/server/default_app/default_app.rb +25 -29
- data/gems/server/lib/itsi/server/default_app/index.html +113 -89
- data/gems/server/lib/itsi/server/{Itsi.rb → default_config/Itsi-rackup.rb} +1 -1
- data/gems/server/lib/itsi/server/default_config/Itsi.rb +107 -0
- data/gems/server/lib/itsi/server/grpc/grpc_call.rb +246 -0
- data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +100 -0
- data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_pb.rb +26 -0
- data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_services_pb.rb +122 -0
- data/gems/server/lib/itsi/server/route_tester.rb +107 -0
- data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +200 -0
- data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +55 -0
- data/gems/server/lib/itsi/server/typed_handlers.rb +17 -0
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/gems/server/lib/itsi/server.rb +82 -12
- data/gems/server/lib/ruby_lsp/itsi/addon.rb +111 -0
- data/gems/server/lib/shell_completions/completions.rb +26 -0
- data/gems/server/test/helpers/test_helper.rb +2 -1
- data/lib/itsi/version.rb +1 -1
- data/sandbox/README.md +5 -0
- data/sandbox/itsi_file/Gemfile +4 -2
- data/sandbox/itsi_file/Gemfile.lock +48 -6
- data/sandbox/itsi_file/Itsi.rb +326 -129
- data/sandbox/itsi_file/call.json +1 -0
- data/sandbox/itsi_file/echo_client/Gemfile +10 -0
- data/sandbox/itsi_file/echo_client/Gemfile.lock +27 -0
- data/sandbox/itsi_file/echo_client/README.md +95 -0
- data/sandbox/itsi_file/echo_client/echo_client.rb +164 -0
- data/sandbox/itsi_file/echo_client/gen_proto.sh +17 -0
- data/sandbox/itsi_file/echo_client/lib/echo_pb.rb +16 -0
- data/sandbox/itsi_file/echo_client/lib/echo_services_pb.rb +29 -0
- data/sandbox/itsi_file/echo_client/run_client.rb +64 -0
- data/sandbox/itsi_file/echo_client/test_compressions.sh +20 -0
- data/sandbox/itsi_file/echo_service_nonitsi/Gemfile +10 -0
- data/sandbox/itsi_file/echo_service_nonitsi/Gemfile.lock +79 -0
- data/sandbox/itsi_file/echo_service_nonitsi/echo.proto +26 -0
- data/sandbox/itsi_file/echo_service_nonitsi/echo_pb.rb +16 -0
- data/sandbox/itsi_file/echo_service_nonitsi/echo_services_pb.rb +29 -0
- data/sandbox/itsi_file/echo_service_nonitsi/server.rb +52 -0
- data/sandbox/itsi_sandbox_async/config.ru +0 -1
- data/sandbox/itsi_sandbox_rack/Gemfile.lock +2 -2
- data/sandbox/itsi_sandbox_rails/Gemfile +2 -2
- data/sandbox/itsi_sandbox_rails/Gemfile.lock +76 -2
- data/sandbox/itsi_sandbox_rails/app/controllers/home_controller.rb +15 -0
- data/sandbox/itsi_sandbox_rails/config/environments/development.rb +1 -0
- data/sandbox/itsi_sandbox_rails/config/environments/production.rb +1 -0
- data/sandbox/itsi_sandbox_rails/config/routes.rb +2 -0
- data/sandbox/itsi_sinatra/app.rb +0 -1
- data/sandbox/static_files/.env +1 -0
- data/sandbox/static_files/404.html +25 -0
- data/sandbox/static_files/_DSC0102.NEF.jpg +0 -0
- data/sandbox/static_files/about.html +68 -0
- data/sandbox/static_files/tiny.html +1 -0
- data/sandbox/static_files/writebook.zip +0 -0
- data/tasks.txt +28 -32
- metadata +87 -26
- data/crates/itsi_error/src/from.rs +0 -68
- data/crates/itsi_server/src/ruby_types/itsi_grpc_request.rs +0 -147
- data/crates/itsi_server/src/ruby_types/itsi_grpc_response.rs +0 -19
- data/crates/itsi_server/src/server/itsi_service.rs +0 -172
- data/crates/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +0 -72
- data/crates/itsi_server/src/server/types.rs +0 -43
- data/gems/server/lib/itsi/server/grpc_interface.rb +0 -213
- data/sandbox/itsi_file/public/assets/index.html +0 -1
- /data/crates/itsi_server/src/server/{bind_protocol.rs → binds/bind_protocol.rs} +0 -0
- /data/crates/itsi_server/src/server/{tls → binds/tls}/locked_dir_cache.rs +0 -0
- /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::
|
3
|
-
|
4
|
-
|
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
|
11
|
-
|
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
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
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<
|
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<
|
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<
|
151
|
+
.collect::<itsi_error::Result<HashMap<JwtAlgorithm, Vec<DecodingKey>>>>()?;
|
218
152
|
self.keys
|
219
153
|
.set(keys)
|
220
|
-
.map_err(|
|
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
|
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
|
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
|
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
|
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
|
193
|
+
self.error_response
|
194
|
+
.to_http_response(req.accept().into())
|
195
|
+
.await,
|
263
196
|
));
|
264
197
|
}
|
265
|
-
let
|
198
|
+
let keys = self.keys.get().unwrap().get(&alg).unwrap();
|
266
199
|
|
267
|
-
|
268
|
-
|
269
|
-
|
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
|
215
|
+
if let Some(leeway) = self.leeway {
|
216
|
+
validation.leeway = leeway;
|
217
|
+
}
|
274
218
|
|
275
|
-
let
|
276
|
-
|
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
|
232
|
+
self.error_response
|
233
|
+
.to_http_response(req.accept().into())
|
234
|
+
.await,
|
279
235
|
));
|
280
|
-
}
|
236
|
+
};
|
281
237
|
|
282
|
-
let claims =
|
238
|
+
let claims = token_data.claims;
|
283
239
|
|
284
240
|
if let Some(expected_audiences) = &self.audiences {
|
285
|
-
|
286
|
-
|
287
|
-
|
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
|
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
|
-
|
297
|
-
|
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
|
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
|
-
|
308
|
-
|
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
|
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
|
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
|
-
|
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,
|
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
|
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 {
|