use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthConfig {
pub methods: Vec<AuthMethodConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AuthMethodConfig {
Oidc {
issuer_url: String,
},
Mtls {
ca_cert: String,
},
}
impl Default for AuthConfig {
fn default() -> Self {
Self { methods: vec![] }
}
}
#[derive(Debug, Clone)]
pub struct AuthIdentity {
pub subject: String,
pub method: String,
}
pub async fn validate_request(
config: &AuthConfig,
bearer_token: Option<&str>,
client_cert_cn: Option<&str>,
) -> Result<Option<AuthIdentity>, AuthError> {
if config.methods.is_empty() {
return Ok(None); }
for method in &config.methods {
match method {
AuthMethodConfig::Oidc { issuer_url } => {
if let Some(token) = bearer_token {
match validate_oidc_token(issuer_url, token).await {
Ok(subject) => return Ok(Some(AuthIdentity {
subject,
method: "oidc".into(),
})),
Err(_) => continue,
}
}
}
AuthMethodConfig::Mtls { .. } => {
if let Some(cn) = client_cert_cn {
return Ok(Some(AuthIdentity {
subject: cn.to_string(),
method: "mtls".into(),
}));
}
}
}
}
Err(AuthError::Unauthorized)
}
#[derive(Debug)]
pub enum AuthError {
Unauthorized,
}
async fn validate_oidc_token(issuer_url: &str, token: &str) -> Result<String, AuthError> {
let userinfo_url = format!("{}/userinfo", issuer_url.trim_end_matches('/'));
let client = reqwest::Client::new();
let resp = client
.get(&userinfo_url)
.header("Authorization", format!("Bearer {}", token))
.send()
.await
.map_err(|_| AuthError::Unauthorized)?;
if !resp.status().is_success() {
return Err(AuthError::Unauthorized);
}
let body: serde_json::Value = resp.json().await.map_err(|_| AuthError::Unauthorized)?;
body.get("sub")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
.ok_or(AuthError::Unauthorized)
}