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

1import logging 

2 

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 

10 

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) 

17 

18logger = logging.getLogger(__name__) 

19 

20 

21# Clase que limita el numero de peticiones de los endpoints de Google. 

22class OAuthThrottle(AnonRateThrottle): 

23 scope = 'oauth_login' 

24 

25# Clase que limita el numero de peticiones del alta con usuarios y contraseña 

26class RegisterThrottle(AnonRateThrottle): 

27 scope = 'register' 

28 

29 

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' 

38 

39 

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' 

48 

49 

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) 

64 

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) 

69 

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) 

88 

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) 

91 

92 

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 

106 

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) 

120 

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) 

125 

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) 

161 

162 return Response(AuthService.build_auth_payload(user), status=status.HTTP_200_OK) 

163 

164 

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)