Confirm email views

This commit is contained in:
Mouse Reeve 2021-08-06 16:24:57 -07:00
parent 247a7f7489
commit 5926224d7e
11 changed files with 78 additions and 28 deletions

View file

@ -36,9 +36,6 @@ FLOWER_PORT=8888
#FLOWER_USER=mouse
#FLOWER_PASSWORD=changeme
# make users confirm their email addresses after registration
CONFIRM_EMAIL=false
EMAIL_HOST="smtp.mailgun.org"
EMAIL_PORT=587
EMAIL_HOST_USER=mail@your.domain.here

View file

@ -36,9 +36,6 @@ FLOWER_PORT=8888
FLOWER_USER=mouse
FLOWER_PASSWORD=changeme
# make users confirm their email addresses after registration
CONFIRM_EMAIL=true
EMAIL_HOST="smtp.mailgun.org"
EMAIL_PORT=587
EMAIL_HOST_USER=mail@your.domain.here

View file

@ -30,6 +30,7 @@ def email_confirmation_email(user):
data["confirmation_link"] = user.confirmation_link
send_email.delay(user.email, *format_email("confirm_email", data))
def invite_email(invite_request):
"""send out an invite code"""
data = email_data()

View file

@ -1,4 +1,4 @@
# Generated by Django 3.2.4 on 2021-08-06 21:41
# Generated by Django 3.2.4 on 2021-08-06 23:24
import bookwyrm.models.base_model
from django.db import migrations, models
@ -11,6 +11,11 @@ class Migration(migrations.Migration):
]
operations = [
migrations.AddField(
model_name="sitesettings",
name="require_confirm_email",
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name="user",
name="confirmation_code",

View file

@ -31,6 +31,7 @@ class SiteSettings(models.Model):
# registration
allow_registration = models.BooleanField(default=True)
allow_invite_requests = models.BooleanField(default=True)
require_confirm_email = models.BooleanField(default=True)
# images
logo = models.ImageField(upload_to="logos/", null=True, blank=True)

View file

@ -26,11 +26,13 @@ from .base_model import BookWyrmModel, DeactivationReason, new_access_code
from .federated_server import FederatedServer
from . import fields, Review
def site_link():
"""helper for generating links to the site"""
protocol = "https" if USE_HTTPS else "http"
return f"{protocol}://{DOMAIN}"
class User(OrderedCollectionPageMixin, AbstractUser):
"""a user who wants to read books"""
@ -218,7 +220,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
self.following.order_by("-updated_date").all(),
remote_id=remote_id,
id_only=True,
**kwargs
**kwargs,
)
def to_followers_activity(self, **kwargs):
@ -228,7 +230,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
self.followers.order_by("-updated_date").all(),
remote_id=remote_id,
id_only=True,
**kwargs
**kwargs,
)
def to_activity(self, **kwargs):

View file

@ -24,9 +24,6 @@ CELERY_ACCEPT_CONTENT = ["application/json"]
CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json"
# make users confirm their email addresses after registration
CONFIRM_EMAIL = env("CONFIRM_EMAIL", True)
# email
EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend")
EMAIL_HOST = env("EMAIL_HOST")

View file

@ -0,0 +1 @@
{% extends "layout.html" %}

View file

