Files
plantillas-proyectos/backend/api/v1/modules/a76/auth/routes.py
acazares b68c4316ff Refactor backend and frontend code for improved structure and functionality
- Rearranged imports in multiple files for consistency and clarity.
- Updated logging middleware to exclude specific paths from logging.
- Enhanced security module by cleaning up token handling and improving tenant validation.
- Added tenant and company scoped mixins for better database model management.
- Implemented generic CRUD routes for tenant-scoped resources.
- Improved error handling and response management in API routes.
- Cleaned up login and logout processes to ensure proper session management.
- Introduced mechanisms to clear local storage and cookies on tenant change.
- Enhanced company store to detect tenant changes and clear data accordingly.
- Added new DTO mixins for currency and value affect flags.
2025-11-11 17:20:47 -06:00

175 lines
5.3 KiB
Python

"""
Endpoints API para autenticación
"""
from core.database import get_core_db
from core.security import get_current_user
from fastapi import APIRouter, Depends, HTTPException, Response
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.orm import Session
from .dto import (
ExchangeCodeRequestDTO,
LoginRequestDTO,
LogoutRequestDTO,
RefreshTokenRequestDTO,
RegisterRequestDTO,
RegisterResponseDTO,
SetCookieRequestDTO,
TokenResponseDTO,
UserInfoResponseDTO,
)
from .service import AuthService
router = APIRouter(prefix="/auth", tags=["Authentication"])
security = HTTPBearer()
@router.post("/register", response_model=RegisterResponseDTO, status_code=201)
async def register(
register_data: RegisterRequestDTO, db: Session = Depends(get_core_db)
):
"""
Registra un nuevo usuario en Keycloak
El usuario debe proporcionar:
- username: Nombre de usuario único
- email: Email único
- password: Contraseña (mínimo 8 caracteres)
- first_name: Nombre
- last_name: Apellido
- tenant_slug: Slug del tenant al que pertenece
El usuario se crea automáticamente en Keycloak con:
- Cuenta habilitada
- Rol 'user' asignado por defecto
- Atributos de tenant
"""
service = AuthService(db)
return service.register(register_data)
@router.post("/login", response_model=TokenResponseDTO)
async def login(login_data: LoginRequestDTO, db: Session = Depends(get_core_db)):
"""
Autentica usuario con Keycloak y retorna tokens JWT
El usuario debe proporcionar:
- username: Usuario o email
- password: Contraseña
- tenant_slug: Slug del tenant al que pertenece
"""
service = AuthService(db)
return service.login(login_data)
@router.post("/refresh", response_model=TokenResponseDTO)
async def refresh_token(
refresh_data: RefreshTokenRequestDTO, db: Session = Depends(get_core_db)
):
"""
Refresca el access token usando el refresh token
"""
service = AuthService(db)
return service.refresh_token(refresh_data)
@router.get("/me", response_model=UserInfoResponseDTO)
async def get_current_user_info(
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_core_db),
):
"""
Obtiene información del usuario actual desde el token
"""
service = AuthService(db)
return service.get_user_info(credentials.credentials)
@router.post("/logout")
async def logout(
logout_data: LogoutRequestDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""
Cierra sesión invalidando el refresh token
"""
service = AuthService(db)
return service.logout(logout_data)
@router.post("/exchange-code", response_model=TokenResponseDTO)
async def exchange_code(
exchange_data: ExchangeCodeRequestDTO, db: Session = Depends(get_core_db)
):
"""
Intercambia un authorization code de OAuth2 por tokens
Este endpoint es útil cuando el frontend usa el flujo de autorización
con proveedores externos (Microsoft, Google, etc.) a través de Keycloak.
El código se obtiene después de que el usuario se autentica con el proveedor
externo y Keycloak lo redirige al frontend con el código en los query params.
"""
service = AuthService(db)
return service.exchange_code(exchange_data)
@router.post("/set-cookie")
async def set_cookie(
cookie_data: SetCookieRequestDTO,
response: Response,
db: Session = Depends(get_core_db),
):
"""
Establece cookies HttpOnly con los tokens de autenticación
Este endpoint se llama desde el frontend después de una autenticación
SSO exitosa para establecer las cookies de sesión necesarias para
la validación server-side en los layouts protegidos.
Las cookies se configuran como:
- HttpOnly: No accesibles desde JavaScript (mayor seguridad)
- Secure: Solo se envían por HTTPS (en producción)
- SameSite=Lax: Protección contra CSRF
- Max-Age: Tiempo de vida del token
"""
# Validar que los tokens sean válidos decodificándolos
service = AuthService(db)
try:
# Validar el access token
user_info = service.get_user_info(cookie_data.access_token)
# Establecer las cookies
# Access token cookie
response.set_cookie(
key="access_token",
value=cookie_data.access_token,
httponly=True, # No accesible desde JavaScript
secure=False, # TODO: Cambiar a True en producción con HTTPS
samesite="lax", # Protección CSRF
max_age=3600, # 1 hora (ajustar según configuración del token)
path="/",
)
# Refresh token cookie
response.set_cookie(
key="refresh_token",
value=cookie_data.refresh_token,
httponly=True,
secure=False, # TODO: Cambiar a True en producción con HTTPS
samesite="lax",
max_age=86400, # 24 horas (ajustar según configuración del token)
path="/",
)
return {
"success": True,
"message": "Cookies establecidas correctamente",
"user": user_info,
}
except Exception as e:
raise HTTPException(status_code=400, detail=f"Error validando tokens: {str(e)}")