Tivix / django-rest-auth

This app makes it extremely easy to build Django powered SPA's (Single Page App) or Mobile apps exposing all registration and authentication related functionality as CBV's (Class Base View) and REST (JSON)
www.tivix.com
MIT License
2.4k stars 663 forks source link

Clarifying User Registration/Email Verification #292

Open okc0mputer opened 7 years ago

okc0mputer commented 7 years ago

I realize this topic has been brought up before, but theres not really a solid solution in the docs or anywhere else.

From the FAQ in the docs regarding registration:

You should override this view/url to handle it in your API client somehow...

does that mean sending the email link to my client for reformatting into the proper API request? Why not have django forward the mailed link to the API url by default?

If you don’t want to use API on that step, then just use ConfirmEmailView view from: django-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py

(I haven't tried that yet but it's on my todo list. I intend to use the API, so i'm looking for alternatives)

My only idea for what the view/url override could contain is a view/url which captures the key in the mailed link url then serializes and posts it to the API 'verify-email' url. Am I in the ballpark? I'm relatively new to python/django, so any advice on where to begin is appreciated.

maxim-kht commented 7 years ago

Hi @okc0mputer, yes it is somewhat less documented feature and we are going to add more information in the docs.

The default case (as seen in the demo project) is using django-allauth view, https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py. This way you don't need to overwrite a view, you can just use allauth view. You just need to add ACCOUNT_EMAIL_VERIFICATION = 'mandatory' and ACCOUNT_EMAIL_REQUIRED = True in settings.

My only idea for what the view/url override could contain is a view/url which captures the key in the mailed link url then serializes and posts it to the API 'verify-email' url.

Yes, this is correct. There's more information in this comment https://github.com/Tivix/django-rest-auth/issues/15#issuecomment-59034727. Hope that helps!

okc0mputer commented 7 years ago

Thanks for clarifying. The docs make it seem like it's optional (which i suppose it is), but to obtain the email functionality its more a necessity, at least to start out with. Perhaps including the view by default would prevent some ambiguity in the future?

stantond commented 7 years ago

I'm still a little confused by this...

Does it make sense to have django-rest-auth override the link that django-allauth sends in the verification email to point directly to endpoint /rest-auth/registration/verify-email/ as GET?

onekiloparsec commented 7 years ago

Hey there. I have been struggling with this as well. I left a comment in #290 that can be of some interest.

I found the sentence, found in the documentation:

If you don’t want to use API on that step, then just use ConfirmEmailView view from: django-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py

rather confusing as well. The documentation seems to indicate it's a bit optional. But to me, a default installation should include this step, anyway. But the most confusing this is that I do want to use an API on that step as well (that's the whole point of using this framework). And I don't understand why there isn't one, and why I should use an Allauth view for that?

Anyone sharing the same ... views ? ;-)

okc0mputer commented 7 years ago

I'm with you on this. I managed to get everything going but it wasn't obvious on how to do it, and now several months later I wouldn't be able to recall how. Having an allauth view as the default is backward and the docs ambiguously say "You should override this view/url to handle it in your API client somehow...", which isn't a whole lot of help. The defaults should be swapped, in my opinion, and have the API handle this by default, and make an allauth view optional.

theanthonyplan commented 7 years ago

Yeah im with you guys, ive hit my limit with this shit. I've been working with this lib for months now and not even just because of the lack of documentation, its mostly due to how much misinformation is just floating out there. It would have made more sense for me to just implement it from scratch.

Its one thing if you don't want to document your projects esp since we can assume you aren't paying to have this project maintained. But at the absolute minimal effort, you could at least clean up some some of the old/bad information. If you don't want the responsibility maybe consider passing down the namespace to a person/project that actually does care.

Anyways, I've wasted too much time on this project's ecosystem as it is.

TLDR: "Explicit is better than implicit", better luck next time if you even care.

chachra commented 7 years ago

@theanthonyplan totally get your frustration. We will re-focus and get issues resolved or at least reduced soon. Thanks.

tannercollin commented 7 years ago

I'm having issue with email verification as well. After setting ACCOUNT_EMAIL_VERIFICATION = 'mandatory', I now get this error when I go to create an account using curl:

