itsi-server 0.1.1 → 0.1.13

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.

Potentially problematic release.


This version of itsi-server might be problematic. Click here for more details.

Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/CODE_OF_CONDUCT.md +7 -0
  4. data/Cargo.lock +4417 -0
  5. data/Cargo.toml +7 -0
  6. data/README.md +4 -0
  7. data/Rakefile +8 -1
  8. data/_index.md +6 -0
  9. data/exe/itsi +94 -45
  10. data/ext/itsi_error/Cargo.toml +2 -0
  11. data/ext/itsi_error/src/from.rs +68 -0
  12. data/ext/itsi_error/src/lib.rs +18 -34
  13. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  14. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  15. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  16. data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
  17. data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
  18. data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
  19. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
  20. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
  21. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
  22. data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
  23. data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
  24. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
  25. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
  26. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
  27. data/ext/itsi_instrument_entry/Cargo.toml +15 -0
  28. data/ext/itsi_instrument_entry/src/lib.rs +31 -0
  29. data/ext/itsi_rb_helpers/Cargo.toml +3 -0
  30. data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
  31. data/ext/itsi_rb_helpers/src/lib.rs +140 -10
  32. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  33. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  34. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  35. data/ext/itsi_rb_helpers/target/debug/build/rb-sys-eb9ed4ff3a60f995/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
  36. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
  37. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
  38. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
  39. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
  40. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
  41. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
  42. data/ext/itsi_scheduler/Cargo.toml +24 -0
  43. data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  44. data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  45. data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  46. data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
  47. data/ext/itsi_scheduler/src/lib.rs +38 -0
  48. data/ext/itsi_server/Cargo.lock +2956 -0
  49. data/ext/itsi_server/Cargo.toml +73 -13
  50. data/ext/itsi_server/extconf.rb +1 -1
  51. data/ext/itsi_server/src/env.rs +43 -0
  52. data/ext/itsi_server/src/lib.rs +100 -40
  53. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +109 -0
  54. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +141 -0
  55. data/ext/itsi_server/src/ruby_types/itsi_grpc_request.rs +147 -0
  56. data/ext/itsi_server/src/ruby_types/itsi_grpc_response.rs +19 -0
  57. data/ext/itsi_server/src/ruby_types/itsi_grpc_stream/mod.rs +216 -0
  58. data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +282 -0
  59. data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +388 -0
  60. data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
  61. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +355 -0
  62. data/ext/itsi_server/src/ruby_types/itsi_server.rs +82 -0
  63. data/ext/itsi_server/src/ruby_types/mod.rs +55 -0
  64. data/ext/itsi_server/src/server/bind.rs +75 -31
  65. data/ext/itsi_server/src/server/bind_protocol.rs +37 -0
  66. data/ext/itsi_server/src/server/byte_frame.rs +32 -0
  67. data/ext/itsi_server/src/server/cache_store.rs +74 -0
  68. data/ext/itsi_server/src/server/io_stream.rs +104 -0
  69. data/ext/itsi_server/src/server/itsi_service.rs +172 -0
  70. data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
  71. data/ext/itsi_server/src/server/listener.rs +332 -132
  72. data/ext/itsi_server/src/server/middleware_stack/middleware.rs +153 -0
  73. data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +47 -0
  74. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +58 -0
  75. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +82 -0
  76. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +321 -0
  77. data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +139 -0
  78. data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +300 -0
  79. data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +287 -0
  80. data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +48 -0
  81. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +127 -0
  82. data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +191 -0
  83. data/ext/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +72 -0
  84. data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +85 -0
  85. data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +195 -0
  86. data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
  87. data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +82 -0
  88. data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +216 -0
  89. data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +124 -0
  90. data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
  91. data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +43 -0
  92. data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +34 -0
  93. data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +93 -0
  94. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +162 -0
  95. data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +158 -0
  96. data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
  97. data/ext/itsi_server/src/server/middleware_stack/mod.rs +315 -0
  98. data/ext/itsi_server/src/server/mod.rs +15 -2
  99. data/ext/itsi_server/src/server/process_worker.rs +229 -0
  100. data/ext/itsi_server/src/server/rate_limiter.rs +565 -0
  101. data/ext/itsi_server/src/server/request_job.rs +11 -0
  102. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +337 -0
  103. data/ext/itsi_server/src/server/serve_strategy/mod.rs +30 -0
  104. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +421 -0
  105. data/ext/itsi_server/src/server/signal.rs +93 -0
  106. data/ext/itsi_server/src/server/static_file_server.rs +984 -0
  107. data/ext/itsi_server/src/server/thread_worker.rs +444 -0
  108. data/ext/itsi_server/src/server/tls/locked_dir_cache.rs +132 -0
  109. data/ext/itsi_server/src/server/tls.rs +187 -60
  110. data/ext/itsi_server/src/server/types.rs +43 -0
  111. data/ext/itsi_tracing/Cargo.toml +5 -0
  112. data/ext/itsi_tracing/src/lib.rs +225 -7
  113. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
  114. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
  115. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
  116. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
  117. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
  118. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
  119. data/lib/itsi/http_request.rb +87 -0
  120. data/lib/itsi/http_response.rb +39 -0
  121. data/lib/itsi/server/Itsi.rb +119 -0
  122. data/lib/itsi/server/config/dsl.rb +506 -0
  123. data/lib/itsi/server/config.rb +131 -0
  124. data/lib/itsi/server/default_app/default_app.rb +38 -0
  125. data/lib/itsi/server/default_app/index.html +91 -0
  126. data/lib/itsi/server/grpc_interface.rb +213 -0
  127. data/lib/itsi/server/rack/handler/itsi.rb +27 -0
  128. data/lib/itsi/server/rack_interface.rb +94 -0
  129. data/lib/itsi/server/scheduler_interface.rb +21 -0
  130. data/lib/itsi/server/scheduler_mode.rb +10 -0
  131. data/lib/itsi/server/signal_trap.rb +29 -0
  132. data/lib/itsi/server/version.rb +1 -1
  133. data/lib/itsi/server.rb +90 -9
  134. data/lib/itsi/standard_headers.rb +86 -0
  135. metadata +122 -31
  136. data/ext/itsi_server/src/request/itsi_request.rs +0 -143
  137. data/ext/itsi_server/src/request/mod.rs +0 -1
  138. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
  139. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
  140. data/ext/itsi_server/src/server/itsi_server.rs +0 -182
  141. data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
  142. data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
  143. data/lib/itsi/request.rb +0 -39
