this_env/middleware/actix/
service.rs

1//this.env/crate/src/middleware/actix/service.rs
2// by suiGn
3use 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;
11/// The middleware service implementation that intercepts the request,
12/// builds an `EnvRequest`, and invokes the handler pipeline.
13pub 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 {
124/* ▗▄▖ ▗▄▄▖ ▗▄▄▖ ▗▄▄▖  ▗▄▖ ▗▖  ▗▖▗▄▄▄▖▗▄▄▄ 
125  ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌  ▐▌▐▌   ▐▌  █
126  ▐▛▀▜▌▐▛▀▘ ▐▛▀▘ ▐▛▀▚▖▐▌ ▐▌▐▌  ▐▌▐▛▀▀▘▐▌  █
127  ▐▌ ▐▌▐▌   ▐▌   ▐▌ ▐▌▝▚▄▞▘ ▝▚▞▘ ▐▙▄▄▖▐▙▄▄*/
128                    EnvStatus::Approved => {
129                        let res = svc_clone.call(req_clone).await?;
130                        return Ok(res.map_into_left_body());
131                    }
132/*▗▄▄▖ ▗▄▄▄▖▗▖  ▗▖▗▄▄▄ ▗▄▄▄▖▗▖  ▗▖ ▗▄▄▖
133  ▐▌ ▐▌▐▌   ▐▛▚▖▐▌▐▌  █  █  ▐▛▚▖▐▌▐▌   
134  ▐▛▀▘ ▐▛▀▀▘▐▌ ▝▜▌▐▌  █  █  ▐▌ ▝▜▌▐▌▝▜▌
135  ▐▌   ▐▙▄▄▖▐▌  ▐▌▐▙▄▄▀▗▄█▄▖▐▌  ▐▌▝▚▄▞▘*/
136                    EnvStatus::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                    }
150/*▗▄▄▖ ▗▖    ▗▄▖  ▗▄▄▖▗▖ ▗▖▗▄▄▄▖▗▄▄▄ 
151  ▐▌ ▐▌▐▌   ▐▌ ▐▌▐▌   ▐▌▗▞▘▐▌   ▐▌  █
152  ▐▛▀▚▖▐▌   ▐▌ ▐▌▐▌   ▐▛▚▖ ▐▛▀▀▘▐▌  █
153  ▐▙▄▞▘▐▙▄▄▖▝▚▄▞▘▝▚▄▄▖▐▌ ▐▌▐▙▄▄▖▐▙▄▄▀*/ 
154                    EnvStatus::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}