Coverage for api\views\auth_views.py: 95.1%
93 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-30 21:28 +0200
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-30 21:28 +0200
1import logging
3from rest_framework import status
4from rest_framework.decorators import api_view, permission_classes, throttle_classes
5from rest_framework.exceptions import ValidationError
6from rest_framework.permissions import AllowAny
7from rest_framework.response import Response
8from rest_framework.throttling import AnonRateThrottle, ScopedRateThrottle
9from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
11from api.serializers.login_serializer import CustomTokenObtainPairSerializer, CustomTokenRefreshSerializer
12from api.serializers.user_serializer import UserRegistrationSerializer
13from users.models import USERNAME_ERROR_MESSAGE
14from users.services.auth_service import EMAIL_DOMAIN_ERROR_MESSAGE, AuthService
15from users.services.exceptions import (EmailAlreadyRegisteredError,EmailDomainNotAllowedError,GoogleEmailMissingError,InvalidGoogleTokenError,
16 InvalidUsernameError,InvalidUserTypeError,UnverifiedEmailError,UsernameAlreadyTakenError,UserNotFoundError)
18logger = logging.getLogger(__name__)
21# Clase que limita el numero de peticiones de los endpoints de Google.
22class OAuthThrottle(AnonRateThrottle):
23 scope = 'oauth_login'
25# Clase que limita el numero de peticiones del alta con usuarios y contraseña
26class RegisterThrottle(AnonRateThrottle):
27 scope = 'register'
30class CustomTokenObtainPairView(TokenObtainPairView):
31 """
32 Endpoint para iniciar sesión con email o nombre de usuario, no requiere autenticacion previa.
33 """
34 serializer_class = CustomTokenObtainPairSerializer
35 permission_classes = [AllowAny]
36 throttle_classes = [ScopedRateThrottle]
37 throttle_scope = 'login'
40class CustomTokenRefreshView(TokenRefreshView):
41 """
42 Endpoint para pedir el refresh token, no requiere de autenticacion previa.
43 """
44 serializer_class = CustomTokenRefreshSerializer
45 permission_classes = [AllowAny]
46 throttle_classes = [ScopedRateThrottle]
47 throttle_scope = 'token_refresh'
50# Funcion para hacer el login con Google
51@api_view(['POST'])
52@permission_classes([AllowAny])
53@throttle_classes([OAuthThrottle])
54def google_login_view(request):
55 """
56 Login con Google para usuarios ya registrados.
57 Recibe id_token de Google y devuelve JWT si el usuario existe.
58 """
59 id_token_str = request.data.get('id_token')
60 if not id_token_str:
61 return Response({
62 'error': 'id_token requerido'
63 }, status=status.HTTP_400_BAD_REQUEST)
65 try:
66 # Se verifica el token de Google y se busca la cuenta asociada
67 payload = AuthService.verify_google_token(id_token_str)
68 user = AuthService.find_google_user(payload)
70 except InvalidGoogleTokenError:
71 return Response({
72 'error': 'Token de Google inválido'
73 }, status=status.HTTP_401_UNAUTHORIZED)
74 except GoogleEmailMissingError:
75 return Response({
76 'error': 'No se pudo obtener el email de Google'
77 }, status=status.HTTP_400_BAD_REQUEST)
78 except UnverifiedEmailError:
79 # 403 y no 401, el token es valido, lo que no vale es el correo que trae
80 return Response({
81 'error': 'Google no ha verificado el correo de esta cuenta. '
82 'Verifícalo en Google e inténtalo de nuevo.'
83 }, status=status.HTTP_403_FORBIDDEN)
84 except UserNotFoundError:
85 return Response({
86 'error': 'No existe una cuenta con este email. Regístrate primero.'
87 }, status=status.HTTP_404_NOT_FOUND)
89 # Si se ha encontrado el usuario, se generan los tokens y se devuelven
90 return Response(AuthService.build_auth_payload(user), status=status.HTTP_200_OK)
93# Endpoint para registrar un usuario con Google
94@api_view(['POST'])
95@permission_classes([AllowAny])
96@throttle_classes([OAuthThrottle])
97def google_register_view(request):
98 """
99 Registro con Google. Recibe id_token, el nombre de usuario y el tipo de usuario, opcionalmente el segundo apellido.
100 Crea el usuario y devuelve el token JWT con los datos del usuario si el registro es exitoso, o error en caso contrario.
101 """
102 id_token_str = request.data.get('id_token')
103 username = request.data.get('username', '').strip()
104 user_type_code = request.data.get('user_type', '').strip()
105 surname2 = request.data.get('surname2', '').strip() or None
107 # Si no viene alguno de los datos obligatorios, se devuelve error
108 if not id_token_str: 108 ↛ 109line 108 didn't jump to line 109 because the condition on line 108 was never true
109 return Response({
110 'error': 'id_token requerido'
111 }, status=status.HTTP_400_BAD_REQUEST)
112 if not username:
113 return Response({
114 'error': 'El nombre de usuario es obligatorio'
115 }, status=status.HTTP_400_BAD_REQUEST)
116 if not user_type_code:
117 return Response({
118 'error': 'El tipo de usuario es obligatorio'
119 }, status=status.HTTP_400_BAD_REQUEST)
121 try:
122 # Se verifica el token de Google y se da de alta la cuenta
123 payload = AuthService.verify_google_token(id_token_str)
124 user = AuthService.register_google_user(payload, username, user_type_code, surname2)
126 except InvalidGoogleTokenError:
127 return Response({
128 'error': 'Token de Google inválido'
129 }, status=status.HTTP_401_UNAUTHORIZED)
130 except GoogleEmailMissingError:
131 return Response({
132 'error': 'No se pudo obtener el email de Google'
133 }, status=status.HTTP_400_BAD_REQUEST)
134 except UnverifiedEmailError:
135 # Sin correo verificado no se puede crear la cuenta
136 return Response({
137 'error': 'Google no ha verificado el correo de esta cuenta. '
138 'Verifícalo en Google e inténtalo de nuevo.'
139 }, status=status.HTTP_403_FORBIDDEN)
140 except EmailDomainNotAllowedError:
141 # La cuenta de Google es real y su correo esta verificado, pero no es de un dominio admitido
142 return Response({
143 'error': EMAIL_DOMAIN_ERROR_MESSAGE
144 }, status=status.HTTP_400_BAD_REQUEST)
145 except EmailAlreadyRegisteredError:
146 return Response({
147 'error': 'Ya existe una cuenta con este email. Inicia sesión con Google.'
148 }, status=status.HTTP_409_CONFLICT)
149 except UsernameAlreadyTakenError:
150 return Response({
151 'error': 'El nombre de usuario ya está en uso'
152 }, status=status.HTTP_409_CONFLICT)
153 except InvalidUsernameError:
154 return Response({
155 'error': USERNAME_ERROR_MESSAGE
156 }, status=status.HTTP_400_BAD_REQUEST)
157 except InvalidUserTypeError:
158 return Response({
159 'error': 'Tipo de usuario no válido'
160 }, status=status.HTTP_400_BAD_REQUEST)
162 return Response(AuthService.build_auth_payload(user), status=status.HTTP_200_OK)
165@api_view(['POST'])
166@permission_classes([AllowAny])
167@throttle_classes([RegisterThrottle])
168def register_view(request):
169 """
170 Endpoint para el registro de usuarios.
171 El POST debe incluir:
172 - username: nombre de usuario
173 - email: email único
174 - password: contraseña (mínimo 6 caracteres)
175 - password_confirm: confirmación de contraseña
176 - name: nombre
177 - surname1: primer apellido
178 - surname2: segundo apellido (opcional)
179 - user_type: ID del tipo de usuario
180 """
181 logger.info("Try to register the user: %s", request.data.get("username"))
182 serializer = UserRegistrationSerializer(data=request.data)
183 if serializer.is_valid(): # Si ha pasado la validacion, se crea el usaurio
184 user = serializer.save()
185 logger.info("User registered successfully: %s", user.id)
186 return Response({ # Se devuelven los datos del usuario
187 'message': 'User registered successfully',
188 'user': {
189 'id': str(user.id),
190 'username': user.username,
191 'email': user.email,
192 }
193 }, status=status.HTTP_201_CREATED)
194 logger.warning("User registration failed: %s", request.data)
195 # Se lanza el error para que pase por el manejador de errores
196 raise ValidationError(serializer.errors)