use axum::Json; use axum::extract::State; use api_types::{LoginRequest, RefreshRequest, RegisterRequest, TokenResponse, UserResponse}; use application::auth::{LoginCommand, RegisterCommand}; use domain::DomainError; use crate::errors::AppError; use crate::extractors::CurrentUser; use crate::state::AppState; const TOKEN_TYPE_BEARER: &str = "Bearer"; const SECS_PER_HOUR: u64 = 3600; pub async fn register( State(state): State, Json(req): Json, ) -> Result, AppError> { let cmd = RegisterCommand { email: req.email, password: req.password, }; let user = application::auth::register::execute(&state.auth_deps, cmd).await?; Ok(Json(UserResponse::from(user))) } pub async fn login( State(state): State, Json(req): Json, ) -> Result, AppError> { let cmd = LoginCommand { email: req.email, password: req.password, }; let user = application::auth::login::execute(&state.auth_deps, cmd).await?; let (access_token, refresh_token) = create_tokens(&user, &state, req.remember_me)?; Ok(Json(TokenResponse { access_token, token_type: TOKEN_TYPE_BEARER.to_string(), expires_in: state.config.jwt_expiry_hours * SECS_PER_HOUR, refresh_token, })) } pub async fn logout() -> Result, AppError> { Ok(Json(serde_json::json!({"message": "logged out"}))) } pub async fn me( CurrentUser(user): CurrentUser, ) -> Result, AppError> { Ok(Json(UserResponse::from(user))) } #[cfg(feature = "auth-jwt")] pub async fn get_token( State(state): State, Json(req): Json, ) -> Result, AppError> { let cmd = LoginCommand { email: req.email, password: req.password, }; let user = application::auth::login::execute(&state.auth_deps, cmd).await?; let (access_token, refresh_token) = create_tokens(&user, &state, req.remember_me)?; Ok(Json(TokenResponse { access_token, token_type: TOKEN_TYPE_BEARER.to_string(), expires_in: state.config.jwt_expiry_hours * SECS_PER_HOUR, refresh_token, })) } #[cfg(feature = "auth-jwt")] pub async fn refresh_token( State(state): State, Json(req): Json, ) -> Result, AppError> { let validator = state .jwt_validator .as_ref() .ok_or_else(|| AppError(DomainError::InfrastructureError("JWT not configured".to_string())))?; let claims = validator.validate_refresh_token(&req.refresh_token).map_err(|e| { tracing::debug!("Refresh token validation failed: {:?}", e); AppError(DomainError::Unauthenticated("Invalid refresh token".to_string())) })?; let user_id: uuid::Uuid = claims .sub .parse() .map_err(|_| AppError(DomainError::Unauthenticated("Invalid user ID in token".to_string())))?; let user = state .auth_deps .user_query .find_by_id(domain::UserId::from(user_id)) .await .map_err(|e| AppError(DomainError::InfrastructureError(format!("Failed to fetch user: {}", e))))? .ok_or_else(|| AppError(DomainError::Unauthenticated("User not found".to_string())))?; let (access_token, refresh_token) = create_tokens(&user, &state, true)?; Ok(Json(TokenResponse { access_token, token_type: TOKEN_TYPE_BEARER.to_string(), expires_in: state.config.jwt_expiry_hours * SECS_PER_HOUR, refresh_token, })) } fn create_tokens( user: &domain::User, state: &AppState, remember_me: bool, ) -> Result<(String, Option), AppError> { #[cfg(feature = "auth-jwt")] { let validator = state .jwt_validator .as_ref() .ok_or_else(|| AppError(DomainError::InfrastructureError("JWT not configured".to_string())))?; let access = validator .create_token(user) .map_err(|e| AppError(DomainError::InfrastructureError(format!("Failed to create token: {}", e))))?; let refresh = if remember_me { Some( validator .create_refresh_token(user) .map_err(|e| AppError(DomainError::InfrastructureError(format!("Failed to create refresh token: {}", e))))?, ) } else { None }; Ok((access, refresh)) } #[cfg(not(feature = "auth-jwt"))] { let _ = (user, state, remember_me); Err(AppError(DomainError::InfrastructureError("JWT feature not enabled".to_string()))) } }