Skip to content

Commit

Permalink
signup flow wip
Browse files Browse the repository at this point in the history
  • Loading branch information
brianjp93 committed Nov 15, 2024
1 parent 3d1151c commit d308a09
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 6 deletions.
4 changes: 2 additions & 2 deletions lolsite/signers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from django.core.signing import Signer, TimestampSigner
from django.core.signing import TimestampSigner

ActivationSigner = TimestampSigner('email-activation-signer')
ActivationSigner = TimestampSigner()
35 changes: 35 additions & 0 deletions player/forms.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,43 @@
from django import forms
from django.contrib.auth import get_user_model

from data.constants import Region


User = get_user_model()


class SummonerSearchForm(forms.Form):
search = forms.CharField(required=True, widget=forms.TextInput(attrs={"autocomplete": 'off'}))
region = forms.ChoiceField(choices=[(x, x) for x in Region], required=True)


class SignupForm(forms.ModelForm):
password_confirmation = forms.CharField()

class Meta:
model = User
fields = ["email", "password", "password_confirmation"]
widgets = {
"password": forms.PasswordInput(),
"password_confirmation": forms.PasswordInput(),
}

def clean_password_confirmation(self):
password = self.cleaned_data["password"]
password_confirmation = self.cleaned_data["password_confirmation"]
if password != password_confirmation:
raise forms.ValidationError("password fields did not match")
return password_confirmation

def clean_email(self):
email = self.cleaned_data["email"].strip()
if User.objects.filter(email__iexact=email).exists():
raise forms.ValidationError("This email already exists, try logging in.")
return email

def save(self, commit: bool = True):
self.instance.username = self.instance.email
self.instance.is_active = False
self.instance.set_password(self.cleaned_data["password"])
return super().save(commit)
6 changes: 5 additions & 1 deletion player/urls.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.urls import path, re_path
from django.contrib.auth.views import LoginView, LogoutView
from django.views.generic import TemplateView
from . import views

app_name = 'player'
Expand All @@ -10,6 +11,9 @@
path("lookup/", views.SummonerLookup.as_view(), name="summoner-lookup"),
path("puuid/<str:puuid>/", views.SummonerPagePuuid.as_view(), name="summoner-puuid"),
path("player/autocomplete/", views.SummonerAutoComplete.as_view(), name="summoner-autocomplete"),
path("player/login/", LoginView.as_view(), name="login-view"),
path("player/login/", LoginView.as_view(), name="login"),
path("player/logout/", LogoutView.as_view(), name="logout-view"),
path("player/signup/", views.SignupView.as_view(), name="signup"),
path("player/activate/", views.EmailActivationView.as_view(), name="activate"),
path("player/account-created/", TemplateView.as_view(template_name="registration/account_created.html"), name="account-created"),
]
99 changes: 97 additions & 2 deletions player/views.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
from datetime import timedelta
from functools import cached_property
import urllib.parse
import logging

from django.core.signing import BadSignature, SignatureExpired
import requests

from django.http import Http404
from django.shortcuts import redirect, render
from django.contrib.auth import authenticate, login, logout
from django.urls import reverse
from django.contrib.auth import authenticate, login, logout, get_user_model
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.views import generic
from django.conf import settings
from django.db import transaction
from django.template.loader import render_to_string
from django.core.mail import send_mail

from data.models import Champion
from data.serializers import BasicChampionWithImageSerializer
Expand All @@ -20,8 +29,32 @@
from player.models import EmailVerification, NameChange, Summoner
from player.serializers import RankPositionSerializer
from player.viewsapi import get_by_puuid
from player.forms import SignupForm
from player import tasks as pt
from stats.views import champion_stats_context
from lolsite.signers import ActivationSigner


logger = logging.getLogger(__name__)
User = get_user_model()



def send_verification_email(request, email):
subject = "Hardstuck.club email verification"
user = User.objects.get(email__iexact=email)
signed = ActivationSigner.sign(str(user.pk))
context = {
"activation_url": request.build_absolute_uri(reverse("player:activate")) + f"?signed={signed}",
}
html_message = render_to_string("mail/activation.html", context)
send_mail(
subject,
html_message,
settings.DEFAULT_FROM_EMAIL,
[email],
html_message=html_message,
)


def get_page_urls(request, query_param='page'):
Expand Down Expand Up @@ -248,3 +281,65 @@ def fetch_spectate_data(self, puuid, region):
spectate_data["team100"] = [x for x in spectate_data['participants'] if x['teamId'] == 100]
spectate_data["team200"] = [x for x in spectate_data['participants'] if x['teamId'] != 100]
return spectate_data


