From 54988a928d37fc12e1f6eb83c6ecfc2babe55e29 Mon Sep 17 00:00:00 2001 From: Bobby Date: Wed, 28 Dec 2022 07:51:38 -0500 Subject: Registraion function with captcha working --- blog/urls.py | 1 - blog/views.py | 70 +++++++------------- ignis/urls.py | 1 + ignis/views.py | 8 +++ requirements.txt | 21 +++--- templates/blog/register.html | 99 +++++----------------------- users/admin.py | 3 +- users/forms.py | 67 +++++++++++++++++++ users/migrations/0009_delete_captchastore.py | 16 +++++ users/models.py | 7 -- users/tokens.py | 16 +++++ users/urls.py | 1 - users/views.py | 51 +------------- 13 files changed, 162 insertions(+), 199 deletions(-) create mode 100644 users/forms.py create mode 100644 users/migrations/0009_delete_captchastore.py diff --git a/blog/urls.py b/blog/urls.py index f575fc7a..4ee3a159 100644 --- a/blog/urls.py +++ b/blog/urls.py @@ -9,7 +9,6 @@ urlpatterns = [ path('account/', RedirectView.as_view(pattern_name='account', permanent=False)), path('my/account', views.account, name='account'), path('register/', views.register, name='register'), - path('register/refresh_captcha/', name='refresh_captcha', view=views.refresh_captcha), path('articles/post/', views.post, name='post'), path('articles/post//comment', views.comment, name='comment'), path('articles/post//edit_comment', views.edit_comment, name='edit_comment'), diff --git a/blog/views.py b/blog/views.py index a31fca7f..04818531 100644 --- a/blog/views.py +++ b/blog/views.py @@ -1,17 +1,16 @@ from datetime import datetime from django.shortcuts import render, redirect, reverse from django.http import HttpResponse -from users.models import UserProfile, CaptchaStore +from users.models import UserProfile import hashlib -from captcha.image import ImageCaptcha from random import choice from string import ascii_letters, digits -import base64 -import json from .models import Post, Comment from .context_processors import recent_posts, avatar_list from announcements.models import Announcement - +from users.forms import RegisterForm +from users.tokens import CaptchaTokenGenerator +from django.contrib import messages # Create your views here. @@ -47,52 +46,31 @@ def account(request): def homepage(request): return render(request, 'blog/homepage.html', {'title': 'Homepage'}) - -def get_base64_captcha(): - image = ImageCaptcha() - random_string = ''.join([choice(ascii_letters + digits) for n in range(6)]) - data = image.generate(random_string) - base64_data = "data:image/png;base64," + base64.b64encode(data.getvalue()).decode() - return base64_data, random_string - def register(request): user = request.user - csrf_token = request.META.get('CSRF_COOKIE') - try: - # Delete old captcha - CaptchaStore.objects.get(csrf_token=csrf_token).delete() - except CaptchaStore.DoesNotExist: - pass + + # print request host + print(request.get_host()) + + if user.is_authenticated: return redirect('blog:account') else: - if not csrf_token: - # Create a new CSRF token - csrf_token = ''.join([choice(ascii_letters + digits) for n in range(100)]) - base64_data, random_string = get_base64_captcha() - try: - # Delete old captcha - CaptchaStore.objects.get(csrf_token=csrf_token).delete() - except CaptchaStore.DoesNotExist: - pass - # Create new captcha - CaptchaStore.objects.create(captcha_string=random_string, csrf_token=csrf_token) - return render(request, 'blog/register.html', {'title': 'Register', 'captcha': base64_data}) - - -def refresh_captcha(request): - csrf_token = request.META.get('CSRF_COOKIE') - if not request.META.get('HTTP_REFERER') or request.META.get('HTTP_REFERER').split('/')[-2] != 'register': - response_data = {'status': 'error', 'message': 'Unauthorized!'} - return HttpResponse(json.dumps(response_data), content_type="application/json", status=401) - base64_data, random_string = get_base64_captcha() - try: - CaptchaStore.objects.get(csrf_token=csrf_token).delete() - except CaptchaStore.DoesNotExist: - pass - CaptchaStore.objects.create(captcha_string=random_string, csrf_token=csrf_token) - response_data = {'captcha': base64_data} - return HttpResponse(json.dumps(response_data), content_type="application/json") + random_string = ''.join([choice(ascii_letters + digits) for n in range(6)]) + captcha = CaptchaTokenGenerator().encrypt(random_string) + if request.method == 'POST': + expected_captcha = CaptchaTokenGenerator().decrypt(request.POST.get('expected_captcha')) + form = RegisterForm(request.POST, expected_captcha=expected_captcha) + if form.is_valid(): + form.save(request=request) + messages.success(request, 'Account was created! Please check your email to verify your account.', extra_tags='accountCreated') + return redirect('blog:register') + # return redirect('blog:home') + else: + return render(request, 'blog/register.html', {'title': 'Register', 'form': form, 'captcha': captcha}) + else: + form = RegisterForm(expected_captcha=random_string) + return render(request, 'blog/register.html', {'title': 'Register', 'form': form, 'captcha': captcha}) def post(request, slug): try: diff --git a/ignis/urls.py b/ignis/urls.py index b190a40f..44ed5de1 100644 --- a/ignis/urls.py +++ b/ignis/urls.py @@ -8,4 +8,5 @@ urlpatterns = [ path('/upload', views.upload_image, name='upload_image'), path('/image//', views.get_image, name='get_image'), path('/cover/', views.cover_image, name='cover_image'), + path('/captcha/', views.captcha_image, name='captcha_image') ] diff --git a/ignis/views.py b/ignis/views.py index 1e822cbf..211346eb 100644 --- a/ignis/views.py +++ b/ignis/views.py @@ -9,6 +9,8 @@ from .models import PostImage, RepositoryTitle import json import requests from django.core.files.base import ContentFile +from captcha.image import ImageCaptcha +from users.tokens import CaptchaTokenGenerator # from .github import get_cover # Create your views here. @@ -134,3 +136,9 @@ def upload_image(request): return HttpResponse(json.dumps(response), content_type='application/json') return HttpResponse('Method not allowed', status=405) +def captcha_image(request, captcha_string): + captcha = CaptchaTokenGenerator().decrypt(captcha_string) + imgcaptcha = ImageCaptcha() + data = imgcaptcha.generate(captcha) + return HttpResponse(data, content_type='image/png') + diff --git a/requirements.txt b/requirements.txt index 47cbb152..684ae086 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,11 @@ -Django==4.0.6 -python-dotenv==0.20.0 -gunicorn==20.1.0 -whitenoise==6.2.0 -six==1.16.0 -cryptocode==0.1 -captcha==0.4 -PyGithub==1.55 -requests==2.28.1 -pillow==9.2.0 \ No newline at end of file +Django +python-dotenv +gunicorn +whitenoise +six +cryptocode +captcha +PyGithub +requests +pillow +pycryptodome \ No newline at end of file diff --git a/templates/blog/register.html b/templates/blog/register.html index d94e545f..b168c153 100644 --- a/templates/blog/register.html +++ b/templates/blog/register.html @@ -1,86 +1,19 @@ {% extends 'blog/partials/base.html' %} {% block content %} -
-
-

Register for an account

-

Register for an account to post your thoughts and get feedback from other users.

-
- {% csrf_token %} -
- - - {% for message in messages %} - {% if 'usernameError' in message.tags %} - {{ message.message }} - {% endif %} - {% endfor %} -
-
- - - {% for message in messages %} - {% if 'emailError' in message.tags %} - {{ message.message }} - {% endif %} - {% endfor %} -
-
- - - {% for message in messages %} - {% if 'passwordError' in message.tags %} - {% load replace %} - {{ message.message|replace:"[']" }} - {% endif %} - {% endfor %} -
-
- - - {% for message in messages %} - {% if 'password2Error' in message.tags %} - {{ message.message }} - {% endif %} - {% endfor %} -
-
- -
- {% load static %} - captcha - Refresh Captcha -
- - {% for message in messages %} - {% if 'captchaError' in message.tags %} - {{ message.message }} - {% endif %} - {% endfor %} -
-
- -
-
- {% for message in messages %} - {% if 'accountCreated' in message.tags %} - {{ message.message }} - {% endif %} - {% endfor %} -
-
- - +

Register for an account

+

Register for an account to post your thoughts and get feedback from other users.

+
+ + + {{ form.as_p }} + Captcha + + + +{% for message in messages %} + {% if 'accountCreated' in message.tags %} +

{{ message.message }}

+ {% endif %} +{% endfor %} {% endblock %} + diff --git a/users/admin.py b/users/admin.py index 49d40cef..f5116502 100644 --- a/users/admin.py +++ b/users/admin.py @@ -1,7 +1,6 @@ from django.contrib import admin # Register your models here. -from .models import UserProfile, CaptchaStore +from .models import UserProfile admin.site.register(UserProfile) -admin.site.register(CaptchaStore) diff --git a/users/forms.py b/users/forms.py new file mode 100644 index 00000000..de13fe27 --- /dev/null +++ b/users/forms.py @@ -0,0 +1,67 @@ +# Registration form + +from django import forms +from django.contrib.auth.models import User +from users.models import UserProfile +from django.core.mail import send_mail +from django.conf import settings +from django.template.loader import render_to_string +from django.utils.html import strip_tags +from django.utils.encoding import force_bytes +from django.utils.http import urlsafe_base64_encode +from .tokens import account_activation_token + +class RegisterForm(forms.Form): + username = forms.CharField(label='Username', max_length=30) + email = forms.EmailField(label='Email') + password1 = forms.CharField(label='Password', widget=forms.PasswordInput) + password2 = forms.CharField(label='Password (again)', widget=forms.PasswordInput) + captcha = forms.CharField(label='Captcha', max_length=6) + expected_captcha = None + + def __init__(self, *args, **kwargs): + if 'expected_captcha' in kwargs: + self.expected_captcha = kwargs.pop('expected_captcha') + super().__init__(*args, **kwargs) + + def clean(self): + cleaned_data = super().clean() + password1 = cleaned_data.get('password1') + password2 = cleaned_data.get('password2') + captcha = cleaned_data.get('captcha') + if password1 and password2: + if password1 != password2: + raise forms.ValidationError('Passwords do not match.') + if str.lower(captcha) != str.lower(self.expected_captcha): + raise forms.ValidationError('Captcha does not match.') + if User.objects.filter(username=cleaned_data.get('username')).exists(): + raise forms.ValidationError('Username already exists.') + if User.objects.filter(email=cleaned_data.get('email')).exists(): + raise forms.ValidationError('Email already exists.') + return cleaned_data + + def save(self, request): + user = User.objects.create_user( + username=self.cleaned_data.get('username'), + email=self.cleaned_data.get('email'), + password=self.cleaned_data.get('password1'), + ) + user.save() + user_profile = UserProfile.objects.create(user=user) + user_profile.save() + + # Send verification email + subject = 'Verify your email address' + message = render_to_string('verification_email.html', { + 'user': user.username if user.first_name is None else user.first_name, + 'site_name': 'That Computer Scientist', + 'uid': urlsafe_base64_encode(force_bytes(user.pk)), + 'token': account_activation_token.make_token(user), + 'protocol': 'https://' if request.is_secure() else 'http://', + 'domain': request.get_host(), + }) + message = strip_tags(message) + send_mail(subject, message, 'That Computer Scientist <' + settings.EMAIL_HOST_USER + '>', [user.email], fail_silently=False) + + return user + diff --git a/users/migrations/0009_delete_captchastore.py b/users/migrations/0009_delete_captchastore.py new file mode 100644 index 00000000..dbde60c8 --- /dev/null +++ b/users/migrations/0009_delete_captchastore.py @@ -0,0 +1,16 @@ +# Generated by Django 4.1.4 on 2022-12-28 12:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("users", "0008_remove_userprofile_gravatar_email_and_more"), + ] + + operations = [ + migrations.DeleteModel( + name="CaptchaStore", + ), + ] diff --git a/users/models.py b/users/models.py index 746814ef..1b97e871 100644 --- a/users/models.py +++ b/users/models.py @@ -16,11 +16,4 @@ class UserProfile(models.Model): def __str__(self): return self.user.username - -class CaptchaStore(models.Model): - captcha_string = models.CharField(max_length=6) - csrf_token = models.CharField(max_length=100) - created_at = models.DateTimeField(auto_now_add=True) - def __str__(self): - return self.captcha_string diff --git a/users/tokens.py b/users/tokens.py index c127fa45..77bd4e88 100644 --- a/users/tokens.py +++ b/users/tokens.py @@ -3,6 +3,9 @@ import os from django.contrib.auth.tokens import PasswordResetTokenGenerator from dotenv import load_dotenv from six import text_type +from Crypto.Cipher import AES + +load_dotenv() class AccountActivationTokenGenerator(PasswordResetTokenGenerator): def _make_hash_value(self, user, timestamp): @@ -20,4 +23,17 @@ class EmailChangeTokenGenerator(): auth_string = os.getenv('AUTHORIZATION_STRING') return cryptocode.decrypt(token, auth_string) +class CaptchaTokenGenerator(): + def encrypt(self, captcha_string): + auth_string = os.getenv('AUTHORIZATION_STRING') + key = auth_string.encode('utf-8')[0:16] + cipher = AES.new(key, AES.MODE_CFB, key) + return cipher.encrypt(captcha_string.encode('utf-8')).hex() + + def decrypt(self, token): + auth_string = os.getenv('AUTHORIZATION_STRING') + key = auth_string.encode('utf-8')[0:16] + cipher = AES.new(key, AES.MODE_CFB, key) + return cipher.decrypt(bytes.fromhex(token)).decode('utf-8') + account_activation_token = AccountActivationTokenGenerator() diff --git a/users/urls.py b/users/urls.py index 220f57f3..02a7e302 100644 --- a/users/urls.py +++ b/users/urls.py @@ -12,7 +12,6 @@ urlpatterns = [ path('/verifyemail//', views.verify_email, name='verifyemail'), path('/sendchangeuseremail', views.send_change_user_email, name='sendchangeuseremail'), path('/changeemail//', views.change_email, name='changeemail'), - path('/register', views.register, name='register'), ] # Configure Admin Site diff --git a/users/views.py b/users/views.py index 7857bbe7..05d379ff 100644 --- a/users/views.py +++ b/users/views.py @@ -1,8 +1,8 @@ from django.http import HttpResponseRedirect -from django.shortcuts import render, redirect +from django.shortcuts import redirect from django.contrib.auth import authenticate, login, logout, update_session_auth_hash from django.contrib import messages -from .models import UserProfile, CaptchaStore +from .models import UserProfile from django.contrib.auth.models import User from django.core.mail import send_mail from django.conf import settings @@ -13,7 +13,6 @@ from django.utils.http import urlsafe_base64_encode from django.contrib.sites.shortcuts import get_current_site from .tokens import account_activation_token, EmailChangeTokenGenerator from django.utils.http import urlsafe_base64_decode -import django.contrib.auth.password_validation as validators # Create your views here. def login_user(request): @@ -195,49 +194,3 @@ def change_email(request, uidb64, token): else: messages.error(request, 'The verification link is invalid!') return redirect('blog:home') - - -def register(request): - if request.method == 'POST': - username = request.POST['username'] - email = request.POST['email'] - password = request.POST['password'] - confirm_password = request.POST['password2'] - captcha = request.POST['captcha'] - csrf_token = request.META.get('CSRF_COOKIE') - current_captcha = CaptchaStore.objects.get(csrf_token=csrf_token).captcha_string - if str(captcha).lower() != str(current_captcha).lower(): - messages.error(request, 'Captcha is incorrect!', extra_tags='captchaError') - return HttpResponseRedirect(request.META.get('HTTP_REFERER') + '?u={}&e={}'.format(username, email)) - if password != confirm_password: - messages.error(request, 'Passwords do not match!', extra_tags='password2Error') - return HttpResponseRedirect(request.META.get('HTTP_REFERER') + '?u={}&e={}'.format(username, email)) - if User.objects.filter(username=username).exists(): - messages.error(request, 'Username is already in use!', extra_tags='usernameError') - return HttpResponseRedirect(request.META.get('HTTP_REFERER') + '?e={}'.format(email)) - if User.objects.filter(email=email).exists(): - messages.error(request, 'Email is already in use!', extra_tags='emailError') - return HttpResponseRedirect(request.META.get('HTTP_REFERER') + '?u={}'.format(username)) - try: - validators.validate_password(password=password) - except Exception as e: - messages.error(request, e, extra_tags='passwordError') - return HttpResponseRedirect(request.META.get('HTTP_REFERER') + '?u={}&e={}'.format(username, email)) - user = User.objects.create_user(username=username, email=email, password=password) - user.save() - user_profile = UserProfile(user=user) - user_profile.save() - # Send verification email - subject = 'Verify your email address' - message = render_to_string('verification_email.html', { - 'user': user.username if user.first_name is None else user.first_name, - 'site_name': 'That Computer Scientist', - 'uid': urlsafe_base64_encode(force_bytes(user.pk)), - 'token': account_activation_token.make_token(user), - 'protocol': 'https://' if request.is_secure() else 'http://', - 'domain': get_current_site(request).domain, - }) - message = strip_tags(message) - send_mail(subject, message, 'That Computer Scientist <' + settings.EMAIL_HOST_USER + '>', [email]) - messages.success(request, 'Account was created! Please check your email to verify your account.', extra_tags='accountCreated') - return redirect('blog:register') -- cgit v1.2.3