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}