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
@@ -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
|
-
|
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
|
258
|
-
|
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
|
2
|
-
|
3
|
-
|
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
|
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
|
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::
|
122
|
-
Middleware::
|
123
|
-
Middleware::
|
124
|
-
Middleware::
|
125
|
-
Middleware::
|
126
|
-
Middleware::
|
127
|
-
Middleware::
|
128
|
-
Middleware::
|
129
|
-
Middleware::
|
130
|
-
Middleware::
|
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
|
2
|
-
|
3
|
-
|
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::
|
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::
|
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
|
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
|
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
|
2
|
-
|
3
|
-
|
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:
|
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
|
38
|
+
_context: &mut HttpRequestContext,
|
30
39
|
) -> Result<Either<HttpRequest, HttpResponse>> {
|
31
|
-
let
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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::
|
13
|
-
|
14
|
-
|
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,
|
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
|
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(
|
75
|
-
|
76
|
-
|
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
|
}
|