adding user authentication
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
use std::sync::Arc;
|
||||
use axum::{
|
||||
Router,
|
||||
routing::{post, get, put},
|
||||
extract::{State, Json, Path, Extension},
|
||||
http::{StatusCode, HeaderMap, header},
|
||||
response::IntoResponse,
|
||||
middleware,
|
||||
};
|
||||
|
||||
use crate::common::di::AppState;
|
||||
use crate::application::dtos::user_dto::{
|
||||
LoginDto, RegisterDto, UserDto, ChangePasswordDto, RefreshTokenDto, AuthResponseDto
|
||||
};
|
||||
use crate::interfaces::middleware::auth::CurrentUser;
|
||||
use crate::common::errors::AppError;
|
||||
|
||||
pub fn auth_routes() -> Router<Arc<AppState>> {
|
||||
Router::new()
|
||||
.route("/register", post(register))
|
||||
.route("/login", post(login))
|
||||
.route("/refresh", post(refresh_token))
|
||||
.route("/me", get(get_current_user))
|
||||
.route("/change-password", put(change_password))
|
||||
.route("/logout", post(logout))
|
||||
}
|
||||
|
||||
async fn register(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(dto): Json<RegisterDto>,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let auth_service = state.auth_service.as_ref()
|
||||
.ok_or_else(|| AppError::internal_error("Servicio de autenticación no configurado"))?;
|
||||
|
||||
let user = auth_service.auth_application_service.register(dto).await?;
|
||||
|
||||
Ok((StatusCode::CREATED, Json(user)))
|
||||
}
|
||||
|
||||
async fn login(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(dto): Json<LoginDto>,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let auth_service = state.auth_service.as_ref()
|
||||
.ok_or_else(|| AppError::internal_error("Servicio de autenticación no configurado"))?;
|
||||
|
||||
let auth_response = auth_service.auth_application_service.login(dto).await?;
|
||||
|
||||
Ok((StatusCode::OK, Json(auth_response)))
|
||||
}
|
||||
|
||||
async fn refresh_token(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(dto): Json<RefreshTokenDto>,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let auth_service = state.auth_service.as_ref()
|
||||
.ok_or_else(|| AppError::internal_error("Servicio de autenticación no configurado"))?;
|
||||
|
||||
let auth_response = auth_service.auth_application_service.refresh_token(dto).await?;
|
||||
|
||||
Ok((StatusCode::OK, Json(auth_response)))
|
||||
}
|
||||
|
||||
async fn get_current_user(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Extension(current_user): Extension<CurrentUser>,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let auth_service = state.auth_service.as_ref()
|
||||
.ok_or_else(|| AppError::internal_error("Servicio de autenticación no configurado"))?;
|
||||
|
||||
let user = auth_service.auth_application_service.get_user_by_id(¤t_user.id).await?;
|
||||
|
||||
Ok((StatusCode::OK, Json(user)))
|
||||
}
|
||||
|
||||
async fn change_password(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Extension(current_user): Extension<CurrentUser>,
|
||||
Json(dto): Json<ChangePasswordDto>,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let auth_service = state.auth_service.as_ref()
|
||||
.ok_or_else(|| AppError::internal_error("Servicio de autenticación no configurado"))?;
|
||||
|
||||
auth_service.auth_application_service.change_password(¤t_user.id, dto).await?;
|
||||
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
async fn logout(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Extension(current_user): Extension<CurrentUser>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let auth_service = state.auth_service.as_ref()
|
||||
.ok_or_else(|| AppError::internal_error("Servicio de autenticación no configurado"))?;
|
||||
|
||||
// Extract refresh token from request
|
||||
let refresh_token = headers
|
||||
.get(header::AUTHORIZATION)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(|value| value.strip_prefix("Bearer "))
|
||||
.ok_or_else(|| AppError::unauthorized("Token de refresco no encontrado"))?;
|
||||
|
||||
auth_service.auth_application_service.logout(¤t_user.id, refresh_token).await?;
|
||||
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ pub mod file_handler;
|
||||
pub mod folder_handler;
|
||||
pub mod i18n_handler;
|
||||
pub mod batch_handler;
|
||||
pub mod auth_handler;
|
||||
|
||||
/// Tipo de resultado para controladores de API
|
||||
pub type ApiResult<T> = Result<T, (axum::http::StatusCode, String)>;
|
||||
|
||||
@@ -3,11 +3,14 @@ use axum::{
|
||||
routing::{get, post, put, delete},
|
||||
Router,
|
||||
extract::{State, Query, Path},
|
||||
middleware,
|
||||
};
|
||||
use tower_http::{
|
||||
compression::CompressionLayer,
|
||||
trace::TraceLayer,
|
||||
};
|
||||
use crate::common::config::AppConfig;
|
||||
use crate::interfaces::middleware::auth::auth_middleware;
|
||||
|
||||
use crate::interfaces::middleware::cache::{HttpCache, start_cache_cleanup_task};
|
||||
|
||||
@@ -29,7 +32,7 @@ pub fn create_api_routes(
|
||||
folder_service: Arc<FolderService>,
|
||||
file_service: Arc<FileService>,
|
||||
i18n_service: Option<Arc<I18nApplicationService>>,
|
||||
) -> Router {
|
||||
) -> Router<Arc<crate::common::di::AppState>> {
|
||||
// Inicializar el servicio de operaciones por lotes
|
||||
let batch_service = Arc::new(BatchOperationService::default(
|
||||
file_service.clone(),
|
||||
@@ -137,6 +140,13 @@ pub fn create_api_routes(
|
||||
router = router.nest("/i18n", i18n_router);
|
||||
}
|
||||
|
||||
// Get the app configuration
|
||||
let config = AppConfig::from_env();
|
||||
|
||||
// For now, just use the router as is - we'll properly implement the auth middleware later
|
||||
// when all implementation details are fixed
|
||||
let router = router;
|
||||
|
||||
// Apply compression and tracing layers
|
||||
router
|
||||
.layer(CompressionLayer::new())
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
use std::sync::Arc;
|
||||
use axum::{
|
||||
extract::{State, Request, FromRequestParts},
|
||||
http::{StatusCode, request::Parts, HeaderMap, header},
|
||||
middleware::Next,
|
||||
response::{Response, IntoResponse},
|
||||
body::Body,
|
||||
RequestPartsExt,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use futures::future::BoxFuture;
|
||||
|
||||
use crate::common::di::AppState;
|
||||
use crate::common::errors::AppError;
|
||||
use crate::domain::entities::user::UserRole;
|
||||
|
||||
// Extensión para almacenar datos del usuario autenticado
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CurrentUser {
|
||||
pub id: String,
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
pub role: String,
|
||||
}
|
||||
|
||||
// Error para las operaciones de autenticación
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AuthError {
|
||||
#[error("Token no proporcionado")]
|
||||
TokenNotProvided,
|
||||
|
||||
#[error("Token inválido: {0}")]
|
||||
InvalidToken(String),
|
||||
|
||||
#[error("Token expirado")]
|
||||
TokenExpired,
|
||||
|
||||
#[error("Usuario no encontrado")]
|
||||
UserNotFound,
|
||||
|
||||
#[error("Acceso denegado: {0}")]
|
||||
AccessDenied(String),
|
||||
}
|
||||
|
||||
impl IntoResponse for AuthError {
|
||||
fn into_response(self) -> Response {
|
||||
let (status, error_message) = match self {
|
||||
AuthError::TokenNotProvided => (StatusCode::UNAUTHORIZED, "Token no proporcionado".to_string()),
|
||||
AuthError::InvalidToken(msg) => (StatusCode::UNAUTHORIZED, msg),
|
||||
AuthError::TokenExpired => (StatusCode::UNAUTHORIZED, "Token expirado".to_string()),
|
||||
AuthError::UserNotFound => (StatusCode::UNAUTHORIZED, "Usuario no encontrado".to_string()),
|
||||
AuthError::AccessDenied(msg) => (StatusCode::FORBIDDEN, msg),
|
||||
};
|
||||
|
||||
let body = axum::Json(serde_json::json!({
|
||||
"error": error_message
|
||||
}));
|
||||
|
||||
(status, body).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
// Middleware de autenticación simplificado - solo valida si existe un token
|
||||
pub async fn auth_middleware(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
mut request: Request,
|
||||
next: Next,
|
||||
) -> Result<Response, AuthError> {
|
||||
// En una primera etapa, simplemente verificar si hay un token, sin validarlo
|
||||
if let Some(token_str) = headers
|
||||
.get(header::AUTHORIZATION)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(|value| value.strip_prefix("Bearer ")) {
|
||||
|
||||
// Crear un usuario ficticio para pruebas (esto se reemplazará con la validación real)
|
||||
let current_user = CurrentUser {
|
||||
id: "test-user-id".to_string(),
|
||||
username: "test-user".to_string(),
|
||||
email: "test@example.com".to_string(),
|
||||
role: "user".to_string(),
|
||||
};
|
||||
|
||||
// Añadir usuario a la request
|
||||
request.extensions_mut().insert(current_user);
|
||||
return Ok(next.run(request).await);
|
||||
}
|
||||
|
||||
// Si no hay token, devolver error de token no proporcionado
|
||||
Err(AuthError::TokenNotProvided)
|
||||
}
|
||||
|
||||
// Middleware simplificado para verificar roles de administrador
|
||||
pub async fn require_admin(
|
||||
headers: HeaderMap,
|
||||
mut request: Request,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
// Implementación simplificada que verifica si hay un token de admin
|
||||
if let Some(auth_value) = headers.get(header::AUTHORIZATION) {
|
||||
if let Ok(auth_str) = auth_value.to_str() {
|
||||
if auth_str.contains("admin") {
|
||||
// Autorizado como admin
|
||||
let current_user = CurrentUser {
|
||||
id: "admin-user-id".to_string(),
|
||||
username: "admin".to_string(),
|
||||
email: "admin@example.com".to_string(),
|
||||
role: "admin".to_string(),
|
||||
};
|
||||
request.extensions_mut().insert(current_user);
|
||||
return next.run(request).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Acceso denegado
|
||||
let error = AuthError::AccessDenied("Se requiere rol de administrador".to_string());
|
||||
error.into_response()
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod cache;
|
||||
pub mod cache;
|
||||
pub mod auth;
|
||||
@@ -1,11 +1,30 @@
|
||||
use axum::Router;
|
||||
use axum::{
|
||||
routing::get,
|
||||
Router,
|
||||
response::Html,
|
||||
};
|
||||
use tower_http::services::ServeDir;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use crate::common::di::AppState;
|
||||
use crate::common::config::AppConfig;
|
||||
|
||||
/// Creates web routes for serving static files
|
||||
pub fn create_web_routes() -> Router {
|
||||
pub fn create_web_routes() -> Router<Arc<AppState>> {
|
||||
// Get config to access static path
|
||||
let config = AppConfig::from_env();
|
||||
let static_path = config.static_path.clone();
|
||||
|
||||
Router::new()
|
||||
// Add specific route for login
|
||||
.route("/login", get(serve_login_page))
|
||||
// Serve static files
|
||||
.fallback_service(
|
||||
ServeDir::new(PathBuf::from("static"))
|
||||
ServeDir::new(static_path)
|
||||
)
|
||||
}
|
||||
|
||||
/// Serve the login page
|
||||
async fn serve_login_page() -> Html<&'static str> {
|
||||
Html(include_str!("../../../static/login.html"))
|
||||
}
|
||||
Reference in New Issue
Block a user