django-rest-auth: Issue with Password Reset functionaliity - python

I have been trying to setup password reset functionality in DRF using django-rest-auth. Earlier I was getting error TemplateDoesNotExist:registration/password_reset_email.html which I resolved by adding the following code
serializer.py
from rest_auth.serializers import PasswordResetSerializer
from allauth.account.forms import ResetPasswordForm
class PasswordSerializer(PasswordResetSerializer):
password_reset_form_class = ResetPasswordForm
settings.py
REST_AUTH_SERIALIZERS = {
'PASSWORD_RESET_SERIALIZER': 'api.serializers.PasswordSerializer',
}
However, Now I am getting into another issue - "NoReverseMatch: Reverse for 'account_reset_password_from_key' not found. 'account_reset_password_from_key' is not a valid view function or pattern name.". And haven't found any solution or workaround for this.
Any help would be appreciated.

So, finally I got the password reset functionality working. Here is how it goes -
We just need one URL in our urls.py -
urlpatterns = [
url(r'^account/', include('allauth.urls')),
url(r'^rest-auth/', include('rest_auth.urls')),
# This is the only URL required for BASIC password reset functionality.
# This URL creates the confirmation link which is sent via e-mail. All of the rest
# password reset features get their reverse lookup via django-allauth and django-rest-auth.
url(r'^password-reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', TemplateView.as_view(), name='password_reset_confirm'),
url(r'^rest-auth/registration/account-confirm-email/(?P<key>[-:\w]+)/$', allauthemailconfirmation,
name="account_confirm_email"),
url(r'^rest-auth/registration/', include('rest_auth.registration.urls'), name='account_signup'),
]
Using this URL configuration raised TemplateDoesNotExist at /api/rest-auth/password/reset/ error first. After a lot of debugging, I found that the issue was raised for the template - registration/password_reset_email.html which resides under the Django Admin's template directory. This happened due to another Django app that I was using and it had disabled the django admin app.
So, adding 'django.contrib.admin' under INSTALLED_APPS and removing the serializers resolved the issue.
I hope this resolves issue for others as well.
PS: Debugger is your best friend. ;)

Related

CSRF token issue when upgrading Django to version 4.*

I was using the Django version 3, but then upgraded it to Django version 4(django==4.0.6).
After logging to admin panel of Django project, it said that CSRF token is invalid.
I found this link in Django documentation and tried to put such variable in settings.py:
ALLOWED_ORIGINS = ['https://*', 'http://*']
But it didn't help. What am I doing wrong?
ALLOWED_ORIGINS is not related to CSRF token. To fix problems related to your issue, you must specify the following setting for the project in production mode to settings.py module:
CSRF_TRUSTED_ORIGINS = [
'https://subdomain.example.com',
'https://*.blob.com',
...
]
For reading more information related to this topic you can read CSRF_TRUSTED_ORIGINS in django documentation.

Password reset django-allauth and django-rest-auth

I cannot wrap my head around this problem. Read a lot of solutions but cannot seem to find the correct combination that works for me.
I want to initiate a users password reset flow from within my (android/iOS) app.
I think I need django-rest-auth for this to expose an API endpoint something like this:
from rest_auth.views import PasswordResetView
urlpatterns = [
path('password/reset/', PasswordResetView.as_view(), name='rest_password_reset'),
]
Now posting to http://127.0.0.1:8000/password/reset/ with a JSON payload of { "email": "test1#test.com" } gives an error: django.urls.exceptions.NoReverseMatch: Reverse for 'password_reset_confirm' not found.
Now, I'm strugeling with the next part. I found that password_reset_confirm is defined in django.contrib.auth but I do not want to expose the admin-like interface to the user.
I'd like to use the allauth PasswordResetFromKeyView.
So, defining password_reset_confirm as:
path('password/reset/<uidb64>/<token>/',
PasswordResetFromKeyView.as_view(),
name='password_reset_confirm'
),
Works. An email is send containing a reset URL. But now, following that URL I'm getting another error: PasswordResetFromKeyView.dispatch() missing 2 required positional arguments: 'uidb36' and 'key'
Ok, obvious, changed the password_reset_confirm path arguments from <uidb64> and <token> to <uidb36> and <key>.
Than the error moves to password_reset_email.html because of the arguments in
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
Ok, also changed that to uidb32=uid and key=token results in a HTML page displaying "BAD TOKEN".
Now, I'm completely at a loss.
How to configure django-allauth and django-rest-auth so that I can do a rest request to send the email containing a valid URL which the user can use to change his/her password?
UPDATE: I just saw django-allauth is no longer maintained and that you should switch to: dj-rest-auth. Now the process starts all over again...
Ok, the following works, posting for reference because I have lost an awful lot of time on this.
Pipfile:
[packages]
django = "~=3.0"
django-allauth = "0.50.0"
django-rest-auth = "0.9.5"
urls.py:
from django.contrib import admin
from django.urls import path, re_path
# Register
from allauth.account.views import ConfirmEmailView
from rest_auth.registration.views import RegisterView, VerifyEmailView
# Password reset
from rest_auth.views import PasswordResetView, PasswordResetConfirmView
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^confirm-email/(?P<key>[-:\w]+)/$',
ConfirmEmailView.as_view(), name='account_confirm_email'),
path('user/register/',
RegisterView.as_view(),
name='rest_register'
),
path('user/verify-email/',
VerifyEmailView.as_view(),
name='rest_verify_email'
),
# Password reset
path('user/password/reset/',
PasswordResetView.as_view(),
name='rest_password_reset'
),
path('user/password/reset/confirm/<uidb64>/<token>/',
PasswordResetConfirmView.as_view(),
name='password_reset_confirm'),
]
I'm able to post to: http://127.0.0.1:8000/user/password/reset/ with a JSON payload of { "email": "test1#test.com" }.
A Email is generated with an reset URL, clicking this URL brings the user to the browsable API page of Django:
However, this page is not intended to be exposed to the user. So my next question on S.O. is: How to create a custom page for the user to reset his/her password?

