diff options
| -rw-r--r-- | templates/blog/partials/sidebar.html | 26 | ||||
| -rw-r--r-- | users/accountFunctions.py | 39 | ||||
| -rw-r--r-- | users/forms.py | 11 | ||||
| -rw-r--r-- | users/migrations/0011_tokenstore.py | 42 | ||||
| -rw-r--r-- | users/models.py | 15 | ||||
| -rw-r--r-- | users/templates/email_change_verification_email.html | 2 | ||||
| -rw-r--r-- | users/templates/verification_email.html | 2 | ||||
| -rw-r--r-- | users/urls.py | 8 | ||||
| -rw-r--r-- | users/views.py | 116 |
9 files changed, 172 insertions, 89 deletions
diff --git a/templates/blog/partials/sidebar.html b/templates/blog/partials/sidebar.html index 5525b6c6..ed260c6b 100644 --- a/templates/blog/partials/sidebar.html +++ b/templates/blog/partials/sidebar.html @@ -26,20 +26,20 @@ </td> </tr> </table> - {% for message in messages %} - {% if 'loginError' in message.tags %} - {% if message.message == "EVERR" and request.GET.username %} - <form method="post" action="{% url 'users:sendverificationemail' %}"> - <p class="message {{message.tags}}"> - Your email is unverified. Please check your inbox for a verification email or to request a new verification email by clicking{% csrf_token %}<input type="hidden" name="username" value="{{ request.GET.username }}"><input style="display: inline; background: none; border: none; color: blue; text-decoration: underline; cursor: pointer; margin: 0;" type="submit" value="here."> - </p> - </form> - {% else %} - <p class="message {{message.tags}}">{{message.message}}</p> - {% endif %} - {% endif %} - {% endfor %} </form> + {% for message in messages %} + {% if 'loginError' in message.tags %} + {% if message.message == "EVERR" and request.GET.username %} + <form method="post" action="{% url 'users:sendverificationemail' %}"> + <p class="message {{message.tags}}"> + Your email is unverified. Please check your inbox for a verification email or to request a new verification email by clicking{% csrf_token %}<input type="hidden" name="username" value="{{ request.GET.username }}"><input style="display: inline; background: none; border: none; color: blue; text-decoration: underline; cursor: pointer; margin: 0;" type="submit" value="here."> + </p> + </form> + {% else %} + <p class="message {{message.tags}}">{{message.message}}</p> + {% endif %} + {% endif %} + {% endfor %} </div> {% endif %} diff --git a/users/accountFunctions.py b/users/accountFunctions.py new file mode 100644 index 00000000..be60e29f --- /dev/null +++ b/users/accountFunctions.py @@ -0,0 +1,39 @@ +from users.models import TokenStore, UserProfile +import uuid +import secrets +from django.utils import timezone + +def generate_token(): + uid = uuid.uuid4().hex + token = secrets.token_urlsafe(32) + print(uid, token) + return uid, token + +def store_token(token_type, user, email=None): + previous_tokens = TokenStore.objects.filter(user=user, token_type=token_type) + if previous_tokens.exists(): + previous_tokens.delete() + uid, token = generate_token() + token_store = TokenStore.objects.create( + user=user, + email=email if email is not None else user.email, + uid=uid, + token=token, + token_type=token_type, + expires=timezone.now() + timezone.timedelta(minutes=30), + ) + token_store.save() + return uid, token + +def verify_token(token_type, uid, token): + try: + token_store = TokenStore.objects.get(token_type=token_type, uid=uid, token=token) + if token_store.expires > timezone.now() and not token_store.verified and token_store.token_type == token_type and token_store.uid == uid and token_store.token == token: + token_store.verified = True + UserProfile.objects.filter(user=token_store.user).update(email_verified=True) + token_store.save() + + return token_store + except TokenStore.DoesNotExist: + return None +
\ No newline at end of file diff --git a/users/forms.py b/users/forms.py index e8c455fd..d66e560d 100644 --- a/users/forms.py +++ b/users/forms.py @@ -3,12 +3,9 @@ from django import forms from django.contrib.auth.models import User from users.models import UserProfile -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 +from .accountFunctions import store_token from .mail_send import send_email class RegisterForm(forms.Form): @@ -52,13 +49,15 @@ class RegisterForm(forms.Form): user_profile = UserProfile.objects.create(user=user) user_profile.save() + uid, token = store_token(token_type='verifyemail', user=user, email=user.email) + # 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), + 'uid': uid, + 'token': token, 'protocol': 'https://' if request.is_secure() else 'http://', 'domain': request.get_host(), }) diff --git a/users/migrations/0011_tokenstore.py b/users/migrations/0011_tokenstore.py new file mode 100644 index 00000000..78a5478b --- /dev/null +++ b/users/migrations/0011_tokenstore.py @@ -0,0 +1,42 @@ +# Generated by Django 4.1.4 on 2023-04-30 03:19 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("users", "0010_userprofile_blinkie_url"), + ] + + operations = [ + migrations.CreateModel( + name="TokenStore", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("uid", models.TextField(unique=True)), + ("token", models.TextField(unique=True)), + ("email", models.EmailField(blank=True, max_length=254)), + ("token_type", models.CharField(max_length=50)), + ("expires", models.DateTimeField(auto_now_add=True)), + ("verified", models.BooleanField(default=False)), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + ] diff --git a/users/models.py b/users/models.py index b076a091..76dfc896 100644 --- a/users/models.py +++ b/users/models.py @@ -18,3 +18,18 @@ class UserProfile(models.Model): def __str__(self): return self.user.username + +class TokenStore(models.Model): + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + ) + uid = models.TextField(unique=True) + token = models.TextField(unique=True) + email = models.EmailField(blank=True) + token_type = models.CharField(max_length=50) + expires = models.DateTimeField() + verified = models.BooleanField(default=False) + + def __str__(self): + return self.user.username diff --git a/users/templates/email_change_verification_email.html b/users/templates/email_change_verification_email.html index a1347fbd..3db0a9d6 100644 --- a/users/templates/email_change_verification_email.html +++ b/users/templates/email_change_verification_email.html @@ -2,7 +2,7 @@ Hi {{ user }}, We received a request to change you email address on {{ site_name }}. To verify and change your email address, please click the link below. -{{ protocol }}{{ domain }}/users/changeemail/{{ uid }}/{{ token }}. +{{ protocol }}{{ domain }}{% url 'users:changeemail' 'changeemail' uid token %} Please ignore this email if you did not make this request. diff --git a/users/templates/verification_email.html b/users/templates/verification_email.html index ee9e8cca..cd85ab38 100644 --- a/users/templates/verification_email.html +++ b/users/templates/verification_email.html @@ -2,7 +2,7 @@ Hi {{ user }}, Thanks for registering an account on {{ site_name }}. To verify your email address, please click the link below. -{{ protocol }}{{ domain }}{% url 'users:verifyemail' uidb64=uid token=token %} +{{ protocol }}{{ domain }}{% url 'users:changeemail' 'verifyemail' uid token %} Thanks, {{ site_name }} Team diff --git a/users/urls.py b/users/urls.py index 3a08602b..9e900638 100644 --- a/users/urls.py +++ b/users/urls.py @@ -8,13 +8,13 @@ urlpatterns = [ path('/logout', views.logout_user, name='logout'), path('/update', views.update_user, name='update'), path('/changepassword', views.change_password, name='changepassword'), - path('/sendverificationemail', views.send_verification_email, name='sendverificationemail'), - path('/verifyemail/<uidb64>/<token>', views.verify_email, name='verifyemail'), path('/sendchangeuseremail', views.send_change_user_email, name='sendchangeuseremail'), - path('/changeemail/<uidb64>/<token>', views.change_email, name='changeemail'), - path('updateavatar', views.update_avatar, name='updateavatar'), + path('/sendverificationemail', views.send_verification_email, name='sendverificationemail'), + path('/updateavatar', views.update_avatar, name='updateavatar'), path('/updateblinkies', views.update_blinkie, name='updateblinkie'), path('/delete', views.delete_user, name='delete'), + path('/<mode>/<uid>/<token>', views.verify_email, name='verifyemail'), + path('/<mode>/<uid>/<token>', views.verify_email, name='changeemail'), ] # Configure Admin Site diff --git a/users/views.py b/users/views.py index 15ef3929..948d690b 100644 --- a/users/views.py +++ b/users/views.py @@ -1,17 +1,13 @@ -from django.http import HttpResponseRedirect +from django.http import HttpResponseRedirect, HttpResponse from django.shortcuts import redirect, reverse from django.contrib.auth import authenticate, login, logout, update_session_auth_hash from django.contrib import messages from .models import UserProfile from django.contrib.auth.models import User -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, EmailChangeTokenGenerator -from django.utils.http import urlsafe_base64_decode from .forms import UpdateUserDetailsForm +from .accountFunctions import store_token, verify_token from .mail_send import send_email # Create your views here. @@ -143,49 +139,6 @@ def change_password(request): messages.error(request, 'Unable to change password! Please try again later.') return redirect('blog:home') - -def send_verification_email(request): - username = request.POST['username'] - user = User.objects.get(username=username) - - 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': request.scheme + '://', - 'domain': request.get_host(), - }) - message = strip_tags(message) - - if (send_email(sender='[email protected]', sender_name='That Computer Scientist', recipient=user.email, subject=subject, body_html=message, body_text=message)): - messages.success(request, 'Verification email was sent! Please check your email.', extra_tags='loginError') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) - else: - messages.error(request, 'Unable to send verification email! Please try again later.', extra_tags='loginError') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) - -def verify_email(request, uidb64, token): - try: - uid = urlsafe_base64_decode(uidb64).decode() - user = User.objects.get(pk=uid) - try: - user_profile = UserProfile.objects.get(user=user.pk) - except UserProfile.DoesNotExist: - user_profile = UserProfile(user=user) - user_profile.save() - except (TypeError, ValueError, OverflowError, User.DoesNotExist): - user = None - if user is not None and account_activation_token.check_token(user, token): - user_profile.email_verified = True - user_profile.save() - messages.success(request, 'Your email has been verified! You can now login.', extra_tags='loginError') - return redirect('blog:home') - else: - messages.error(request, 'The verification link is invalid!') - return redirect('blog:home') - def send_change_user_email(request): user = request.user new_email = request.POST['email'] @@ -202,11 +155,13 @@ def send_change_user_email(request): return HttpResponseRedirect(request.META.get('HTTP_REFERER')) # Send verification email subject = 'Verify your email address' + uid, token = store_token(token_type='changeemail', user=user, email=new_email) + message = render_to_string('email_change_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': EmailChangeTokenGenerator().encrypt(new_email), + 'uid': uid, + 'token': token, 'protocol': request.scheme + '://', 'domain': request.get_host(), }) @@ -223,19 +178,52 @@ def send_change_user_email(request): else: messages.error(request, 'Unable to change email! Please try again later.') return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + +def send_verification_email(request): + # this is a post only view + if request.method == 'POST': + username = request.POST.get('username') + subject = 'Verify your email address' + user = User.objects.get(username=username) + uid, token = store_token(token_type='verifyemail', user=user, email=user.email) -def change_email(request, uidb64, token): - try: - uid = urlsafe_base64_decode(uidb64).decode() - user = User.objects.get(pk=uid) - new_email = EmailChangeTokenGenerator().decrypt(token) - except (TypeError, ValueError, OverflowError, User.DoesNotExist): - user = None - if user is not None: - user.email = new_email + 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': uid, + 'token': token, + 'protocol': 'https://' if request.is_secure() else 'http://', + 'domain': request.get_host(), + }) + message = strip_tags(message) + if (send_email(sender='[email protected]', sender_name='That Computer Scientist', recipient=user.email, subject=subject, body_html=message, body_text=message)): + messages.success(request, 'Verification email was sent! Please check your email.', extra_tags='loginError') + return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + else: + messages.error(request, 'Unable to send verification email! Please try again later.', extra_tags='loginError') + return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + else: + messages.error(request, 'Unable to send verification email! Please try again later.', extra_tags='loginError') + return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + +def verify_email(request, mode, uid, token): + token_object = verify_token(mode, uid, token) + redirect_to = reverse('blog:account') + '?tab=email' if mode == 'changeemail' else 'blog:home' + success_message = 'Email was successfully changed!' if mode == 'changeemail' else 'Email was successfully verified!' + error_message = 'Unable to verify email! Please try again later.' + + if token_object is not None and token_object.verified: + user = User.objects.get(pk=token_object.user_id) + user.email = token_object.email user.save() - messages.success(request, 'Email was successfully changed!') - return redirect(reverse('blog:account') + '?tab=email') + token_object.delete() + + if not request.user.is_authenticated and mode == 'changeemail': + login(request, user) + + messages.success(request, success_message, extra_tags='loginError' if mode == 'verifyemail' else '') + return redirect(redirect_to) else: - messages.error(request, 'The verification link is invalid!') - return redirect('blog:home') + messages.error(request, error_message) + return redirect(redirect_to) +
\ No newline at end of file |
