use axum::http::StatusCode; use sha2::{Digest, Sha256}; use sqlx::PgPool; use uuid::Uuid; use crate::auth::{create_token, Claims}; use crate::repositories; use crate::repositories::session_repo; pub struct LoginResponse { pub token: String, pub user_id: Uuid, pub username: String, pub role: String, } pub async fn login( pool: &PgPool, username: &str, password: &str, ip_address: Option<&str>, user_agent: Option<&str>, jwt_secret: &str, jwt_expiry_hours: i64, ) -> Result { let user = repositories::user_repo::find_by_username(pool, username) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? .ok_or(StatusCode::UNAUTHORIZED)?; if !user.is_active { return Err(StatusCode::FORBIDDEN); } if user.is_locked { return Err(StatusCode::LOCKED); } let password_valid = bcrypt::verify(password, &user.password_hash) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; if !password_valid { let _ = repositories::user_repo::increment_failed_login(pool, username).await; return Err(StatusCode::UNAUTHORIZED); } let _ = repositories::user_repo::update_last_login(pool, user.id, ip_address.unwrap_or("")).await; let role = "USER"; let token = create_token(user.id, &user.username, role, jwt_secret, jwt_expiry_hours)?; let token_hash = hex::encode(Sha256::digest(token.as_bytes())); let expires_at = chrono::Utc::now() .checked_add_signed(chrono::Duration::hours(jwt_expiry_hours)) .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?; let _ = session_repo::create_session( pool, user.id, &token_hash, ip_address, user_agent, &expires_at, ) .await; Ok(LoginResponse { token, user_id: user.id, username: user.username, role: role.to_string(), }) } pub async fn logout(pool: &PgPool, token: &str) -> Result<(), StatusCode> { let token_hash = hex::encode(Sha256::digest(token.as_bytes())); session_repo::invalidate_session(pool, &token_hash) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) } pub fn validate_token(token: &str, jwt_secret: &str) -> Result { crate::auth::validate_token(token, jwt_secret) }