How to appropriately outline the path for django-duo-auth?

I am currently working on implementing Duo Two-Factor Authentication into my django project. Currently it looks like django-duo-auth is the best package for this. I installed the package and went through the basic instructions on their README:
https://github.com/Elemnir/django-duo-auth/blob/master/README.rst
However this has caused my project to continuously redirect to a nonexistent subdirectory of 'duo' which is what I named the path. For example my app is loaded in XX.XX.XX.XX:YYYY Going to that url auto redirects the page to:
http://XX.XX.XX.XX:YYYY/duo/login/?next=/
Or, XX.XX.XX.XX:YYYY/admin auto redirects to:
http://XX.XX.XX.XX:YYYY/duo/login/?next=/admin
This simply will lead to django's generic base.html that duo_auth_form.html extends
Here are some snippets of relevant code, though it doesn't differ to much from the package's README suggestions
/urls.py
urlpatterns = [
...
path('admin/', admin.site.urls),
path('duo/', include('duo_auth.urls')),
]
/settings.py
INSTALLED_APPS = [
...
'duo_auth',
]
MIDDLEWARE = [
...
'duo_auth.middleware.DuoAuthMiddleware',
]
DUO_CONFIG = {
'DEFAULT': {
'HOST': '<api-host-url>',
'IKEY': '<integration_key>',
'AKEY': '<app_secret_key>',
'SKEY': '<secret_key>',
'FIRST_STAGE_BACKENDS': [
'django.contrib.auth.backends.ModelBackend',
]
}
}
The only difference anywhere from the read me is a slight redirection in the sample do_auth_form.html
where I extend to a subdirectory of my templates i.e. {% extends "dir\base.html" %} at the top of the file.
It appears like this package is fairly new and there isn't a lot of forums for issues so I figured it would be best to ask here. Any help would be appreciated!
I believe this is actually the django-duo-auth package working as intended. The way the middleware works is that after adding it to your project, any authenticated user who was authenticated using one of the FIRST_STAGE_BACKENDS will be checked to see if they've been authenticated with Duo as well, if not, they'll be redirected to the Duo login page at duo/login/ and prompted to complete a Duo authentication, similar to how Django's built-in #login_required decorator redirects an anonymous user to accounts/login/ to log in.
If you have users you don't want to force a second factor for, I would recommend creating a subclass of the ModelBackend that only authenticates for those users. Similar to the approach described in this issue, but inverting the logic:
https://github.com/Elemnir/django-duo-auth/issues/1

LocaleMiddleware redirects on a non-i18n url pattern

