Skip to main content

api_gateway/
config.rs

1use serde::{Deserialize, Serialize};
2
3fn default_require_auth_by_default() -> bool {
4    true
5}
6
7fn default_body_limit_bytes() -> usize {
8    16 * 1024 * 1024
9}
10
11/// API gateway configuration - reused from `api_gateway` module
12#[derive(Debug, Clone, Deserialize, Serialize, Default)]
13#[serde(deny_unknown_fields)]
14#[allow(clippy::struct_excessive_bools)]
15pub struct ApiGatewayConfig {
16    pub bind_addr: String,
17    #[serde(default)]
18    pub enable_docs: bool,
19    #[serde(default)]
20    pub cors_enabled: bool,
21    /// Optional detailed CORS configuration
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub cors: Option<CorsConfig>,
24
25    /// `OpenAPI` document metadata
26    #[serde(default)]
27    pub openapi: OpenApiConfig,
28
29    /// Global defaults
30    #[serde(default)]
31    pub defaults: Defaults,
32
33    /// Disable authentication and authorization completely.
34    /// When true, middleware automatically injects a default `SecurityContext` for all requests,
35    /// providing access with no tenant filtering.
36    /// This bypasses all tenant isolation and should only be used for single-user on-premise installations.
37    /// Default: false (authentication required via `AuthN` Resolver).
38    #[serde(default)]
39    pub auth_disabled: bool,
40
41    /// If true, routes without explicit security requirement still require authentication (AuthN-only).
42    #[serde(default = "default_require_auth_by_default")]
43    pub require_auth_by_default: bool,
44
45    /// Optional URL path prefix prepended to every route (e.g. `"/cf"` → `/cf/users`).
46    /// Must start with a leading slash; trailing slashes are stripped automatically.
47    /// Empty string (the default) means no prefix.
48    #[serde(default)]
49    pub prefix_path: String,
50
51    /// Route-level policy configuration.
52    /// Allows early rejection of requests based on token scopes without calling the PDP.
53    /// Rules are evaluated in declaration order (first match wins).
54    #[serde(default)]
55    pub route_policies: RoutePoliciesConfig,
56
57    /// HTTP metrics configuration.
58    #[serde(default)]
59    pub metrics: MetricsConfig,
60}
61
62#[derive(Debug, Clone, Deserialize, Serialize)]
63#[serde(deny_unknown_fields, default)]
64pub struct Defaults {
65    /// Fallback rate limit when operation does not specify one
66    pub rate_limit: RateLimitDefaults,
67    /// Global request body size limit in bytes
68    pub body_limit_bytes: usize,
69}
70
71impl Default for Defaults {
72    fn default() -> Self {
73        Self {
74            rate_limit: RateLimitDefaults::default(),
75            body_limit_bytes: default_body_limit_bytes(),
76        }
77    }
78}
79
80#[derive(Debug, Clone, Deserialize, Serialize)]
81#[serde(deny_unknown_fields, default)]
82pub struct RateLimitDefaults {
83    pub rps: u32,
84    pub burst: u32,
85    pub in_flight: u32,
86}
87
88impl Default for RateLimitDefaults {
89    fn default() -> Self {
90        Self {
91            rps: 50,
92            burst: 100,
93            in_flight: 64,
94        }
95    }
96}
97
98#[derive(Debug, Clone, Deserialize, Serialize)]
99#[serde(deny_unknown_fields, default)]
100pub struct CorsConfig {
101    /// Allowed origins: `["*"]` means any
102    pub allowed_origins: Vec<String>,
103    /// Allowed HTTP methods, e.g. `["GET","POST","OPTIONS","PUT","DELETE","PATCH"]`
104    pub allowed_methods: Vec<String>,
105    /// Allowed request headers; `["*"]` means any
106    pub allowed_headers: Vec<String>,
107    /// Whether to allow credentials
108    pub allow_credentials: bool,
109    /// Max age for preflight caching in seconds
110    pub max_age_seconds: u64,
111}
112
113impl Default for CorsConfig {
114    fn default() -> Self {
115        Self {
116            allowed_origins: vec!["*".to_owned()],
117            allowed_methods: vec![
118                "GET".to_owned(),
119                "POST".to_owned(),
120                "PUT".to_owned(),
121                "PATCH".to_owned(),
122                "DELETE".to_owned(),
123                "OPTIONS".to_owned(),
124            ],
125            allowed_headers: vec!["*".to_owned()],
126            allow_credentials: false,
127            max_age_seconds: 600,
128        }
129    }
130}
131
132/// HTTP metrics configuration.
133#[derive(Debug, Clone, Deserialize, Serialize, Default)]
134#[serde(deny_unknown_fields, default)]
135pub struct MetricsConfig {
136    /// Optional prefix for HTTP metrics instrument names.
137    ///
138    /// When set, metric names become `{prefix}.http.server.request.duration`
139    /// and `{prefix}.http.server.active_requests` instead of the default
140    /// OpenTelemetry semantic convention names.
141    ///
142    /// Empty string (the default) means no prefix — standard `OTel` names are used.
143    pub prefix: String,
144}
145
146/// `OpenAPI` document metadata configuration
147#[derive(Debug, Clone, Deserialize, Serialize)]
148#[serde(deny_unknown_fields, default)]
149pub struct OpenApiConfig {
150    /// API title shown in `OpenAPI` documentation
151    pub title: String,
152    /// API version
153    pub version: String,
154    /// API description (optional)
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub description: Option<String>,
157}
158
159impl Default for OpenApiConfig {
160    fn default() -> Self {
161        Self {
162            title: "API Documentation".to_owned(),
163            version: "0.1.0".to_owned(),
164            description: None,
165        }
166    }
167}
168
169/// Route-level policy configuration.
170///
171/// Enables coarse-grained early rejection of requests based on token scopes
172/// without calling the PDP. This is an optimization for performance-critical routes.
173///
174/// # Example YAML
175///
176/// ```yaml
177/// route_policies:
178///   enabled: true
179///   rules:
180///     - path: "/admin/**"
181///       required_scopes: ["admin"]
182///     - path: "/events/v1/*"
183///       required_scopes: ["read:events", "write:events"]  # any of these
184/// ```
185///
186/// # Behavior
187///
188/// - Rules are evaluated in declaration order (first match wins)
189/// - If `token_scopes: ["*"]` → always pass (first-party app)
190/// - If `token_scopes` contains any of `required_scopes` → pass
191/// - Otherwise → 403 Forbidden (before PDP call)
192#[derive(Debug, Clone, Default, Deserialize, Serialize)]
193#[serde(deny_unknown_fields, default)]
194pub struct RoutePoliciesConfig {
195    /// Whether route policy enforcement is enabled.
196    pub enabled: bool,
197    /// Route policy rules evaluated in declaration order.
198    /// Patterns support glob syntax (e.g., `/admin/*`, `/events/v1/**`).
199    pub rules: Vec<RoutePolicyRule>,
200}
201
202/// A single route policy rule.
203#[derive(Debug, Clone, Deserialize, Serialize)]
204#[serde(deny_unknown_fields)]
205pub struct RoutePolicyRule {
206    /// Path pattern to match. Supports glob syntax (`*` = one segment, `**` = any depth).
207    pub path: String,
208    /// HTTP method to match (GET, POST, PUT, PATCH, DELETE, etc.).
209    /// If not specified, matches any method.
210    #[serde(default, skip_serializing_if = "Option::is_none")]
211    pub method: Option<String>,
212    /// Required scopes for this route. Request passes if token has ANY of these scopes.
213    /// Must not be empty.
214    pub required_scopes: Vec<String>,
215    // Future fields: rate_limit, timeout, operation_id, etc.
216}