@ -46,7 +46,14 @@ urlpatterns = [
re_path("^api/updates/stream/(?P<stream>[a-z]+)/?$", views.get_unread_status_count),
# authentication
re_path(r"^login/?$", views.Login.as_view(), name="login"),
re_path(r"^login/(?P<confirmed>confirmed)?$", views.Login.as_view(), name="login"),
re_path(r"^register/?$", views.Register.as_view()),
re_path(r"confirm-email/?$", views.ConfirmEmail.as_view(), name="confirm-email"),
re_path(
r"confirm-email/(?P<code>[A-Za-z0-9]+)/?$",
views.ConfirmEmailCode.as_view(),
name="confirm-email-code",
),
re_path(r"^logout/?$", views.Logout.as_view(), name="logout"),
re_path(
r"^password-reset/?$",

View file

@ -1,6 +1,7 @@
""" make sure all our nice views are available """
from .announcements import Announcements, Announcement, delete_announcement
from .authentication import Login, Register, Logout
from .authentication import ConfirmEmail, ConfirmEmailCode
from .author import Author, EditAuthor
from .block import Block, unblock
from .books import Book, EditBook, ConfirmEditBook, Editions

View file

@ -10,20 +10,21 @@ from django.views.decorators.csrf import csrf_exempt
from django.views import View
from bookwyrm import emailing, forms, models
from bookwyrm.settings import DOMAIN, CONFIRM_EMAIL
from bookwyrm.settings import DOMAIN
# pylint: disable= no-self-use
# pylint: disable=no-self-use
@method_decorator(csrf_exempt, name="dispatch")
class Login(View):
"""authenticate an existing user"""
def get(self, request):
def get(self, request, confirmed=None):
"""login page"""
if request.user.is_authenticated:
return redirect("/")
# sene user to the login page
# send user to the login page
data = {
"show_confirmed_email": confirmed,
"login_form": forms.LoginForm(),
"register_form": forms.RegisterForm(),
}
@ -37,14 +38,15 @@ class Login(View):
localname = login_form.data["localname"]
if "@" in localname: # looks like an email address to me
email = localname
try:
username = models.User.objects.get(email=email)
username = models.User.objects.get(email=localname).username
except models.User.DoesNotExist: # maybe it's a full username?
username = localname
else:
username = "%s@%s" % (localname, DOMAIN)
password = login_form.data["password"]
# perform authentication
user = authenticate(request, username=username, password=password)
if user is not None:
# successful login
@ -53,6 +55,12 @@ class Login(View):
user.save(broadcast=False, update_fields=["last_active_date"])
return redirect(request.GET.get("next", "/"))
# maybe the user is pending email confirmation
if models.User.objects.filter(
username=username, is_active=False, deactivation_reason="pending"
).exists():
return redirect("confirm-email")
# login errors
login_form.non_field_errors = "Username or password are incorrect"
register_form = forms.RegisterForm()
@ -60,12 +68,23 @@ class Login(View):
return TemplateResponse(request, "login.html", data)
@method_decorator(login_required, name="dispatch")
class Logout(View):
"""log out"""
def get(self, request):
"""done with this place! outa here!"""
logout(request)
return redirect("/")
class Register(View):
"""register a user"""
def post(self, request):
"""join the server"""
if not models.SiteSettings.get().allow_registration:
settings = models.SiteSettings.get()
if not settings.allow_registration:
invite_code = request.POST.get("invite_code")
if not invite_code:
@ -109,14 +128,15 @@ class Register(View):
password,
localname=localname,
local=True,
is_active=not CONFIRM_EMAIL,
deactivation_reason="pending" if settings.require_confirm_email else None,
is_active=not settings.require_confirm_email,
)
if invite:
invite.times_used += 1
invite.invitees.add(user)
invite.save()
if CONFIRM_EMAIL:
if settings.require_confirm_email:
emailing.email_confirmation_email(user)
return redirect("confirm-email")
@ -124,11 +144,32 @@ class Register(View):
return redirect("get-started-profile")
@method_decorator(login_required, name="dispatch")
class Logout(View):
"""log out"""
class ConfirmEmail(View):
"""enter code to confirm email address"""
def get(self, request):
"""done with this place! outa here!"""
logout(request)
return redirect("/")
def get(self, request): # pylint: disable=unused-argument
"""you need a code! keep looking"""
settings = models.SiteSettings.get()
if request.user.is_authenticated or not settings.require_confirm_email:
return redirect("/")
return TemplateResponse(request, "confirm_email.html")
class ConfirmEmailCode(View):
"""confirm email address"""
def get(self, request, code): # pylint: disable=unused-argument
"""you got the code! good work"""
settings = models.SiteSettings.get()
if request.user.is_authenticated or not settings.require_confirm_email:
return redirect("/")
# look up the user associated with this code
user = get_object_or_404(models.User, confirmation_code=code)
# update the user
user.is_active = True
user.deactivation_reason = None
user.save(broadcast=False, update_fields=["is_active", "deactivation_reason"])
# direct the user to log in
return redirect("login", confirmed="confirmed")