Skip to content

Commit

Permalink
Improved resend email feature
Browse files Browse the repository at this point in the history
  • Loading branch information
TreyWW committed Feb 24, 2024
1 parent 1a18f4c commit 2ae6011
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 40 deletions.
29 changes: 29 additions & 0 deletions backend/migrations/0021_alter_verificationcodes_expiry_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 5.0.2 on 2024-02-23 19:00

import datetime

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("backend", "0020_alter_verificationcodes_options_and_more"),
]

operations = [
migrations.AlterField(
model_name="verificationcodes",
name="expiry",
field=models.DateTimeField(
default=datetime.datetime(
2024, 2, 23, 22, 0, 25, 744643, tzinfo=datetime.timezone.utc
)
),
),
migrations.AlterField(
model_name="verificationcodes",
name="token",
field=models.TextField(default="XBNKTM", editable=False),
),
]
10 changes: 9 additions & 1 deletion backend/views/core/auth/login.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import django_ratelimit
from django.contrib.auth import authenticate, login, logout
from django.http import HttpRequest
from django.shortcuts import render
from django.urls import resolve, Resolver404, reverse
from django.views.decorators.csrf import csrf_exempt
from django_ratelimit.decorators import ratelimit

from backend.decorators import *
from backend.models import LoginLog
Expand All @@ -15,6 +17,9 @@

