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.
This commit is contained in:
@@ -5,18 +5,18 @@ Core module - Configuración y utilidades centrales de la aplicación
|
||||
from .config import settings
|
||||
from .database import (
|
||||
Base,
|
||||
get_core_db,
|
||||
get_async_core_db,
|
||||
get_core_db,
|
||||
get_tenant_db,
|
||||
init_db,
|
||||
init_async_db,
|
||||
init_db,
|
||||
)
|
||||
from .security import (
|
||||
verify_token,
|
||||
get_current_user,
|
||||
get_current_active_user,
|
||||
has_role,
|
||||
get_current_user,
|
||||
get_tenant_from_token,
|
||||
has_role,
|
||||
verify_token,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
Configuración centralizada de la aplicación usando Pydantic Settings
|
||||
"""
|
||||
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from typing import List
|
||||
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""Configuración de la aplicación"""
|
||||
|
||||
@@ -4,12 +4,13 @@ Configuración de base de datos con soporte multi-tenant
|
||||
- Bases de datos dedicadas para clientes enterprise
|
||||
"""
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker, Session
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
||||
from typing import Generator, Dict, Optional, AsyncGenerator
|
||||
from contextlib import contextmanager
|
||||
from typing import AsyncGenerator, Dict, Generator, Optional
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||
from sqlalchemy.orm import Session, declarative_base, sessionmaker
|
||||
|
||||
from .config import settings
|
||||
|
||||
# Base declarativa para modelos ORM
|
||||
@@ -21,7 +22,7 @@ core_engine = create_engine(
|
||||
pool_pre_ping=True,
|
||||
pool_size=10,
|
||||
max_overflow=20,
|
||||
echo=settings.DEBUG,
|
||||
echo=False,
|
||||
)
|
||||
|
||||
CoreSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=core_engine)
|
||||
|
||||
@@ -5,16 +5,16 @@ Middleware personalizado para Anexo76
|
||||
- Logging de requests
|
||||
"""
|
||||
|
||||
from fastapi import Request, HTTPException
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from typing import Callable
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime
|
||||
from sqlalchemy.orm import Session
|
||||
from .database import CoreSessionLocal
|
||||
from .security import verify_token, get_tenant_from_token
|
||||
from typing import Callable
|
||||
|
||||
from fastapi import HTTPException, Request
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
|
||||
from .config import settings
|
||||
from .database import CoreSessionLocal
|
||||
from .security import get_tenant_from_token, verify_token
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -158,6 +158,19 @@ class RequestLoggingMiddleware(BaseHTTPMiddleware):
|
||||
async def dispatch(self, request: Request, call_next: Callable):
|
||||
start_time = time.time()
|
||||
|
||||
excluded_paths = [
|
||||
"/api/docs",
|
||||
"/api/redoc",
|
||||
"/openapi.json",
|
||||
"/api/v1/status",
|
||||
"/api/health",
|
||||
]
|
||||
if any(
|
||||
request.url.path == path or request.url.path.startswith(path + "/")
|
||||
for path in excluded_paths
|
||||
):
|
||||
return await call_next(request)
|
||||
|
||||
# Log request
|
||||
logger.info(f"Request: {request.method} {request.url.path}")
|
||||
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
Utilidades de seguridad y autenticación con Keycloak
|
||||
"""
|
||||
|
||||
from fastapi import HTTPException, Security, Depends
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from keycloak import KeycloakOpenID
|
||||
from jose import jwt, JWTError
|
||||
from typing import Optional, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from .config import settings
|
||||
import logging
|
||||
from core.database import get_core_db
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from fastapi import Depends, HTTPException, Security
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from jose import JWTError, jwt
|
||||
from keycloak import KeycloakOpenID
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -74,7 +75,7 @@ async def get_current_user(
|
||||
current_user: dict = Depends(get_current_user)
|
||||
"""
|
||||
token = credentials.credentials
|
||||
user_info = verify_token(token)
|
||||
user_info = verify_token(token)
|
||||
return user_info
|
||||
|
||||
|
||||
@@ -125,7 +126,7 @@ def get_tenant_from_token(user_info: Dict[str, Any]) -> Optional[int]:
|
||||
- En atributos del usuario
|
||||
"""
|
||||
# Intentar obtener de claims personalizados
|
||||
tenant_id = user_info.get("tenant_id")
|
||||
tenant_id = user_info.get("tenant_id")
|
||||
if not tenant_id:
|
||||
# Intentar obtener de atributos
|
||||
tenant_id = user_info.get("attributes", {}).get("tenant_id")
|
||||
@@ -136,7 +137,9 @@ def get_tenant_from_token(user_info: Dict[str, Any]) -> Optional[int]:
|
||||
return None
|
||||
|
||||
|
||||
def validate_company_access(db: Session, company_id: int, current_user: Dict[str, Any]) -> bool:
|
||||
def validate_company_access(
|
||||
db: Session, company_id: int, current_user: Dict[str, Any]
|
||||
) -> bool:
|
||||
"""
|
||||
Valida que el usuario tenga acceso a la compañía solicitada
|
||||
|
||||
@@ -150,7 +153,7 @@ def validate_company_access(db: Session, company_id: int, current_user: Dict[str
|
||||
Nota:
|
||||
Verifica que la compañía pertenezca al tenant del usuario consultando la BD.
|
||||
"""
|
||||
|
||||
|
||||
tenant_id = get_tenant_from_token(current_user)
|
||||
|
||||
# Si no hay tenant_id en el token, denegar acceso
|
||||
@@ -160,18 +163,21 @@ def validate_company_access(db: Session, company_id: int, current_user: Dict[str
|
||||
# Consultar si la compañía pertenece al tenant
|
||||
try:
|
||||
from api.v1.modules.a76.company.models import Company
|
||||
|
||||
company = db.query(Company).filter(
|
||||
Company.id == company_id,
|
||||
Company.tenant_id == tenant_id
|
||||
).first()
|
||||
|
||||
|
||||
company = (
|
||||
db.query(Company)
|
||||
.filter(Company.id == company_id, Company.tenant_id == tenant_id)
|
||||
.first()
|
||||
)
|
||||
|
||||
return company is not None
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def validate_access_to_resource(db: Session, company_id: int, current_user: Dict[str, Any]) -> int:
|
||||
def validate_access_to_resource(
|
||||
db: Session, company_id: int, current_user: Dict[str, Any]
|
||||
) -> int:
|
||||
"""
|
||||
Valida que el usuario tenga acceso a un recurso específico basado en company_id
|
||||
y regresa el tenant_id
|
||||
@@ -196,23 +202,3 @@ def validate_access_to_resource(db: Session, company_id: int, current_user: Dict
|
||||
|
||||
# Validar que el tenant_id del usuario coincida con el del recurso
|
||||
return tenant_id
|
||||
|
||||
|
||||
class KeycloakClient:
|
||||
"""Cliente para interactuar con Keycloak Admin API"""
|
||||
|
||||
def __init__(self):
|
||||
self.openid = keycloak_openid
|
||||
|
||||
def create_user(self, email: str, password: str, tenant_id: int, **kwargs):
|
||||
"""Crea un usuario en Keycloak"""
|
||||
# Implementar lógica para crear usuario usando keycloak admin
|
||||
pass
|
||||
|
||||
def assign_role(self, user_id: str, role: str):
|
||||
"""Asigna un rol a un usuario"""
|
||||
pass
|
||||
|
||||
def create_tenant_realm(self, tenant_name: str):
|
||||
"""Crea un realm para un nuevo tenant"""
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user