NoReverseMatch at /rest-auth/registration/
django.urls.exceptions.NoReverseMatch: Reverse for 'account_email_verification_sent' not found. 'account_email_verification_sent' is not a valid view function or pattern name.

This was the command I used:

curl domain.com:8000/rest-auth/registration/ -d email="noconfirm@domain.com" -d password1="complexpass" -d password2="complexpass"

I want to interact with my app strictly over the REST api. Therefore I don't need it to be returning any info pages. Do you know how I could fix this? Nothing is showing up on Google.

onekiloparsec commented 7 years ago

Check my comment in #290 , it might help.

tannercollin commented 7 years ago

Alright I seemed to have fixed all of the API endpoints so that they are able to be interacted with by only the API. I did it by overriding the URLs that were causing the Reverse for 'something' not found. errors. Here's how I did it:

In urls.py, add these before the rest-auth urls:

    url(r'^rest-auth/registration/account-email-verification-sent/', views.null_view, name='account_email_verification_sent'),
    url(r'^rest-auth/registration/account-confirm-email/', views.null_view, name='account_confirm_email'),
    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})/$', views.null_view, name='password_reset_confirm'),

Then in views.py, create a dummy function-based view:

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view()
def null_view(request):
    return Response(status=status.HTTP_400_BAD_REQUEST)

I hope this saves someone even a fraction of the time I've wasted with this bloody error.

If it's django-rest-auth, wouldn't it make sense for all the API endpoints be able to be accessed over the API? Or am I not understanding something here?

okc0mputer commented 7 years ago

You're understanding it, it's the reason for this thread. Thanks for the info, see if you can get the maintainers to put it in the docs, or even better modify the package to avoid the issue altogether

djstein commented 6 years ago

@okc0mputer @tannercollin thanks for the updates guys. Have you guys made a pull request? If not I'll try to whip one up. Would love to have some conversation on naming conventions / implementation too

tannercollin commented 6 years ago

Please go ahead and make a pull request

djstein commented 6 years ago

I'll write a pull request once I've got all the parts together. Here is a solution I wrote based on the above to allow for verification through the API. A full example can be seen here https://github.com/modernproject/backend

The process for verifying would be this:

(Using Django 2.X):

views.py

from allauth.account.models import EmailConfirmation, EmailConfirmationHMAC
from django.contrib.auth import get_user_model
from django.utils.translation import ugettext_lazy as _
from project.users.serializers import UserSerializer
from rest_auth.registration.serializers import VerifyEmailSerializer
from rest_framework import status
from rest_framework.decorators import api_view, APIView
from rest_framework.permissions import IsAdminUser, AllowAny
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet

@api_view()
def django_rest_auth_null():
    return Response(status=status.HTTP_400_BAD_REQUEST)

class UserViewSet(ModelViewSet):
    """
    A simple ViewSet for viewing and editing accounts.
    """
    queryset = get_user_model().objects.all()
    serializer_class = UserSerializer
    permission_classes = [IsAdminUser]

class VerifyEmailView(APIView):
    permission_classes = (AllowAny,)
    allowed_methods = ('POST', 'OPTIONS', 'HEAD')

    def get_serializer(self, *args, **kwargs):
        return VerifyEmailSerializer(*args, **kwargs)

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.kwargs['key'] = serializer.validated_data['key']
        try:
            confirmation = self.get_object()
            confirmation.confirm(self.request)
            return Response({'detail': _('Successfully confirmed email.')}, status=status.HTTP_200_OK)
        except EmailConfirmation.DoesNotExist:
            return Response({'detail': _('Error. Incorrect key.')}, status=status.HTTP_404_NOT_FOUND)

    def get_object(self, queryset=None):
        key = self.kwargs['key']
        emailconfirmation = EmailConfirmationHMAC.from_key(key)
        if not emailconfirmation:
            if queryset is None:
                queryset = self.get_queryset()
            try:
                emailconfirmation = queryset.get(key=key.lower())
            except EmailConfirmation.DoesNotExist:
                raise EmailConfirmation.DoesNotExist
        return emailconfirmation

    def get_queryset(self):
        qs = EmailConfirmation.objects.all_valid()
        qs = qs.select_related("email_address__user")
        return qs

urls.py

