batuta/serve/banco/
middleware.rs1use axum::{
7 body::Body,
8 http::{HeaderValue, Request, Response, StatusCode},
9 middleware::Next,
10};
11
12use crate::serve::backends::PrivacyTier;
13
14use super::types::ErrorResponse;
15
16pub async fn privacy_layer(
18 tier: PrivacyTier,
19 request: Request<Body>,
20 next: Next,
21) -> Result<Response<Body>, StatusCode> {
22 if tier == PrivacyTier::Sovereign {
24 if let Some(val) = request.headers().get("x-banco-backend") {
25 let hint = val.to_str().unwrap_or("");
26 let is_external = ![
27 "realizar",
28 "ollama",
29 "llamacpp",
30 "llamafile",
31 "candle",
32 "vllm",
33 "tgi",
34 "localai",
35 ]
36 .iter()
37 .any(|local| hint.eq_ignore_ascii_case(local));
38
39 if is_external {
40 let body = serde_json::to_string(&ErrorResponse::new(
41 "External backend not allowed in Sovereign mode",
42 "privacy_violation",
43 403,
44 ))
45 .unwrap_or_default();
46
47 return Ok(Response::builder()
48 .status(StatusCode::FORBIDDEN)
49 .header("content-type", "application/json")
50 .header("x-privacy-tier", tier_header(tier))
51 .body(Body::from(body))
52 .expect("valid response"));
53 }
54 }
55 }
56
57 if request.method() == axum::http::Method::OPTIONS {
59 return Ok(cors_preflight(tier));
60 }
61
62 let mut response = next.run(request).await;
63 let headers = response.headers_mut();
64 headers.insert("x-privacy-tier", tier_header(tier));
65 headers.insert("access-control-allow-origin", HeaderValue::from_static("*"));
67 headers.insert(
68 "access-control-allow-methods",
69 HeaderValue::from_static("GET, POST, PUT, DELETE, OPTIONS"),
70 );
71 headers.insert(
72 "access-control-allow-headers",
73 HeaderValue::from_static("content-type, authorization, x-banco-backend"),
74 );
75 headers.insert("access-control-expose-headers", HeaderValue::from_static("x-privacy-tier"));
76 Ok(response)
77}
78
79fn cors_preflight(tier: PrivacyTier) -> Response<Body> {
81 Response::builder()
82 .status(StatusCode::NO_CONTENT)
83 .header("access-control-allow-origin", "*")
84 .header("access-control-allow-methods", "GET, POST, PUT, DELETE, OPTIONS")
85 .header("access-control-allow-headers", "content-type, authorization, x-banco-backend")
86 .header("access-control-expose-headers", "x-privacy-tier")
87 .header("access-control-max-age", "86400")
88 .header("x-privacy-tier", tier_header(tier))
89 .body(Body::empty())
90 .expect("valid response")
91}
92
93fn tier_header(tier: PrivacyTier) -> HeaderValue {
94 match tier {
95 PrivacyTier::Sovereign => HeaderValue::from_static("sovereign"),
96 PrivacyTier::Private => HeaderValue::from_static("private"),
97 PrivacyTier::Standard => HeaderValue::from_static("standard"),
98 }
99}