I'm using Django's LocaleMiddleware to internationalize a part of the website I'm working on.
Here is my project's urls.py:
from django.conf.urls import patterns, include, url
from django.conf.urls.i18n import i18n_patterns
urlpatterns = patterns('',
url(r'^api/stuff/(?P<stuff_id>)\d+/$', ApiStuff.as_view()),
)
urlpatterns += i18n_patterns('',
url(r'^stuff/', DoStuff.as_view()),
)
The problem is, when ApiStuff.as_view() returns a 404 response (other error codes behave as expected), the LocaleMiddleware operates the request to make it redirect to /en/api/stuff/<stuff_id>, even though the /api namespace is clearly not in the i18n_patterns (at the end, it generates a 404 error too, but the content of my original response is lost).
Here is the code of ApiStuff:
import django.http
from django.views.generic import View
from project.stuff.models import Stuff
class ApiStuff(View):
#staticmethod
def get(request, *args, **kwargs):
stuff_id = kwargs['stuff_id']
try:
stuff = Stuff.objects.get(pk=stuff_id)
except Stuff.DoesNotExist:
return response({"error": "stuff not found"}, 404)
result = stuff.serialize()
return response(result)
def response(data, status=200):
data = json.dumps(data)
return django.http.HttpResponse(data, status=status, content_type='application/json')
I'm using django 1.6.10 (I know, it's late, but I can't update the version right now).
Am I missing something?
This is an old bug that has been tracked here:
https://code.djangoproject.com/ticket/17734
The 404 error handler is a default handler of Django. It obviously takes the current language into account in order to translate the "not found" message.
Unless you can upgrade to a newer version you probably need to define your own error handler (including route/url).
EDIT:
As a workaround it might be possible to extend the LocaleMiddleware and maybe also CommonMiddleware. However, it could be that the 404 handler does its magic nevertheless. Might be worth a shot though because it should only require few lines of code.
Citing the ticket:
when you request /api/raising/404/, it is redirecting the user to /en/api/raising/404/ (assuming it detects the language as en). If it tests before redirecting the user if /en/api/raising/404/ is resolvable, it would find out that this would raise a 404 as well and thus would not redirect the user.
EDIT 2:
As an alternative you could simply not use i18n_patterns and just detect/switch the language via browser/cookie/parameter. I did that for a project (which included Django CMS) where I kept running into problems with these URLs. It is definitely not a prerequisite for localized Django sites to use i18n URL patterns. It's just something on top, but not at all necessary. Localization will work just fine without it. If you need help with the middleware for switching languages, drop a comment.
One can get around this issue by using following url config
urlpatterns += i18n_patterns(
url(r'^(?!api.*)stuff', DoStuff.as_view()),
)

django-allauth, how can I only allow signup/login through social?

I only want to allow people to sign up or log in with their social account. I have the social sign up and log in working, but I cant figure out how to disable the local sign up.
I've read the docs and this sounds close to what I want
ACCOUNT_FORMS (={})
Used to override forms, for example: {‘login’: ‘myapp.forms.LoginForm’}
It seems like I can make a new sign up form and only include the social log in link, but I was hoping there is any easier way that I'm overlooking. I'm still new to this all so I tend to miss the obvious a lot still.
I also tried changing the code below to False, but that disabled social sign up as well.
allauth.account.adapter.py
def is_open_for_signup(self, request):
"""
Checks whether or not the site is open for signups.
Next to simply returning True/False you can also intervene the
regular flow by raising an ImmediateHttpResponse
"""
return True
Change templates and urlpatterns
You would have to change both the templates (login, signup, etc.) and urlpatterns provided by allauth by default, which relate to the classic signup/login flow using email.
Changing/reducing the available routes via the urlpatterns ensures that only the routes are available that should be there. HTTP error 404 is then shown for any attempt to hack into existing allauth default functionality (related to email) if you do it right.
Changing the templates can ensure that the user interface does not provide what is related to email-based authentication.
No easy option available
Unfortunately, as of today there is no easy switch or setting to simply disable email-based signup and authentication with django-allauth. More details may be on GitHub in future, see:
Issue #1227 ("Social only: disable all local account handling by means of a simple setting")
Issue #345 ("How to disable form login/signup?")
Sample: urls.py
An urls.py like this will work with the current django-allauth (v0.30.0) on Django 1.10:
from django.conf.urls import include, url
from allauth.account.views import confirm_email, login, logout
from allauth.compat import importlib
from allauth.socialaccount import providers
providers_urlpatterns = []
for provider in providers.registry.get_list():
prov_mod = importlib.import_module(provider.get_package() + '.urls')
providers_urlpatterns += getattr(prov_mod, 'urlpatterns', [])
urlpatterns = [
url(r'^auth/', include(providers_urlpatterns)),
url(r'^confirm-email/(?P<key>[-:\w]+)/$', confirm_email, name='account_confirm_email'),
url(r'^login/$', login, name='account_login'),
url(r'^logout/$', logout, name='account_logout'),
url(r'^signup/$', login, name='account_signup'), # disable email signup
]
The solution wasn't what I originally thought. The much easier way to do this, instead of changing the forms, was to change the template and just remove any other options in that template.
My page now correctly only shows social auth and I am happy.
If anyone has a better or more secure answer I'd be open to it. Being new still, I don't know if this is the best solution, but for now it seems great and will mark as answered.
Ok, here is the thing. If you are not using any social account to link to your users, then it's very simple to finish the task you described by simply only include urls you need. However, if you need to use social account to link your users, then you have to include all urls because most third party application will not certify the request from your app. they only accept request from allauth.
from django.urls import path, re_path
from allauth.account import views as accountviews
urlpatterns = [
path('admin/', admin.site.urls),
# remember to comment out the following line since it will
# include all urls from allauth lib
# path('accounts/', include('allauth.urls'))
]
# assume you only want singup page and login page from allauth
urlpatterns += [path("acc/signup/", accountviews.signup, name="account_signup"),
path("acc/login/", accountviews.login, name="account_login")
]

Categories