use std::sync::Arc;
use std::time::Duration;
use uuid::Uuid;
use super::error::TestAuthError;
use super::identity::SessionIdentity;
use super::secondary::SecondaryAuth;
use super::secondary::TotpSecondaryAuth;
use super::traits::ForceLoginUser;
use crate::client::APIClient;
pub struct AuthBuilder<'a> {
client: &'a APIClient,
}
impl<'a> AuthBuilder<'a> {
pub(crate) fn new(client: &'a APIClient) -> Self {
Self { client }
}
pub fn session(
self,
user: &impl ForceLoginUser,
backend: Arc<dyn reinhardt_middleware::session::AsyncSessionBackend>,
) -> SessionAuthBuilder<'a> {
SessionAuthBuilder {
client: self.client,
identity: SessionIdentity::from_user(user),
backend,
ttl: Duration::from_secs(30 * 60),
secondary: vec![],
}
}
pub fn jwt(self, user: &impl ForceLoginUser, config: JwtTestConfig) -> JwtAuthBuilder<'a> {
JwtAuthBuilder {
client: self.client,
identity: SessionIdentity::from_user(user),
config,
secondary: vec![],
}
}
}
pub struct SessionAuthBuilder<'a> {
client: &'a APIClient,
identity: SessionIdentity,
backend: Arc<dyn reinhardt_middleware::session::AsyncSessionBackend>,
ttl: Duration,
secondary: Vec<Box<dyn SecondaryAuth>>,
}
impl<'a> SessionAuthBuilder<'a> {
pub fn with_staff(mut self, is_staff: bool) -> Self {
self.identity.is_staff = is_staff;
self
}
pub fn with_superuser(mut self, is_superuser: bool) -> Self {
self.identity.is_superuser = is_superuser;
self
}
pub fn with_ttl(mut self, ttl: Duration) -> Self {
self.ttl = ttl;
self
}
pub fn with_mfa_code(self, code: impl Into<String>) -> Self {
self.with_secondary(TotpSecondaryAuth::with_code_only(code))
}
pub fn with_secondary(mut self, auth: impl SecondaryAuth + 'static) -> Self {
self.secondary.push(Box::new(auth));
self
}
pub async fn apply(self) -> Result<(), TestAuthError> {
let session_id = Uuid::now_v7().to_string();
let session_data = self.identity.to_session_data(&session_id, self.ttl);
self.backend
.save(&session_data)
.await
.map_err(|e| TestAuthError::SessionError(e.to_string()))?;
self.client
.set_cookie("sessionid", &session_id)
.await
.map_err(|e| TestAuthError::ClientError(e.to_string()))?;
for secondary in &self.secondary {
secondary
.apply_to_client(self.client, &self.identity)
.await?;
}
Ok(())
}
}
pub struct JwtAuthBuilder<'a> {
client: &'a APIClient,
identity: SessionIdentity,
config: JwtTestConfig,
secondary: Vec<Box<dyn SecondaryAuth>>,
}
impl<'a> JwtAuthBuilder<'a> {
pub fn with_secondary(mut self, auth: impl SecondaryAuth + 'static) -> Self {
self.secondary.push(Box::new(auth));
self
}
pub async fn apply(self) -> Result<(), TestAuthError> {
use reinhardt_auth::jwt::{Claims, JwtAuth};
let claims = Claims::new(
self.identity.user_id.clone(),
self.identity.user_id.clone(),
chrono::Duration::seconds(self.config.expiry.as_secs() as i64),
self.identity.is_staff,
self.identity.is_superuser,
);
let jwt_auth = JwtAuth::new(self.config.secret.as_bytes());
let token = jwt_auth
.encode(&claims)
.map_err(|e| TestAuthError::JwtError(e.to_string()))?;
self.client
.set_header("Authorization", &format!("Bearer {token}"))
.await
.map_err(|e| TestAuthError::ClientError(e.to_string()))?;
for secondary in &self.secondary {
secondary
.apply_to_client(self.client, &self.identity)
.await?;
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct JwtTestConfig {
pub secret: String,
pub expiry: Duration,
}
impl Default for JwtTestConfig {
fn default() -> Self {
Self {
secret: "test-secret-key-for-testing-only".into(),
expiry: Duration::from_secs(3600),
}
}
}