use async_trait::async_trait;
use chrono::{DateTime, Utc};
use paladin_core::platform::container::user::UserRole;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthToken {
pub token: String,
pub expires_at: DateTime<Utc>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthClaims {
pub user_id: Uuid,
pub role: UserRole,
pub expires_at: DateTime<Utc>,
}
#[derive(Debug, thiserror::Error)]
pub enum AuthError {
#[error("Missing authentication token")]
MissingToken,
#[error("Invalid authentication token")]
InvalidToken,
#[error("Authentication token has expired")]
Expired,
#[error("Authentication internal error: {0}")]
Internal(String),
}
#[async_trait]
pub trait AuthPort: Send + Sync {
async fn issue_token(&self, user_id: Uuid, role: UserRole) -> Result<AuthToken, AuthError>;
async fn verify_token(&self, token: &str) -> Result<AuthClaims, AuthError>;
async fn revoke_token(&self, token: &str) -> Result<(), AuthError>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn auth_error_messages_are_stable() {
assert_eq!(
AuthError::MissingToken.to_string(),
"Missing authentication token"
);
assert_eq!(
AuthError::InvalidToken.to_string(),
"Invalid authentication token"
);
assert_eq!(
AuthError::Expired.to_string(),
"Authentication token has expired"
);
assert_eq!(
AuthError::Internal("boom".to_string()).to_string(),
"Authentication internal error: boom"
);
}
#[test]
fn auth_claims_round_trip_through_json() {
let claims = AuthClaims {
user_id: Uuid::nil(),
role: UserRole::Admin,
expires_at: DateTime::<Utc>::from_timestamp(0, 0).unwrap(),
};
let json = serde_json::to_string(&claims).unwrap();
let decoded: AuthClaims = serde_json::from_str(&json).unwrap();
assert_eq!(claims, decoded);
}
}