An open API service indexing awesome lists of open source software.

https://github.com/joshuachinemezu/django-rest-resetpassword

An extension of django rest framework, providing configurable password reset feature
https://github.com/joshuachinemezu/django-rest-resetpassword

django django-password-reset django-rest-auth django-rest-framework rest-auth

Last synced: 7 months ago
JSON representation

An extension of django rest framework, providing configurable password reset feature

Awesome Lists containing this project

README

          

# Django Rest Password Reset

This python package provides a simple password reset strategy for django rest framework, where users can request password
reset tokens via their registered e-mail address.

Credits - https://github.com/anexia-it/django-rest-passwordreset

The main idea behind this package is to not make any assumptions about how the token is delivered to the end-user (e-mail, text-message, etc...) and to major issues in the origin package.
Also, this package provides a signal that can be reacted on (e.g., by sending an e-mail or a text message).

This package basically provides two REST endpoints:

- Request a token
- Verify (confirm) a token (and change the password)

## Quickstart

1. Install the package from pypi using pip:

```bash
pip install django-rest-resetpassword
```

2. Add `django_rest_resetpassword` to your `INSTALLED_APPS` (after `rest_framework`) within your Django settings file:

```python
INSTALLED_APPS = (
...
'django.contrib.auth',
...
'rest_framework',
...
'django_rest_resetpassword',
...
)
```

3. This package stores tokens in a separate database table (see [django_rest_resetpassword/models.py](django_rest_resetpassword/models.py)). Therefore, you have to run django migrations:

```bash
python manage.py migrate
```

4. This package provides two endpoints, which can be included by including `django_rest_resetpassword.urls` in your `urls.py` as follows:

```python
from django.conf.urls import url, include

urlpatterns = [
...
url(r'^accounts/password-reset/',
include('django_rest_resetpassword.urls', namespace='password_reset')),
...
]
```

**Note**: You can adapt the url to your needs.

### Endpoints

The following endpoints are provided:

- `POST ${API_URL}/password-reset/` - request a reset password token by using the `email` parameter
- `POST ${API_URL}//password-reset/validate_token/` - will return a 200 if a given `token` is valid
- `POST ${API_URL}password-reset/confirm/` - using a valid `token`, the users password is set to the provided `password`

where `${API_URL}/` is the url specified in your _urls.py_ (e.g., `api/password_reset/`)

### Signals

- `reset_password_token_created(sender, instance, reset_password_token)` Fired when a reset password token is generated
- `pre_password_reset(user)` - fired just before a password is being reset
- `post_password_reset(user)` - fired after a password has been reset

### Example for sending an e-mail

1. Create two new django templates: `email/user_reset_password.html` and `email/user_reset_password.txt`. Those templates will contain the e-mail message sent to the user, aswell as the password reset link (or token).
Within the templates, you can access the following context variables: `current_user`, `username`, `email`, `reset_password_url`. Feel free to adapt this to your needs.

