aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2023-04-30 00:34:22 -0400
committerBobby <[email protected]>2023-04-30 00:34:22 -0400
commitb3439b867b81a2d7cfb363b62b203ee2e64c0613 (patch)
tree79f85dd89786d95228e82977d4e22df8ecb4cc7e
parent714953207a6c01d88c826206a41423a597a2ca2c (diff)
downloadthatcomputerscientist-b3439b867b81a2d7cfb363b62b203ee2e64c0613.tar.xz
thatcomputerscientist-b3439b867b81a2d7cfb363b62b203ee2e64c0613.zip
Email Verification Update w/ Token Expiration
-rw-r--r--templates/blog/partials/sidebar.html26
-rw-r--r--users/accountFunctions.py39
-rw-r--r--users/forms.py11
-rw-r--r--users/migrations/0011_tokenstore.py42
-rw-r--r--users/models.py15
-rw-r--r--users/templates/email_change_verification_email.html2
-rw-r--r--users/templates/verification_email.html2
-rw-r--r--users/urls.py8
-rw-r--r--users/views.py116
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