Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Custom User Email Account Verification

Tags:

django

I am looking to add email account verification in Django. I have attempted using the django-registration app to do so, but it doesn't appear that it has been updated to be fully compatible with custom user models which causes too many problems. Is there another reliable and well-documented app out there which will allow me to send a verification email on user registration in django?

like image 402
sdotslezek Avatar asked Jul 24 '14 13:07

sdotslezek


1 Answers

How I handle the email registration personally:

First of all, my Profile extending Django Users (models.py):

class Profile(models.Model):     user = models.OneToOneField(User, related_name='profile') #1 to 1 link with Django User     activation_key = models.CharField(max_length=40)     key_expires = models.DateTimeField() 

In forms.py, the Registration class :

class RegistrationForm(forms.Form):     username = forms.CharField(label="",widget=forms.TextInput(attrs={'placeholder': 'Nom d\'utilisateur','class':'form-control input-perso'}),max_length=30,min_length=3,validators=[isValidUsername, validators.validate_slug])     email = forms.EmailField(label="",widget=forms.EmailInput(attrs={'placeholder': 'Email','class':'form-control input-perso'}),max_length=100,error_messages={'invalid': ("Email invalide.")},validators=[isValidEmail])     password1 = forms.CharField(label="",max_length=50,min_length=6,                                 widget=forms.PasswordInput(attrs={'placeholder': 'Mot de passe','class':'form-control input-perso'}))     password2 = forms.CharField(label="",max_length=50,min_length=6,                                 widget=forms.PasswordInput(attrs={'placeholder': 'Confirmer mot de passe','class':'form-control input-perso'}))      #recaptcha = ReCaptchaField()      #Override clean method to check password match     def clean(self):         password1 = self.cleaned_data.get('password1')         password2 = self.cleaned_data.get('password2')          if password1 and password1 != password2:             self._errors['password2'] = ErrorList([u"Le mot de passe ne correspond pas."])          return self.cleaned_data      #Override of save method for saving both User and Profile objects     def save(self, datas):         u = User.objects.create_user(datas['username'],                                      datas['email'],                                      datas['password1'])         u.is_active = False         u.save()         profile=Profile()         profile.user=u         profile.activation_key=datas['activation_key']         profile.key_expires=datetime.datetime.strftime(datetime.datetime.now() + datetime.timedelta(days=2), "%Y-%m-%d %H:%M:%S")         profile.save()         return u      #Sending activation email ------>>>!! Warning : Domain name is hardcoded below !!<<<------     #The email is written in a text file (it contains templatetags which are populated by the method below)     def sendEmail(self, datas):         link="http://yourdomain.com/activate/"+datas['activation_key']         c=Context({'activation_link':link,'username':datas['username']})         f = open(MEDIA_ROOT+datas['email_path'], 'r')         t = Template(f.read())         f.close()         message=t.render(c)         #print unicode(message).encode('utf8')         send_mail(datas['email_subject'], message, 'yourdomain <[email protected]>', [datas['email']], fail_silently=False) 

Now, in views.py, we need to handle all that, let's go :

The register view:

def register(request):     if request.user.is_authenticated():         return redirect(home)     registration_form = RegistrationForm()     if request.method == 'POST':         form = RegistrationForm(request.POST)         if form.is_valid():             datas={}             datas['username']=form.cleaned_data['username']             datas['email']=form.cleaned_data['email']             datas['password1']=form.cleaned_data['password1']              #We generate a random activation key             salt = hashlib.sha1(str(random.random())).hexdigest()[:5]             usernamesalt = datas['username']             if isinstance(usernamesalt, unicode):                 usernamesalt = usernamesalt.encode('utf8')             datas['activation_key']= hashlib.sha1(salt+usernamesalt).hexdigest()              datas['email_path']="/ActivationEmail.txt"             datas['email_subject']="Activation de votre compte yourdomain"              form.sendEmail(datas)             form.save(datas) #Save the user and his profile              request.session['registered']=True #For display purposes             return redirect(home)         else:             registration_form = form #Display form with error messages (incorrect fields, etc)     return render(request, 'siteApp/register.html', locals()) 

The activation views :

#View called from activation email. Activate user if link didn't expire (48h default), or offer to #send a second link if the first expired. def activation(request, key):     activation_expired = False     already_active = False     profile = get_object_or_404(Profile, activation_key=key)     if profile.user.is_active == False:         if timezone.now() > profile.key_expires:             activation_expired = True #Display: offer the user to send a new activation link             id_user = profile.user.id         else: #Activation successful             profile.user.is_active = True             profile.user.save()      #If user is already active, simply display error message     else:         already_active = True #Display : error message     return render(request, 'siteApp/activation.html', locals())  def new_activation_link(request, user_id):     form = RegistrationForm()     datas={}     user = User.objects.get(id=user_id)     if user is not None and not user.is_active:         datas['username']=user.username         datas['email']=user.email         datas['email_path']="/ResendEmail.txt"         datas['email_subject']="Nouveau lien d'activation yourdomain"          salt = hashlib.sha1(str(random.random())).hexdigest()[:5]         usernamesalt = datas['username']         if isinstance(usernamesalt, unicode):             usernamesalt = usernamesalt.encode('utf8')         datas['activation_key']= hashlib.sha1(salt+usernamesalt).hexdigest()          profile = Profile.objects.get(user=user)         profile.activation_key = datas['activation_key']         profile.key_expires = datetime.datetime.strftime(datetime.datetime.now() + datetime.timedelta(days=2), "%Y-%m-%d %H:%M:%S")         profile.save()          form.sendEmail(datas)         request.session['new_link']=True #Display: new link sent      return redirect(home) 

Finally, in urls.py:

url(r'^register/$', 'register'), url(r'^activate/(?P<key>.+)$', 'activation'), url(r'^new-activation-link/(?P<user_id>\d+)/$', 'new_activation_link'), 

With all that you should have something to start with, use the appropriate templatetags in the .txt emails and HTML and it should work.

NB: This code isn't perfect, there is duplication (for instance, the generation of the random key could be defined in a function), but it does the job. Also: the activation key is not generated using proper cryptographic functions. An alternative is to use a function like the following to generate the keys:

from django.utils.crypto import get_random_string  def generate_activation_key(username):     chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'     secret_key = get_random_string(20, chars)     return hashlib.sha256((secret_key + username).encode('utf-8')).hexdigest() 

NB2: Django send_mail doesn't provide any tools to authenticate your emails. If you want to authenticate your emails (DKIM, SPF), I advise you to look into this: https://djangosnippets.org/snippets/1995/

NB3: There is a security issue with the view new_activation_link: it should check if the user requesting the re-send is the right one and also if he isn't already authenticated. I let you correct that.

like image 67
Raphael Laurent Avatar answered Nov 11 '22 18:11

Raphael Laurent