""" Authentication API endpoints for multi-tenant SaaS platform. Provides REST API endpoints for: - User registration and login - Multi-method authentication - MFA verification and management - Token management and refresh - Password management - Social authentication - Magic link authentication """ from datetime import datetime, timedelta from typing import Dict, Any, Optional from django.contrib.auth import get_user_model from django.contrib.auth.models import AnonymousUser from django.utils import timezone from django.core.cache import cache from rest_framework import status, generics, viewsets from rest_framework.decorators import action, api_view, permission_classes from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.response import Response from rest_framework.request import Request from rest_framework_simplejwt.tokens import RefreshToken from drf_spectacular.utils import extend_schema, OpenApiParameter from drf_spectacular.types import OpenApiTypes from logging import getLogger from ..models.tenant import Tenant from ..auth.jwt_service import jwt_service from ..auth.authentication import auth_backend from ..auth.mfa import mfa_service from ..auth.permissions import TenantPermission, HasPermission from ..serializers.auth_serializers import ( LoginSerializer, RegisterSerializer, MFASerializer, MFASetupSerializer, TokenRefreshSerializer, PasswordChangeSerializer, PasswordResetSerializer, PasswordResetConfirmSerializer, MagicLinkSerializer, SocialAuthSerializer, BiometricAuthSerializer, BackupCodeSerializer, AuthStatusSerializer, ) from ..services.user_service import user_service from ..exceptions import AuthenticationError, ValidationError User = get_user_model() logger = getLogger(__name__) class AuthViewSet(viewsets.GenericViewSet): """ Authentication API endpoints. Provides comprehensive authentication functionality including: - Multi-method login/logout - User registration - MFA setup and verification - Token management - Password management - Social authentication - Magic link authentication """ permission_classes = [AllowAny] serializer_class = AuthStatusSerializer def get_serializer_class(self): """Return appropriate serializer based on action.""" if self.action == 'login': return LoginSerializer elif self.action == 'register': return RegisterSerializer elif self.action == 'mfa_verify': return MFASerializer elif self.action == 'mfa_setup': return MFASetupSerializer elif self.action == 'refresh_token': return TokenRefreshSerializer elif self.action == 'change_password': return PasswordChangeSerializer elif self.action == 'reset_password': return PasswordResetSerializer elif self.action == 'confirm_password_reset': return PasswordResetConfirmSerializer elif self.action == 'magic_link': return MagicLinkSerializer elif self.action == 'social_auth': return SocialAuthSerializer elif self.action == 'biometric_auth': return BiometricAuthSerializer elif self.action == 'backup_codes': return BackupCodeSerializer return super().get_serializer_class() @extend_schema( summary="User Login", description="Authenticate user with multiple methods", responses={200: AuthStatusSerializer}, ) @action(detail=False, methods=['post']) def login(self, request: Request) -> Response: """ Login user with specified authentication method. Supported methods: - password: Email/username + password - ic: Malaysian IC number + password - company: Company registration + password - phone: Phone number + SMS code - magic: Magic link token - biometric: Biometric authentication - google: Google OAuth - facebook: Facebook OAuth """ serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) try: method = serializer.validated_data['method'] credentials = serializer.validated_data['credentials'] # Perform authentication auth_result = auth_backend.authenticate(request, method=method, **credentials) # Handle MFA requirement if isinstance(auth_result, dict) and auth_result.get('requires_mfa'): user = auth_result['user'] return Response({ 'requires_mfa': True, 'user_id': str(user.id), 'email': user.email, 'mfa_methods': user.get_available_mfa_methods(), }, status=status.HTTP_200_OK) # Generate JWT tokens user = auth_result tenant = getattr(request, 'tenant', None) or getattr(user, 'tenant', None) device_info = { 'user_agent': request.META.get('HTTP_USER_AGENT', ''), 'ip_address': request.META.get('REMOTE_ADDR', ''), 'device_type': self._get_device_type(request), } tokens = jwt_service.generate_token_pair(user, tenant, device_info) # Update user last login user.last_login = timezone.now() user.save(update_fields=['last_login']) logger.info(f"User {user.id} logged in successfully via {method}") return Response({ 'user': self._serialize_user(user), 'tokens': tokens, 'mfa_status': mfa_service.get_mfa_status(user), }, status=status.HTTP_200_OK) except AuthenticationError as e: logger.warning(f"Login failed: {str(e)}") return Response( {'error': str(e)}, status=status.HTTP_401_UNAUTHORIZED ) except Exception as e: logger.error(f"Login error: {str(e)}") return Response( {'error': 'Authentication failed'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @extend_schema( summary="User Registration", description="Register new user with tenant context", responses={201: AuthStatusSerializer}, ) @action(detail=False, methods=['post']) def register(self, request: Request) -> Response: """ Register new user account. Supports individual and tenant-based registration with: - Email verification - Phone verification (optional) - Malaysian IC validation - Company registration validation """ serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) try: # Create user user = user_service.create_user( email=serializer.validated_data['email'], password=serializer.validated_data['password'], first_name=serializer.validated_data.get('first_name', ''), last_name=serializer.validated_data.get('last_name', ''), phone_number=serializer.validated_data.get('phone_number'), malaysian_ic=serializer.validated_data.get('malaysian_ic'), tenant_id=serializer.validated_data.get('tenant_id'), role=serializer.validated_data.get('role', 'user'), ) # Generate verification codes if required verification_codes = {} if user.email and not user.email_verified: email_code = auth_backend.generate_registration_otp(user.email) verification_codes['email_otp'] = email_code['email_otp'] if user.phone_number and not user.phone_verified: phone_code = auth_backend.generate_phone_verification_code(user.phone_number) verification_codes['phone_otp'] = phone_code logger.info(f"User {user.id} registered successfully") return Response({ 'user': self._serialize_user(user), 'verification_codes': verification_codes, 'message': 'User registered successfully. Please verify your email.', }, status=status.HTTP_201_CREATED) except ValidationError as e: logger.warning(f"Registration validation error: {str(e)}") return Response( {'error': str(e)}, status=status.HTTP_400_BAD_REQUEST ) except Exception as e: logger.error(f"Registration error: {str(e)}") return Response( {'error': 'Registration failed'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @extend_schema( summary="MFA Verification", description="Verify MFA code for authentication", responses={200: AuthStatusSerializer}, ) @action(detail=False, methods=['post']) def mfa_verify(self, request: Request) -> Response: """ Verify MFA code to complete authentication. Supported MFA methods: - totp: Time-based One-Time Password - sms: SMS verification code - email: Email verification code - backup: Backup code """ serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) try: user_id = serializer.validated_data['user_id'] method = serializer.validated_data['method'] code = serializer.validated_data['code'] user = User.objects.get(id=user_id) # Verify MFA code if not mfa_service.validate_mfa_attempt(user, method, code): return Response( {'error': 'Invalid MFA code'}, status=status.HTTP_400_BAD_REQUEST ) # Generate JWT tokens tenant = getattr(request, 'tenant', None) or getattr(user, 'tenant', None) device_info = { 'user_agent': request.META.get('HTTP_USER_AGENT', ''), 'ip_address': request.META.get('REMOTE_ADDR', ''), 'device_type': self._get_device_type(request), } tokens = jwt_service.generate_token_pair(user, tenant, device_info) # Update user last login user.last_login = timezone.now() user.save(update_fields=['last_login']) logger.info(f"User {user.id} MFA verification successful") return Response({ 'user': self._serialize_user(user), 'tokens': tokens, 'mfa_status': mfa_service.get_mfa_status(user), }, status=status.HTTP_200_OK) except User.DoesNotExist: return Response( {'error': 'User not found'}, status=status.HTTP_404_NOT_FOUND ) except AuthenticationError as e: return Response( {'error': str(e)}, status=status.HTTP_401_UNAUTHORIZED ) except Exception as e: logger.error(f"MFA verification error: {str(e)}") return Response( {'error': 'MFA verification failed'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @extend_schema( summary="MFA Setup", description="Set up MFA for user account", responses={200: MFASetupSerializer}, ) @action(detail=False, methods=['post']) def mfa_setup(self, request: Request) -> Response: """ Set up MFA for user account. Supports TOTP setup with QR code generation and verification. """ if not request.user.is_authenticated: return Response( {'error': 'Authentication required'}, status=status.HTTP_401_UNAUTHORIZED ) serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) try: user = request.user method = serializer.validated_data.get('method', 'totp') if method == 'totp': # Generate TOTP setup totp_data = mfa_service.setup_totp(user) if 'verification_code' in serializer.validated_data: # Verify TOTP setup secret = serializer.validated_data['secret'] code = serializer.validated_data['verification_code'] if mfa_service.verify_totp_setup(user, secret, code): return Response({ 'message': 'MFA enabled successfully', 'backup_codes': mfa_service.generate_backup_codes(user), }) else: return Response( {'error': 'Invalid verification code'}, status=status.HTTP_400_BAD_REQUEST ) return Response(totp_data) else: return Response( {'error': f'MFA method {method} not supported'}, status=status.HTTP_400_BAD_REQUEST ) except Exception as e: logger.error(f"MFA setup error: {str(e)}") return Response( {'error': 'MFA setup failed'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @extend_schema( summary="Token Refresh", description="Refresh access token using refresh token", responses={200: TokenRefreshSerializer}, ) @action(detail=False, methods=['post']) def refresh_token(self, request: Request) -> Response: """ Refresh access token using refresh token. """ serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) try: refresh_token = serializer.validated_data['refresh_token'] new_tokens = jwt_service.refresh_access_token(refresh_token) return Response(new_tokens, status=status.HTTP_200_OK) except AuthenticationError as e: return Response( {'error': str(e)}, status=status.HTTP_401_UNAUTHORIZED ) except Exception as e: logger.error(f"Token refresh error: {str(e)}") return Response( {'error': 'Token refresh failed'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @extend_schema( summary="Logout", description="Logout user and blacklist tokens", responses={200: {'type': 'object', 'properties': {'message': {'type': 'string'}}}}, ) @action(detail=False, methods=['post']) def logout(self, request: Request) -> Response: """ Logout user and blacklist current tokens. """ if not request.user.is_authenticated: return Response( {'error': 'Authentication required'}, status=status.HTTP_401_UNAUTHORIZED ) try: # Get authorization header auth_header = request.META.get('HTTP_AUTHORIZATION', '') if auth_header.startswith('Bearer '): token = auth_header.split(' ')[1] jwt_service.blacklist_token(token) # Blacklist all user sessions if requested blacklist_all = request.data.get('blacklist_all_sessions', False) if blacklist_all: jwt_service.blacklist_token(token, blacklist_all_sessions=True) logger.info(f"User {request.user.id} logged out successfully") return Response({'message': 'Logged out successfully'}, status=status.HTTP_200_OK) except Exception as e: logger.error(f"Logout error: {str(e)}") return Response( {'error': 'Logout failed'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @extend_schema( summary="Change Password", description="Change user password", responses={200: {'type': 'object', 'properties': {'message': {'type': 'string'}}}}, ) @action(detail=False, methods=['post']) def change_password(self, request: Request) -> Response: """ Change user password. """ if not request.user.is_authenticated: return Response( {'error': 'Authentication required'}, status=status.HTTP_401_UNAUTHORIZED ) serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) try: user = request.user current_password = serializer.validated_data['current_password'] new_password = serializer.validated_data['new_password'] if not user.check_password(current_password): return Response( {'error': 'Current password is incorrect'}, status=status.HTTP_400_BAD_REQUEST ) user.set_password(new_password) user.save(update_fields=['password']) # Blacklist all existing tokens jwt_service.blacklist_token('', blacklist_all_sessions=True) logger.info(f"Password changed for user {user.id}") return Response({'message': 'Password changed successfully'}, status=status.HTTP_200_OK) except Exception as e: logger.error(f"Password change error: {str(e)}") return Response( {'error': 'Password change failed'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @extend_schema( summary="Reset Password", description="Request password reset", responses={200: {'type': 'object', 'properties': {'message': {'type': 'string'}}}}, ) @action(detail=False, methods=['post']) def reset_password(self, request: Request) -> Response: """ Request password reset email. """ serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) try: email = serializer.validated_data['email'] try: user = User.objects.get(email__iexact=email) except User.DoesNotExist: # Don't reveal whether email exists return Response({'message': 'Password reset email sent if email exists'}) # Generate password reset token token = secrets.token_urlsafe(32) reset_key = f"password_reset:{token}" cache.set(reset_key, str(user.id), timeout=3600) # 1 hour # Send password reset email # This would integrate with email service logger.info(f"Password reset requested for user {user.id}") return Response({'message': 'Password reset email sent if email exists'}) except Exception as e: logger.error(f"Password reset request error: {str(e)}") return Response( {'error': 'Password reset request failed'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @extend_schema( summary="Confirm Password Reset", description="Confirm password reset with token", responses={200: {'type': 'object', 'properties': {'message': {'type': 'string'}}}}, ) @action(detail=False, methods=['post']) def confirm_password_reset(self, request: Request) -> Response: """ Confirm password reset with token. """ serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) try: token = serializer.validated_data['token'] new_password = serializer.validated_data['new_password'] # Verify token reset_key = f"password_reset:{token}" user_id = cache.get(reset_key) if not user_id: return Response( {'error': 'Invalid or expired token'}, status=status.HTTP_400_BAD_REQUEST ) try: user = User.objects.get(id=user_id) except User.DoesNotExist: return Response( {'error': 'User not found'}, status=status.HTTP_404_NOT_FOUND ) # Update password user.set_password(new_password) user.save(update_fields=['password']) # Clear reset token cache.delete(reset_key) # Blacklist all existing tokens jwt_service.blacklist_token('', blacklist_all_sessions=True) logger.info(f"Password reset completed for user {user.id}") return Response({'message': 'Password reset successfully'}, status=status.HTTP_200_OK) except Exception as e: logger.error(f"Password reset confirmation error: {str(e)}") return Response( {'error': 'Password reset failed'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @extend_schema( summary="Magic Link", description="Generate magic link for email authentication", responses={200: {'type': 'object', 'properties': {'token': {'type': 'string'}}}}, ) @action(detail=False, methods=['post']) def magic_link(self, request: Request) -> Response: """ Generate magic link for email authentication. """ serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) try: email = serializer.validated_data['email'] tenant = getattr(request, 'tenant', None) token = auth_backend.generate_magic_link(email, tenant) # Send magic link email # This would integrate with email service logger.info(f"Magic link generated for {email}") return Response({'token': token}, status=status.HTTP_200_OK) except AuthenticationError as e: return Response( {'error': str(e)}, status=status.HTTP_400_BAD_REQUEST ) except Exception as e: logger.error(f"Magic link generation error: {str(e)}") return Response( {'error': 'Magic link generation failed'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @extend_schema( summary="Social Authentication", description="Authenticate with social providers", responses={200: AuthStatusSerializer}, ) @action(detail=False, methods=['post']) def social_auth(self, request: Request) -> Response: """ Authenticate with social providers (Google, Facebook). """ serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) try: provider = serializer.validated_data['provider'] access_token = serializer.validated_data['access_token'] # Map provider to authentication method method_map = { 'google': 'google', 'facebook': 'facebook', } method = method_map.get(provider) if not method: return Response( {'error': f'Provider {provider} not supported'}, status=status.HTTP_400_BAD_REQUEST ) # Authenticate with social provider credentials = {'access_token': access_token} if 'id_token' in serializer.validated_data: credentials['id_token'] = serializer.validated_data['id_token'] user = auth_backend.authenticate(request, method=method, **credentials) if not user: return Response( {'error': 'Social authentication failed'}, status=status.HTTP_401_UNAUTHORIZED ) # Generate JWT tokens tenant = getattr(request, 'tenant', None) or getattr(user, 'tenant', None) device_info = { 'user_agent': request.META.get('HTTP_USER_AGENT', ''), 'ip_address': request.META.get('REMOTE_ADDR', ''), 'device_type': self._get_device_type(request), } tokens = jwt_service.generate_token_pair(user, tenant, device_info) # Update user last login user.last_login = timezone.now() user.save(update_fields=['last_login']) logger.info(f"User {user.id} logged in via {provider}") return Response({ 'user': self._serialize_user(user), 'tokens': tokens, 'mfa_status': mfa_service.get_mfa_status(user), }, status=status.HTTP_200_OK) except AuthenticationError as e: return Response( {'error': str(e)}, status=status.HTTP_401_UNAUTHORIZED ) except Exception as e: logger.error(f"Social authentication error: {str(e)}") return Response( {'error': 'Social authentication failed'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @extend_schema( summary="Biometric Authentication", description="Authenticate with biometric data", responses={200: AuthStatusSerializer}, ) @action(detail=False, methods=['post']) def biometric_auth(self, request: Request) -> Response: """ Authenticate with biometric data. """ serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) try: user_id = serializer.validated_data['user_id'] biometric_token = serializer.validated_data['biometric_token'] user = auth_backend.authenticate( request, method='biometric', user_id=user_id, biometric_token=biometric_token ) if not user: return Response( {'error': 'Biometric authentication failed'}, status=status.HTTP_401_UNAUTHORIZED ) # Generate JWT tokens tenant = getattr(request, 'tenant', None) or getattr(user, 'tenant', None) device_info = { 'user_agent': request.META.get('HTTP_USER_AGENT', ''), 'ip_address': request.META.get('REMOTE_ADDR', ''), 'device_type': self._get_device_type(request), } tokens = jwt_service.generate_token_pair(user, tenant, device_info) # Update user last login user.last_login = timezone.now() user.save(update_fields=['last_login']) logger.info(f"User {user.id} logged in via biometric authentication") return Response({ 'user': self._serialize_user(user), 'tokens': tokens, 'mfa_status': mfa_service.get_mfa_status(user), }, status=status.HTTP_200_OK) except AuthenticationError as e: return Response( {'error': str(e)}, status=status.HTTP_401_UNAUTHORIZED ) except Exception as e: logger.error(f"Biometric authentication error: {str(e)}") return Response( {'error': 'Biometric authentication failed'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @extend_schema( summary="MFA Status", description="Get MFA status for current user", responses={200: {'type': 'object'}}, ) @action(detail=False, methods=['get']) def mfa_status(self, request: Request) -> Response: """ Get MFA status for current user. """ if not request.user.is_authenticated: return Response( {'error': 'Authentication required'}, status=status.HTTP_401_UNAUTHORIZED ) try: mfa_status = mfa_service.get_mfa_status(request.user) return Response(mfa_status, status=status.HTTP_200_OK) except Exception as e: logger.error(f"MFA status error: {str(e)}") return Response( {'error': 'Failed to get MFA status'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @extend_schema( summary="Backup Codes", description="Generate new backup codes", responses={200: BackupCodeSerializer}, ) @action(detail=False, methods=['post']) def backup_codes(self, request: Request) -> Response: """ Generate new backup codes. """ if not request.user.is_authenticated: return Response( {'error': 'Authentication required'}, status=status.HTTP_401_UNAUTHORIZED ) try: backup_codes = mfa_service.generate_backup_codes(request.user) return Response({'backup_codes': backup_codes}, status=status.HTTP_200_OK) except Exception as e: logger.error(f"Backup codes generation error: {str(e)}") return Response( {'error': 'Failed to generate backup codes'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @extend_schema( summary="Disable MFA", description="Disable MFA for current user", responses={200: {'type': 'object', 'properties': {'message': {'type': 'string'}}}}, ) @action(detail=False, methods=['post']) def disable_mfa(self, request: Request) -> Response: """ Disable MFA for current user. """ if not request.user.is_authenticated: return Response( {'error': 'Authentication required'}, status=status.HTTP_401_UNAUTHORIZED ) try: if mfa_service.disable_mfa(request.user): return Response({'message': 'MFA disabled successfully'}, status=status.HTTP_200_OK) else: return Response( {'error': 'Failed to disable MFA'}, status=status.HTTP_400_BAD_REQUEST ) except Exception as e: logger.error(f"MFA disable error: {str(e)}") return Response( {'error': 'Failed to disable MFA'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) # Helper methods def _serialize_user(self, user: User) -> Dict[str, Any]: """Serialize user data for API response.""" return { 'id': str(user.id), 'email': user.email, 'first_name': user.first_name, 'last_name': user.last_name, 'role': user.role, 'is_active': user.is_active, 'email_verified': user.email_verified, 'phone_number': user.phone_number, 'phone_verified': user.phone_verified, 'malaysian_ic': user.malaysian_ic, 'tenant_id': str(user.tenant.id) if user.tenant else None, 'last_login': user.last_login, 'created_at': user.created_at, 'updated_at': user.updated_at, } def _get_device_type(self, request: Request) -> str: """Detect device type from user agent.""" user_agent = request.META.get('HTTP_USER_AGENT', '').lower() if 'mobile' in user_agent: return 'mobile' elif 'tablet' in user_agent: return 'tablet' elif 'desktop' in user_agent: return 'desktop' else: return 'unknown' # View functions for specific endpoints @api_view(['GET']) @permission_classes([IsAuthenticated]) def auth_status(request: Request) -> Response: """ Get authentication status for current user. """ try: user = request.user return Response({ 'authenticated': True, 'user': { 'id': str(user.id), 'email': user.email, 'first_name': user.first_name, 'last_name': user.last_name, 'role': user.role, 'tenant_id': str(user.tenant.id) if user.tenant else None, }, 'mfa_status': mfa_service.get_mfa_status(user), }, status=status.HTTP_200_OK) except Exception as e: logger.error(f"Auth status error: {str(e)}") return Response( {'error': 'Failed to get auth status'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @api_view(['POST']) @permission_classes([AllowAny]) def verify_email(request: Request) -> Response: """ Verify email address with OTP code. """ try: email = request.data.get('email') code = request.data.get('code') if not email or not code: return Response( {'error': 'Email and code are required'}, status=status.HTTP_400_BAD_REQUEST ) # Verify email OTP if not auth_backend.verify_registration_otp(email, email_otp=code): return Response( {'error': 'Invalid verification code'}, status=status.HTTP_400_BAD_REQUEST ) # Update user email verification status try: user = User.objects.get(email__iexact=email) user.email_verified = True user.save(update_fields=['email_verified']) except User.DoesNotExist: pass return Response({'message': 'Email verified successfully'}, status=status.HTTP_200_OK) except Exception as e: logger.error(f"Email verification error: {str(e)}") return Response( {'error': 'Email verification failed'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @api_view(['POST']) @permission_classes([AllowAny]) def verify_phone(request: Request) -> Response: """ Verify phone number with OTP code. """ try: phone_number = request.data.get('phone_number') code = request.data.get('code') if not phone_number or not code: return Response( {'error': 'Phone number and code are required'}, status=status.HTTP_400_BAD_REQUEST ) # Verify phone OTP if not auth_backend.verify_registration_otp('', phone_otp=code, phone_number=phone_number): return Response( {'error': 'Invalid verification code'}, status=status.HTTP_400_BAD_REQUEST ) # Update user phone verification status try: user = User.objects.get(phone_number=phone_number) user.phone_verified = True user.save(update_fields=['phone_verified']) except User.DoesNotExist: pass return Response({'message': 'Phone number verified successfully'}, status=status.HTTP_200_OK) except Exception as e: logger.error(f"Phone verification error: {str(e)}") return Response( {'error': 'Phone verification failed'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR )