first commit
This commit is contained in:
160
app/core/auth.py
Normal file
160
app/core/auth.py
Normal file
@@ -0,0 +1,160 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, List
|
||||
import os
|
||||
#========================
|
||||
import jwt
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
|
||||
|
||||
security = HTTPBearer()
|
||||
|
||||
SECRET_KEY = os.getenv("SECRET_KEY", "xma-secret-key-change-in-production")
|
||||
ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_HOURS = int(os.getenv("ACCESS_TOKEN_EXPIRE_HOURS", "1"))
|
||||
|
||||
|
||||
class UserType:
|
||||
""" User type with permisions"""
|
||||
|
||||
ROOT ="root"
|
||||
ADMIN ="admin"
|
||||
PRIVILEGED ="privileged"
|
||||
ADMIN_LICENCIAS ="admin_licencias"
|
||||
UPDATER = "updater"
|
||||
USER ="user"
|
||||
|
||||
|
||||
HIERARCHY = {
|
||||
ROOT :6,
|
||||
ADMIN :5,
|
||||
ADMIN_LICENCIAS : 4,
|
||||
PRIVILEGED : 3,
|
||||
UPDATER : 2,
|
||||
USER : 1,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def has_permission(cls, user_type: str, required_type: str) -> bool:
|
||||
""" VERIFY if user_type has permition equal o plus required_type"""
|
||||
return cls.HIERARCHY.get(user_type, 0) >= cls.HIERARCHY.get(required_type, 0)
|
||||
|
||||
class OperationalRole:
|
||||
""" Role's"""
|
||||
COMPRAS ="compras"
|
||||
VENTAS ="ventas"
|
||||
LOGISTICA ="logistica"
|
||||
ADUANA_SOFT ="aduana_soft"
|
||||
CLIENTE ="cliente"
|
||||
OPERATIVO ="operativo"
|
||||
|
||||
class AuthService:
|
||||
""" Service auth"""
|
||||
|
||||
def __init__(self):
|
||||
self.secret_key = SECRET_KEY
|
||||
self.algorithm = ALGORITHM
|
||||
|
||||
|
||||
def create_token(self, user_id: int, email: str, user_type: str, rol_operativo: str) -> str:
|
||||
|
||||
expire = datetime.utcnow() + timedelta(hours=ACCESS_TOKEN_EXPIRE_HOURS)
|
||||
payload = {
|
||||
"sub": str(user_id),
|
||||
"email": email,
|
||||
"user_type": user_type,
|
||||
"rol_operativo": rol_operativo,
|
||||
"exp": expire,
|
||||
"iat": datetime.utcnow()
|
||||
}
|
||||
return jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
|
||||
|
||||
def verify_token(self, token: str) -> dict:
|
||||
|
||||
try:
|
||||
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
|
||||
return payload
|
||||
except jwt.ExpiredSignatureError:
|
||||
raise HTTPException(status_code=401, detail="Token expirado")
|
||||
except jwt.InvalidTokenError:
|
||||
raise HTTPException(status_code=401, detail="Token inválido")
|
||||
|
||||
def decode_token(self, token: str) -> dict:
|
||||
|
||||
try:
|
||||
return jwt.decode(token, self.secret_key, algorithms=[self.algorithm], options={"verify_exp": False})
|
||||
except jwt.InvalidTokenError:
|
||||
raise HTTPException(status_code=401, detail="Token inválido")
|
||||
|
||||
class CurrentUser:
|
||||
"""Modelo del usuario autenticado"""
|
||||
|
||||
def __init__(self, id: int, email: str, user_type: str, rol_operativo: str):
|
||||
self.id = id
|
||||
self.email = email
|
||||
self.user_type = user_type
|
||||
self.rol_operativo = rol_operativo
|
||||
self.ip_address = None
|
||||
|
||||
def is_root(self) -> bool:
|
||||
return self.user_type == UserType.ROOT
|
||||
|
||||
def is_admin(self) -> bool:
|
||||
return self.user_type == UserType.ADMIN
|
||||
|
||||
def is_admin_licencias(self) -> bool:
|
||||
return self.user_type == UserType.ADMIN_LICENCIAS
|
||||
|
||||
def has_permission(self, required_type: str) -> bool:
|
||||
"""Verifica si el usuario tiene el tipo requerido o superior"""
|
||||
return UserType.has_permission(self.user_type, required_type)
|
||||
|
||||
def can_access_enterprise(self, enterprise_id: int, user_enterprise_id: int = None) -> bool:
|
||||
"""Verifica si puede acceder a una empresa"""
|
||||
if self.is_root():
|
||||
return True
|
||||
if user_enterprise_id and self.user_type == UserType.ADMIN:
|
||||
return enterprise_id == user_enterprise_id
|
||||
return False
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(security),
|
||||
auth_service: AuthService = Depends(lambda: AuthService())
|
||||
) -> CurrentUser:
|
||||
"""Dependencia para obtener el usuario actual desde el token"""
|
||||
token = credentials.credentials
|
||||
payload = auth_service.verify_token(token)
|
||||
|
||||
user = CurrentUser(
|
||||
id=int(payload.get("sub")),
|
||||
email=payload.get("email"),
|
||||
user_type=payload.get("user_type", UserType.USER),
|
||||
rol_operativo=payload.get("rol_operativo", OperationalRole.OPERATIVO)
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
def require_user_type(required_type: str):
|
||||
"""Dependencia para requerir un tipo de usuario específico"""
|
||||
async def dependency(current_user: CurrentUser = Depends(get_current_user)):
|
||||
if not current_user.has_permission(required_type):
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail=f"Se requiere tipo de usuario: {required_type}"
|
||||
)
|
||||
return current_user
|
||||
return dependency
|
||||
|
||||
|
||||
def require_rol(required_rol: str):
|
||||
"""Dependencia para requerir un rol operativo específico"""
|
||||
async def dependency(current_user: CurrentUser = Depends(get_current_user)):
|
||||
if current_user.rol_operativo != required_rol and not current_user.is_root():
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail=f"Se requiere rol operativo: {required_rol}"
|
||||
)
|
||||
return current_user
|
||||
return dependency
|
||||
Reference in New Issue
Block a user