146 lines
4.6 KiB
Rust
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())))
|
|
}
|
|
}
|