1pub mod oidc;
11
12#[cfg(test)]
13mod tests;
14
15use async_trait::async_trait;
16use std::{fmt, sync::Arc};
17use serde::Deserialize;
18use crate::core::{ProxyError, ProxyRequest, ProxyResponse};
19use crate::{debug_fmt, error_fmt, info_fmt, trace_fmt};
20use crate::security::oidc::{OidcConfig, OidcProvider};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum SecurityStage {
25 Pre,
26 Post,
27 Both,
28}
29
30impl SecurityStage {
31 pub const fn is_pre(self) -> bool { matches!(self, Self::Pre | Self::Both) }
32 pub const fn is_post(self) -> bool { matches!(self, Self::Post | Self::Both) }
33}
34
35#[async_trait]
37pub trait SecurityProvider: fmt::Debug + Send + Sync {
38 fn stage(&self) -> SecurityStage;
40 fn name(&self) -> &str;
42
43 async fn pre(
45 &self,
46 request: ProxyRequest,
47 ) -> Result<ProxyRequest, ProxyError> {
48 trace_fmt!("SecurityChain", "Security provider '{}' skipping pre-auth (default implementation)", self.name());
49 Ok(request)
50 }
51
52 async fn post(
54 &self,
55 _request: ProxyRequest,
56 response: ProxyResponse,
57 ) -> Result<ProxyResponse, ProxyError> {
58 trace_fmt!("SecurityChain", "Security provider '{}' skipping post-auth (default implementation)", self.name());
59 Ok(response)
60 }
61}
62
63#[derive(Debug)]
65pub struct SecurityChain {
66 providers: Vec<Arc<dyn SecurityProvider>>,
67 bypass_routes: Vec<String>,
68}
69
70impl SecurityChain {
71 pub fn new(bypass_routes: Vec<String>) -> Self {
72 Self { providers: Vec::new(), bypass_routes }
73 }
74
75 pub async fn from_configs(cfgs: Vec<ProviderConfig>) -> Result<Self, ProxyError> {
77 let mut chain = SecurityChain { providers: Vec::new(), bypass_routes: Vec::new() };
78
79 debug_fmt!("SecurityChain", "Building security chain from {} provider configs", cfgs.len());
80
81 for c in cfgs {
82 match c {
83 ProviderConfig::Oidc { config } => {
84 debug_fmt!("SecurityChain", "Initializing OIDC provider with issuer: {}", config.issuer_uri);
85 match OidcProvider::discover(config).await {
86 Ok(p) => {
87 info_fmt!("SecurityChain", "Successfully initialized OIDC provider");
88 chain.add(Arc::new(p));
89 },
90 Err(e) => {
91 error_fmt!("SecurityChain", "Failed to initialize OIDC provider: {}", e);
92 return Err(e);
93 }
94 }
95 }
96 }
97 }
98
99 Ok(chain)
100 }
101
102 pub fn add(&mut self, p: Arc<dyn SecurityProvider>) { self.providers.push(p); }
103
104 fn is_bypassed(&self, path: &str) -> bool {
105 let bypassed = self.bypass_routes.iter().any(|p| path.starts_with(p));
106 if bypassed {
107 debug_fmt!("SecurityChain", "Security bypass for path: {}", path);
108 }
109 bypassed
110 }
111
112 pub async fn apply_pre(
113 &self,
114 mut req: ProxyRequest,
115 ) -> Result<ProxyRequest, ProxyError> {
116 if self.is_bypassed(&req.path) {
117 return Ok(req);
118 }
119
120 trace_fmt!("SecurityChain", "Applying security pre-auth chain with {} providers", self.providers.len());
121
122 for p in &self.providers {
123 if p.stage().is_pre() {
124 trace_fmt!("SecurityChain", "Running pre-auth provider: {}", p.name());
125 match p.pre(req).await {
126 Ok(new_req) => {
127 req = new_req;
128 },
129 Err(e) => {
130 let err = ProxyError::SecurityError(format!("{}: {}", p.name(), e));
131 error_fmt!("SecurityChain", "Security pre-auth failed: {}", err);
132 return Err(err);
133 }
134 }
135 }
136 }
137 Ok(req)
138 }
139
140 pub async fn apply_post(
141 &self,
142 req: ProxyRequest,
143 mut resp: ProxyResponse,
144 ) -> Result<ProxyResponse, ProxyError> {
145 if self.is_bypassed(&req.path) {
146 return Ok(resp);
147 }
148
149 trace_fmt!("SecurityChain", "Applying security post-auth chain with {} providers", self.providers.len());
150
151 for p in &self.providers {
152 if p.stage().is_post() {
153 trace_fmt!("SecurityChain", "Running post-auth provider: {}", p.name());
154 match p.post(req.clone(), resp).await {
155 Ok(new_resp) => {
156 resp = new_resp;
157 },
158 Err(e) => {
159 let err = ProxyError::SecurityError(format!("{}: {}", p.name(), e));
160 error_fmt!("SecurityChain", "Security post-auth failed: {}", err);
161 return Err(err);
162 }
163 }
164 }
165 }
166 Ok(resp)
167 }
168}
169
170#[derive(Debug, Deserialize)]
171#[serde(tag = "type", rename_all = "lowercase")]
172pub enum ProviderConfig {
173 Oidc { config: OidcConfig },
174}