2025-11-12 11:51:27 +00:00
|
|
|
from rest_framework import status, generics
|
|
|
|
|
from rest_framework.decorators import api_view, permission_classes
|
|
|
|
|
from rest_framework.response import Response
|
|
|
|
|
from rest_framework.permissions import AllowAny, IsAuthenticated
|
|
|
|
|
from rest_framework_simplejwt.tokens import RefreshToken
|
|
|
|
|
from django.contrib.auth import authenticate
|
|
|
|
|
from .models import CustomUser, UserProfile
|
2025-11-22 02:19:44 +00:00
|
|
|
from .serializers import UserRegistrationSerializer, UserSerializer, ResetPasswordSerializer, ForgotPasswordSerializer, VerifyPasswordResetOTPSerializer
|
|
|
|
|
from .utils import send_otp_via_email, is_otp_expired, generate_otp
|
|
|
|
|
from django.utils import timezone
|
|
|
|
|
from datetime import timedelta
|
|
|
|
|
from rest_framework.reverse import reverse
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@api_view(['GET'])
|
|
|
|
|
@permission_classes([AllowAny])
|
|
|
|
|
def api_root(request, format=None):
|
|
|
|
|
"""
|
|
|
|
|
# Authentication API Documentation
|
|
|
|
|
|
|
|
|
|
Welcome to the Authentication API. This service provides complete user authentication functionality including registration, email verification, login, and password reset using OTP.
|
|
|
|
|
|
|
|
|
|
## Base URL
|
|
|
|
|
```
|
|
|
|
|
{{ request.build_absolute_uri }}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Quick Start
|
|
|
|
|
|
|
|
|
|
1. **Register** a new user account
|
|
|
|
|
2. **Verify** email with OTP sent to your email
|
|
|
|
|
3. **Login** with your credentials
|
|
|
|
|
4. Use the **access token** for authenticated requests
|
|
|
|
|
|
|
|
|
|
## API Endpoints
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
endpoints = {
|
|
|
|
|
'documentation': {
|
|
|
|
|
'description': 'This API documentation',
|
|
|
|
|
'url': request.build_absolute_uri(),
|
|
|
|
|
'methods': ['GET']
|
|
|
|
|
},
|
|
|
|
|
'register': {
|
|
|
|
|
'description': 'Register a new user and send verification OTP',
|
|
|
|
|
'url': request.build_absolute_uri('register/'),
|
|
|
|
|
'methods': ['POST'],
|
|
|
|
|
'required_fields': ['email', 'first_name', 'last_name', 'password', 'password2'],
|
|
|
|
|
'example_request': {
|
|
|
|
|
'email': 'user@example.com',
|
|
|
|
|
'first_name': 'John',
|
|
|
|
|
'last_name': 'Doe',
|
|
|
|
|
'phone_number': '+1234567890',
|
|
|
|
|
'password': 'SecurePassword123',
|
|
|
|
|
'password2': 'SecurePassword123'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
'verify_otp': {
|
|
|
|
|
'description': 'Verify email address using OTP',
|
|
|
|
|
'url': request.build_absolute_uri('verify-otp/'),
|
|
|
|
|
'methods': ['POST'],
|
|
|
|
|
'required_fields': ['email', 'otp'],
|
|
|
|
|
'example_request': {
|
|
|
|
|
'email': 'user@example.com',
|
|
|
|
|
'otp': '123456'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
'login': {
|
|
|
|
|
'description': 'Authenticate user and return JWT tokens',
|
|
|
|
|
'url': request.build_absolute_uri('login/'),
|
|
|
|
|
'methods': ['POST'],
|
|
|
|
|
'required_fields': ['email', 'password'],
|
|
|
|
|
'example_request': {
|
|
|
|
|
'email': 'user@example.com',
|
|
|
|
|
'password': 'SecurePassword123'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
'resend_otp': {
|
|
|
|
|
'description': 'Resend OTP for email verification or password reset',
|
|
|
|
|
'url': request.build_absolute_uri('resend-otp/'),
|
|
|
|
|
'methods': ['POST'],
|
|
|
|
|
'required_fields': ['email'],
|
|
|
|
|
'optional_fields': ['context (registration/password_reset)'],
|
|
|
|
|
'example_request': {
|
|
|
|
|
'email': 'user@example.com',
|
|
|
|
|
'context': 'registration'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
'forgot_password': {
|
|
|
|
|
'description': 'Initiate password reset process',
|
|
|
|
|
'url': request.build_absolute_uri('forgot-password/'),
|
|
|
|
|
'methods': ['POST'],
|
|
|
|
|
'required_fields': ['email'],
|
|
|
|
|
'example_request': {
|
|
|
|
|
'email': 'user@example.com'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
'verify_password_reset_otp': {
|
|
|
|
|
'description': 'Verify OTP for password reset',
|
|
|
|
|
'url': request.build_absolute_uri('verify-password-reset-otp/'),
|
|
|
|
|
'methods': ['POST'],
|
|
|
|
|
'required_fields': ['email', 'otp'],
|
|
|
|
|
'example_request': {
|
|
|
|
|
'email': 'user@example.com',
|
|
|
|
|
'otp': '123456'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
'reset_password': {
|
|
|
|
|
'description': 'Reset password after OTP verification',
|
|
|
|
|
'url': request.build_absolute_uri('reset-password/'),
|
|
|
|
|
'methods': ['POST'],
|
|
|
|
|
'required_fields': ['email', 'otp', 'new_password', 'confirm_password'],
|
|
|
|
|
'example_request': {
|
|
|
|
|
'email': 'user@example.com',
|
|
|
|
|
'otp': '123456',
|
|
|
|
|
'new_password': 'NewSecurePassword123',
|
|
|
|
|
'confirm_password': 'NewSecurePassword123'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
'token_refresh': {
|
|
|
|
|
'description': 'Refresh access token using refresh token',
|
|
|
|
|
'url': request.build_absolute_uri('token/refresh/'),
|
|
|
|
|
'methods': ['POST'],
|
|
|
|
|
'required_fields': ['refresh'],
|
|
|
|
|
'example_request': {
|
|
|
|
|
'refresh': 'your_refresh_token_here'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Response({
|
|
|
|
|
'message': 'Authentication API',
|
|
|
|
|
'version': '1.0.0',
|
|
|
|
|
'endpoints': endpoints,
|
|
|
|
|
'authentication_flows': {
|
|
|
|
|
'registration_flow': [
|
|
|
|
|
'1. POST /register/ - Register user and send OTP',
|
|
|
|
|
'2. POST /verify-otp/ - Verify email with OTP',
|
|
|
|
|
'3. POST /login/ - Login with credentials'
|
|
|
|
|
],
|
|
|
|
|
'password_reset_flow': [
|
|
|
|
|
'1. POST /forgot-password/ - Request password reset OTP',
|
|
|
|
|
'2. POST /verify-password-reset-otp/ - Verify OTP',
|
|
|
|
|
'3. POST /reset-password/ - Set new password'
|
|
|
|
|
],
|
|
|
|
|
'login_flow_unverified': [
|
|
|
|
|
'1. POST /login/ - Returns email not verified error',
|
|
|
|
|
'2. POST /resend-otp/ - Resend verification OTP',
|
|
|
|
|
'3. POST /verify-otp/ - Verify email',
|
|
|
|
|
'4. POST /login/ - Successful login'
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
'specifications': {
|
|
|
|
|
'otp': {
|
|
|
|
|
'length': 6,
|
|
|
|
|
'expiry_minutes': 10,
|
|
|
|
|
'delivery_method': 'email'
|
|
|
|
|
},
|
|
|
|
|
'tokens': {
|
|
|
|
|
'access_token_lifetime': '5 minutes',
|
|
|
|
|
'refresh_token_lifetime': '24 hours'
|
|
|
|
|
},
|
|
|
|
|
'password_requirements': [
|
|
|
|
|
'Minimum 8 characters',
|
|
|
|
|
'Cannot be entirely numeric',
|
|
|
|
|
'Cannot be too common',
|
|
|
|
|
'Should include uppercase, lowercase, and numbers'
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
'error_handling': {
|
|
|
|
|
'common_status_codes': {
|
|
|
|
|
'200': 'Success',
|
|
|
|
|
'201': 'Created',
|
|
|
|
|
'400': 'Bad Request (validation errors)',
|
|
|
|
|
'401': 'Unauthorized (invalid credentials)',
|
|
|
|
|
'403': 'Forbidden (unverified email, inactive account)',
|
|
|
|
|
'404': 'Not Found',
|
|
|
|
|
'500': 'Internal Server Error'
|
|
|
|
|
},
|
|
|
|
|
'error_response_format': {
|
|
|
|
|
'error': 'Error description',
|
|
|
|
|
'message': 'User-friendly message'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
'security_notes': [
|
|
|
|
|
'Always use HTTPS in production',
|
|
|
|
|
'Store tokens securely (httpOnly cookies recommended)',
|
|
|
|
|
'Implement token refresh logic',
|
|
|
|
|
'Validate all inputs on frontend and backend',
|
|
|
|
|
'Handle token expiration gracefully'
|
|
|
|
|
]
|
|
|
|
|
})
|
2025-11-12 11:51:27 +00:00
|
|
|
|
|
|
|
|
@api_view(['POST'])
|
|
|
|
|
@permission_classes([AllowAny])
|
|
|
|
|
def register_user(request):
|
|
|
|
|
serializer = UserRegistrationSerializer(data=request.data)
|
|
|
|
|
if serializer.is_valid():
|
|
|
|
|
user = serializer.save()
|
|
|
|
|
|
|
|
|
|
UserProfile.objects.create(user=user)
|
|
|
|
|
|
2025-11-22 02:19:44 +00:00
|
|
|
otp = generate_otp()
|
|
|
|
|
user.verify_otp = otp
|
|
|
|
|
user.verify_otp_expiry = timezone.now() + timedelta(minutes=10)
|
|
|
|
|
user.save()
|
|
|
|
|
|
|
|
|
|
user_name = f"{user.first_name} {user.last_name}".strip() or user.email
|
|
|
|
|
email_sent = send_otp_via_email(user.email, otp, user_name, 'registration')
|
|
|
|
|
|
|
|
|
|
if not email_sent:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'Failed to send OTP. Please try again later.'
|
|
|
|
|
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
2025-11-12 11:51:27 +00:00
|
|
|
|
|
|
|
|
return Response({
|
|
|
|
|
'user': UserSerializer(user).data,
|
2025-11-22 02:19:44 +00:00
|
|
|
'otp_sent': email_sent,
|
|
|
|
|
'otp_expires_in': 10
|
2025-11-12 11:51:27 +00:00
|
|
|
}, status=status.HTTP_201_CREATED)
|
|
|
|
|
|
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
2025-11-22 02:19:44 +00:00
|
|
|
@api_view(['POST'])
|
|
|
|
|
@permission_classes([AllowAny])
|
|
|
|
|
def verify_otp(request):
|
|
|
|
|
email = request.data.get('email')
|
|
|
|
|
otp = request.data.get('otp')
|
|
|
|
|
|
|
|
|
|
if not email or not otp:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'Email and OTP are required'
|
|
|
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
user = CustomUser.objects.get(email=email)
|
|
|
|
|
|
|
|
|
|
if user.isVerified:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'User is already verified'
|
|
|
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
if (user.verify_otp == otp and
|
|
|
|
|
not is_otp_expired(user.verify_otp_expiry)):
|
|
|
|
|
|
|
|
|
|
user.isVerified = True
|
|
|
|
|
user.verify_otp = None
|
|
|
|
|
user.verify_otp_expiry = None
|
|
|
|
|
user.save()
|
|
|
|
|
|
|
|
|
|
refresh = RefreshToken.for_user(user)
|
|
|
|
|
|
|
|
|
|
return Response({
|
|
|
|
|
'message': 'Email verified successfully',
|
|
|
|
|
'verified': True,
|
|
|
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
|
else:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'Invalid or expired OTP'
|
|
|
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
except CustomUser.DoesNotExist:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'User not found'
|
|
|
|
|
}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@api_view(['POST'])
|
|
|
|
|
@permission_classes([AllowAny])
|
|
|
|
|
def resend_otp(request):
|
|
|
|
|
email = request.data.get('email')
|
|
|
|
|
context = request.data.get('context', 'registration')
|
|
|
|
|
|
|
|
|
|
if not email:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'Email is required'
|
|
|
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
user = CustomUser.objects.get(email=email)
|
|
|
|
|
|
|
|
|
|
if user.isVerified and context == 'registration':
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'Already verified',
|
|
|
|
|
'message': 'Your email is already verified. You can login now.'
|
|
|
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
otp = generate_otp()
|
|
|
|
|
|
|
|
|
|
if context == 'password_reset':
|
|
|
|
|
user.forgot_password_otp = otp
|
|
|
|
|
user.forgot_password_otp_expiry = timezone.now() + timedelta(minutes=10)
|
|
|
|
|
else:
|
|
|
|
|
user.verify_otp = otp
|
|
|
|
|
user.verify_otp_expiry = timezone.now() + timedelta(minutes=10)
|
|
|
|
|
|
|
|
|
|
user.save()
|
|
|
|
|
|
|
|
|
|
user_name = f"{user.first_name} {user.last_name}".strip() or user.email
|
|
|
|
|
email_sent = send_otp_via_email(user.email, otp, user_name, context)
|
|
|
|
|
|
|
|
|
|
if not email_sent:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'Failed to send OTP',
|
|
|
|
|
'message': 'Please try again later.'
|
|
|
|
|
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
|
|
|
|
|
|
return Response({
|
|
|
|
|
'message': f'OTP resent to your email successfully',
|
|
|
|
|
'otp_sent': email_sent,
|
|
|
|
|
'otp_expires_in': 10,
|
|
|
|
|
'context': context
|
|
|
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
|
|
|
|
|
|
except CustomUser.DoesNotExist:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'User not found',
|
|
|
|
|
'message': 'No account found with this email address.'
|
|
|
|
|
}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
|
|
2025-11-12 11:51:27 +00:00
|
|
|
@api_view(['POST'])
|
|
|
|
|
@permission_classes([AllowAny])
|
|
|
|
|
def login_user(request):
|
|
|
|
|
email = request.data.get('email')
|
|
|
|
|
password = request.data.get('password')
|
|
|
|
|
|
|
|
|
|
user = authenticate(request, email=email, password=password)
|
|
|
|
|
|
|
|
|
|
if user is not None:
|
2025-11-22 02:19:44 +00:00
|
|
|
if not user.isVerified:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'Email not verified',
|
|
|
|
|
'message': 'Please verify your email address before logging in.',
|
|
|
|
|
'email': user.email,
|
|
|
|
|
'can_resend_otp': True
|
|
|
|
|
}, status=status.HTTP_403_FORBIDDEN)
|
|
|
|
|
|
|
|
|
|
if not user.is_active:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'Account deactivated',
|
|
|
|
|
'message': 'Your account has been deactivated. Please contact support.'
|
|
|
|
|
}, status=status.HTTP_403_FORBIDDEN)
|
|
|
|
|
|
2025-11-12 11:51:27 +00:00
|
|
|
refresh = RefreshToken.for_user(user)
|
2025-11-22 02:19:44 +00:00
|
|
|
|
2025-11-12 11:51:27 +00:00
|
|
|
return Response({
|
|
|
|
|
'user': UserSerializer(user).data,
|
|
|
|
|
'refresh': str(refresh),
|
|
|
|
|
'access': str(refresh.access_token),
|
2025-11-22 02:19:44 +00:00
|
|
|
'message': 'Login successful'
|
2025-11-12 11:51:27 +00:00
|
|
|
})
|
|
|
|
|
else:
|
|
|
|
|
return Response(
|
|
|
|
|
{'error': 'Invalid credentials'},
|
|
|
|
|
status=status.HTTP_401_UNAUTHORIZED
|
|
|
|
|
)
|
|
|
|
|
|
2025-11-22 02:19:44 +00:00
|
|
|
@api_view(['POST'])
|
|
|
|
|
@permission_classes([AllowAny])
|
|
|
|
|
def forgot_password(request):
|
|
|
|
|
serializer = ForgotPasswordSerializer(data=request.data)
|
|
|
|
|
|
|
|
|
|
if serializer.is_valid():
|
|
|
|
|
email = serializer.validated_data['email']
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
user = CustomUser.objects.get(email=email)
|
|
|
|
|
|
|
|
|
|
if not user.isVerified:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'Email not verified',
|
|
|
|
|
'message': 'Please verify your email address first.'
|
|
|
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
if not user.is_active:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'Account deactivated',
|
|
|
|
|
'message': 'Your account has been deactivated.'
|
|
|
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
otp = generate_otp()
|
|
|
|
|
user.forgot_password_otp = otp
|
|
|
|
|
user.forgot_password_otp_expiry = timezone.now() + timedelta(minutes=10)
|
|
|
|
|
user.save()
|
|
|
|
|
|
|
|
|
|
user_name = f"{user.first_name} {user.last_name}".strip() or user.email
|
|
|
|
|
email_sent = send_otp_via_email(user.email, otp, user_name, 'password_reset')
|
|
|
|
|
|
|
|
|
|
if not email_sent:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'Failed to send OTP',
|
|
|
|
|
'message': 'Please try again later.'
|
|
|
|
|
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
|
|
|
|
|
|
return Response({
|
|
|
|
|
'message': 'Password reset OTP sent to your email',
|
|
|
|
|
'otp_sent': True,
|
|
|
|
|
'otp_expires_in': 10,
|
|
|
|
|
'email': user.email
|
|
|
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
|
|
|
|
|
|
except CustomUser.DoesNotExist:
|
|
|
|
|
return Response({
|
|
|
|
|
'message': 'If the email exists, a password reset OTP has been sent.'
|
|
|
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
|
|
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
@api_view(['POST'])
|
|
|
|
|
@permission_classes([AllowAny])
|
|
|
|
|
def verify_password_reset_otp(request):
|
|
|
|
|
serializer = VerifyPasswordResetOTPSerializer(data=request.data)
|
|
|
|
|
|
|
|
|
|
if serializer.is_valid():
|
|
|
|
|
email = serializer.validated_data['email']
|
|
|
|
|
otp = serializer.validated_data['otp']
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
user = CustomUser.objects.get(email=email)
|
|
|
|
|
|
|
|
|
|
if (user.forgot_password_otp == otp and
|
|
|
|
|
not is_otp_expired(user.forgot_password_otp_expiry)):
|
|
|
|
|
|
|
|
|
|
return Response({
|
|
|
|
|
'message': 'OTP verified successfully',
|
|
|
|
|
'verified': True,
|
|
|
|
|
'email': user.email
|
|
|
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
|
else:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'Invalid or expired OTP'
|
|
|
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
except CustomUser.DoesNotExist:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'User not found'
|
|
|
|
|
}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
|
|
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
@api_view(['POST'])
|
|
|
|
|
@permission_classes([AllowAny])
|
|
|
|
|
def reset_password(request):
|
|
|
|
|
serializer = ResetPasswordSerializer(data=request.data)
|
|
|
|
|
|
|
|
|
|
if serializer.is_valid():
|
|
|
|
|
email = serializer.validated_data['email']
|
|
|
|
|
otp = serializer.validated_data['otp']
|
|
|
|
|
new_password = serializer.validated_data['new_password']
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
user = CustomUser.objects.get(email=email)
|
|
|
|
|
|
|
|
|
|
if (user.forgot_password_otp == otp and
|
|
|
|
|
not is_otp_expired(user.forgot_password_otp_expiry)):
|
|
|
|
|
|
|
|
|
|
# Set new password
|
|
|
|
|
user.set_password(new_password)
|
|
|
|
|
|
|
|
|
|
user.forgot_password_otp = None
|
|
|
|
|
user.forgot_password_otp_expiry = None
|
|
|
|
|
user.save()
|
|
|
|
|
|
|
|
|
|
return Response({
|
|
|
|
|
'message': 'Password reset successfully',
|
|
|
|
|
'success': True
|
|
|
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
|
else:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'Invalid or expired OTP'
|
|
|
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
except CustomUser.DoesNotExist:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'User not found'
|
|
|
|
|
}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
|
|
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
@api_view(['POST'])
|
|
|
|
|
@permission_classes([AllowAny])
|
|
|
|
|
def resend_password_reset_otp(request):
|
|
|
|
|
serializer = ForgotPasswordSerializer(data=request.data)
|
|
|
|
|
|
|
|
|
|
if serializer.is_valid():
|
|
|
|
|
email = serializer.validated_data['email']
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
user = CustomUser.objects.get(email=email)
|
|
|
|
|
|
|
|
|
|
otp = generate_otp()
|
|
|
|
|
user.forgot_password_otp = otp
|
|
|
|
|
user.forgot_password_otp_expiry = timezone.now() + timedelta(minutes=10)
|
|
|
|
|
user.save()
|
|
|
|
|
|
|
|
|
|
user_name = f"{user.first_name} {user.last_name}".strip() or user.email
|
|
|
|
|
email_sent = send_otp_via_email(user.email, otp, user_name, 'password_reset')
|
|
|
|
|
|
|
|
|
|
if not email_sent:
|
|
|
|
|
return Response({
|
|
|
|
|
'error': 'Failed to send OTP',
|
|
|
|
|
'message': 'Please try again later.'
|
|
|
|
|
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
|
|
|
|
|
|
return Response({
|
|
|
|
|
'message': 'Password reset OTP resent to your email',
|
|
|
|
|
'otp_sent': True,
|
|
|
|
|
'otp_expires_in': 10
|
|
|
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
|
|
|
|
|
|
except CustomUser.DoesNotExist:
|
|
|
|
|
return Response({
|
|
|
|
|
'message': 'If the email exists, a password reset OTP has been sent.'
|
|
|
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
|
|
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
2025-11-12 11:51:27 +00:00
|
|
|
@api_view(['GET'])
|
|
|
|
|
@permission_classes([IsAuthenticated])
|
|
|
|
|
def get_user_profile(request):
|
|
|
|
|
serializer = UserSerializer(request.user)
|
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
|
|
|
|
|
@api_view(['PUT'])
|
|
|
|
|
@permission_classes([IsAuthenticated])
|
|
|
|
|
def update_user_profile(request):
|
|
|
|
|
serializer = UserSerializer(request.user, data=request.data, partial=True)
|
|
|
|
|
if serializer.is_valid():
|
|
|
|
|
serializer.save()
|
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
class UserDetailView(generics.RetrieveAPIView):
|
|
|
|
|
serializer_class = UserSerializer
|
|
|
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
|
|
|
|
|
|
def get_object(self):
|
|
|
|
|
return self.request.user
|