gatel_core/hoops/
forward_auth.rs1use salvo::http::StatusCode;
2use salvo::{Depot, FlowCtrl, Request, Response, async_trait};
3use tracing::debug;
4
5pub struct ForwardAuthHoop {
16 auth_url: String,
17 copy_headers: Vec<String>,
18 client: reqwest::Client,
19}
20
21impl ForwardAuthHoop {
22 pub fn new(auth_url: String, copy_headers: Vec<String>) -> Self {
23 Self {
24 auth_url,
25 copy_headers,
26 client: reqwest::Client::new(),
27 }
28 }
29}
30
31#[async_trait]
32impl salvo::Handler for ForwardAuthHoop {
33 async fn handle(
34 &self,
35 req: &mut Request,
36 depot: &mut Depot,
37 res: &mut Response,
38 ctrl: &mut FlowCtrl,
39 ) {
40 let forwarded_uri = req
43 .uri()
44 .path_and_query()
45 .map(|pq| pq.as_str().to_string())
46 .unwrap_or_else(|| "/".to_string());
47 let forwarded_method = req.method().to_string();
48
49 let mut auth_req = self.client.get(&self.auth_url);
50
51 for (name, value) in req.headers() {
53 if let Ok(val_str) = value.to_str() {
54 auth_req = auth_req.header(name.as_str(), val_str);
55 }
56 }
57
58 auth_req = auth_req
60 .header("X-Forwarded-Uri", &forwarded_uri)
61 .header("X-Forwarded-Method", &forwarded_method);
62
63 let auth_resp = match auth_req.send().await {
64 Ok(r) => r,
65 Err(e) => {
66 debug!(error = %e, "forward-auth request failed");
67 res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
68 res.body(format!("forward-auth request failed: {e}"));
69 ctrl.skip_rest();
70 return;
71 }
72 };
73
74 let auth_status = auth_resp.status();
75
76 if auth_status.is_success() {
77 for header_name in &self.copy_headers {
80 if let Some(value) = auth_resp.headers().get(header_name.as_str())
81 && let Ok(hn) = header_name.parse::<http::header::HeaderName>()
82 && let Ok(hv) = http::header::HeaderValue::from_bytes(value.as_bytes())
83 {
84 req.headers_mut().insert(hn, hv);
85 }
86 }
87
88 debug!(auth_url = %self.auth_url, "forward-auth passed, continuing chain");
89 ctrl.call_next(req, depot, res).await;
90 } else {
91 debug!(
93 auth_url = %self.auth_url,
94 status = %auth_status,
95 "forward-auth denied, returning auth response"
96 );
97
98 let status =
99 StatusCode::from_u16(auth_status.as_u16()).unwrap_or(StatusCode::UNAUTHORIZED);
100
101 let auth_resp_headers = auth_resp.headers().clone();
103
104 let body_bytes = auth_resp.bytes().await.unwrap_or_default();
106
107 res.status_code(status);
108
109 for (name, value) in &auth_resp_headers {
112 if name.as_str().eq_ignore_ascii_case("transfer-encoding") {
113 continue;
114 }
115 if let Ok(hv) = http::header::HeaderValue::from_bytes(value.as_bytes()) {
116 res.headers_mut().insert(name.clone(), hv);
117 }
118 }
119
120 res.body(body_bytes.to_vec());
121 ctrl.skip_rest();
122 }
123 }
124}