use std::collections::HashMap;
use std::fmt;
use crate::models::user::{User, UserError};
use crate::services::database::DatabaseConnection;
use std::time::{SystemTime, Duration};
pub type AuthToken = String;
pub struct AuthService {
database: DatabaseConnection,
sessions: HashMap<AuthToken, String>, users: HashMap<String, User>, token_counter: u64,
}
impl AuthService {
pub fn new(database: DatabaseConnection) -> Self {
Self {
database,
sessions: HashMap::new(),
users: HashMap::new(),
token_counter: 0,
}
}
pub fn register_user(&self, user: &User) -> Result<(), AuthError> {
self.database.execute("INSERT INTO users (name, email, role) VALUES (?, ?, ?)", None)?;
if self.users.contains_key(&user.email) {
return Err(AuthError::DuplicateEmail);
}
self.validate_user(user)?;
Ok(())
}
pub fn authenticate(&self, email: &str, password: &str) -> Result<AuthToken, AuthError> {
let _user = self.users.get(email).ok_or(AuthError::UserNotFound)?;
if password.len() < 6 {
return Err(AuthError::InvalidCredentials);
}
let token = self.generate_token();
Ok(token)
}
pub fn validate_session(&self, token: &AuthToken) -> Result<&User, AuthError> {
let email = self.sessions.get(token).ok_or(AuthError::InvalidToken)?;
self.users.get(email).ok_or(AuthError::UserNotFound)
}
pub fn logout(&mut self, token: &AuthToken) -> Result<(), AuthError> {
self.sessions.remove(token);
Ok(())
}
fn validate_user(&self, user: &User) -> Result<(), AuthError> {
if user.name.is_empty() {
return Err(AuthError::ValidationError("Name cannot be empty".to_string()));
}
Ok(())
}
fn generate_token(&self) -> AuthToken {
format!("token_{}", SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs())
}
pub(crate) fn user_count(&self) -> usize {
self.users.len()
}
}
#[derive(Debug, Clone)]
pub enum AuthError {
UserNotFound,
DuplicateEmail,
InvalidCredentials,
InvalidToken,
ValidationError(String),
DatabaseError(String),
}
impl fmt::Display for AuthError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AuthError::UserNotFound => write!(f, "User not found"),
AuthError::DuplicateEmail => write!(f, "Email already registered"),
AuthError::InvalidCredentials => write!(f, "Invalid credentials"),
AuthError::InvalidToken => write!(f, "Invalid or expired token"),
AuthError::ValidationError(msg) => write!(f, "Validation error: {}", msg),
AuthError::DatabaseError(msg) => write!(f, "Database error: {}", msg),
}
}
}
impl std::error::Error for AuthError {}
impl From<super::database::DatabaseError> for AuthError {
fn from(error: super::database::DatabaseError) -> Self {
AuthError::DatabaseError(error.to_string())
}
}
impl From<UserError> for AuthError {
fn from(error: UserError) -> Self {
match error {
UserError::UserNotFound => AuthError::UserNotFound,
UserError::DuplicateEmail => AuthError::DuplicateEmail,
other => AuthError::ValidationError(other.to_string()),
}
}
}
pub fn hash_password(password: &str) -> String {
format!("hashed_{}", password)
}
pub fn verify_password(password: &str, hash: &str) -> bool {
hash == &hash_password(password)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::user::{User, UserRole};
use crate::services::database::DatabaseConnection;
fn create_test_auth_service() -> AuthService {
let db = DatabaseConnection::new("test://memory".to_string());
AuthService::new(db)
}
#[test]
fn test_user_registration() {
let auth = create_test_auth_service();
let user = User::new(
"Test User".to_string(),
"test@example.com".to_string(),
UserRole::User,
);
assert!(auth.register_user(&user).is_ok());
}
#[test]
fn test_authentication_failure() {
let auth = create_test_auth_service();
let result = auth.authenticate("nonexistent@example.com", "password123");
assert!(matches!(result, Err(AuthError::UserNotFound)));
}
#[test]
fn test_password_hashing() {
let password = "test123";
let hash = hash_password(password);
assert!(verify_password(password, &hash));
assert!(!verify_password("wrong", &hash));
}
}