class SignupView(generic.CreateView):
form_class = SignupForm
template_name = "registration/signup.html"
success_url = reverse_lazy("player:account-created")

def get_context_data(self, **kwargs):
return super().get_context_data(**kwargs) | {
"GOOGLE_RECAPTCHA_KEY": settings.GOOGLE_RECAPTCHA_KEY,
}

def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated:
return redirect("home")
return super().dispatch(request, *args, **kwargs)

@transaction.atomic
def form_valid(self, form):
token = self.request.POST.get("g-recaptcha-response", "")
email = form.cleaned_data['email']
response = requests.post(
f'https://recaptchaenterprise.googleapis.com/v1/projects/{settings.GOOGLE_RECAPTCHA_PROJECT_ID}/assessments?key={settings.GOOGLE_RECAPTCHA_API_KEY}',
json={
"event": {
"token": token,
"siteKey": settings.GOOGLE_RECAPTCHA_KEY,
"expectedAction": "SIGNUP"
}
}
)
score = response.json()['riskAnalysis']['score']
logger.info(f'Got {score=} for signup: {email}')
transaction.on_commit(lambda: send_verification_email(self.request, email))
return super().form_valid(form)


class EmailActivationView(generic.TemplateView):
template_name = "registration/activation.html"

def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
signed = self.request.GET.get("signed", "")
try:
user_id = ActivationSigner.unsign(signed, max_age=timedelta(days=60))
except SignatureExpired:
context["type"] = "signature_expired"
user_id = signed.split(":")[0]
user = User.objects.filter(id=user_id).first()
if user:
send_verification_email(self.request, user.email)
except BadSignature:
context["type"] = "bad_signature"
else:
context["type"] = "success"
user = User.objects.filter(id=user_id).first()
user.is_active = True
user.save()
return context
2 changes: 1 addition & 1 deletion templates/layout/navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<button class="btn btn-link" type="submit">Logout</button>
</form>
{% else %}
<a href="{% url 'player:login-view' %}">Login</a>
<a href="{% url 'player:login' %}">Login</a>
{% endif %}
</div>
</div>
13 changes: 13 additions & 0 deletions templates/mail/activation.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% block content %}
<div>
You signed up for an account on hardstuck.club
</div>

<div>
Please activate your account.
</div>

<div>
<a href="{{ activation_url }}">{{ activation_url }}</a>
</div>
{% endblock content %}
Empty file added templates/mail/base_mail.html
Empty file.
10 changes: 10 additions & 0 deletions templates/registration/account_created.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% extends "layout/base.html" %}

{% block content %}
<div class="container mx-auto">
<h1 class="mx-auto">Account Created</h1>
<div>Please check your email and click the link to activate your account.</div>

<div>After you activate, you can <a href="{% url 'player:login' %}">Login</a></div>
</div>
{% endblock content %}
25 changes: 25 additions & 0 deletions templates/registration/activation.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{% extends "layout/base.html" %}

{% block content %}
<div class="container mx-auto">
<h1 class="mx-auto">Account Activation</h1>

{% if type == 'success' %}
<div>
You successfully activated your account.
</div>
<div>
Please <a href="{% url 'player:login' %}">log in</a>.
</div>
{% elif type == 'bad_signature' %}
<div>
This link was invalid. Please check your email again.
</div>
{% elif type == 'signature_expired' %}
<div>
This link has expired, please check your email for a new link.
</div>
{% endif %}

</div>
{% endblock content %}
31 changes: 31 additions & 0 deletions templates/registration/signup.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{% extends "layout/base.html" %}

{% block header_js %}
{{ block.super }}
<script src="https://www.google.com/recaptcha/enterprise.js?render={{ GOOGLE_RECAPTCHA_KEY }}"></script>
{% endblock header_js %}

{% block content %}
<div class="container mx-auto">
<h1 class="mx-auto">Sign Up</h1>
<form id="signup" class="forms" action="" method="POST">
{% csrf_token %}
{{ form }}
<button
class="g-recaptcha btn btn-success w-full"
data-sitekey="{{ GOOGLE_RECAPTCHA_KEY }}"
data-callback='onSubmit'
data-action='submit'
type="submit"
>
Sign Up
</button>
</form>
</div>

<script>
function onSubmit(token) {
document.getElementById("signup").submit();
}
</script>
{% endblock content %}

0 comments on commit d308a09

Please sign in to comment.