@csrf_exempt
@not_authenticated
@ratelimit(key="post:email", method=django_ratelimit.UNSAFE, rate="5/m")
@ratelimit(key="post:email", method=django_ratelimit.UNSAFE, rate="10/5m")
@ratelimit(key="ip", method=django_ratelimit.UNSAFE, rate="5/m")
def login_page(request):
if request.user.is_authenticated:
return redirect("dashboard")
Expand All @@ -32,8 +37,11 @@ def login_page(request):
if not user.is_active:
if user.awaiting_email_verification:
messages.error(request, f"""
Your account is awaiting email verification
<a href='{request.build_absolute_uri(reverse("auth:login create_account verify resend", kwargs={"uid": user.id}))}'
<button hx-post='
{request.build_absolute_uri(reverse("auth:login create_account verify resend"))}'
hx-vals='{{"email": "{email}"}}'
class='link link-success'>
click here to send a new verification email
</a>.
Expand Down
4 changes: 2 additions & 2 deletions backend/views/core/auth/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.urls import path

from . import login, create_account,verify
from . import login, create_account, verify
from .passwords import view as passwords_view, generate as passwords_generate, set as passwords_set

urlpatterns = [
Expand All @@ -27,7 +27,7 @@
name="login create_account verify",
),
path(
"create_account/verify/resend/<int:uid>/",
"create_account/verify/resend/",
verify.resend_verification_code,
name="login create_account verify resend",
),
Expand Down
20 changes: 16 additions & 4 deletions backend/views/core/auth/verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from django.shortcuts import redirect
from django.urls import reverse
from django.utils import timezone
from django.views.decorators.http import require_POST
from django_ratelimit.decorators import ratelimit

from backend.models import VerificationCodes, User, TracebackError
from settings import settings
Expand Down Expand Up @@ -42,15 +44,25 @@ def create_account_verify(request, uuid, token):
return redirect("auth:login")


def resend_verification_code(request, uid):
@ratelimit(group="resend_verification_code", key="ip", rate="1/m")
@ratelimit(group="resend_verification_code", key="ip", rate="3/25m")
@ratelimit(group="resend_verification_code", key="ip", rate="10/6h")
@ratelimit(group="resend_verification_code", key="post:email", rate="1/m")
@ratelimit(group="resend_verification_code", key="post:email", rate="3/25m")
@require_POST
def resend_verification_code(request):
email = request.POST.get("email")
if not email:
messages.error(request, "Invalid resend verification request")
return redirect("auth:login")
if not ARE_EMAILS_ENABLED:
messages.error(request, "Emails are currently disabled.")
TracebackError.objects.create(
error="Emails are currently disabled."
)
return redirect("auth:login create_account")
try:
user = User.objects.get(pk=uid)
user = User.objects.get(email=email)
except User.DoesNotExist:
messages.error(request, "Invalid resend verification request")
return redirect("auth:create_account")
Expand All @@ -62,8 +74,8 @@ def resend_verification_code(request, uid):
"auth:login create_account verify", kwargs={"uuid": magic_link.uuid, "token": token_plain}
)

send_email(destination=request.user.email, subject="Verify your email", message=f"""
Hi {request.user.first_name if request.user.first_name else "User"},
send_email(destination=email, subject="Verify your email", message=f"""
Hi {user.first_name if user.first_name else "User"},
Verification for your email has been requested to link this email to your MyFinances account.
If this wasn't you, you can simply ignore this email.
Expand Down
11 changes: 7 additions & 4 deletions backend/views/core/other/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,13 @@ def e_403(request: HttpRequest, exception=None):
request,
"Woah, slow down there. You've been temporarily blocked from this page due to extreme requests.",
)
traceback.print_exc()
exec_error = traceback.format_exc()
if len(exec_error) < 4999:
TracebackError(error=exec_error).save()
user_ip = request.META.get("REMOTE_ADDR")
user_id = f"User #{request.user.id}" if request.user.is_authenticated else "Not logged in"
action = f"{user_ip} | Ratelimited | {user_id}"
auditlog = AuditLog(action=action)
if request.user.is_authenticated:
auditlog.user = request.user
auditlog.save()
return redirect("auth:login")
else:
messages.error(
Expand Down
63 changes: 34 additions & 29 deletions frontend/templates/base/auth.html
Original file line number Diff line number Diff line change
@@ -1,37 +1,42 @@
{% load static %}
<html>
{% include 'base/_head.html' %}
<body>
<div class="min-h-screen bg-base-200 flex items-center">
<div class="card my-4 mx-auto w-full max-w-5xl shadow-xl">
<div class="grid md:grid-cols-2 grid-cols-1 bg-base-100 rounded-xl">
<div class="hero min-h-full rounded-l-xl bg-base-200">
<div class="hero-content py-12">
<div class="max-w-md">
<h1 class="text-3xl text-center font-bold ">Dashboard</h1>
<div class="text-center mt-12 mb-24">
<img src="{% block image %}{% endblock image %}" class="w-48 inline-block">
</div>
<h1 class="text-2xl mt-8 mb-4 font-bold text-center">What do you get to manage?</h1>
<p class="py-2">✓ Client Lists</p>
<p class="py-2">✓ Invoices</p>
<p class="py-2">✓ Receipt Storage</p>
<p class="pt-2">✓ Financial Reports</p>
</div>
{% include 'base/_head.html' %}
<body>
<div class="min-h-screen bg-base-200 flex items-center">
<div class="card my-4 mx-auto w-full max-w-5xl shadow-xl">
<div class="grid md:grid-cols-2 grid-cols-1 bg-base-100 rounded-xl">
<div class="hero min-h-full rounded-l-xl bg-base-200">
<div class="hero-content py-12">
<div class="max-w-md">
<h1 class="text-3xl text-center font-bold ">Dashboard</h1>
<div class="text-center mt-12 mb-24">
<img src="{% block image %}{% endblock image %}" class="w-48 inline-block">
</div>
</div>
<div class="pt-12 pb-8 px-10">
{# {% component "messages_list" %}#}
{% include "base/toasts.html" %}
<h2 class="text-2xl font-semibold mb-2 text-center">
{% block title %}
{% endblock title %}
</h2>
{% block content %}
{% endblock content %}
<h1 class="text-2xl mt-8 mb-4 font-bold text-center">What do you get to manage?</h1>
<p class="py-2">✓ Client Lists</p>
<p class="py-2">✓ Invoices</p>
<p class="py-2">✓ Receipt Storage</p>
<p class="pt-2">✓ Financial Reports</p>
</div>
</div>
</div>
<div class="pt-12 pb-8 px-10">
{# {% component "messages_list" %}#}
{% include "base/toasts.html" %}
<h2 class="text-2xl font-semibold mb-2 text-center">
{% block title %}
{% endblock title %}
</h2>
{% block content %}
{% endblock content %}
</div>
</div>
</body>
</div>
</div>
<script>
document.body.addEventListener('htmx:configRequest', (event) => {
event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
})
</script>
</body>
</html>
11 changes: 11 additions & 0 deletions settings/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import boto3
import environ
from django_ratelimit.core import get_usage
from mypy_boto3_sesv2.client import SESV2Client

### NEEDS REFACTOR
Expand All @@ -24,6 +25,16 @@ def get_var(key, default=None, required=False):
return value


def increment_rate_limit(request, group):
"""
Alias of is_ratelimited that just increments the rate limit for the given group.
Returns the new usage count.
"""
usage = get_usage(request, group, increment=True)
return usage.get("count", 0)


EMAIL_CLIENT: SESV2Client = boto3.client(
"sesv2",
region_name="eu-west-2",
Expand Down

0 comments on commit 2ae6011

Please sign in to comment.