Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#2187] Implemented first pass at email verification #1084

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/open_inwoner/accounts/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ class _UserAdmin(ImageCroppingMixin, UserAdmin):
"first_name",
)
fieldsets = (
(None, {"fields": ("uuid", "email", "password", "login_type")}),
(
None,
{"fields": ("uuid", "email", "verified_email", "password", "login_type")},
),
(
_("Personal info"),
{
Expand Down
21 changes: 20 additions & 1 deletion src/open_inwoner/accounts/middleware.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from django.urls import reverse_lazy

from open_inwoner.cms.utils.page_display import profile_page_is_published
from open_inwoner.configurations.models import SiteConfiguration
from open_inwoner.utils.middleware import BaseConditionalUserRedirectMiddleware


Expand All @@ -12,4 +14,21 @@ class NecessaryFieldsMiddleware(BaseConditionalUserRedirectMiddleware):

def requires_redirect(self, request) -> bool:
user = request.user
return user.require_necessary_fields() # and profile_page_is_published()
return user.require_necessary_fields() and profile_page_is_published()


class EmailVerificationMiddleware(BaseConditionalUserRedirectMiddleware):
"""
Redirect the user to a view to verify email
"""

redirect_url = reverse_lazy("profile:email_verification_user")
extra_pass_prefixes = (reverse_lazy("mail:verification"),)

def requires_redirect(self, request) -> bool:
user = request.user
return (
not user.has_verified_email()
and profile_page_is_published()
and SiteConfiguration.get_solo().email_verification_required
)
20 changes: 20 additions & 0 deletions src/open_inwoner/accounts/migrations/0075_user_verified_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 4.2.11 on 2024-03-28 15:25

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("accounts", "0074_merge_20240228_1544"),
]

operations = [
migrations.AddField(
model_name="user",
name="verified_email",
field=models.EmailField(
blank=True, default="", max_length=254, verbose_name="Verified email"
),
),
]
9 changes: 9 additions & 0 deletions src/open_inwoner/accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(
verbose_name=_("Email address"),
)
verified_email = models.EmailField(
verbose_name=_("Verified email"),
blank=True,
default="",
)

def has_verified_email(self):
return self.verified_email != "" and self.email == self.verified_email

phonenumber = models.CharField(
verbose_name=_("Phonenumber"),
blank=True,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{% extends 'master.html' %}
{% load i18n static form_tags card_tags grid_tags solo_tags %}

{% block content %}
<div class="registration-grid">
{% render_grid %}
{% render_column span=9 %}
{% render_card tinted=True %}
{% get_solo 'configurations.SiteConfiguration' as config %}
<h1 class="h1">{% trans "E-mailadres bevestigen" %}</h1><br>
{% if config.email_verification_text %}<p class="p">{{ config.email_verification_text|urlize|linebreaksbr }}</p><br>{% endif %}
<form method="POST" id="email-verification-form" action="{{ request.get_full_path }}" class="form" novalidate>
{% csrf_token %}
{# {% for field in form.fields %}#}
{# {% autorender_field form field %}#}
{# {% endfor %}#}
{% trans "Verficatie email verzenden" as button_text %}
{% form_actions primary_icon='arrow_forward' primary_text=button_text %}
</form>
{% endrender_card %}
{% endrender_column %}
{% endrender_grid %}
</div>
{% endblock content %}
30 changes: 29 additions & 1 deletion src/open_inwoner/accounts/views/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.forms import Form
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import NoReverseMatch, reverse
from django.utils.translation import gettext as _
from django.views.generic import UpdateView
from django.views.generic import FormView, UpdateView

from django_registration.backends.one_step.views import RegistrationView
from furl import furl
Expand All @@ -18,6 +19,7 @@
from open_inwoner.utils.hash import generate_email_from_string
from open_inwoner.utils.views import CommonPageMixin, LogMixin

from ...mail.verification import send_user_email_verification_mail
from ...utils.url import get_next_url_from
from ..forms import CustomRegistrationForm, NecessaryUserForm
from ..models import Invite, User
Expand Down Expand Up @@ -179,3 +181,29 @@ def get_initial(self):
initial["email"] = ""

return initial


class EmailVerificationUserView(LogMixin, LoginRequiredMixin, FormView):
model = User
# dummy form
form_class = Form
template_name = "accounts/email_verification.html"

def page_title(self):
return _("E-mailadres bevestigen")

def form_valid(self, form):
user = self.request.user

send_user_email_verification_mail(user)

messages.add_message(
self.request, messages.SUCCESS, _("Bevestigings e-mail is verzonden")
)

self.log_user_action(user, _("user requested e-mail address verification"))

return super().form_valid(form)

def get_success_url(self):
return get_next_url_from(self.request, default=reverse("pages-root"))
6 changes: 6 additions & 0 deletions src/open_inwoner/cms/profile/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
NewsletterSubscribeView,
)
from open_inwoner.accounts.views.actions import ActionDeleteView
from open_inwoner.accounts.views.registration import EmailVerificationUserView

app_name = "profile"

Expand Down Expand Up @@ -103,6 +104,11 @@
NecessaryFieldsUserView.as_view(),
name="registration_necessary",
),
path(
"register/email/verification/",
EmailVerificationUserView.as_view(),
name="email_verification_user",
),
path(
"newsletters",
NewsletterSubscribeView.as_view(),
Expand Down
1 change: 1 addition & 0 deletions src/open_inwoner/conf/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@
"open_inwoner.extended_sessions.middleware.SessionTimeoutMiddleware",
"open_inwoner.kvk.middleware.KvKLoginMiddleware",
"open_inwoner.accounts.middleware.NecessaryFieldsMiddleware",
"open_inwoner.accounts.middleware.EmailVerificationMiddleware",
"open_inwoner.cms.utils.middleware.AnonymousHomePageRedirectMiddleware",
"mozilla_django_oidc_db.middleware.SessionRefresh",
]
Expand Down
5 changes: 5 additions & 0 deletions src/open_inwoner/conf/parts/email_verification.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<p>Beste</p>
<p>Om door te gaan moet je je email-adres <a href="{{ verification_link }}">bevestigen</a><span class="color--secondary"> &rarr;</span></p>
<p>Mocht je geen behoefte hieraan hebben dan staat het je vrij om dit bericht te negeren </p>
<p>Met vriendelijke groet,
{{ site_name }} </p>
22 changes: 22 additions & 0 deletions src/open_inwoner/conf/parts/maileditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,28 @@ def _readfile(name):
},
],
},
"email_verification": {
"name": _("Email adress verification"),
"description": _("This email is used by users to verify their email address"),
"subject_default": _("Bevestig je email address voor {{ site_name }}"),
"body_default": _readfile("email_verification.html"),
"subject": [
{
"name": "site_name",
"description": _("Name of the site."),
},
],
"body": [
{
"name": "verification_link",
"description": _("Link to confirm email address"),
},
{
"name": "site_name",
"description": _("Name of the site"),
},
],
},
}
MAIL_EDITOR_BASE_CONTEXT = {"site_name": "Open Inwoner Platform"}
MAIL_EDITOR_DYNAMIC_CONTEXT = "open_inwoner.mail.context.mail_context"
1 change: 1 addition & 0 deletions src/open_inwoner/configurations/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ class SiteConfigurationAdmin(OrderedInlineModelAdminMixin, SingletonModelAdmin):
"email_new_message",
"recipients_email_digest",
"contact_phonenumber",
"email_verification_required",
)
},
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 4.2.11 on 2024-03-28 15:25

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("configurations", "0064_auto_20240304_1200"),
]

operations = [
migrations.AddField(
model_name="siteconfiguration",
name="email_verification_required",
field=models.BooleanField(
default=False,
help_text="Whether to require users to verify their email address",
verbose_name="Email verification required",
),
),
]
5 changes: 5 additions & 0 deletions src/open_inwoner/configurations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,11 @@ class SiteConfiguration(SingletonModel):
blank=True,
default=list,
)
email_verification_required = models.BooleanField(
verbose_name=_("Email verification required"),
default=False,
help_text=_("Whether to require users to verify their email address"),
)

# contact info
contact_phonenumber = models.CharField(
Expand Down
Loading
Loading