174 lines
5.9 KiB
Python
174 lines
5.9 KiB
Python
from django.shortcuts import render
|
|
from rest_framework import generics, status
|
|
from rest_framework.response import Response
|
|
from rest_framework.permissions import AllowAny, IsAdminUser, IsAuthenticated
|
|
from rest_framework.views import APIView
|
|
from django.contrib.auth import get_user_model
|
|
from django.contrib.auth.password_validation import validate_password
|
|
from django.core.exceptions import ValidationError
|
|
from django.utils import timezone
|
|
from datetime import timedelta
|
|
from django.core.mail import send_mail
|
|
from django.conf import settings
|
|
from .serializers import UserRegistrationSerializer, UserSerializer, ResetPasswordSerializer, ResetPasswordConfirmSerializer
|
|
from .models import User, PasswordResetToken
|
|
import secrets
|
|
import string
|
|
|
|
User = get_user_model()
|
|
|
|
# Create your views here.
|
|
|
|
class RegisterView(generics.CreateAPIView):
|
|
"""
|
|
API endpoint for user registration.
|
|
|
|
create:
|
|
Register a new user with the following fields:
|
|
- username (required)
|
|
- password (required)
|
|
- password2 (required, must match password)
|
|
- email (required)
|
|
- first_name (required)
|
|
- last_name (required)
|
|
- role (optional, defaults to 'user')
|
|
"""
|
|
permission_classes = (AllowAny,)
|
|
serializer_class = UserRegistrationSerializer
|
|
|
|
def create(self, request, *args, **kwargs):
|
|
serializer = self.get_serializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
self.perform_create(serializer)
|
|
headers = self.get_success_headers(serializer.data)
|
|
return Response(
|
|
{"message": "User registered successfully. Please login to continue."},
|
|
status=status.HTTP_201_CREATED,
|
|
headers=headers
|
|
)
|
|
|
|
class UserViewSet(generics.ListAPIView):
|
|
"""
|
|
API endpoint for viewing and editing users.
|
|
"""
|
|
queryset = User.objects.all()
|
|
serializer_class = UserSerializer
|
|
permission_classes = [IsAdminUser]
|
|
|
|
class UserDetailView(generics.RetrieveUpdateAPIView):
|
|
"""
|
|
API endpoint for retrieving and updating the current user's data.
|
|
"""
|
|
serializer_class = UserSerializer
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
def get_object(self):
|
|
return self.request.user
|
|
|
|
def update(self, request, *args, **kwargs):
|
|
partial = kwargs.pop('partial', False)
|
|
instance = self.get_object()
|
|
serializer = self.get_serializer(instance, data=request.data, partial=partial)
|
|
serializer.is_valid(raise_exception=True)
|
|
self.perform_update(serializer)
|
|
return Response(serializer.data)
|
|
|
|
class ChangePasswordView(APIView):
|
|
"""
|
|
API endpoint for changing user password.
|
|
"""
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
def post(self, request):
|
|
user = request.user
|
|
old_password = request.data.get('old_password')
|
|
new_password = request.data.get('new_password')
|
|
|
|
if not old_password or not new_password:
|
|
return Response(
|
|
{"detail": "Both old and new password are required."},
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
)
|
|
|
|
if not user.check_password(old_password):
|
|
return Response(
|
|
{"detail": "Current password is incorrect."},
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
)
|
|
|
|
try:
|
|
validate_password(new_password, user)
|
|
except ValidationError as e:
|
|
return Response(
|
|
{"detail": list(e.messages)},
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
)
|
|
|
|
user.set_password(new_password)
|
|
user.save()
|
|
return Response(
|
|
{"detail": "Password successfully updated."},
|
|
status=status.HTTP_200_OK
|
|
)
|
|
|
|
class ResetPasswordView(generics.GenericAPIView):
|
|
permission_classes = [AllowAny]
|
|
serializer_class = ResetPasswordSerializer
|
|
|
|
def post(self, request):
|
|
serializer = self.get_serializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
email = serializer.validated_data['email']
|
|
try:
|
|
user = User.objects.get(email=email)
|
|
# Create a new reset token
|
|
token = PasswordResetToken.objects.create(
|
|
user=user,
|
|
expires_at=timezone.now() + timedelta(hours=24)
|
|
)
|
|
|
|
# Send reset email
|
|
reset_url = f"{settings.FRONTEND_URL}/reset-password/{token.token}"
|
|
send_mail(
|
|
'Password Reset Request',
|
|
f'Click the following link to reset your password: {reset_url}',
|
|
settings.DEFAULT_FROM_EMAIL,
|
|
[email],
|
|
fail_silently=False,
|
|
)
|
|
|
|
return Response({
|
|
'detail': 'If an account exists with this email, you will receive password reset instructions.'
|
|
}, status=status.HTTP_200_OK)
|
|
except User.DoesNotExist:
|
|
# Don't reveal that the email doesn't exist
|
|
return Response({
|
|
'detail': 'If an account exists with this email, you will receive password reset instructions.'
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
class ResetPasswordConfirmView(generics.GenericAPIView):
|
|
permission_classes = [AllowAny]
|
|
serializer_class = ResetPasswordConfirmSerializer
|
|
|
|
def post(self, request):
|
|
serializer = self.get_serializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
token = serializer.validated_data['token']
|
|
new_password = serializer.validated_data['new_password']
|
|
|
|
reset_token = PasswordResetToken.objects.get(token=token)
|
|
user = reset_token.user
|
|
|
|
# Update password
|
|
user.set_password(new_password)
|
|
user.save()
|
|
|
|
# Mark token as used
|
|
reset_token.used = True
|
|
reset_token.save()
|
|
|
|
return Response({
|
|
'detail': 'Password has been reset successfully.'
|
|
}, status=status.HTTP_200_OK) |