11

I am having a web-app in Django. I tried using the tokengenerator for password reset to create a verification mail, it's not activating the email.

Coming to the problem:

  1. While the user provides email, it should check whether the email is present in the DB. (DB will be updated with user emails)
  2. After verifying whether the email is present in the DB, the user is prompted to create a password.
  3. After creating a password user can log in to the respective page.

Is there any solution? I tried and followed:

https://medium.com/@frfahim/django-registration-with-confirmation-email-bb5da011e4ef

The above post helped me to send the email, but the user is not activated after verifying the email. The post doesn't meet my requirement, though I tried to check whether email verification is possible.

Is there any third-party module for Django or any solution for the requirements I have mentioned?

Joel Deleep
  • 1,057
  • 2
  • 12
  • 27

3 Answers3

26

I figured out a solution , but for the second requirement user has to input the password at the time of account creation . The main goal was to verify the user supplied email.

Models

class Yourmodel(models.Model):
    first_name = models.CharField(max_length=200)
    second_name = models.CharField(max_length=200)
    email = models.EmailField(max_length=100)

Tokens

from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils import six
class TokenGenerator(PasswordResetTokenGenerator):
    def _make_hash_value(self, user, timestamp):
        return (
            six.text_type(user.pk) + six.text_type(timestamp) +
            six.text_type(user.is_active)
        )
account_activation_token = TokenGenerator()

Views

from django.contrib.auth import get_user_model
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.contrib.sites.shortcuts import get_current_site
from .tokens import account_activation_token
from django.core.mail import send_mail

def signup(request):
    User = get_user_model()
    if request.method == 'POST':
        form = SignupForm(request.POST)
        if form.is_valid():
            email = form.cleaned_data.get('email')
            if Yourmodel.objects.filter(email__iexact=email).count() == 1:
                user = form.save(commit=False)
                user.is_active = False
                user.save()
                current_site = get_current_site(request)
                mail_subject = 'Activate your account.'
                message = render_to_string('email_template.html', {
                            'user': user,
                            'domain': current_site.domain,
                            'uid': urlsafe_base64_encode(force_bytes(user.pk)),
                            'token': account_activation_token.make_token(user),
                        })
                to_email = form.cleaned_data.get('email')
                send_mail(mail_subject, message, 'youremail', [to_email])
                return HttpResponse('Please confirm your email address to complete the registration')
     else:
        form = SignupForm()
    return render(request, 'regform.html', {'form': form})

def activate(request, uidb64, token):
    User = get_user_model()
    try:
        uid = force_text(urlsafe_base64_decode(uidb64))
        user = User.objects.get(pk=uid)
    except(TypeError, ValueError, OverflowError, User.DoesNotExist):
        user = None
    if user is not None and account_activation_token.check_token(user, token):
        user.is_active = True
        user.save()
        return HttpResponse('Thank you for your email confirmation. Now you can login your account.')
    else:
        return HttpResponse('Activation link is invalid!')

Forms

from django.contrib.auth.forms import UserCreationForm


class SignupForm(UserCreationForm):
    class Meta:
        model = User
        fields = ('username', 'email', 'password1', 'password2')

Email Template

{% autoescape off %}
Hi ,
Please click on the link to confirm your registration,
http://{{ domain }}{% url 'activate' uidb64=uid token=token %}
{% endautoescape %}

regform.html

{% csrf_token %}
{% for field in form %}
<label >{{ field.label_tag }}</label>
{{ field }}
{% endfor %}

If you don't want to compare with email address in your model you can skip, this will send the email to the email address which was supplied at the time registration without further validation.

email = form.cleaned_data.get('email')
if Yourmodel.objects.filter(email__iexact=email).count() == 1:
Joel Deleep
  • 1,057
  • 2
  • 12
  • 27
  • Why is the choice of strings for the hash different than [Django auth's](https://github.com/django/django/blob/2978c63a34b4aa0f170a1e5a0f8f4cb2811fa248/django/contrib/auth/tokens.py#L94) built-in `default_token_generator` where they use pk, pasword, login_timestamp, timestamp, and email. Does altering it make it any less secure? – Addison Klinke May 27 '21 at 05:19
  • 1
    where are your getting force_byte from, and i'm getting cannot import six from django utils – nassim Jun 05 '21 at 11:52
  • 2
    @nassim https://stackoverflow.com/questions/59193514/importerror-cannot-import-name-six-from-django-utils – Joel Deleep Jun 05 '21 at 12:21
  • @nassim By the way, the `force_bytes` mostly likely comes from `from django.utils.encoding import force_bytes`. – Nat Riddle Nov 25 '21 at 18:35
  • add `from django.template.loader import render_to_string` on top as well – Shayan Feb 22 '22 at 01:02
2

I have an answer on your first problem:

If you user password reset based on the PasswordResetView + PasswordResetConfirmView you could do following:

PasswordResetView is in charge for sending emails to the users. It uses it own form for typing in user emails -PasswordResetForm. You could make your own form and inherit it from PasswordResetForm. For example:


class PRForm(PasswordResetForm):
    def clean_email(self):
        email = self.cleaned_data['email']
        if not User.objects.filter(email__iexact=email, is_active=True).exists():
            msg = "There is no user with this email."
            self.add_error('email', msg)
        return email

# User – your user model or any custom model if you have one instead of the default one

this code will not allow controller to send email to a email address that you dont have in your DB.

Then specify this form in your VIEW:


class PassResView(RatelimitMixin,  PasswordResetView):
    success_url = 
    from_email = 
    subject_template_name =
    email_template_name =
    success_message = 
    template_name = 
    form_class = PRForm  # here is a custom form
    ratelimit_key = 'ip'
    ratelimit_rate = '10/5m'
    ratelimit_block = True
    ratelimit_method = ('GET', 'POST')

RatelimitMixin will not allow someone to brute-force your DB by running your BD out. Your could use it or not -its up to you.

Aleksei Khatkevich
  • 1,399
  • 2
  • 8
  • 21
2

For the first answer, you need to add on urls.py

path('emailVerification/<uidb64>/<token>', views.activate, name='emailActivate')

And emailVerification.html must be like this:

    Hi ,
Please click on the link to confirm your registration,
http://{{ domain }}/emailVerification/{{ uid }}/{{ token }}