sunscrapers / djoser

REST implementation of Django authentication system.
MIT License
2.53k stars 459 forks source link

Guidance needed - user activation - how to POST #248

Open leferaycloud opened 6 years ago

leferaycloud commented 6 years ago

I am using DREF and Djoser for Authentication and User Registration. When a new user registers, Djoser sends an activation email with a link that does a GET request. In order to activate, I need to extract uid and token from the activation url and make a POST request for Djoser to be able to activate the user.

My environment is Python 3 and Django 1.11, Djoser 1.0.1.

Any help on how to handle this in Django / Djoser?

What I would like to do is to handle the get request in Django, extract uid and token and then make a POST request. I have extracted uid and token and would like to make a POST (within this GET request). I do not know how to make this POST request in the background.

pszpetkowski commented 6 years ago

Hi @leferaycloud. Firstly - sorry it took so long for me to respond.

I believe that you may have confused the REST architecture with a traditional single layer. In djoser we expect that your backend is used to provide API for mobile apps or your frontend layer (e.g. Single Page App) and therefore the process you have described is not something we had in mind with djoser.

Although - what you say is possible I'm not sure why you would need REST API if you wish to handle everything within the single Django layer. Here is how you would usually handle user activation:

  1. User creates his account via POST to users/create/.
  2. User receives email with URL to frontend activation page defined via ACTIVATION_URL djoser setting.
  3. User heads to the given page, which calls the users/activate/ with POST with uid and token, which indeed you extract from the URL GET params.

If for whatever reason you want to handle everything within Django, my first thought would be to use requests to call the djoser activation URL, but as I said it's not how you would usually want to do this.

Hope this answers your question.

lgalant commented 6 years ago

Hi, i have the same question. I understand we can create a page to redirect the data to the activate url as a POST. However I would like the user to be able to activate directly by clicking on that URL. Is that somehow possible without having to create my own GET API?

pszpetkowski commented 6 years ago

Hi @lgalant. At the moment - no, it's not possible, mostly because Djoser has been created with SPA and mobile apps in mind and therefore it would be a terrible UX flaw to make users visit e.g. api.example.org to activate account.

I doubt that in near future we will decide to bring such view into djoser, but it's very simple to create it if you really need it - in spare time I'll add it to djoser example docs with a disclaimer that it is not the architecture, for which djoser was made.

lgalant commented 6 years ago

@piotr-szpetkowski Thanks for your quick reply, It'd be really helpful if you can write such view as an example.

Now supposing I'm developing a mobile app and I want to verify the email of my users. What is the recommended behavior for the user to confirm that the email he provided belongs to him, using djoser ?

Thanks again!

pplonski commented 5 years ago

Working on this issue right now, maybe it will be usable for others.

I created APIView:

from djoser.conf import django_settings

class ActivateUserByGet(views.APIView):

    def get(self, request, uid, token, format = None):
        payload = {'uid': uid, 'token': token}

        url = '{0}://{1}{2}'.format(django_settings.PROTOCOL, django_settings.DOMAIN, reverse('user-activate'))
        response = requests.post(url, data = payload)

        if response.status_code == 204:
            return Response({'detail': 'all good sir'})
        else:
            return Response(response.json())

and added it to mu urls:

path('activate/<str:uid>/<str:token>/', ActivateUserByGet.as_view()),

django_settings.PROTOCOL and django_settings.DOMAIN are used for bulding proper url. Hope it helps you!

Belanchuk commented 5 years ago
    response = requests.post(url, data = payload)

    if response.status_code == 204:

Django is singlethreaded. The "requests" will never be fulfilled, because maib process will be busy waiting for a response.

zefciu commented 5 years ago

Even if you run django in several threads (with gunicorn e.g.), this is still a suboptimal strategy to call django from django. You should rather simply copy the existing POST view and create a GET one that does the same.

Still I don't think that doing activation with GET fits into REST architecture. What do you think @dekoza @haxoza ? Will we implement it or can I close?

sudomann commented 5 years ago

@zefciu Could you please elaborate on that?

sudomann commented 5 years ago

From what I'm seeing here, I can't settle on whether it's more acceptable to handle everything in Django (sounds more appropriate to me) or cycle through the network and web server again with a POST request to activate account.

sudomann commented 5 years ago

Working on this issue right now, maybe it will be usable for others.

I created APIView:

from djoser.conf import django_settings

