2025-03-20 09:22:31 +01:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
use axum::{
|
2025-03-24 16:47:42 +01:00
|
|
|
extract::{State, Request},
|
|
|
|
|
http::{StatusCode, HeaderMap, header},
|
2025-03-20 09:22:31 +01:00
|
|
|
middleware::Next,
|
|
|
|
|
response::{Response, IntoResponse},
|
|
|
|
|
body::Body,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use crate::common::di::AppState;
|
|
|
|
|
|
2026-02-02 23:56:40 +01:00
|
|
|
// Re-export CurrentUser from application layer for use in handlers
|
|
|
|
|
pub use crate::application::dtos::user_dto::CurrentUser;
|
2025-03-20 09:22:31 +01:00
|
|
|
|
2025-03-24 16:47:42 +01:00
|
|
|
// Estructura para usar en extractores de Axum
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub struct AuthUser {
|
|
|
|
|
pub id: String,
|
|
|
|
|
pub username: String,
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-20 09:22:31 +01:00
|
|
|
// 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()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-24 16:47:42 +01:00
|
|
|
// Implementamos el extractor para AuthUser
|
|
|
|
|
// Use a function instead of an extractor for now
|
|
|
|
|
// We'll use this directly in handlers until we solve the extractor lifetime issues
|
|
|
|
|
pub async fn get_auth_user(req: &Request<Body>) -> Result<AuthUser, AuthError> {
|
|
|
|
|
// Get the current user from extensions
|
|
|
|
|
if let Some(current_user) = req.extensions().get::<CurrentUser>() {
|
|
|
|
|
return Ok(AuthUser {
|
|
|
|
|
id: current_user.id.clone(),
|
|
|
|
|
username: current_user.username.clone(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return error if user not found
|
|
|
|
|
Err(AuthError::UserNotFound)
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-20 09:22:31 +01:00
|
|
|
// Middleware de autenticación simplificado - solo valida si existe un token
|
|
|
|
|
pub async fn auth_middleware(
|
2026-02-03 17:59:04 +01:00
|
|
|
State(state): State<Arc<AppState>>,
|
2025-03-20 09:22:31 +01:00
|
|
|
headers: HeaderMap,
|
|
|
|
|
mut request: Request,
|
|
|
|
|
next: Next,
|
|
|
|
|
) -> Result<Response, AuthError> {
|
2025-03-31 06:20:15 +02:00
|
|
|
// Check URL for special no_validation parameter to break auth loops
|
|
|
|
|
let uri = request.uri().to_string();
|
|
|
|
|
let skip_validation = uri.contains("no_redirect=true") || uri.contains("bypass_auth=true");
|
|
|
|
|
|
|
|
|
|
if skip_validation {
|
|
|
|
|
tracing::info!("Bypassing token validation due to special URL parameter");
|
|
|
|
|
// Create a default user for the request
|
|
|
|
|
let current_user = CurrentUser {
|
|
|
|
|
id: "default-user-id".to_string(),
|
|
|
|
|
username: "usuario".to_string(),
|
|
|
|
|
email: "usuario@example.com".to_string(),
|
|
|
|
|
role: "user".to_string(),
|
|
|
|
|
};
|
|
|
|
|
request.extensions_mut().insert(current_user);
|
|
|
|
|
return Ok(next.run(request).await);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-20 09:22:31 +01:00
|
|
|
// En una primera etapa, simplemente verificar si hay un token, sin validarlo
|
2025-03-31 06:20:15 +02:00
|
|
|
if let Some(token_str) = headers
|
2025-03-20 09:22:31 +01:00
|
|
|
.get(header::AUTHORIZATION)
|
|
|
|
|
.and_then(|value| value.to_str().ok())
|
|
|
|
|
.and_then(|value| value.strip_prefix("Bearer ")) {
|
|
|
|
|
|
2025-03-31 06:20:15 +02:00
|
|
|
// Handle mock tokens differently
|
|
|
|
|
let is_mock = token_str.contains("mock") || token_str == "mock_access_token";
|
|
|
|
|
|
|
|
|
|
if is_mock {
|
|
|
|
|
tracing::info!("Mock token detected, using simplified validation");
|
|
|
|
|
let current_user = CurrentUser {
|
|
|
|
|
id: "test-user-id".to_string(),
|
|
|
|
|
username: "test".to_string(),
|
|
|
|
|
email: "test@example.com".to_string(),
|
|
|
|
|
role: "user".to_string(),
|
|
|
|
|
};
|
|
|
|
|
request.extensions_mut().insert(current_user);
|
|
|
|
|
return Ok(next.run(request).await);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-03 17:59:04 +01:00
|
|
|
// Process normal token - try to validate it using JWT service
|
2025-03-31 06:20:15 +02:00
|
|
|
tracing::info!("Processing token: {}", token_str.chars().take(8).collect::<String>() + "...");
|
2025-03-24 16:47:42 +01:00
|
|
|
|
2026-02-03 17:59:04 +01:00
|
|
|
// Try to get the token service and validate the token
|
|
|
|
|
if let Some(auth_service) = state.auth_service.as_ref() {
|
|
|
|
|
let token_service = &auth_service.token_service;
|
|
|
|
|
match token_service.validate_token(token_str) {
|
|
|
|
|
Ok(claims) => {
|
|
|
|
|
tracing::info!("Token validated successfully for user: {}", claims.username);
|
|
|
|
|
let current_user = CurrentUser {
|
|
|
|
|
id: claims.sub,
|
|
|
|
|
username: claims.username,
|
|
|
|
|
email: claims.email,
|
|
|
|
|
role: claims.role,
|
|
|
|
|
};
|
|
|
|
|
request.extensions_mut().insert(current_user);
|
|
|
|
|
return Ok(next.run(request).await);
|
|
|
|
|
},
|
|
|
|
|
Err(e) => {
|
|
|
|
|
tracing::warn!("Token validation failed: {}", e);
|
|
|
|
|
return Err(AuthError::InvalidToken(format!("Token inválido: {}", e)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-20 09:22:31 +01:00
|
|
|
|
2026-02-03 17:59:04 +01:00
|
|
|
// Fallback: if no auth service available, use token claims from parsing JWT manually
|
|
|
|
|
// Try to decode the token manually using jsonwebtoken
|
|
|
|
|
use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};
|
|
|
|
|
|
|
|
|
|
// Try with default secret (from environment or config)
|
|
|
|
|
let jwt_secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| "oxicloud_secret_key_please_change_in_production".to_string());
|
|
|
|
|
|
|
|
|
|
#[derive(serde::Deserialize)]
|
|
|
|
|
struct Claims {
|
|
|
|
|
sub: String,
|
|
|
|
|
username: String,
|
|
|
|
|
email: String,
|
|
|
|
|
role: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let validation = Validation::new(Algorithm::HS256);
|
|
|
|
|
match decode::<Claims>(
|
|
|
|
|
token_str,
|
|
|
|
|
&DecodingKey::from_secret(jwt_secret.as_bytes()),
|
|
|
|
|
&validation
|
|
|
|
|
) {
|
|
|
|
|
Ok(token_data) => {
|
|
|
|
|
tracing::info!("Token decoded successfully for user: {}", token_data.claims.username);
|
|
|
|
|
let current_user = CurrentUser {
|
|
|
|
|
id: token_data.claims.sub,
|
|
|
|
|
username: token_data.claims.username,
|
|
|
|
|
email: token_data.claims.email,
|
|
|
|
|
role: token_data.claims.role,
|
|
|
|
|
};
|
|
|
|
|
request.extensions_mut().insert(current_user);
|
|
|
|
|
return Ok(next.run(request).await);
|
|
|
|
|
},
|
|
|
|
|
Err(e) => {
|
|
|
|
|
tracing::warn!("Fallback token decode failed: {}", e);
|
|
|
|
|
return Err(AuthError::InvalidToken(format!("Token inválido: {}", e)));
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-20 09:22:31 +01:00
|
|
|
}
|
|
|
|
|
|
2025-03-31 06:20:15 +02:00
|
|
|
// Si hay un indicador para evitar redirección, permitir el acceso sin token
|
|
|
|
|
if uri.contains("api/") && uri.contains("login") {
|
|
|
|
|
tracing::info!("Allowing access to login endpoint without token");
|
|
|
|
|
return Ok(next.run(request).await);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-20 09:22:31 +01:00
|
|
|
// 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()
|
|
|
|
|
}
|