Package: django-ninja-passwordreset
Django Rest Password ResetThis 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.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...).
Instead, 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 tokenVerify (confirm) a token (and change the password)QuickstartInstall the package from pypi using pip:pipinstalldjango-ninja-passwordresetAdddjango_rest_passwordresetto yourINSTALLED_APPS(afterrest_framework) within your Django settings file:INSTALLED_APPS=(...'django.contrib.auth',...'ninja_extra',...'django_rest_passwordreset',...)This package stores tokens in a separate database table (seedjango_rest_passwordreset/models.py). Therefore, you have to run django migrations:pythonmanage.pymigrateThis package provides three endpoints, which can be included by includingdjango_rest_passwordreset.urlsin yoururls.pyas follows:fromdjango.urlsimportpathfromninja_extraimportNinjaExtraAPIfromdjango_rest_passwordreset.controllerimportResetPasswordControllerapi=NinjaExtraAPI(urls_namespace='password_test')api.register_controllers(ResetPasswordController)urlpatterns=[...,path(r'^api/',api.urls),...]Note: You can adapt the URL to your needs.EndpointsThe following endpoints are provided:POST ${API_URL}/- request a reset password token by using theemailparameterPOST ${API_URL}/confirm/- using a validtoken, the users password is set to the providedpasswordPOST ${API_URL}/validate_token/- will return a 200 if a giventokenis validwhere${API_URL}/is the url specified in yoururls.py(e.g.,api/password_reset/as in the example above)Signalsreset_password_token_created(sender, instance, reset_password_token)Fired when a reset password token is generatedpre_password_reset(sender, user)- fired just before a password is being resetpost_password_reset(sender, user)- fired after a password has been resetExample for sending an e-mailCreate two new django templates:email/user_reset_password.htmlandemail/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.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 sectionThereset_password_token_createdsignal is not firedbelow, aswell asthis part of the django documentationandHow to Create Django Signals Tutorialfor more information).fromdjango.core.mailimportEmailMultiAlternativesfromdjango.dispatchimportreceiverfromdjango.template.loaderimportrender_to_stringfromdjango.urlsimportreversefromdjango_rest_passwordreset.signalsimportreset_password_token_created@receiver(reset_password_token_created)defpassword_reset_token_created(sender,instance,reset_password_token,*args,**kwargs):"""Handles password reset tokensWhen 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 usercontext={'current_user':reset_password_token.user,'username':reset_password_token.user.username,'email':reset_password_token.user.email,'reset_password_url':"{}?token={}".format(instance.request.build_absolute_uri(reverse('password_reset:reset-password-confirm')),reset_password_token.key)}# render email textemail_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()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 / SettingsThe following settings can be set in Djangosettings.pyfile: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 ofResetPasswordRequestToken.post.DJANGO_REST_PASSWORDRESET_NO_INFORMATION_LEAKAGE- will cause a 200 to be returned onPOST ${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 nothave a usable password(Default: True)Custom Email LookupBy default,emaillookup is used to find the user instance. You can change that by addingDJANGO_REST_LOOKUP_FIELD='custom_email_field'into Django settings.py file.Custom Remote IP Address and User Agent Header LookupIf 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):DJANGO_REST_PASSWORDRESET_IP_ADDRESS_HEADER='HTTP_X_FORWARDED_FOR'The same is true for the user agent:HTTP_USER_AGENT_HEADER='HTTP_USER_AGENT'Custom Token GeneratorBy default, a random string token of length 10 to 50 is generated using theRandomStringTokenGeneratorclass.
This library offers a possibility to configure the params ofRandomStringTokenGeneratoras well as switch to
another token generator, e.g.RandomNumberTokenGenerator. You can also generate your own token generator class.You can change that by addingDJANGO_REST_PASSWORDRESET_TOKEN_CONFIG={"CLASS":...,"OPTIONS":{...}}into Django settings.py file.RandomStringTokenGeneratorThis is the default configuration.DJANGO_REST_PASSWORDRESET_TOKEN_CONFIG={"CLASS":"django_rest_passwordreset.tokens.RandomStringTokenGenerator"}You can configure the length as follows:DJANGO_REST_PASSWORDRESET_TOKEN_CONFIG={"CLASS":"django_rest_passwordreset.tokens.RandomStringTokenGenerator","OPTIONS":{"min_length":20,"max_length":30}}It usesos.urandom()to generate a good random string.RandomNumberTokenGeneratorDJANGO_REST_PASSWORDRESET_TOKEN_CONFIG={"CLASS":"django_rest_passwordreset.tokens.RandomNumberTokenGenerator"}You can configure the minimum and maximum number as follows:DJANGO_REST_PASSWORDRESET_TOKEN_CONFIG={"CLASS":"django_rest_passwordreset.tokens.RandomNumberTokenGenerator","OPTIONS":{"min_number":1500,"max_number":9999}}It usesrandom.SystemRandom().randint()to generate a good random number.Write your own Token GeneratorPlease seetoken_configuration/django_rest_passwordreset/tokens.pyfor example implementation of number and string token generator.The basic idea is to create a new class that inherits from BaseTokenGenerator, takes arbitrary arguments (argsandkwargs)
in the__init__function as well as implementing agenerate_tokenfunction.fromdjango_rest_passwordreset.tokensimportBaseTokenGeneratorclassRandomStringTokenGenerator(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_lengthself.max_length=max_lengthdefgenerate_token(self,*args,**kwargs):""" generates a pseudo random code using os.urandom and binascii.hexlify """# determine the length based on min_length and max_lengthlength=random.randint(self.min_length,self.max_length)# generate the token using os.urandom and hexlifyreturnbinascii.hexlify(os.urandom(self.max_length)).decode()[0:length]Known Issues / FAQDjango 2.1 Migrations - Multiple Primary keys for table ...Django 2.1 introduced a breaking change for migrations (seeDjango Issue #29790). We therefore had to rewrite the migration0002_pk_migration.pysuch that it covers Django versions before (<) 2.1 and later (>=) 2.1.Some information is written down in Issue #8.Thereset_password_token_createdsignal is not firedYou 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:Put the code at a place that is automatically loaded by Django (e.g., models.py, views.py), orImport the file that contains the signal within your app.pyreadyfunction:some_app/signals.pyfromdjango.core.mailimportEmailMultiAlternativesfromdjango.dispatchimportreceiverfromdjango.template.loaderimportrender_to_stringfromdjango.urlsimportreversefromdjango_rest_passwordreset.signalsimportreset_password_token_created@receiver(reset_password_token_created)defpassword_reset_token_created(sender,instance,reset_password_token,*args,**kwargs):# ...some_app/app.pyfromdjango.appsimportAppConfigclassSomeAppConfig(AppConfig):name='your_django_project.some_app'verbose_name='Some App'defready(self):importyour_django_project.some_app.signals# noqasome_app/init.pydefault_app_config='your_django_project.some_app.SomeAppConfig'MongoDB not workingApparently, the following piece of code in the Django Model prevents MongodB from working:id=models.AutoField(primary_key=True)See issue #49 for details.ContributionsThis 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.TestsSee foldertests/. Basically, all endpoints are covered with multiple
unit tests.Use this code snippet to run tests:makeinstall
maketest