From a588f03871b3538b986fbb0998b2a215bb8061bb Mon Sep 17 00:00:00 2001 From: itxsoumya Date: Fri, 1 Mar 2024 19:28:23 +0530 Subject: [PATCH 1/6] reset password schema for API docs swagger updated --- care/users/reset_password_views.py | 84 ++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 4 deletions(-) diff --git a/care/users/reset_password_views.py b/care/users/reset_password_views.py index 6cec930e4c..52d33f5c17 100644 --- a/care/users/reset_password_views.py +++ b/care/users/reset_password_views.py @@ -21,7 +21,8 @@ pre_password_reset, reset_password_token_created, ) -from drf_spectacular.utils import extend_schema +from drf_spectacular.utils import extend_schema, OpenApiExample, OpenApiParameter, OpenApiResponse, OpenApiSchemaBase +from drf_spectacular.types import OpenApiTypes from rest_framework import exceptions, serializers, status from rest_framework.generics import GenericAPIView from rest_framework.response import Response @@ -49,7 +50,36 @@ class ResetPasswordCheck(GenericAPIView): permission_classes = () - @extend_schema(tags=["auth", "users"]) + @extend_schema( + tags=('users', 'auth'), + + + examples=[ + OpenApiExample( + + name='Password Reset Check Example', + summary='Password Reset Check', + description='Provide your unique token', + value={ + "token": "your token" + }, + request_only=True + + ), + + OpenApiExample( + name='Password Reset Check Response Example', + summary='Password Reset Check Response', + description='Response for password reset check request', + value={ + "status": "your status", + "detail": "your detail" + }, + response_only=True, + + ), + ] + ) def post(self, request, *args, **kwargs): token = request.data.get("token", None) @@ -95,7 +125,25 @@ class ResetPasswordConfirm(GenericAPIView): permission_classes = () serializer_class = PasswordTokenSerializer - @extend_schema(tags=["auth", "users"]) + @extend_schema( + tags=('users', 'auth'), + + + examples=[ + OpenApiExample( + + name='Password Reset Confirm', + summary='Password Reset Confirm', + description='Provide password and token to reset password', + value={ + "password": "your password", + "token": "your token" + }, + request_only=True + + ), + ] + ) def post(self, request, *args, **kwargs): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) @@ -174,7 +222,35 @@ class ResetPasswordRequestToken(GenericAPIView): permission_classes = () serializer_class = ResetPasswordUserSerializer - @extend_schema(tags=["auth", "users"]) + @extend_schema( + tags=('users', 'auth'), + + + examples=[ + OpenApiExample( + + name='Password Reset Request Example', + summary='Password reset', + description='Provide your username for password reset', + value={ + "username": "your username" + }, + request_only=True + + ), + + OpenApiExample( + name='Password Reset Response Example', + summary='Password Reset Response', + description='Response for password reset request', + value={ + "status": "OK" + }, + response_only=True, + + ), + ] + ) def post(self, request, *args, **kwargs): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) From e750ab32ecbb53b135616e28da5f78ba816062ce Mon Sep 17 00:00:00 2001 From: itxsoumya Date: Fri, 1 Mar 2024 19:38:16 +0530 Subject: [PATCH 2/6] removed unused libs --- care/users/reset_password_views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/care/users/reset_password_views.py b/care/users/reset_password_views.py index 52d33f5c17..d9a52b3ef6 100644 --- a/care/users/reset_password_views.py +++ b/care/users/reset_password_views.py @@ -21,8 +21,7 @@ pre_password_reset, reset_password_token_created, ) -from drf_spectacular.utils import extend_schema, OpenApiExample, OpenApiParameter, OpenApiResponse, OpenApiSchemaBase -from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import extend_schema, OpenApiExample from rest_framework import exceptions, serializers, status from rest_framework.generics import GenericAPIView from rest_framework.response import Response From 75c0110f3b2646202f4cb8d1980e1de097611421 Mon Sep 17 00:00:00 2001 From: itxsoumya Date: Sat, 2 Mar 2024 08:48:16 +0530 Subject: [PATCH 3/6] fixed parameters --- care/users/reset_password_views.py | 62 ++++++++++++++---------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/care/users/reset_password_views.py b/care/users/reset_password_views.py index d9a52b3ef6..8f966cfa81 100644 --- a/care/users/reset_password_views.py +++ b/care/users/reset_password_views.py @@ -42,6 +42,14 @@ class ResetPasswordUserSerializer(serializers.Serializer): username = serializers.CharField() +class ResetPasswordUserResponseSerializer(serializers.Serializer): + status = serializers.CharField() + + +class ResetPasswordConfirmResponseSerializer(serializers.Serializer): + status = serializers.CharField() + + class ResetPasswordCheck(GenericAPIView): """ An Api View which provides a method to check if a password reset token is valid @@ -49,36 +57,6 @@ class ResetPasswordCheck(GenericAPIView): permission_classes = () - @extend_schema( - tags=('users', 'auth'), - - - examples=[ - OpenApiExample( - - name='Password Reset Check Example', - summary='Password Reset Check', - description='Provide your unique token', - value={ - "token": "your token" - }, - request_only=True - - ), - - OpenApiExample( - name='Password Reset Check Response Example', - summary='Password Reset Check Response', - description='Response for password reset check request', - value={ - "status": "your status", - "detail": "your detail" - }, - response_only=True, - - ), - ] - ) def post(self, request, *args, **kwargs): token = request.data.get("token", None) @@ -126,8 +104,11 @@ class ResetPasswordConfirm(GenericAPIView): @extend_schema( tags=('users', 'auth'), - - + parameters=[ + PasswordTokenSerializer + ], + request=PasswordTokenSerializer, + responses=ResetPasswordConfirmResponseSerializer, examples=[ OpenApiExample( @@ -141,6 +122,16 @@ class ResetPasswordConfirm(GenericAPIView): request_only=True ), + OpenApiExample( + name='Password Reset Confirm Example', + summary='Password Reset Confirm', + description='Response for password confirm request', + value={ + "status": "OK" + }, + response_only=True, + + ), ] ) def post(self, request, *args, **kwargs): @@ -223,8 +214,11 @@ class ResetPasswordRequestToken(GenericAPIView): @extend_schema( tags=('users', 'auth'), - - + parameters=[ + ResetPasswordUserSerializer, + ], + request=ResetPasswordUserSerializer, + responses=ResetPasswordUserResponseSerializer, examples=[ OpenApiExample( From c1987a4bb4bc5b6459d6f143bed87771a4076d68 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Mon, 18 Mar 2024 15:25:31 +0530 Subject: [PATCH 4/6] lint --- care/users/reset_password_views.py | 66 +++++++++++------------------- 1 file changed, 24 insertions(+), 42 deletions(-) diff --git a/care/users/reset_password_views.py b/care/users/reset_password_views.py index 8f966cfa81..85d16381dd 100644 --- a/care/users/reset_password_views.py +++ b/care/users/reset_password_views.py @@ -21,7 +21,7 @@ pre_password_reset, reset_password_token_created, ) -from drf_spectacular.utils import extend_schema, OpenApiExample +from drf_spectacular.utils import OpenApiExample, extend_schema from rest_framework import exceptions, serializers, status from rest_framework.generics import GenericAPIView from rest_framework.response import Response @@ -103,36 +103,26 @@ class ResetPasswordConfirm(GenericAPIView): serializer_class = PasswordTokenSerializer @extend_schema( - tags=('users', 'auth'), - parameters=[ - PasswordTokenSerializer - ], + tags=("users", "auth"), + parameters=[PasswordTokenSerializer], request=PasswordTokenSerializer, responses=ResetPasswordConfirmResponseSerializer, examples=[ OpenApiExample( - - name='Password Reset Confirm', - summary='Password Reset Confirm', - description='Provide password and token to reset password', - value={ - "password": "your password", - "token": "your token" - }, - request_only=True - + name="Password Reset Confirm", + summary="Password Reset Confirm", + description="Provide password and token to reset password", + value={"password": "your password", "token": "your token"}, + request_only=True, ), OpenApiExample( - name='Password Reset Confirm Example', - summary='Password Reset Confirm', - description='Response for password confirm request', - value={ - "status": "OK" - }, + name="Password Reset Confirm Example", + summary="Password Reset Confirm", + description="Response for password confirm request", + value={"status": "OK"}, response_only=True, - ), - ] + ], ) def post(self, request, *args, **kwargs): serializer = self.serializer_class(data=request.data) @@ -213,7 +203,7 @@ class ResetPasswordRequestToken(GenericAPIView): serializer_class = ResetPasswordUserSerializer @extend_schema( - tags=('users', 'auth'), + tags=("users", "auth"), parameters=[ ResetPasswordUserSerializer, ], @@ -221,28 +211,20 @@ class ResetPasswordRequestToken(GenericAPIView): responses=ResetPasswordUserResponseSerializer, examples=[ OpenApiExample( - - name='Password Reset Request Example', - summary='Password reset', - description='Provide your username for password reset', - value={ - "username": "your username" - }, - request_only=True - + name="Password Reset Request Example", + summary="Password reset", + description="Provide your username for password reset", + value={"username": "your username"}, + request_only=True, ), - OpenApiExample( - name='Password Reset Response Example', - summary='Password Reset Response', - description='Response for password reset request', - value={ - "status": "OK" - }, + name="Password Reset Response Example", + summary="Password Reset Response", + description="Response for password reset request", + value={"status": "OK"}, response_only=True, - ), - ] + ], ) def post(self, request, *args, **kwargs): serializer = self.serializer_class(data=request.data) From cd7bd1511c5c43ca4d20118acc01119e61145ba4 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Mon, 18 Mar 2024 16:05:06 +0530 Subject: [PATCH 5/6] use serializers to correctly generate schema --- care/users/reset_password_views.py | 93 ++++++++++-------------------- 1 file changed, 32 insertions(+), 61 deletions(-) diff --git a/care/users/reset_password_views.py b/care/users/reset_password_views.py index 85d16381dd..28ab1cfe12 100644 --- a/care/users/reset_password_views.py +++ b/care/users/reset_password_views.py @@ -15,13 +15,13 @@ get_password_reset_lookup_field, get_password_reset_token_expiry_time, ) -from django_rest_passwordreset.serializers import PasswordTokenSerializer +from django_rest_passwordreset.serializers import PasswordValidateMixin from django_rest_passwordreset.signals import ( post_password_reset, pre_password_reset, reset_password_token_created, ) -from drf_spectacular.utils import OpenApiExample, extend_schema +from drf_spectacular.utils import extend_schema from rest_framework import exceptions, serializers, status from rest_framework.generics import GenericAPIView from rest_framework.response import Response @@ -38,16 +38,24 @@ ) -class ResetPasswordUserSerializer(serializers.Serializer): - username = serializers.CharField() +class ResetPasswordCheckSerializer(serializers.Serializer): + token = serializers.CharField( + write_only=True, help_text="The token that was sent to the user's email address" + ) + status = serializers.CharField(read_only=True, help_text="Request status") -class ResetPasswordUserResponseSerializer(serializers.Serializer): - status = serializers.CharField() +class ResetPasswordConfirmSerializer(PasswordValidateMixin, serializers.Serializer): + token = serializers.CharField( + write_only=True, help_text="The token that was sent to the user's email address" + ) + password = serializers.CharField(write_only=True, help_text="The new password") + status = serializers.CharField(read_only=True, help_text="Request status") -class ResetPasswordConfirmResponseSerializer(serializers.Serializer): - status = serializers.CharField() +class ResetPasswordRequestTokenSerializer(serializers.Serializer): + username = serializers.CharField(write_only=True) + status = serializers.CharField(read_only=True, help_text="Request status") class ResetPasswordCheck(GenericAPIView): @@ -55,10 +63,15 @@ class ResetPasswordCheck(GenericAPIView): An Api View which provides a method to check if a password reset token is valid """ + authentication_classes = () permission_classes = () + serializer_class = ResetPasswordCheckSerializer + @extend_schema(tags=["auth"]) def post(self, request, *args, **kwargs): - token = request.data.get("token", None) + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + token = serializer.validated_data["token"] if ratelimit(request, "reset", [token], "20/h"): return Response( @@ -99,31 +112,11 @@ class ResetPasswordConfirm(GenericAPIView): An Api View which provides a method to reset a password based on a unique token """ + authentication_classes = () permission_classes = () - serializer_class = PasswordTokenSerializer - - @extend_schema( - tags=("users", "auth"), - parameters=[PasswordTokenSerializer], - request=PasswordTokenSerializer, - responses=ResetPasswordConfirmResponseSerializer, - examples=[ - OpenApiExample( - name="Password Reset Confirm", - summary="Password Reset Confirm", - description="Provide password and token to reset password", - value={"password": "your password", "token": "your token"}, - request_only=True, - ), - OpenApiExample( - name="Password Reset Confirm Example", - summary="Password Reset Confirm", - description="Response for password confirm request", - value={"status": "OK"}, - response_only=True, - ), - ], - ) + serializer_class = ResetPasswordConfirmSerializer + + @extend_schema(tags=["auth"]) def post(self, request, *args, **kwargs): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) @@ -199,33 +192,11 @@ class ResetPasswordRequestToken(GenericAPIView): """ throttle_classes = () + authentication_classes = () permission_classes = () - serializer_class = ResetPasswordUserSerializer - - @extend_schema( - tags=("users", "auth"), - parameters=[ - ResetPasswordUserSerializer, - ], - request=ResetPasswordUserSerializer, - responses=ResetPasswordUserResponseSerializer, - examples=[ - OpenApiExample( - name="Password Reset Request Example", - summary="Password reset", - description="Provide your username for password reset", - value={"username": "your username"}, - request_only=True, - ), - OpenApiExample( - name="Password Reset Response Example", - summary="Password Reset Response", - description="Response for password reset request", - value={"status": "OK"}, - response_only=True, - ), - ], - ) + serializer_class = ResetPasswordRequestTokenSerializer + + @extend_schema(tags=["auth"]) def post(self, request, *args, **kwargs): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) @@ -269,9 +240,9 @@ def post(self, request, *args, **kwargs): ): raise exceptions.ValidationError( { - "email": [ + "username": [ _( - "There is no active user associated with this e-mail address or the password can not be changed" + "There is no active user associated with this username or the password can not be changed" ) ], } From 8b1267a7238ad7c000e4da434c2b10d61ccc7033 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Mon, 18 Mar 2024 16:14:14 +0530 Subject: [PATCH 6/6] fix test cases --- care/users/tests/test_auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/care/users/tests/test_auth.py b/care/users/tests/test_auth.py index 29e5f4168c..c03665f0b6 100644 --- a/care/users/tests/test_auth.py +++ b/care/users/tests/test_auth.py @@ -162,8 +162,8 @@ def test_verify_password_reset_token(self): def test_verify_password_reset_token_with_missing_fields(self): response = self.client.post("/api/v1/password_reset/check/") - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.assertEqual(response.data["detail"], "The password reset link is invalid") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.json()["token"], ["This field is required."]) def test_verify_password_reset_token_with_invalid_token(self): response = self.client.post(