this_env/middleware/actix/
service.rs1use std::rc::Rc;
4use std::task::{Context, Poll};
5use actix_service::Service;
6use actix_web::dev::{ServiceRequest, ServiceResponse};
7use actix_web::Error;
8use actix_web::body::{BoxBody, EitherBody};
9use futures_util::future::LocalBoxFuture;
10use crate::middleware::actix::ActixMwConfig;
11pub struct ActixMiddlewareService<S> {
14 pub(crate) service: Rc<S>,
15 pub(crate) config: ActixMwConfig,
16}
17
18impl<S, B> Service<ServiceRequest> for ActixMiddlewareService<S>
19where
20 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
21 S::Future: 'static,
22 B: 'static,
23{
24 type Response = ServiceResponse<EitherBody<B, BoxBody>>;
25 type Error = Error;
26 type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
27
28 fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
29 self.service.poll_ready(ctx)
30 }
31
32 fn call(&self, req: ServiceRequest) -> Self::Future {
33 use crate::middleware::env_request::{EnvRequest, EnvRequestHttp, EnvRequestWs};
34 use crate::env::{Env, EnvStatus};
35 use actix_web::{HttpResponse, http::StatusCode};
36
37 log::info!("🧩 ActixMiddleware: interceptando request {} {}", req.method(), req.path());
38
39 let svc = Rc::clone(&self.service);
40 let config = self.config.clone();
41
42 let mut headers = std::collections::HashMap::new();
43 for (key, value) in req.headers().iter() {
44 if let Ok(val) = value.to_str() {
45 headers.insert(key.to_string(), val.to_string());
46 }
47 }
48
49 let host = req
50 .headers()
51 .get("host")
52 .and_then(|v| v.to_str().ok())
53 .unwrap_or_default()
54 .to_string();
55 let ip = req.connection_info().realip_remote_addr().map(|s| s.to_string());
56 let method = req.method().to_string();
57 let path = req.path().to_string();
58
59 let is_ws = req
60 .headers()
61 .get("upgrade")
62 .and_then(|v| v.to_str().ok())
63 .map(|v| v.eq_ignore_ascii_case("websocket"))
64 .unwrap_or(false);
65 let env_request_result = (|| {
66 if is_ws {
67 Some(EnvRequest::Ws(EnvRequestWs {
68 host,
69 ip,
70 headers,
71 payload: None,
72 }))
73 } else {
74 Some(EnvRequest::Http(EnvRequestHttp {
75 host,
76 ip,
77 method,
78 path,
79 headers,
80 }))
81 }
82 })();
83
84 let accepts_html = req
85 .headers()
86 .get("accept")
87 .and_then(|v| v.to_str().ok())
88 .map(|v| v.contains("text/html"))
89 .unwrap_or(false);
90
91 if config.manual_mode {
92 if let Some(env_request) = &env_request_result {
93 match Env::resolve(env_request) {
94 Ok(status) => log::info!("this.env status (manual mode): {:?}", status),
95 Err(e) => log::error!("this.env resolve error (manual mode): {:?}", e),
96 }
97 }
98 return Box::pin(async move {
99 let res = svc.call(req).await?;
100 Ok(res.map_into_left_body())
101 });
102 }
103
104 let decision_status = match env_request_result.as_ref().and_then(|env_request| Env::resolve(env_request).ok()) {
105 Some(EnvStatus::PendingApproval(_)) if config.allow_pending => EnvStatus::Approved,
106 Some(EnvStatus::Blocked(_)) if config.allow_blocked => EnvStatus::Approved,
107 Some(status) => status,
108 None => {
109 if let Some(env_request) = &env_request_result {
110 if let Err(e) = Env::resolve(env_request) {
111 log::error!("this.env resolve error: {e:?}");
112 }
113 }
114 EnvStatus::Blocked("internal-error".into())
115 }
116 };
117
118 let svc_clone = Rc::clone(&svc);
119 let req_clone = req;
120
121 Box::pin(async move {
122 if let Some(_env_request) = env_request_result {
123 match decision_status {
124EnvStatus::Approved => {
129 let res = svc_clone.call(req_clone).await?;
130 return Ok(res.map_into_left_body());
131 }
132EnvStatus::PendingApproval(_) => {
137 let wants_html = accepts_html || config.prefer_html;
138 if wants_html {
139 let resp = HttpResponse::build(StatusCode::UNAUTHORIZED)
140 .content_type("text/html")
141 .body(include_str!("../../html/pending_approval.html"));
142 return Ok(req_clone.into_response(resp.map_into_right_body()));
143 } else {
144 let resp = HttpResponse::build(StatusCode::UNAUTHORIZED)
145 .content_type("text/plain")
146 .body("PendingApproval");
147 return Ok(req_clone.into_response(resp.map_into_right_body()));
148 }
149 }
150EnvStatus::Blocked(_) => {
155 let wants_html = accepts_html || config.prefer_html;
156 if wants_html {
157 let resp = HttpResponse::build(StatusCode::FORBIDDEN)
158 .content_type("text/html")
159 .body(include_str!("../../html/blocked.html"));
160 return Ok(req_clone.into_response(resp.map_into_right_body()));
161 } else {
162 let resp = HttpResponse::build(StatusCode::FORBIDDEN)
163 .content_type("text/plain")
164 .body("Blocked");
165 return Ok(req_clone.into_response(resp.map_into_right_body()));
166 }
167 }
168 }
169 }
170 let res = svc_clone.call(req_clone).await?;
171 Ok(res.map_into_left_body())
172 })
173 }
174}