use super::auth_traits::{AuthProvider, AuthResponse, Credentials};
use super::http::HttpClient;
use crate::core::error::Result;
use crate::core::models::User;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Serialize)]
struct LoginRequest {
username: String,
password: String,
}
#[derive(Debug, Deserialize)]
struct LoginResponse {
id: String,
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct SessionProperties {
#[serde(rename = "auth-provider")]
auth_provider: Option<String>,
#[serde(rename = "auth-session-type")]
auth_session_type: Option<String>,
#[serde(rename = "auth-session-id")]
auth_session_id: Option<String>,
}
pub struct HttpAuthProvider {
client: HttpClient,
}
impl HttpAuthProvider {
pub fn new(client: HttpClient) -> Self {
Self { client }
}
fn extract_session_token(&self, login_response: &LoginResponse) -> String {
login_response.id.clone()
}
}
#[async_trait]
impl AuthProvider for HttpAuthProvider {
async fn authenticate(&self, credentials: &Credentials) -> Result<AuthResponse> {
match credentials {
Credentials::EmailPassword { email, password } => {
let login_request = LoginRequest {
username: email.clone(),
password: password.clone(),
};
let login_response: LoginResponse =
self.client.post("/api/session", &login_request).await?;
let session_token = self.extract_session_token(&login_response);
let user = self.get_user(&session_token).await?;
Ok(AuthResponse {
session_token,
user,
expires_in: Some(Duration::from_secs(14 * 24 * 60 * 60)), })
}
Credentials::ApiKey(api_key) => {
let user = self.get_user(api_key).await?;
Ok(AuthResponse {
session_token: api_key.clone(),
user,
expires_in: None, })
}
Credentials::SessionToken(token) => {
if self.validate_token(token).await? {
let user = self.get_user(token).await?;
Ok(AuthResponse {
session_token: token.clone(),
user,
expires_in: Some(Duration::from_secs(14 * 24 * 60 * 60)),
})
} else {
Err(crate::core::error::Error::Authentication(
"Invalid session token".to_string(),
))
}
}
}
}
async fn refresh_session(&self, session_token: &str) -> Result<AuthResponse> {
if self.validate_token(session_token).await? {
let user = self.get_user(session_token).await?;
Ok(AuthResponse {
session_token: session_token.to_string(),
user,
expires_in: Some(Duration::from_secs(14 * 24 * 60 * 60)),
})
} else {
Err(crate::core::error::Error::Authentication(
"Session expired or invalid".to_string(),
))
}
}
async fn validate_token(&self, session_token: &str) -> Result<bool> {
match self
.client
.get::<SessionProperties>(&format!(
"/api/session/properties?session_id={}",
session_token
))
.await
{
Ok(_) => Ok(true),
Err(crate::core::error::Error::Authentication(_)) => Ok(false),
Err(crate::core::error::Error::Http { status: 401, .. }) => Ok(false),
Err(e) => Err(e),
}
}
async fn logout(&self, session_token: &str) -> Result<()> {
self.client
.delete(&format!("/api/session/{}", session_token))
.await
}
async fn get_user(&self, _session_token: &str) -> Result<User> {
Err(crate::core::error::Error::NotImplemented(
"get_user requires HttpClient header support".to_string(),
))
}
}
pub struct HttpAuthProviderBuilder {
base_url: String,
timeout: Option<Duration>,
}
impl HttpAuthProviderBuilder {
pub fn new(base_url: impl Into<String>) -> Self {
Self {
base_url: base_url.into(),
timeout: None,
}
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn build(self) -> Result<HttpAuthProvider> {
let mut builder = super::http::HttpClientBuilder::new(self.base_url);
if let Some(timeout) = self.timeout {
builder = builder.timeout(timeout);
}
let client = builder.build()?;
Ok(HttpAuthProvider::new(client))
}
}