#[cfg(feature = "server")]
use axum::{http::StatusCode, response::IntoResponse, Extension, Json};
#[cfg(feature = "server")]
use chrono::Utc;
#[cfg(feature = "server")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "server")]
use serde_json::json;
#[cfg(feature = "server")]
use std::sync::Arc;
#[cfg(feature = "server")]
use uuid::Uuid;
#[cfg(feature = "server")]
use crate::server::auth::{Claims, JwtManager};
#[cfg(feature = "server")]
use crate::server::storage::{password, User, UserStorage};
#[cfg(feature = "server")]
#[derive(Debug, Deserialize)]
pub struct LoginRequest {
pub email: String,
pub password: String,
}
#[cfg(feature = "server")]
#[derive(Debug, Deserialize)]
pub struct RegisterRequest {
pub email: String,
pub password: String,
pub name: String,
}
#[cfg(feature = "server")]
#[derive(Debug, Serialize)]
pub struct AuthResponse {
pub token: String,
pub user: UserInfo,
}
#[cfg(feature = "server")]
#[derive(Debug, Serialize)]
pub struct UserInfo {
pub id: String,
pub email: String,
pub name: String,
pub roles: Vec<String>,
}
#[cfg(feature = "server")]
pub async fn login(
Extension(user_storage): Extension<Arc<dyn UserStorage>>,
Extension(jwt_manager): Extension<Arc<JwtManager>>,
Json(payload): Json<LoginRequest>,
) -> impl IntoResponse {
let user = match user_storage.get_user_by_email(&payload.email).await {
Ok(Some(user)) => user,
Ok(None) => {
return (
StatusCode::UNAUTHORIZED,
Json(json!({
"error": "Invalid email or password"
})),
)
.into_response();
}
Err(e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": format!("Database error: {}", e)
})),
)
.into_response();
}
};
if !user.is_active {
return (
StatusCode::FORBIDDEN,
Json(json!({
"error": "User account is disabled"
})),
)
.into_response();
}
match password::verify_password(&payload.password, &user.password_hash) {
Ok(true) => {
if let Err(e) = user_storage.update_last_login(user.id).await {
eprintln!("Failed to update last login: {}", e);
}
match jwt_manager.generate_token(&user.id.to_string(), &user.email, user.roles.clone())
{
Ok(token) => {
let response = AuthResponse {
token,
user: UserInfo {
id: user.id.to_string(),
email: user.email,
name: user.name,
roles: user.roles,
},
};
(StatusCode::OK, Json(response)).into_response()
}
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": format!("Failed to generate token: {}", e)
})),
)
.into_response(),
}
}
Ok(false) => (
StatusCode::UNAUTHORIZED,
Json(json!({
"error": "Invalid email or password"
})),
)
.into_response(),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": format!("Password verification error: {}", e)
})),
)
.into_response(),
}
}
#[cfg(feature = "server")]
pub async fn register(
Extension(user_storage): Extension<Arc<dyn UserStorage>>,
Extension(jwt_manager): Extension<Arc<JwtManager>>,
Json(payload): Json<RegisterRequest>,
) -> impl IntoResponse {
if !payload.email.contains('@') || !payload.email.contains('.') {
return (
StatusCode::BAD_REQUEST,
Json(json!({
"error": "Invalid email format"
})),
)
.into_response();
}
if payload.password.len() < 8 {
return (
StatusCode::BAD_REQUEST,
Json(json!({
"error": "Password must be at least 8 characters"
})),
)
.into_response();
}
match user_storage.email_exists(&payload.email).await {
Ok(true) => {
return (
StatusCode::CONFLICT,
Json(json!({
"error": "User with this email already exists"
})),
)
.into_response();
}
Err(e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": format!("Database error: {}", e)
})),
)
.into_response();
}
Ok(false) => {}
}
let password_hash = match password::hash_password(&payload.password) {
Ok(hash) => hash,
Err(e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": format!("Password hashing error: {}", e)
})),
)
.into_response();
}
};
let now = Utc::now();
let user = User {
id: Uuid::new_v4(),
email: payload.email.to_lowercase(),
name: payload.name,
password_hash,
roles: vec!["user".to_string()], is_active: true,
email_verified: false, created_at: now,
updated_at: now,
last_login_at: None,
};
let user_id = match user_storage.create_user(&user).await {
Ok(id) => id,
Err(e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": format!("Failed to create user: {}", e)
})),
)
.into_response();
}
};
match jwt_manager.generate_token(&user_id.to_string(), &user.email, user.roles.clone()) {
Ok(token) => {
let response = AuthResponse {
token,
user: UserInfo {
id: user_id.to_string(),
email: user.email,
name: user.name,
roles: user.roles,
},
};
(StatusCode::CREATED, Json(response)).into_response()
}
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": format!("Failed to generate token: {}", e)
})),
)
.into_response(),
}
}
#[cfg(feature = "server")]
pub async fn get_current_user(Extension(claims): Extension<Claims>) -> impl IntoResponse {
(
StatusCode::OK,
Json(json!({
"id": claims.sub,
"email": claims.email,
"roles": claims.roles,
})),
)
}
#[cfg(feature = "server")]
pub async fn refresh_token(
Extension(jwt_manager): Extension<Arc<JwtManager>>,
Extension(claims): Extension<Claims>,
) -> impl IntoResponse {
match jwt_manager.generate_token(&claims.sub, &claims.email, claims.roles.clone()) {
Ok(token) => (
StatusCode::OK,
Json(json!({
"token": token
})),
)
.into_response(),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": format!("Failed to refresh token: {}", e)
})),
)
.into_response(),
}
}