diff --git a/backend/Ugra_hackaton/settings.py b/backend/Ugra_hackaton/settings.py index 01121a8..6a3ccad 100644 --- a/backend/Ugra_hackaton/settings.py +++ b/backend/Ugra_hackaton/settings.py @@ -147,3 +147,21 @@ EMAIL_HOST_USER = 'artemss20030906@gmail.com' EMAIL_HOST_PASSWORD = 'hbqgvdsqhvkuijuc' EMAIL_PORT = 587 EMAIL_USE_TLS = True + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + 'OPTIONS': { + 'min_length': 7, + } + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] diff --git a/backend/back/models.py b/backend/back/models.py index 73ceebc..4663d91 100644 --- a/backend/back/models.py +++ b/backend/back/models.py @@ -7,6 +7,9 @@ class Profile(models.Model): user = models.OneToOneField(User, on_delete = models.CASCADE) points = models.DecimalField(max_digits=7, decimal_places=0, default = 0) + def __str__(self): + return(f'{self.user.username} - {self.user.email}') + @receiver(post_save, sender = User) def create_profile(sender, instance, created, **kwargs): if(created): diff --git a/backend/back/serializers.py b/backend/back/serializers.py index 926f84a..35f0250 100644 --- a/backend/back/serializers.py +++ b/backend/back/serializers.py @@ -10,4 +10,19 @@ class UserRegisterSerializer(serializers.ModelSerializer): 'username', 'password', 'email' + ] + +class UserResetPasswordSerializer(serializers.ModelSerializer): + email = serializers.EmailField(required=True) + class Meta: + model = User + fields = [ + 'email' + ] + +class UserPasswordSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = [ + 'password' ] \ No newline at end of file diff --git a/backend/back/urls.py b/backend/back/urls.py index 4278d80..c99d2eb 100644 --- a/backend/back/urls.py +++ b/backend/back/urls.py @@ -7,4 +7,6 @@ urlpatterns = [ path('login', obtain_auth_token, name = 'UserLogin'), path('logout', views.UserLogoutAPIView, name = 'UserLogout'), path('activate//', views.UserActivationAPIView, name = 'UserActivation'), + path('reset_password_request', views.UserResetPasswordRequestAPIView, name = 'UserResetPasswordRequest'), + path('reset_password//', views.UserResetPasswordAPIView, name = 'UserResetPassword'), ] diff --git a/backend/back/views.py b/backend/back/views.py index 7f4a236..297fda2 100644 --- a/backend/back/views.py +++ b/backend/back/views.py @@ -10,8 +10,9 @@ from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode from django.template.loader import render_to_string from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.core.mail import EmailMultiAlternatives -from .serializers import UserRegisterSerializer +from .serializers import UserRegisterSerializer, UserResetPasswordSerializer, UserPasswordSerializer from django.utils.html import strip_tags +from django.contrib.auth.password_validation import validate_password import six import threading @@ -20,6 +21,7 @@ class EmailTokenGenerator(PasswordResetTokenGenerator): return(six.text_type(user.pk) + six.text_type(timestamp) + six.text_type(user.is_active)) email_token_gen = EmailTokenGenerator() +password_token_gen = PasswordResetTokenGenerator() @api_view(['POST']) @permission_classes([~permissions.IsAuthenticated]) @@ -27,8 +29,12 @@ def UserRegisterAPIView(request): serializer = UserRegisterSerializer(data = request.data) if(serializer.is_valid()): data = serializer.validated_data - if(User.objects.filter(username = data['username']).exists()): + if(User.objects.filter(username = data['username']).exists() or User.objects.filter(email = data['email']).exists()): return Response({'ok':False, 'error':'User already exists'}, status = status.HTTP_400_BAD_REQUEST) + try: + validate_password(data['password']) + except Exception as errors: + return Response({'ok':False, 'error':errors} ,status = status.HTTP_400_BAD_REQUEST) user = User.objects.create(username = data['username'], email = data['email'], is_active = False) user.set_password(data['password']) user.save() @@ -52,12 +58,57 @@ def UserActivationAPIView(request, uidb64, token): user = User.objects.filter(id = uid) if(user.exists()): user = user.first() - if(email_token_gen.check_token(user, token) and not user.is_active): + if(email_token_gen.check_token(user, token)): user.is_active = True user.save() return Response({'ok':True}, status = status.HTTP_200_OK) return Response({'ok':False, 'error':'Invalid activation link'}, status = status.HTTP_400_BAD_REQUEST) +@api_view(['POST']) +@permission_classes([~permissions.IsAuthenticated]) +def UserResetPasswordRequestAPIView(request): + serializer = UserResetPasswordSerializer(data = request.data) + if(serializer.is_valid()): + data = serializer.validated_data + user = User.objects.filter(email = data['email']) + if(user.exists()): + user = user.first() + domain = get_current_site(request).domain + mail_subject = 'Password reset' + message = render_to_string('password_reset.html',{ + 'user':user, + 'domain': domain, + 'uidb64': urlsafe_base64_encode(force_bytes(user.pk)), + 'token': password_token_gen.make_token(user), + }) + message_task = threading.Thread(target = async_email_send, args=( mail_subject, message, [data['email']] )) + message_task.start() + return Response({'ok':True}, status = status.HTTP_200_OK) + return Response({'ok':False, 'error':'User does not exist'}, status = status.HTTP_400_BAD_REQUEST) + return Response({'ok':False, 'error':serializer.errors}, status = status.HTTP_400_BAD_REQUEST) + +@api_view(['POST']) +@permission_classes([~permissions.IsAuthenticated]) +def UserResetPasswordAPIView(request, uidb64, token): + serializer = UserPasswordSerializer(data = request.data) + if(serializer.is_valid()): + data = serializer.validated_data + try: + validate_password(data['password']) + except Exception as errors: + return Response({'ok':False, 'error':errors} ,status = status.HTTP_400_BAD_REQUEST) + uid = force_text(urlsafe_base64_decode(uidb64)) + user = User.objects.filter(id = uid) + if(user.exists()): + user = user.first() + if(password_token_gen.check_token(user, token)): + user.set_password(data['password']) + user.save() + return Response({'ok':True}, status = status.HTTP_200_OK) + return Response({'ok':False, 'error':'Invalid password reset link'} ,status = status.HTTP_400_BAD_REQUEST) + return Response({'ok':False, 'error':'User does not exist'} ,status = status.HTTP_400_BAD_REQUEST) + return Response({'ok':False, 'error':serializer.errors}, status = status.HTTP_400_BAD_REQUEST) + @api_view(['POST']) @permission_classes([permissions.IsAuthenticated]) def UserLogoutAPIView(request): diff --git a/backend/templates/password_reset.html b/backend/templates/password_reset.html new file mode 100644 index 0000000..4392267 --- /dev/null +++ b/backend/templates/password_reset.html @@ -0,0 +1,6 @@ +{% autoescape off %} +

HI {{ user.username }}

+

This email was used to reset your account password

+

To reset password follow the link below:

+

Reset Password

+{% endautoescape %} \ No newline at end of file