from django.urls import path, include
from rest_framework import routers
from project.users.views import UserViewSet, django_rest_auth_null, VerifyEmailView

router = routers.SimpleRouter()
router.register(r'users', UserViewSet)

urlpatterns = [
    path('', include('rest_auth.urls')),
    path('registration/verify-email/', VerifyEmailView.as_view(), name='rest_verify_email'),
    path('registration/', include('rest_auth.registration.urls')),
    path('rest-auth/registration/account-email-verification-sent/', django_rest_auth_null, name='account_email_verification_sent'),
    path('password-reset/confirm/<str:uidb64>)/<str:token>/', django_rest_auth_null, name='password_reset_confirm')
]
EvanZ commented 6 years ago

Is there a way I can just change the url that gets sent with the verification email so that it hits an endpoint on my frontend? I'd rather just direct the user there and do the verification using axios.

rikoz commented 6 years ago

I am also looking for same solution @EvanZ .... please can someone help with the answer. I need it seriously. Because am using VueJs hosted on another domain

bobbyhadz commented 6 years ago

@EvanZ @rikoz I just managed to change the url that is sent with the verification email, so it hits my frontend. I followed the steps described by Storm in this stackoverflow post: https://stackoverflow.com/questions/27984901/how-to-customize-activate-url-on-django-allauth

Note that the ACCOUNT_ADAPTER setting is = 'yourProject.yourAdapterModule.yourCustomAdapterName'

iMerica commented 6 years ago

I've documented how I solved the problem here:

https://gist.github.com/iMerica/a6a7efd80d49d6de82c7928140676957

My Context:

crazyscientist commented 6 years ago

Hey guys, maybe I have overlooked it, but following any suggestion regarding email validation does not solve the actual issue. My project still raises an exception on the account_confirm_email view:

TemplateResponseMixin requires either a definition of 'template_name' or an implementation of 'get_template_names()'

opeodedeyi commented 4 years ago

Alright I seemed to have fixed all of the API endpoints so that they are able to be interacted with by only the API. I did it by overriding the URLs that were causing the Reverse for 'something' not found. errors. Here's how I did it:

In urls.py, add these before the rest-auth urls:

    url(r'^rest-auth/registration/account-email-verification-sent/', views.null_view, name='account_email_verification_sent'),
    url(r'^rest-auth/registration/account-confirm-email/', views.null_view, name='account_confirm_email'),
    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})/$', views.null_view, name='password_reset_confirm'),

Then in views.py, create a dummy function-based view:

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view()
def null_view(request):
    return Response(status=status.HTTP_400_BAD_REQUEST)

I hope this saves someone even a fraction of the time I've wasted with this bloody error.

If it's django-rest-auth, wouldn't it make sense for all the API endpoints be able to be accessed over the API? Or am I not understanding something here?

years later and that post is saving lives

okc0mputer commented 4 years ago

Amazing that people are still having this issue 3 years later.

robdox commented 4 years ago

I'm here 3 years later and am astonished that this issue still exists. It took a few hours before i finally accepted that it did. How is it possible that we are still dealing with these template issues? Why is this the only thing that isn't an API which is easily accessible? It was so much more effort for them to have made this into a view instead of an API... Who thought this was a good idea.........?

Anyone coming here lately this answer is perfect. Just gotta update the url stuff to use path and it easily hits whatever api you want it to hit like you'd expect it to. https://stackoverflow.com/questions/27984901/how-to-customize-activate-url-on-django-allauth

onekiloparsec commented 4 years ago

Migrating to https://github.com/sunscrapers/djoser/. End of this (too long) story.

opeodedeyi commented 4 years ago

I've documented how I solved the problem here:

https://gist.github.com/iMerica/a6a7efd80d49d6de82c7928140676957

My Context:

  • DRF + SPA (React/Redux)
  • Django Rest Auth + Django All Auth for email confirmation
  • Zero templates or messages (pure Single page application modern app).

this solved my problem, thanks a lot for this

Shavinder commented 3 years ago

I just went over to djoser git page, cloned the testprojects and it works! I don't have many years left to waste on this. Moving to djoser.

MuhammadAnas47 commented 4 months ago

Hello,

Just refer this Demo. It working for me.

https://github.com/MuhammadAnas47/django-rest-auth/tree/master/demo