use serde_json::{Value, json};
use super::error::AuthError;
use super::session::SessionClient;
#[derive(Debug, Clone)]
pub struct IntrospectionConfig {
pub url: String,
pub client_id: String,
pub client_secret: String,
}
#[derive(Debug, Clone)]
pub struct IntrospectionSessionClient {
client: reqwest::Client,
config: IntrospectionConfig,
}
impl IntrospectionSessionClient {
pub fn new(config: IntrospectionConfig) -> Self {
Self {
client: reqwest::Client::new(),
config,
}
}
pub fn new_with(
url: impl Into<String>,
client_id: impl Into<String>,
client_secret: impl Into<String>,
) -> Self {
Self::new(IntrospectionConfig {
url: url.into(),
client_id: client_id.into(),
client_secret: client_secret.into(),
})
}
}
#[async_trait::async_trait]
impl SessionClient for IntrospectionSessionClient {
async fn to_session(
&self,
_cookie: Option<&str>,
token: Option<&str>,
) -> Result<Value, AuthError> {
let token = token.ok_or_else(|| AuthError::InvalidSession("missing token".to_string()))?;
let response = self
.client
.post(&self.config.url)
.basic_auth(&self.config.client_id, Some(&self.config.client_secret))
.form(&[("token", token)])
.send()
.await
.map_err(|e| AuthError::InvalidSession(format!("introspection request failed: {e}")))?;
if !response.status().is_success() {
return Err(AuthError::InvalidSession(format!(
"introspection returned {}",
response.status()
)));
}
let body: Value = response.json().await.map_err(|e| {
AuthError::InvalidSession(format!("invalid introspection response: {e}"))
})?;
let active = body
.get("active")
.and_then(|v| v.as_bool())
.unwrap_or(false);
if !active {
return Err(AuthError::InvalidSession("token inactive".to_string()));
}
let subject = body
.get("sub")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
if subject.is_empty() {
return Err(AuthError::InvalidSession(
"introspection response missing sub".to_string(),
));
}
Ok(json!({
"identity": { "id": subject },
"scope": body.get("scope").cloned().unwrap_or_else(|| json!("")),
}))
}
}