class ActivateUserByGet(views.APIView):

    def get(self, request, uid, token, format = None):
        payload = {'uid': uid, 'token': token}

        url = '{0}://{1}{2}'.format(django_settings.PROTOCOL, django_settings.DOMAIN, reverse('user-activate'))
        response = requests.post(url, data = payload)

        if response.status_code == 204:
            return Response({'detail': 'all good sir'})
        else:
            return Response(response.json())

and added it to mu urls:

path('activate/<str:uid>/<str:token>/', ActivateUserByGet.as_view()),

django_settings.PROTOCOL and django_settings.DOMAIN are used for bulding proper url. Hope it helps you!

Hey @pplonski How did this solution turn out for you?

pplonski commented 5 years ago

Hi @sudomann, it works, I'm sending link with email, and it has link for GET request, which triggers POST request.

khashashin commented 5 years ago

I'm using @pplonski approach and it works for me aswell.

safakat commented 4 years ago

hi @pplonski It gives me a error. AttributeError: 'Settings' object has no attribute 'PROTOCOL'

pplonski commented 4 years ago

@safakat have you set PROTOCL in your settings? It is needed to construct the url.

safakat commented 4 years ago

no.can you send me the protocol code?

pplonski commented 4 years ago

I don't know what protocol are you going to use, but I have used the following:

PROTOCOL = "https"
safakat commented 4 years ago

This is my djoser setting. DJOSER = { 'PASSWORD_RESET_CONFIRM_URL': '/password/reset/confirm/{uid}/{token}', 'USERNAME_RESET_CONFIRM_URL': '/username/reset/confirm/{uid}/{token}', 'ACTIVATION_URL': '/activate/{uid}/{token}', 'SEND_ACTIVATION_EMAIL': True, 'SEND_CONFRIMATION_EMAIL':True, 'SERIALIZERS': {}, }

safakat commented 4 years ago

And what will be the domain? currently i am doing it in localhost.

pplonski commented 4 years ago

The domain at which you would like to get the POST request (it will be your domain).

safakat commented 4 years ago

and the last problem is:

Exception Value: | Reverse for 'user-activate' not found. 'user-activate' is not a valid view function or pattern name.

pplonski commented 4 years ago

looks like you dont have djoser urls set

safakat commented 4 years ago

path('admin/', admin.site.urls), path('auth/',include('djoser.urls')), path('auth/',include('djoser.urls.jwt')), path("api/data/",include("data.urls")),

safakat commented 4 years ago

i did it with this

def ActivateUserAccount(request, uidb64=None,token=None):
 #print(force_text(urlsafe_base64_decode(uidb64)))
    #print(token)
    try:
        uid = force_text(urlsafe_base64_decode(uidb64))
        #print(type(uid),uid)
        user = User.objects.get(pk=uid)
        print(user)
    except User.DoesNotExist:
        user = None
    if user and default_token_generator.check_token(user,token):
        user.is_email_verified = True
        user.is_active = True
        user.save()
        login(request,user)
        print("Activaton done")
    else:
        print("i dont know ")
jyothiprakashk commented 4 years ago

users/activate/ has now changed to users/activation/

storrabadellaf commented 4 years ago

Is anybody using users/activation/ endpoint successfully? When I make POST request to users/activation/ I get a 404 not found.

This is the POST request: curl -X POST http://127.0.0.1/auth/users/activation/ --data 'uid=myuid&token=mytoken'

My urls.py is as follows:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/', include('api.urls')),
    re_path(r'^auth/', include('djoser.urls')),
]

What I'm doing wrong? Thanks in advance!

theSekyi commented 4 years ago

@storrabadellaf how did you generate your uid? I have a similar problem

luke-goddard commented 3 years ago

Any update on how to do this without the development server hanging?

yusra-dev commented 2 years ago

@theSekyi , @luke-goddard I've added these to my settings file

DEBUG = True
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
PROTOCOL='http'
DOMAIN= '127.0.0.1:8000'

with the email backend attribute after poting a new user on auth/create it will show the activation email in terminal in this pattern '#/activate/{uid}/{token}', ` Content-Transfer-Encoding: 7bit

You're receiving this email because you need to finish activation process on 127.0.0.1:8000.

Please go to the following page to activate account: http://127.0.0.1:8000/#/activate/MTE/b0y14v-85c128ab17129709d256c155f8a7ef61

Thanks for using our site! ` while I'm running backend, in a new terminal I run the below command and I use the uid and token that is there in the activation link taht's exist in the terminal for the below command and it works for me and make the posted user activated .

curl -X POST http://127.0.0.1/auth/users/activation/ --data 'uid=myuid&token=mytoken'

Hope you understand, happy coding ;)