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