Files
k-tv/crates/presentation/src/handlers/auth.rs

146 lines
4.6 KiB
Rust

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<AppState>,
Json(req): Json<RegisterRequest>,
) -> Result<Json<UserResponse>, 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<AppState>,
Json(req): Json<LoginRequest>,
) -> Result<Json<TokenResponse>, 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<Json<serde_json::Value>, AppError> {
Ok(Json(serde_json::json!({"message": "logged out"})))
}
pub async fn me(
CurrentUser(user): CurrentUser,
) -> Result<Json<UserResponse>, AppError> {
Ok(Json(UserResponse::from(user)))
}
#[cfg(feature = "auth-jwt")]
pub async fn get_token(
State(state): State<AppState>,
Json(req): Json<LoginRequest>,
) -> Result<Json<TokenResponse>, 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<AppState>,
Json(req): Json<RefreshRequest>,
) -> Result<Json<TokenResponse>, 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<String>), 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())))
}
}