2. Add the following code, which contains a Django Signal Receiver (`@receiver(...)`), to your application. Take care where to put this code, as it needs to be executed by the python interpreter (see the section _The `reset_password_token_created` signal is not fired_ below, aswell as [this part of the django documentation](https://docs.djangoproject.com/en/1.11/topics/signals/#connecting-receiver-functions) and [How to Create Django Signals Tutorial](https://simpleisbetterthancomplex.com/tutorial/2016/07/28/how-to-create-django-signals.html) for more information).

```python
from django.core.mail import EmailMultiAlternatives
from django.dispatch import receiver
from django.template.loader import render_to_string
from django.urls import reverse

from django_rest_resetpassword.signals import reset_password_token_created

@receiver(reset_password_token_created)
def password_reset_token_created(sender, instance, reset_password_token, *args, **kwargs):
"""
Handles password reset tokens
When a token is created, an e-mail needs to be sent to the user
:param sender: View Class that sent the signal
:param instance: View Instance that sent the signal
:param reset_password_token: Token Model Object
:param args:
:param kwargs:
:return:
"""
# send an e-mail to the user
context = {
'current_user': reset_password_token.user,
'username': reset_password_token.user.username,
'email': reset_password_token.user.email,
'reset_password_url': "{}?token={}".format(reverse('password_reset:reset-password-request'), reset_password_token.key)
}

# render email text
email_html_message = render_to_string('email/user_reset_password.html', context)
email_plaintext_message = render_to_string('email/user_reset_password.txt', context)

msg = EmailMultiAlternatives(
# title:
"Password Reset for {title}".format(title="Some website title"),
# message:
email_plaintext_message,
# from:
"noreply@somehost.local",
# to:
[reset_password_token.user.email]
)
msg.attach_alternative(email_html_message, "text/html")
msg.send()

```

3. You should now be able to use the endpoints to request a password reset token via your e-mail address.
If you want to test this locally, I recommend using some kind of fake mailserver (such as maildump).

# Configuration / Settings

The following settings can be set in Djangos `settings.py` file:

- `DJANGO_REST_MULTITOKENAUTH_RESET_TOKEN_EXPIRY_TIME` - time in hours about how long the token is active (Default: 24)

**Please note**: expired tokens are automatically cleared based on this setting in every call of `ResetPasswordRequestToken.post`.

- `DJANGO_REST_RESETPASSWORD_NO_INFORMATION_LEAKAGE` - will cause a 200 to be returned on `POST ${API_URL}/reset_password/`
even if the user doesn't exist in the databse (Default: False)

- `DJANGO_REST_MULTITOKENAUTH_REQUIRE_USABLE_PASSWORD` - allows password reset for a user that does not
[have a usable password](https://docs.djangoproject.com/en/2.2/ref/contrib/auth/#django.contrib.auth.models.User.has_usable_password) (Default: True)

## Custom Email Lookup

By default, `email` lookup is used to find the user instance. You can change that or add more matching fields by adding

```python
DJANGO_REST_LOOKUP_FIELDS = ['custom_email_field', 'other_lookup_field']
```

into Django settings.py file.

## 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):

```python
DJANGO_REST_RESETPASSWORD_IP_ADDRESS_HEADER = 'HTTP_X_FORWARDED_FOR'
```

The same is true for the user agent:

```python
HTTP_USER_AGENT_HEADER = 'HTTP_USER_AGENT'
```

## Custom Token Generator

By default, a random string token of length 10 to 50 is generated using the `RandomStringTokenGenerator` class.
This library offers a possibility to configure the params of `RandomStringTokenGenerator` as well as switch to
another token generator, e.g. `RandomNumberTokenGenerator`. You can also generate your own token generator class.

You can change that by adding

```python
DJANGO_REST_RESETPASSWORD_TOKEN_CONFIG = {
"CLASS": ...,
"OPTIONS": {...}
}
```

into Django settings.py file.

### RandomStringTokenGenerator

This is the default configuration.

```python
DJANGO_REST_RESETPASSWORD_TOKEN_CONFIG = {
"CLASS": "django_rest_resetpassword.tokens.RandomStringTokenGenerator"
}
```

You can configure the length as follows:

```python
DJANGO_REST_RESETPASSWORD_TOKEN_CONFIG = {
"CLASS": "django_rest_resetpassword.tokens.RandomStringTokenGenerator",
"OPTIONS": {
"min_length": 20,
"max_length": 30
}
}
```

It uses `os.urandom()` to generate a good random string.

### RandomNumberTokenGenerator

```python
DJANGO_REST_RESETPASSWORD_TOKEN_CONFIG = {
"CLASS": "django_rest_resetpassword.tokens.RandomNumberTokenGenerator"
}
```

You can configure the minimum and maximum number as follows:

```python
DJANGO_REST_RESETPASSWORD_TOKEN_CONFIG = {
"CLASS": "django_rest_resetpassword.tokens.RandomNumberTokenGenerator",
"OPTIONS": {
"min_number": 1500,
"max_number": 9999
}
}
```

It uses `random.SystemRandom().randint()` to generate a good random number.

### Write your own Token Generator

Please see [token_configuration/django_rest_resetpassword/tokens.py](token_configuration/django_rest_resetpassword/tokens.py) for example implementation of number and string token generator.

The basic idea is to create a new class that inherits from BaseTokenGenerator, takes arbitrary arguments (`args` and `kwargs`)
in the `__init__` function as well as implementing a `generate_token` function.

```python
from django_rest_resetpassword.tokens import BaseTokenGenerator

class RandomStringTokenGenerator(BaseTokenGenerator):
"""
Generates a random string with min and max length using os.urandom and binascii.hexlify
"""

def __init__(self, min_length=10, max_length=50, *args, **kwargs):
self.min_length = min_length
self.max_length = max_length

def generate_token(self, *args, **kwargs):
""" generates a pseudo random code using os.urandom and binascii.hexlify """
# determine the length based on min_length and max_length
length = random.randint(self.min_length, self.max_length)

# generate the token using os.urandom and hexlify
return binascii.hexlify(
os.urandom(self.max_length)
).decode()[0:length]
```

## Documentation / Browsable API

This package supports the [DRF auto-generated documentation](https://www.django-rest-framework.org/topics/documenting-your-api/) (via `coreapi`) as well as the [DRF browsable API](https://www.django-rest-framework.org/topics/browsable-api/).

![drf_browsable_email_validation](docs/browsable_api_email_validation.png 'Browsable API E-Mail Validation')

![drf_browsable_password_validation](docs/browsable_api_password_validation.png 'Browsable API E-Mail Validation')

![coreapi_docs](docs/coreapi_docs.png 'Core API Docs')

## Known Issues / FAQ

### Django 2.1 Migrations - Multiple Primary keys for table ...

Django 2.1 introduced a breaking change for migrations (see [Django Issue #29790](https://code.djangoproject.com/ticket/29790)). We therefore had to rewrite the migration [0002_pk_migration.py](django_rest_resetpassword/migrations/0002_pk_migration.py) such that it covers Django versions before (`<`) 2.1 and later (`>=`) 2.1.

Some information is written down in Issue #8.

### The `reset_password_token_created` signal is not fired

You need to make sure that the code with `@receiver(reset_password_token_created)` is executed by the python interpreter. To ensure this, you have two options:

1. Put the code at a place that is automatically loaded by Django (e.g., models.py, views.py), or

2. Import the file that contains the signal within your app.py `ready` function:

_some_app/signals.py_

```python
from django.core.mail import EmailMultiAlternatives
from django.dispatch import receiver
from django.template.loader import render_to_string
from django.urls import reverse

from django_rest_resetpassword.signals import reset_password_token_created

@receiver(reset_password_token_created)
def password_reset_token_created(sender, instance, reset_password_token, *args, **kwargs):
# ...
```

_some_app/app.py_

```python
from django.apps import AppConfig

class SomeAppConfig(AppConfig):
name = 'your_django_project.some_app'
verbose_name = 'Some App'

def ready(self):
import your_django_project.some_app.signals # noqa
```

_some_app/**init**.py_

```python
default_app_config = 'your_django_project.some_app.SomeAppConfig'
```

### MongoDB not working

Apparently, the following piece of code in the Django Model prevents MongodB from working:

```python
id = models.AutoField(
primary_key=True
)
```

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.

## Tests

See folder [tests/](tests/). Basically, all endpoints are covered with multiple
unit tests.

Use this code snippet to run tests:

```bash
pip install -r requirements_test.txt
python setup.py install
cd tests
python manage.py test
```

## Release on PyPi

To release this package on pypi, the following steps are used:

```bash
rm -rf dist/ build/
python setup.py sdist
twine upload dist/*
```