""" Authentication domain serializers for ThrillWiki API v1. This module contains all serializers related to user authentication, registration, password management, and social authentication. """ from rest_framework import serializers from django.contrib.auth import get_user_model, authenticate from django.contrib.auth.password_validation import validate_password from django.core.exceptions import ValidationError as DjangoValidationError from drf_spectacular.utils import ( extend_schema_serializer, OpenApiExample, ) UserModel = get_user_model() # === USER SERIALIZERS === @extend_schema_serializer( examples=[ OpenApiExample( "User Output Example", summary="Example user response", description="A typical user object in API responses", value={ "id": 1, "username": "thrillseeker", "email": "user@example.com", "first_name": "John", "last_name": "Doe", "is_active": True, "date_joined": "2024-01-01T00:00:00Z", }, ) ] ) class UserOutputSerializer(serializers.ModelSerializer): """Output serializer for user data.""" class Meta: model = UserModel fields = [ "id", "username", "email", "first_name", "last_name", "is_active", "date_joined", ] read_only_fields = ["id", "date_joined"] # === LOGIN SERIALIZERS === @extend_schema_serializer( examples=[ OpenApiExample( "Login Input Example", summary="Example login request", description="Login with username or email and password", value={ "username": "thrillseeker", "password": "securepassword123", }, ) ] ) class LoginInputSerializer(serializers.Serializer): """Input serializer for user login.""" username = serializers.CharField( max_length=150, help_text="Username or email address", ) password = serializers.CharField( write_only=True, style={"input_type": "password"}, help_text="User password", ) def validate(self, attrs): """Validate login credentials.""" username = attrs.get("username") password = attrs.get("password") if username and password: # Try to authenticate with the provided credentials user = authenticate( request=self.context.get("request"), username=username, password=password, ) if not user: # Try email-based authentication if username failed if "@" in username: try: user_obj = UserModel.objects.get(email=username) user = authenticate( request=self.context.get("request"), username=user_obj.username, # type: ignore[attr-defined] password=password, ) except UserModel.DoesNotExist: pass if not user: raise serializers.ValidationError("Invalid credentials") if not user.is_active: raise serializers.ValidationError("Account is disabled") attrs["user"] = user else: raise serializers.ValidationError("Must include username and password") return attrs @extend_schema_serializer( examples=[ OpenApiExample( "Login Output Example", summary="Example login response", description="Successful login response with token and user data", value={ "token": "abc123def456ghi789", "user": { "id": 1, "username": "thrillseeker", "email": "user@example.com", "first_name": "John", "last_name": "Doe", }, "message": "Login successful", }, ) ] ) class LoginOutputSerializer(serializers.Serializer): """Output serializer for login response.""" token = serializers.CharField(help_text="Authentication token") user = UserOutputSerializer(help_text="User information") message = serializers.CharField(help_text="Success message") # === SIGNUP SERIALIZERS === @extend_schema_serializer( examples=[ OpenApiExample( "Signup Input Example", summary="Example registration request", description="Register a new user account", value={ "username": "newuser", "email": "newuser@example.com", "password": "securepassword123", "password_confirm": "securepassword123", "first_name": "Jane", "last_name": "Smith", }, ) ] ) class SignupInputSerializer(serializers.ModelSerializer): """Input serializer for user registration.""" password = serializers.CharField( write_only=True, style={"input_type": "password"}, help_text="User password", ) password_confirm = serializers.CharField( write_only=True, style={"input_type": "password"}, help_text="Password confirmation", ) class Meta: model = UserModel fields = [ "username", "email", "password", "password_confirm", "first_name", "last_name", ] def validate_email(self, value): """Validate email uniqueness.""" if UserModel.objects.filter(email=value).exists(): raise serializers.ValidationError("Email already registered") return value def validate_username(self, value): """Validate username uniqueness.""" if UserModel.objects.filter(username=value).exists(): raise serializers.ValidationError("Username already taken") return value def validate_password(self, value): """Validate password strength.""" try: validate_password(value) except DjangoValidationError as e: raise serializers.ValidationError(list(e.messages)) return value def validate(self, attrs): """Cross-field validation.""" password = attrs.get("password") password_confirm = attrs.get("password_confirm") if password != password_confirm: raise serializers.ValidationError("Passwords do not match") return attrs def create(self, validated_data): """Create new user.""" validated_data.pop("password_confirm") password = validated_data.pop("password") user = UserModel.objects.create_user( # type: ignore[attr-defined] password=password, **validated_data, ) return user @extend_schema_serializer( examples=[ OpenApiExample( "Signup Output Example", summary="Example registration response", description="Successful registration response with token and user data", value={ "token": "abc123def456ghi789", "user": { "id": 2, "username": "newuser", "email": "newuser@example.com", "first_name": "Jane", "last_name": "Smith", }, "message": "Registration successful", }, ) ] ) class SignupOutputSerializer(serializers.Serializer): """Output serializer for registration response.""" token = serializers.CharField(help_text="Authentication token") user = UserOutputSerializer(help_text="User information") message = serializers.CharField(help_text="Success message") # === LOGOUT SERIALIZERS === @extend_schema_serializer( examples=[ OpenApiExample( "Logout Output Example", summary="Example logout response", description="Successful logout response", value={ "message": "Logout successful", }, ) ] ) class LogoutOutputSerializer(serializers.Serializer): """Output serializer for logout response.""" message = serializers.CharField(help_text="Success message") # === PASSWORD RESET SERIALIZERS === @extend_schema_serializer( examples=[ OpenApiExample( "Password Reset Input Example", summary="Example password reset request", description="Request password reset email", value={ "email": "user@example.com", }, ) ] ) class PasswordResetInputSerializer(serializers.Serializer): """Input serializer for password reset request.""" email = serializers.EmailField(help_text="Email address for password reset") def validate_email(self, value): """Validate email exists.""" if not UserModel.objects.filter(email=value).exists(): # Don't reveal if email exists for security pass return value def save(self, **kwargs): # type: ignore[override] """Send password reset email.""" email = self.validated_data["email"] # type: ignore[index] try: _user = UserModel.objects.get(email=email) # Here you would typically send a password reset email # For now, we'll just pass pass except UserModel.DoesNotExist: # Don't reveal if email exists for security pass @extend_schema_serializer( examples=[ OpenApiExample( "Password Reset Output Example", summary="Example password reset response", description="Password reset email sent response", value={ "detail": "Password reset email sent", }, ) ] ) class PasswordResetOutputSerializer(serializers.Serializer): """Output serializer for password reset response.""" detail = serializers.CharField(help_text="Success message") # === PASSWORD CHANGE SERIALIZERS === @extend_schema_serializer( examples=[ OpenApiExample( "Password Change Input Example", summary="Example password change request", description="Change current user's password", value={ "old_password": "oldpassword123", "new_password": "newpassword456", "new_password_confirm": "newpassword456", }, ) ] ) class PasswordChangeInputSerializer(serializers.Serializer): """Input serializer for password change.""" old_password = serializers.CharField( write_only=True, style={"input_type": "password"}, help_text="Current password", ) new_password = serializers.CharField( write_only=True, style={"input_type": "password"}, help_text="New password", ) new_password_confirm = serializers.CharField( write_only=True, style={"input_type": "password"}, help_text="New password confirmation", ) def validate_old_password(self, value): """Validate current password.""" user = self.context["request"].user if not user.check_password(value): raise serializers.ValidationError("Current password is incorrect") return value def validate_new_password(self, value): """Validate new password strength.""" try: validate_password(value, user=self.context["request"].user) except DjangoValidationError as e: raise serializers.ValidationError(list(e.messages)) return value def validate(self, attrs): """Cross-field validation.""" new_password = attrs.get("new_password") new_password_confirm = attrs.get("new_password_confirm") if new_password != new_password_confirm: raise serializers.ValidationError("New passwords do not match") return attrs def save(self, **kwargs): # type: ignore[override] """Change user password.""" user = self.context["request"].user user.set_password(self.validated_data["new_password"]) # type: ignore[index] user.save() return user @extend_schema_serializer( examples=[ OpenApiExample( "Password Change Output Example", summary="Example password change response", description="Password changed successfully response", value={ "detail": "Password changed successfully", }, ) ] ) class PasswordChangeOutputSerializer(serializers.Serializer): """Output serializer for password change response.""" detail = serializers.CharField(help_text="Success message") # === SOCIAL PROVIDER SERIALIZERS === @extend_schema_serializer( examples=[ OpenApiExample( "Social Provider Example", summary="Example social provider", description="Available social authentication provider", value={ "id": "google", "name": "Google", "authUrl": "https://example.com/accounts/google/login/", }, ) ] ) class SocialProviderOutputSerializer(serializers.Serializer): """Output serializer for social authentication providers.""" id = serializers.CharField(help_text="Provider ID") name = serializers.CharField(help_text="Provider display name") authUrl = serializers.URLField(help_text="Authentication URL") # === AUTH STATUS SERIALIZERS === @extend_schema_serializer( examples=[ OpenApiExample( "Auth Status Authenticated Example", summary="Example authenticated status", description="Response when user is authenticated", value={ "authenticated": True, "user": { "id": 1, "username": "thrillseeker", "email": "user@example.com", "first_name": "John", "last_name": "Doe", }, }, ), OpenApiExample( "Auth Status Unauthenticated Example", summary="Example unauthenticated status", description="Response when user is not authenticated", value={ "authenticated": False, "user": None, }, ), ] ) class AuthStatusOutputSerializer(serializers.Serializer): """Output serializer for authentication status.""" authenticated = serializers.BooleanField(help_text="Whether user is authenticated") user = UserOutputSerializer( allow_null=True, help_text="User information if authenticated" )