Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable response messages #65

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,17 @@ DJANGO_REST_LOOKUP_FIELD = 'custom_email_field'
```
into Django settings.py file.

## Configurable Response

For optional configurable response messages, add the following properties to settings.py
```python
PASSWORD_CHANGED = 'Your password has been changed'
TOKEN_EXPIRED = 'Your token has expired'
PASSWORD_REQUEST_ACCEPT = 'Please check your email for the reset password link'
TOKEN_NOT_FOUND = 'Your token is invalid'
TOKEN_VALID = 'Your token is valid'
```

## Custom Remote IP Address and User Agent Header Lookup

If your setup demands that the IP adress of the user is in another header (e.g., 'X-Forwarded-For'), you can configure that (using Django Request Headers):
Expand Down Expand Up @@ -334,6 +345,8 @@ Apparently, the following piece of code in the Django Model prevents MongodB fro

See issue #49 for details.



## Contributions

This library tries to follow the unix philosophy of "do one thing and do it well" (which is providing a basic password reset endpoint for Django Rest Framework). Contributions are welcome in the form of pull requests and issues! If you create a pull request, please make sure that you are not introducing breaking changes.
Expand Down
49 changes: 40 additions & 9 deletions django_rest_passwordreset/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,31 @@ def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
token = serializer.validated_data['token']

response_dict = dict({"status_code": None, "status": None, "message": None})
# get token validation time
password_reset_token_validation_time = get_password_reset_token_expiry_time()

# find token
reset_password_token = ResetPasswordToken.objects.filter(key=token).first()

if reset_password_token is None:
return Response({'status': 'notfound'}, status=status.HTTP_404_NOT_FOUND)
message = get_response_message("TOKEN_NOT_FOUND")
response_dict.update({"status_code": 404, "status": "notfound", "message": message})
return Response(response_dict, status=status.HTTP_404_NOT_FOUND)

# check expiry date
expiry_date = reset_password_token.created_at + timedelta(hours=password_reset_token_validation_time)

if timezone.now() > expiry_date:
# delete expired token
reset_password_token.delete()
return Response({'status': 'expired'}, status=status.HTTP_404_NOT_FOUND)

return Response({'status': 'OK'})
message = get_response_message("TOKEN_EXPIRED")
response_dict.update({"status_code": 401, 'status': 'expired', "message": message})
return Response(response_dict, status=status.HTTP_404_NOT_FOUND)

message = get_response_message("TOKEN_VALID")
response_dict.update({"status_code": 200, 'status': 'OK', "message": message})
return Response(response_dict)


class ResetPasswordConfirm(GenericAPIView):
Expand All @@ -75,6 +81,7 @@ def post(self, request, *args, **kwargs):
serializer.is_valid(raise_exception=True)
password = serializer.validated_data['password']
token = serializer.validated_data['token']
response_dict = dict({"status_code": None, "message": None, "status": None})

# get token validation time
password_reset_token_validation_time = get_password_reset_token_expiry_time()
Expand All @@ -83,15 +90,20 @@ def post(self, request, *args, **kwargs):
reset_password_token = ResetPasswordToken.objects.filter(key=token).first()

if reset_password_token is None:
return Response({'status': 'notfound'}, status=status.HTTP_404_NOT_FOUND)
message = get_response_message("TOKEN_NOT_FOUND")
response_dict.update({"status_code": 404, 'status': 'notfound', "message": message})
return Response(response_dict, status=status.HTTP_404_NOT_FOUND)

# check expiry date
expiry_date = reset_password_token.created_at + timedelta(hours=password_reset_token_validation_time)

if timezone.now() > expiry_date:
# delete expired token
reset_password_token.delete()
return Response({'status': 'expired'}, status=status.HTTP_404_NOT_FOUND)
message = get_response_message("TOKEN_EXPIRED")
response_dict.update({"status_code": 404, 'status': 'expired', "message": message})

return Response(response_dict, status=status.HTTP_404_NOT_FOUND)

# change users password (if we got to this code it means that the user is_active)
if reset_password_token.user.eligible_for_reset():
Expand All @@ -116,7 +128,11 @@ def post(self, request, *args, **kwargs):
# Delete all password reset tokens for this user
ResetPasswordToken.objects.filter(user=reset_password_token.user).delete()

return Response({'status': 'OK'})
# done
message = get_response_message("PASSWORD_CHANGED")
response_dict.update({"status_code": 200, "status": "OK", "message": message})

return Response(response_dict)


class ResetPasswordRequestToken(GenericAPIView):
Expand Down Expand Up @@ -159,6 +175,7 @@ def post(self, request, *args, **kwargs):
# but not if DJANGO_REST_PASSWORDRESET_NO_INFORMATION_LEAKAGE == True
if not active_user_found and not getattr(settings, 'DJANGO_REST_PASSWORDRESET_NO_INFORMATION_LEAKAGE', False):
raise exceptions.ValidationError({

'email': [_(
"There is no active user associated with this e-mail address or the password can not be changed")],
})
Expand All @@ -185,7 +202,21 @@ def post(self, request, *args, **kwargs):
# let whoever receives this signal handle sending the email for the password reset
reset_password_token_created.send(sender=self.__class__, instance=self, reset_password_token=token)
# done
return Response({'status': 'OK'})
message = get_response_message("PASSWORD_REQUEST_ACCEPT")
return Response({"status_code": 200, "status": "OK", "message": message})


def get_response_message(message_key):
"""
If message_key exists in settings.py, the corresponding message will be returned. Otherwise message_key
is returned
:param message_key: Key of the message in settings.py
:return: status_message
"""

status_message = getattr(settings, message_key, message_key)

return status_message


reset_password_validate_token = ResetPasswordValidateToken.as_view()
Expand Down