feat(roadmap): implementar Sprints 1, 2 y 3 (MVC, Stateless JWT API y Spring Security)
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
from functools import wraps
|
||||
from flask import request, jsonify, g
|
||||
import jwt
|
||||
from app.services.jwt_service import JWTService
|
||||
from app.models.user import User
|
||||
|
||||
def jwt_required(f):
|
||||
"""
|
||||
Decorador para proteger endpoints REST mediante Bearer Token JWT.
|
||||
Extrae y valida el token, inyectando el usuario en 'g.jwt_user' y el payload en 'g.jwt_payload'.
|
||||
"""
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
auth_header = request.headers.get('Authorization', '')
|
||||
token = None
|
||||
|
||||
if auth_header.startswith('Bearer '):
|
||||
token = auth_header.split(' ', 1)[1].strip()
|
||||
elif 'access_token' in request.cookies:
|
||||
token = request.cookies.get('access_token')
|
||||
|
||||
if not token:
|
||||
return jsonify({
|
||||
'error': 'Unauthorized',
|
||||
'message': 'Encabezado Authorization con Bearer token no proporcionado.'
|
||||
}), 401
|
||||
|
||||
try:
|
||||
payload = JWTService.decode_token(token, expected_type='access')
|
||||
user_id = int(payload.get('sub'))
|
||||
from app import db
|
||||
user = db.session.get(User, user_id)
|
||||
|
||||
if not user or not user.is_active:
|
||||
return jsonify({
|
||||
'error': 'Unauthorized',
|
||||
'message': 'Usuario no encontrado o cuenta desactivada.'
|
||||
}), 401
|
||||
|
||||
g.jwt_user = user
|
||||
g.jwt_payload = payload
|
||||
g.jwt_token = token
|
||||
|
||||
except jwt.ExpiredSignatureError:
|
||||
return jsonify({
|
||||
'error': 'TokenExpired',
|
||||
'message': 'El token de acceso ha expirado. Por favor renueve su sesión.'
|
||||
}), 401
|
||||
except jwt.InvalidTokenError as e:
|
||||
return jsonify({
|
||||
'error': 'InvalidToken',
|
||||
'message': str(e)
|
||||
}), 401
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'error': 'AuthenticationError',
|
||||
'message': f'Fallo en la verificación del token: {str(e)}'
|
||||
}), 401
|
||||
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
|
||||
def jwt_role_required(*allowed_roles):
|
||||
"""
|
||||
Decorador que verifica que el usuario autenticado vía JWT posea alguno de los roles permitidos.
|
||||
"""
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
@jwt_required
|
||||
def decorated_function(*args, **kwargs):
|
||||
user: User = g.jwt_user
|
||||
if user.is_admin():
|
||||
return f(*args, **kwargs)
|
||||
|
||||
user_role = (user.role_obj.name if user.role_obj else user.role).upper()
|
||||
allowed = [r.upper() for r in allowed_roles]
|
||||
|
||||
if user_role not in allowed:
|
||||
return jsonify({
|
||||
'error': 'Forbidden',
|
||||
'message': f'Acceso denegado. Se requiere uno de los siguientes roles: {", ".join(allowed_roles)}'
|
||||
}), 403
|
||||
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
return decorator
|
||||
Reference in New Issue
Block a user