Skip to main content

systemprompt_api/services/middleware/
cors.rs

1//! CORS layer construction from profile config.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use axum::http::Method;
7use systemprompt_models::Config;
8use thiserror::Error;
9use tower_http::cors::{AllowOrigin, CorsLayer};
10
11#[derive(Debug, Error)]
12pub enum CorsError {
13    #[error("Invalid origin '{origin}' in cors_allowed_origins: {reason}")]
14    InvalidOrigin { origin: String, reason: String },
15    #[error("cors_allowed_origins must contain at least one valid origin")]
16    EmptyOrigins,
17}
18
19#[derive(Debug, Clone, Copy)]
20pub struct CorsMiddleware;
21
22impl CorsMiddleware {
23    pub fn build_layer(config: &Config) -> Result<CorsLayer, CorsError> {
24        let mut origins = Vec::new();
25        for origin in &config.cors_allowed_origins {
26            let trimmed = origin.trim();
27            if trimmed.is_empty() {
28                continue;
29            }
30            let header_value =
31                trimmed
32                    .parse::<http::HeaderValue>()
33                    .map_err(|e| CorsError::InvalidOrigin {
34                        origin: origin.clone(),
35                        reason: e.to_string(),
36                    })?;
37            origins.push(header_value);
38        }
39
40        if origins.is_empty() {
41            return Err(CorsError::EmptyOrigins);
42        }
43
44        Ok(CorsLayer::new()
45            .allow_origin(AllowOrigin::list(origins))
46            .allow_credentials(true)
47            .allow_methods([
48                Method::GET,
49                Method::POST,
50                Method::PUT,
51                Method::DELETE,
52                Method::OPTIONS,
53            ])
54            .allow_headers([
55                http::header::AUTHORIZATION,
56                http::header::CONTENT_TYPE,
57                http::header::ACCEPT,
58                http::header::ORIGIN,
59                http::header::ACCESS_CONTROL_REQUEST_METHOD,
60                http::header::ACCESS_CONTROL_REQUEST_HEADERS,
61                http::HeaderName::from_static("mcp-protocol-version"),
62                http::HeaderName::from_static("x-context-id"),
63                http::HeaderName::from_static("x-gateway-conversation-id"),
64                http::HeaderName::from_static("x-provider-request-id"),
65                http::HeaderName::from_static("x-trace-id"),
66                http::HeaderName::from_static("x-call-source"),
67            ])
68            .expose_headers([http::header::WWW_AUTHENTICATE]))
69    }
70}