testing / auth_api /views.py
Danielsz's picture
Update views.py in auth_api
bf0a86d
Raw
History Blame Contribute Delete
10.3 kB
"""
auth_api/views.py — Admin authentication views.
Endpoints:
POST /api/auth/login/ — authenticate admin, set HttpOnly cookies
POST /api/auth/logout/ — clear cookies
POST /api/auth/refresh/ — refresh access token via cookie
GET /api/auth/me/ — return current user info (requires valid access cookie)
"""
from datetime import datetime, timezone as dt_timezone
from django.conf import settings
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes, throttle_classes
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.throttling import ScopedRateThrottle
from django.views.decorators.csrf import ensure_csrf_cookie
from django.utils.decorators import method_decorator
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.exceptions import TokenError, InvalidToken
from django.contrib.auth import authenticate
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
def _set_auth_cookies(response: Response, refresh: RefreshToken) -> None:
"""Write access and refresh tokens into HttpOnly cookies."""
jwt_settings = settings.SIMPLE_JWT
secure = jwt_settings.get('AUTH_COOKIE_SECURE', not settings.DEBUG)
samesite = jwt_settings.get('AUTH_COOKIE_SAMESITE', 'Lax')
access_lifetime = jwt_settings.get('ACCESS_TOKEN_LIFETIME')
refresh_lifetime = jwt_settings.get('REFRESH_TOKEN_LIFETIME')
response.set_cookie(
key='access_token',
value=str(refresh.access_token),
max_age=int(access_lifetime.total_seconds()) if access_lifetime else 1800,
httponly=True,
secure=secure,
samesite=samesite,
path='/',
)
response.set_cookie(
key='refresh_token',
value=str(refresh),
max_age=int(refresh_lifetime.total_seconds()) if refresh_lifetime else 604800,
httponly=True,
secure=secure,
samesite=samesite,
path='/api/auth/refresh/',
)
def _clear_auth_cookies(response: Response) -> None:
"""Delete auth cookies from the browser."""
response.delete_cookie('access_token', path='/')
response.delete_cookie('refresh_token', path='/api/auth/refresh/')
response.delete_cookie('refresh_token', path='/api/token/refresh/')
from rest_framework.decorators import api_view, permission_classes, throttle_classes, authentication_classes
@api_view(['POST'])
@authentication_classes([])
@permission_classes([AllowAny])
@throttle_classes([ScopedRateThrottle])
@ensure_csrf_cookie
def login_view(request):
"""
POST /api/auth/login/
Body: { "username": "...", "password": "..." }
Only users with role='admin' can log in to the Dashboard.
Returns user info and sets HttpOnly cookies.
"""
request.throttle_scope = 'login'
username = request.data.get('username', '').strip()
password = request.data.get('password', '').strip()
if not username or not password:
return Response(
{'detail': 'Usuario y contraseña son requeridos.'},
status=status.HTTP_400_BAD_REQUEST,
)
user = authenticate(request, username=username, password=password)
if user is None and '@' in username:
from django.contrib.auth import get_user_model
User = get_user_model()
try:
user_obj = User.objects.get(email=username)
user = authenticate(request, username=user_obj.username, password=password)
except User.DoesNotExist:
pass
if user is None:
return Response(
{'detail': 'Credenciales incorrectas. Verifica usuario y contraseña.'},
status=status.HTTP_401_UNAUTHORIZED,
)
if not getattr(user, 'role', None) == 'admin' and not user.is_superuser:
return Response(
{'detail': 'Acceso denegado. Esta área es solo para administradores.'},
status=status.HTTP_403_FORBIDDEN,
)
if not user.is_active:
return Response(
{'detail': 'Cuenta inactiva. Contacta al administrador.'},
status=status.HTTP_401_UNAUTHORIZED,
)
refresh = RefreshToken.for_user(user)
response = Response({
'username': user.username,
'role': getattr(user, 'role', 'admin'),
'first_name': user.first_name,
'last_name': user.last_name,
})
_set_auth_cookies(response, refresh)
return response
@api_view(['POST'])
@authentication_classes([])
@permission_classes([AllowAny])
def logout_view(request):
"""
POST /api/auth/logout/
Clears auth cookies. No body required.
"""
response = Response({'detail': 'Sesión cerrada correctamente.'})
_clear_auth_cookies(response)
return response
@api_view(['POST'])
@permission_classes([AllowAny])
@throttle_classes([ScopedRateThrottle])
def refresh_view(request):
"""
POST /api/auth/refresh/
Reads refresh_token from HttpOnly cookie and issues a new access_token cookie.
"""
request.throttle_scope = 'login'
refresh_token = request.COOKIES.get('refresh_token')
if not refresh_token:
return Response(
{'detail': 'No hay token de refresco. Inicia sesión nuevamente.'},
status=status.HTTP_401_UNAUTHORIZED,
)
try:
refresh = RefreshToken(refresh_token)
user_id = refresh.get('user_id', None)
from django.contrib.auth import get_user_model
User = get_user_model()
user = User.objects.get(id=user_id)
# Force rotate: generate a new refresh token
new_refresh = RefreshToken.for_user(user)
except (TokenError, InvalidToken, Exception):
response = Response(
{'detail': 'Token de refresco inválido o expirado. Inicia sesión nuevamente.'},
status=status.HTTP_401_UNAUTHORIZED,
)
_clear_auth_cookies(response)
return response
response = Response({'detail': 'Token renovado.'})
_set_auth_cookies(response, new_refresh)
return response
@api_view(['GET'])
@ensure_csrf_cookie
@permission_classes([IsAuthenticated])
def me_admin_view(request):
"""
GET /api/auth/me/
Returns current authenticated admin user info.
Used by the Dashboard to rehydrate session on page reload.
"""
user = request.user
return Response({
'username': user.username,
'role': getattr(user, 'role', 'user'),
'first_name': user.first_name,
'last_name': user.last_name,
'is_superuser': user.is_superuser,
})
@method_decorator(ensure_csrf_cookie, name='dispatch')
class CookieTokenObtainPairView(TokenObtainPairView):
throttle_classes = [ScopedRateThrottle]
throttle_scope = 'login'
def post(self, request, *args, **kwargs):
response = super().post(request, *args, **kwargs)
if response.status_code == 200:
access = response.data.get('access')
refresh = response.data.get('refresh')
# Clear response data so javascript cannot read them
response.data = {'detail': 'Sesión iniciada correctamente.'}
jwt_settings = settings.SIMPLE_JWT
secure = jwt_settings.get('AUTH_COOKIE_SECURE', not settings.DEBUG)
samesite = jwt_settings.get('AUTH_COOKIE_SAMESITE', 'Lax')
access_lifetime = jwt_settings.get('ACCESS_TOKEN_LIFETIME')
refresh_lifetime = jwt_settings.get('REFRESH_TOKEN_LIFETIME')
response.set_cookie(
key='access_token',
value=access,
max_age=int(access_lifetime.total_seconds()) if access_lifetime else 1800,
httponly=True,
secure=secure,
samesite=samesite,
path='/',
)
response.set_cookie(
key='refresh_token',
value=refresh,
max_age=int(refresh_lifetime.total_seconds()) if refresh_lifetime else 604800,
httponly=True,
secure=secure,
samesite=samesite,
path='/api/token/refresh/',
)
return response
class CookieTokenRefreshView(TokenRefreshView):
throttle_classes = [ScopedRateThrottle]
throttle_scope = 'login'
def post(self, request, *args, **kwargs):
refresh_token = request.COOKIES.get('refresh_token')
if not refresh_token:
return Response(
{'detail': 'Token de refresco faltante en cookies.'},
status=status.HTTP_401_UNAUTHORIZED,
)
request.data['refresh'] = refresh_token
response = super().post(request, *args, **kwargs)
if response.status_code == 200:
access = response.data.get('access')
refresh = response.data.get('refresh')
# Clear response data so javascript cannot read them
response.data = {'detail': 'Token renovado.'}
jwt_settings = settings.SIMPLE_JWT
secure = jwt_settings.get('AUTH_COOKIE_SECURE', not settings.DEBUG)
samesite = jwt_settings.get('AUTH_COOKIE_SAMESITE', 'Lax')
access_lifetime = jwt_settings.get('ACCESS_TOKEN_LIFETIME')
response.set_cookie(
key='access_token',
value=access,
max_age=int(access_lifetime.total_seconds()) if access_lifetime else 1800,
httponly=True,
secure=secure,
samesite=samesite,
path='/',
)
if refresh:
refresh_lifetime = jwt_settings.get('REFRESH_TOKEN_LIFETIME')
response.set_cookie(
key='refresh_token',
value=refresh,
max_age=int(refresh_lifetime.total_seconds()) if refresh_lifetime else 604800,
httponly=True,
secure=secure,
samesite=samesite,
path='/api/token/refresh/',
)
return response