@@ -0,0 +1,153 @@
1
+ use super::middlewares::*;
2
+ use crate::server::{
3
+ itsi_service::RequestContext,
4
+ types::{HttpRequest, HttpResponse},
5
+ };
6
+ use async_trait::async_trait;
7
+ use either::Either;
8
+ use magnus::error::Result;
9
+ use std::cmp::Ordering;
10
+
11
+ #[derive(Debug)]
12
+ pub enum Middleware {
13
+ AllowList(AllowList),
14
+ AuthAPIKey(AuthAPIKey),
15
+ AuthBasic(AuthBasic),
16
+ AuthJwt(Box<AuthJwt>),
17
+ CacheControl(CacheControl),
18
+ Compression(Compression),
19
+ Cors(Box<Cors>),
20
+ DenyList(DenyList),
21
+ ETag(ETag),
22
+ IntrusionProtection(IntrusionProtection),
23
+ LogRequests(LogRequests),
24
+ Proxy(Proxy),
25
+ RateLimit(RateLimit),
26
+ Redirect(Redirect),
27
+ RequestHeaders(RequestHeaders),
28
+ ResponseHeaders(ResponseHeaders),
29
+ RubyApp(RubyApp),
30
+ StaticAssets(StaticAssets),
31
+ }
32
+
33
+ #[async_trait]
34
+ impl MiddlewareLayer for Middleware {
35
+ /// Called just once, to initialize the middleware state.
36
+ async fn initialize(&self) -> Result<()> {
37
+ match self {
38
+ Middleware::DenyList(filter) => filter.initialize().await,
39
+ Middleware::AllowList(filter) => filter.initialize().await,
40
+ Middleware::AuthBasic(filter) => filter.initialize().await,
41
+ Middleware::AuthJwt(filter) => filter.initialize().await,
42
+ Middleware::AuthAPIKey(filter) => filter.initialize().await,
43
+ Middleware::IntrusionProtection(filter) => filter.initialize().await,
44
+ Middleware::RateLimit(filter) => filter.initialize().await,
45
+ Middleware::RequestHeaders(filter) => filter.initialize().await,
46
+ Middleware::ResponseHeaders(filter) => filter.initialize().await,
47
+ Middleware::CacheControl(filter) => filter.initialize().await,
48
+ Middleware::Cors(filter) => filter.initialize().await,
49
+ Middleware::ETag(filter) => filter.initialize().await,
50
+ Middleware::StaticAssets(filter) => filter.initialize().await,
51
+ Middleware::Compression(filter) => filter.initialize().await,
52
+ Middleware::LogRequests(filter) => filter.initialize().await,
53
+ Middleware::Redirect(filter) => filter.initialize().await,
54
+ Middleware::Proxy(filter) => filter.initialize().await,
55
+ Middleware::RubyApp(filter) => filter.initialize().await,
56
+ }
57
+ }
58
+
59
+ async fn before(
60
+ &self,
61
+ req: HttpRequest,
62
+ context: &mut RequestContext,
63
+ ) -> Result<Either<HttpRequest, HttpResponse>> {
64
+ match self {
65
+ Middleware::DenyList(filter) => filter.before(req, context).await,
66
+ Middleware::AllowList(filter) => filter.before(req, context).await,
67
+ Middleware::AuthBasic(filter) => filter.before(req, context).await,
68
+ Middleware::AuthJwt(filter) => filter.before(req, context).await,
69
+ Middleware::AuthAPIKey(filter) => filter.before(req, context).await,
70
+ Middleware::IntrusionProtection(filter) => filter.before(req, context).await,
71
+ Middleware::RequestHeaders(filter) => filter.before(req, context).await,
72
+ Middleware::ResponseHeaders(filter) => filter.before(req, context).await,
73
+ Middleware::RateLimit(filter) => filter.before(req, context).await,
74
+ Middleware::CacheControl(filter) => filter.before(req, context).await,
75
+ Middleware::Cors(filter) => filter.before(req, context).await,
76
+ Middleware::ETag(filter) => filter.before(req, context).await,
77
+ Middleware::StaticAssets(filter) => filter.before(req, context).await,
78
+ Middleware::Compression(filter) => filter.before(req, context).await,
79
+ Middleware::LogRequests(filter) => filter.before(req, context).await,
80
+ Middleware::Redirect(filter) => filter.before(req, context).await,
81
+ Middleware::Proxy(filter) => filter.before(req, context).await,
82
+ Middleware::RubyApp(filter) => filter.before(req, context).await,
83
+ }
84
+ }
85
+
86
+ async fn after(&self, res: HttpResponse, context: &mut RequestContext) -> HttpResponse {
87
+ match self {
88
+ Middleware::DenyList(filter) => filter.after(res, context).await,
89
+ Middleware::AllowList(filter) => filter.after(res, context).await,
90
+ Middleware::AuthBasic(filter) => filter.after(res, context).await,
91
+ Middleware::AuthJwt(filter) => filter.after(res, context).await,
92
+ Middleware::AuthAPIKey(filter) => filter.after(res, context).await,
93
+ Middleware::IntrusionProtection(filter) => filter.after(res, context).await,
94
+ Middleware::RateLimit(filter) => filter.after(res, context).await,
95
+ Middleware::RequestHeaders(filter) => filter.after(res, context).await,
96
+ Middleware::ResponseHeaders(filter) => filter.after(res, context).await,
97
+ Middleware::CacheControl(filter) => filter.after(res, context).await,
98
+ Middleware::Cors(filter) => filter.after(res, context).await,
99
+ Middleware::ETag(filter) => filter.after(res, context).await,
100
+ Middleware::StaticAssets(filter) => filter.after(res, context).await,
101
+ Middleware::Compression(filter) => filter.after(res, context).await,
102
+ Middleware::LogRequests(filter) => filter.after(res, context).await,
103
+ Middleware::Redirect(filter) => filter.after(res, context).await,
104
+ Middleware::Proxy(filter) => filter.after(res, context).await,
105
+ Middleware::RubyApp(filter) => filter.after(res, context).await,
106
+ }
107
+ }
108
+ }
109
+
110
+ impl Middleware {
111
+ fn variant_order(&self) -> usize {
112
+ match self {
113
+ Middleware::DenyList(_) => 0,
114
+ Middleware::AllowList(_) => 1,
115
+ Middleware::IntrusionProtection(_) => 2,
116
+ Middleware::Redirect(_) => 3,
117
+ Middleware::LogRequests(_) => 4,
118
+ Middleware::CacheControl(_) => 5,
119
+ Middleware::RequestHeaders(_) => 6,
120
+ Middleware::ResponseHeaders(_) => 7,
121
+ Middleware::AuthBasic(_) => 8,
122
+ Middleware::AuthJwt(_) => 9,
123
+ Middleware::AuthAPIKey(_) => 10,
124
+ Middleware::RateLimit(_) => 11,
125
+ Middleware::ETag(_) => 12,
126
+ Middleware::Compression(_) => 13,
127
+ Middleware::Proxy(_) => 14,
128
+ Middleware::Cors(_) => 15,
129
+ Middleware::StaticAssets(_) => 16,
130
+ Middleware::RubyApp(_) => 17,
131
+ }
132
+ }
133
+ }
134
+
135
+ impl PartialEq for Middleware {
136
+ fn eq(&self, other: &Self) -> bool {
137
+ self.variant_order() == other.variant_order()
138
+ }
139
+ }
140
+
141
+ impl Eq for Middleware {}
142
+
143
+ impl PartialOrd for Middleware {
144
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
145
+ Some(self.variant_order().cmp(&other.variant_order()))
146
+ }
147
+ }
148
+
149
+ impl Ord for Middleware {
150
+ fn cmp(&self, other: &Self) -> Ordering {
151
+ self.variant_order().cmp(&other.variant_order())
152
+ }
153
+ }
@@ -0,0 +1,47 @@
1
+ use super::{ErrorResponse, FromValue, MiddlewareLayer};
2
+ use crate::server::{
3
+ itsi_service::RequestContext,
4
+ types::{HttpRequest, HttpResponse},
5
+ };
6
+ use async_trait::async_trait;
7
+ use either::Either;
8
+ use itsi_error::ItsiError;
9
+ use magnus::error::Result;
10
+ use regex::RegexSet;
11
+ use serde::Deserialize;
12
+ use std::sync::OnceLock;
13
+
14
+ #[derive(Debug, Clone, Deserialize)]
15
+ pub struct AllowList {
16
+ #[serde(skip_deserializing)]
17
+ pub allowed_ips: OnceLock<RegexSet>,
18
+ pub allowed_patterns: Vec<String>,
19
+ pub error_response: ErrorResponse,
20
+ }
21
+
22
+ #[async_trait]
23
+ impl MiddlewareLayer for AllowList {
24
+ async fn initialize(&self) -> Result<()> {
25
+ let allowed_ips = RegexSet::new(&self.allowed_patterns).map_err(ItsiError::default)?;
26
+ self.allowed_ips
27
+ .set(allowed_ips)
28
+ .map_err(|e| ItsiError::default(format!("Failed to set allowed IPs: {:?}", e)))?;
29
+ Ok(())
30
+ }
31
+
32
+ async fn before(
33
+ &self,
34
+ req: HttpRequest,
35
+ context: &mut RequestContext,
36
+ ) -> Result<Either<HttpRequest, HttpResponse>> {
37
+ if let Some(allowed_ips) = self.allowed_ips.get() {
38
+ if !allowed_ips.is_match(&context.addr) {
39
+ return Ok(Either::Right(
40
+ self.error_response.to_http_response(&req).await,
41
+ ));
42
+ }
43
+ }
44
+ Ok(Either::Left(req))
45
+ }
46
+ }
47
+ impl FromValue for AllowList {}
@@ -0,0 +1,58 @@
1
+ use crate::server::{
2
+ itsi_service::RequestContext,
3
+ types::{HttpRequest, HttpResponse, RequestExt},
4
+ };
5
+
6
+ use super::{error_response::ErrorResponse, token_source::TokenSource, FromValue, MiddlewareLayer};
7
+
8
+ use async_trait::async_trait;
9
+ use either::Either;
10
+ use magnus::error::Result;
11
+ use serde::Deserialize;
12
+
13
+ /// A simple API key filter.
14
+ /// The API key can be given inside the header or a query string
15
+ /// Keys are validated against a list of allowed key values (Changing these requires a restart)
16
+ ///
17
+ #[derive(Debug, Clone, Deserialize)]
18
+ pub struct AuthAPIKey {
19
+ pub valid_keys: Vec<String>,
20
+ pub token_source: TokenSource,
21
+ pub error_response: ErrorResponse,
22
+ }
23
+
24
+ #[async_trait]
25
+ impl MiddlewareLayer for AuthAPIKey {
26
+ async fn before(
27
+ &self,
28
+ req: HttpRequest,
29
+ _context: &mut RequestContext,
30
+ ) -> Result<Either<HttpRequest, HttpResponse>> {
31
+ let submitted_value = match &self.token_source {
32
+ TokenSource::Header { name, prefix } => {
33
+ if let Some(header) = req.header(name) {
34
+ if let Some(prefix) = prefix {
35
+ Some(header.strip_prefix(prefix).unwrap_or("").trim_ascii())
36
+ } else {
37
+ Some(header.trim_ascii())
38
+ }
39
+ } else {
40
+ None
41
+ }
42
+ }
43
+ TokenSource::Query(query_name) => req.query_param(query_name),
44
+ };
45
+ if !self
46
+ .valid_keys
47
+ .iter()
48
+ .any(|key| submitted_value.is_some_and(|sv| sv == key))
49
+ {
50
+ Ok(Either::Right(
51
+ self.error_response.to_http_response(&req).await,
52
+ ))
53
+ } else {
54
+ Ok(Either::Left(req))
55
+ }
56
+ }
57
+ }
58
+ impl FromValue for AuthAPIKey {}
@@ -0,0 +1,82 @@
1
+ use async_trait::async_trait;
2
+ use base64::{engine::general_purpose, Engine};
3
+ use bytes::Bytes;
4
+ use either::Either;
5
+ use http::{Response, StatusCode};
6
+ use http_body_util::{combinators::BoxBody, Full};
7
+ use magnus::error::Result;
8
+ use serde::{Deserialize, Serialize};
9
+ use std::collections::HashMap;
10
+ use std::str;
11
+
12
+ use crate::server::{
13
+ itsi_service::RequestContext,
14
+ types::{HttpRequest, HttpResponse, RequestExt},
15
+ };
16
+
17
+ use super::{FromValue, MiddlewareLayer};
18
+
19
+ #[derive(Debug, Clone, Serialize, Deserialize)]
20
+ pub struct AuthBasic {
21
+ pub realm: String,
22
+ /// Maps usernames to passwords.
23
+ pub credential_pairs: HashMap<String, String>,
24
+ }
25
+
26
+ impl AuthBasic {
27
+ fn basic_auth_failed_response(&self) -> HttpResponse {
28
+ Response::builder()
29
+ .status(StatusCode::UNAUTHORIZED)
30
+ .header(
31
+ "WWW-Authenticate",
32
+ format!("Basic realm=\"{}\"", self.realm),
33
+ )
34
+ .body(BoxBody::new(Full::new(Bytes::from("Unauthorized"))))
35
+ .unwrap()
36
+ }
37
+ }
38
+ #[async_trait]
39
+ impl MiddlewareLayer for AuthBasic {
40
+ async fn before(
41
+ &self,
42
+ req: HttpRequest,
43
+ _context: &mut RequestContext,
44
+ ) -> Result<Either<HttpRequest, HttpResponse>> {
45
+ // Retrieve the Authorization header.
46
+ let auth_header = req.header("Authorization");
47
+
48
+ if !auth_header.is_some_and(|header| header.starts_with("Basic ")) {
49
+ return Ok(Either::Right(self.basic_auth_failed_response()));
50
+ }
51
+
52
+ let auth_header = auth_header.unwrap();
53
+
54
+ let encoded_credentials = &auth_header["Basic ".len()..];
55
+ let decoded = match general_purpose::STANDARD.decode(encoded_credentials) {
56
+ Ok(bytes) => bytes,
57
+ Err(_) => {
58
+ return Ok(Either::Right(self.basic_auth_failed_response()));
59
+ }
60
+ };
61
+
62
+ let decoded_str = match str::from_utf8(&decoded) {
63
+ Ok(s) => s,
64
+ Err(_) => {
65
+ return Ok(Either::Right(self.basic_auth_failed_response()));
66
+ }
67
+ };
68
+
69
+ let mut parts = decoded_str.splitn(2, ':');
70
+ let username = parts.next().unwrap_or("");
71
+ let password = parts.next().unwrap_or("");
72
+
73
+ match self.credential_pairs.get(username) {
74
+ Some(expected_password) if expected_password == password => Ok(Either::Left(req)),
75
+ _ => {
76
+ return Ok(Either::Right(self.basic_auth_failed_response()));
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ impl FromValue for AuthBasic {}
@@ -0,0 +1,321 @@
1
+ use super::{error_response::ErrorResponse, token_source::TokenSource, FromValue, MiddlewareLayer};
2
+ use crate::server::{
3
+ itsi_service::RequestContext,
4
+ types::{HttpRequest, HttpResponse, RequestExt},
5
+ };
6
+ use async_trait::async_trait;
7
+ use base64::{engine::general_purpose, Engine};
8
+ use either::Either;
9
+ 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,
18
+ };
19
+ use magnus::error::Result;
20
+ use serde::Deserialize;
21
+ use std::str;
22
+ use std::{
23
+ collections::{HashMap, HashSet},
24
+ sync::OnceLock,
25
+ };
26
+
27
+ #[derive(Debug, Clone, Deserialize)]
28
+ pub struct AuthJwt {
29
+ pub token_source: TokenSource,
30
+ pub verifiers: HashMap<JwtAlgorithm, Vec<String>>,
31
+ #[serde(skip_deserializing)]
32
+ pub keys: OnceLock<HashMap<JwtAlgorithm, Vec<JwtKey>>>,
33
+ pub audiences: Option<HashSet<String>>,
34
+ pub subjects: Option<HashSet<String>>,
35
+ pub issuers: Option<HashSet<String>>,
36
+ pub leeway: Option<u64>,
37
+ pub error_response: ErrorResponse,
38
+ }
39
+
40
+ #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Hash)]
41
+ pub enum JwtAlgorithm {
42
+ #[serde(rename(deserialize = "hs256"))]
43
+ Hs256,
44
+ #[serde(rename(deserialize = "hs384"))]
45
+ Hs384,
46
+ #[serde(rename(deserialize = "hs512"))]
47
+ Hs512,
48
+ #[serde(rename(deserialize = "rs256"))]
49
+ Rs256,
50
+ #[serde(rename(deserialize = "rs384"))]
51
+ Rs384,
52
+ #[serde(rename(deserialize = "rs512"))]
53
+ Rs512,
54
+ #[serde(rename(deserialize = "es256"))]
55
+ Es256,
56
+ #[serde(rename(deserialize = "es384"))]
57
+ Es384,
58
+ #[serde(rename(deserialize = "ps256"))]
59
+ Ps256,
60
+ #[serde(rename(deserialize = "ps384"))]
61
+ Ps384,
62
+ #[serde(rename(deserialize = "ps512"))]
63
+ Ps512,
64
+ }
65
+
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)?),
140
+ }
141
+ }
142
+ }
143
+
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
+ )),
178
+ }
179
+ }
180
+ }
181
+
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
+ }
201
+ }
202
+
203
+ #[async_trait]
204
+ impl MiddlewareLayer for AuthJwt {
205
+ async fn initialize(&self) -> Result<()> {
206
+ let keys: HashMap<JwtAlgorithm, Vec<JwtKey>> = self
207
+ .verifiers
208
+ .iter()
209
+ .map(|(algorithm, key_strings)| {
210
+ let algo = algorithm.clone();
211
+ let keys: Result<Vec<JwtKey>> = key_strings
212
+ .iter()
213
+ .map(|key_string| algorithm.key_from(key_string))
214
+ .collect();
215
+ keys.map(|keys| (algo, keys))
216
+ })
217
+ .collect::<Result<HashMap<JwtAlgorithm, Vec<JwtKey>>>>()?;
218
+ self.keys
219
+ .set(keys)
220
+ .map_err(|e| ItsiError::default(format!("Failed to set keys: {:?}", e)))?;
221
+ Ok(())
222
+ }
223
+
224
+ async fn before(
225
+ &self,
226
+ req: HttpRequest,
227
+ _context: &mut RequestContext,
228
+ ) -> Result<Either<HttpRequest, HttpResponse>> {
229
+ let token_str = match &self.token_source {
230
+ TokenSource::Header { name, prefix } => {
231
+ if let Some(header) = req.header(name) {
232
+ if let Some(prefix) = prefix {
233
+ Some(header.strip_prefix(prefix).unwrap_or("").trim_ascii())
234
+ } else {
235
+ Some(header.trim_ascii())
236
+ }
237
+ } else {
238
+ None
239
+ }
240
+ }
241
+ TokenSource::Query(query_name) => req.query_param(query_name),
242
+ };
243
+
244
+ if token_str.is_none() {
245
+ return Ok(Either::Right(
246
+ self.error_response.to_http_response(&req).await,
247
+ ));
248
+ }
249
+
250
+ let token_str = token_str.unwrap();
251
+ let token_meta = Token::decode_metadata(token_str);
252
+
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() {
261
+ return Ok(Either::Right(
262
+ self.error_response.to_http_response(&req).await,
263
+ ));
264
+ }
265
+ let algorithm = token_meta.unwrap();
266
+
267
+ if !self.verifiers.contains_key(&algorithm) {
268
+ return Ok(Either::Right(
269
+ self.error_response.to_http_response(&req).await,
270
+ ));
271
+ }
272
+
273
+ let keys = self.keys.get().unwrap().get(&algorithm).unwrap();
274
+
275
+ let verified_claims = keys.iter().find_map(|key| key.verify(token_str).ok());
276
+ if verified_claims.is_none() {
277
+ return Ok(Either::Right(
278
+ self.error_response.to_http_response(&req).await,
279
+ ));
280
+ }
281
+
282
+ let claims = verified_claims.unwrap();
283
+
284
+ 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) {
288
+ return Ok(Either::Right(
289
+ self.error_response.to_http_response(&req).await,
290
+ ));
291
+ }
292
+ }
293
+ }
294
+
295
+ 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) {
299
+ return Ok(Either::Right(
300
+ self.error_response.to_http_response(&req).await,
301
+ ));
302
+ }
303
+ }
304
+ }
305
+
306
+ 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) {
310
+ return Ok(Either::Right(
311
+ self.error_response.to_http_response(&req).await,
312
+ ));
313
+ }
314
+ }
315
+ }
316
+
317
+ Ok(Either::Left(req))
318
+ }
319
+ }
320
+
321
+ impl FromValue for AuthJwt {}