use serde::{Deserialize, Serialize};
#[derive(Debug, thiserror::Error)]
pub enum AuthError {
#[error("Invalid credentials")]
InvalidCredentials,
#[error("Token generation failed: {0}")]
TokenGeneration(String),
#[error("Invalid token")]
InvalidToken,
#[error("Token expired")]
TokenExpired,
#[error("User not found: {0}")]
UserNotFound(String),
#[error("Password hash failed: {0}")]
PasswordHash(String),
#[error("User storage limit reached: {0}")]
UserLimitReached(String),
}
impl crate::i18n::error_ext::LocalizedMsg for AuthError {
fn message_key(&self) -> &'static str {
match self {
Self::InvalidCredentials => "auth-invalid-credentials",
Self::TokenGeneration(_) => "auth-token-generation",
Self::InvalidToken => "auth-invalid-token",
Self::TokenExpired => "auth-token-expired",
Self::UserNotFound(_) => "auth-user-not-found",
Self::PasswordHash(_) => "auth-password-hash",
Self::UserLimitReached(_) => "auth-user-limit-reached",
}
}
fn message_args(&self) -> Vec<(&str, String)> {
match self {
Self::InvalidCredentials => vec![],
Self::TokenGeneration(reason) => vec![("reason", reason.clone())],
Self::InvalidToken => vec![],
Self::TokenExpired => vec![],
Self::UserNotFound(user) => vec![("user", user.clone())],
Self::PasswordHash(reason) => vec![("reason", reason.clone())],
Self::UserLimitReached(reason) => vec![("reason", reason.clone())],
}
}
}
pub type AuthResult<T> = Result<T, AuthError>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub id: String,
pub username: String,
#[serde(skip)]
pub password_hash: String,
pub role: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<String>, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthCredentials {
pub username: String,
#[serde(skip)]
pub password: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TokenType {
Access,
Refresh,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JwtClaims {
pub sub: String,
pub username: String,
pub role: String,
pub exp: usize,
pub iat: usize,
pub token_type: TokenType,
pub jti: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_auth_error_display() {
let err = AuthError::InvalidCredentials;
assert_eq!(format!("{}", err), "Invalid credentials");
}
#[test]
fn test_user_serialization() {
let user = User {
id: "123".to_string(),
username: "test_user".to_string(),
password_hash: "hash".to_string(),
role: "admin".to_string(),
email: Some("test@example.com".to_string()),
created_at: None,
};
let json = serde_json::to_string(&user).unwrap();
assert!(json.contains("test_user"));
assert!(!json.contains("password_hash"));
assert!(!json.contains("hash"));
}
}