aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fietsboek/actions.py43
-rw-r--r--fietsboek/locale/de/LC_MESSAGES/messages.mobin13807 -> 14476 bytes
-rw-r--r--fietsboek/locale/de/LC_MESSAGES/messages.po103
-rw-r--r--fietsboek/locale/en/LC_MESSAGES/messages.mobin12916 -> 13505 bytes
-rw-r--r--fietsboek/locale/en/LC_MESSAGES/messages.po102
-rw-r--r--fietsboek/locale/fietslog.pot96
-rw-r--r--fietsboek/routes.py1
-rw-r--r--fietsboek/templates/login.jinja22
-rw-r--r--fietsboek/templates/resend_verification.jinja222
-rw-r--r--fietsboek/views/account.py23
-rw-r--r--fietsboek/views/default.py37
-rw-r--r--tests/integration/test_register.py40
12 files changed, 332 insertions, 137 deletions
diff --git a/fietsboek/actions.py b/fietsboek/actions.py
index b9221c3..395843f 100644
--- a/fietsboek/actions.py
+++ b/fietsboek/actions.py
@@ -12,15 +12,17 @@ from typing import List, Optional
import brotli
import gpxpy
+from pyramid.i18n import TranslationString as _
from pyramid.request import Request
from sqlalchemy import select
from sqlalchemy.orm.session import Session
-from . import models
+from . import email, models
from . import transformers as mod_transformers
from . import util
from .data import DataManager, TrackDataDir
from .models.track import TrackType, Visibility
+from .models.user import TokenType
LOGGER = logging.getLogger(__name__)
@@ -219,3 +221,42 @@ def execute_transformers(request: Request, track: models.Track) -> Optional[gpxp
track.ensure_cache(gpx)
request.dbsession.add(track.cache)
return gpx
+
+
+def send_verification_token(request: Request, user: models.User):
+ """Creates a verification token and sends it to the user.
+
+ If no verification token exists yet, a fresh one is created.
+
+ If a token already exists, a fresh one is still created. The old token
+ stays valid until its expiry date.
+
+ Note that this function does not provide the user with feedback other than
+ the email. You may want to show a flash message or similar to show that
+ something has happened.
+
+ :param request: The request.
+ :param user: The user who to generate a verification token for.
+ """
+ # Some of this code appears in the password reset form as well, but that's
+ # fine for now.
+ # pylint: disable=duplicate-code
+ token = models.Token.generate(user, TokenType.VERIFY_EMAIL)
+ request.dbsession.add(token)
+
+ message = email.prepare_message(
+ request.config.email_from,
+ user.email,
+ request.localizer.translate(_("email.verify_mail.subject")),
+ )
+ message.set_content(
+ request.localizer.translate(_("email.verify.text")).format(
+ request.route_url("use-token", uuid=token.uuid)
+ )
+ )
+ email.send_message(
+ request.config.email_smtp_url,
+ request.config.email_username,
+ request.config.email_password.get_secret_value(),
+ message,
+ )
diff --git a/fietsboek/locale/de/LC_MESSAGES/messages.mo b/fietsboek/locale/de/LC_MESSAGES/messages.mo
index 887f9bc..10f468c 100644
--- a/fietsboek/locale/de/LC_MESSAGES/messages.mo
+++ b/fietsboek/locale/de/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/fietsboek/locale/de/LC_MESSAGES/messages.po b/fietsboek/locale/de/LC_MESSAGES/messages.po
index f5528c9..cb62dbb 100644
--- a/fietsboek/locale/de/LC_MESSAGES/messages.po
+++ b/fietsboek/locale/de/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2023-05-22 22:40+0200\n"
+"POT-Creation-Date: 2023-05-31 20:46+0200\n"
"PO-Revision-Date: 2022-07-02 17:35+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
@@ -18,11 +18,22 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.12.1\n"
-#: fietsboek/util.py:280
+#: fietsboek/actions.py:250
+msgid "email.verify_mail.subject"
+msgstr "Fietsboek Konto Bestätigung"
+
+#: fietsboek/actions.py:253
+msgid "email.verify.text"
+msgstr ""
+"Um Dein Fietsboek-Konto zu bestätigen, nutze diesen Link: {}\n"
+"\n"
+"Falls Du kein Konto angelegt hast, ignoriere diese E-Mail."
+
+#: fietsboek/util.py:304
msgid "password_constraint.mismatch"
msgstr "Passwörter stimmen nicht überein"
-#: fietsboek/util.py:282
+#: fietsboek/util.py:306
msgid "password_constraint.length"
msgstr "Passwort zu kurz"
@@ -478,72 +489,72 @@ msgstr "Absenden"
msgid "page.upload.form.cancel"
msgstr "Abbrechen"
-#: fietsboek/templates/home.jinja2:5
+#: fietsboek/templates/home.jinja2:6
msgid "page.home.title"
msgstr "Startseite"
-#: fietsboek/templates/home.jinja2:8
+#: fietsboek/templates/home.jinja2:17
msgid "page.home.unfinished_uploads"
msgstr ""
"Es sind noch nicht abgeschlossene Uploads vorhanden. Klicke auf die "
"Links, um sie fortzusetzen:"
-#: fietsboek/templates/home.jinja2:27 fietsboek/templates/home.jinja2:34
-#: fietsboek/templates/home.jinja2:52
+#: fietsboek/templates/home.jinja2:31 fietsboek/templates/home.jinja2:38
+#: fietsboek/templates/home.jinja2:56
msgid "page.home.summary.track"
msgid_plural "page.home.summary.tracks"
msgstr[0] "%(num)d Strecke"
msgstr[1] "%(num)d Strecken"
-#: fietsboek/templates/home.jinja2:52
+#: fietsboek/templates/home.jinja2:56
msgid "page.home.total"
msgstr "Gesamt"
-#: fietsboek/templates/layout.jinja2:41
+#: fietsboek/templates/layout.jinja2:43
msgid "page.navbar.toggle"
msgstr "Navigation umschalten"
-#: fietsboek/templates/layout.jinja2:52
+#: fietsboek/templates/layout.jinja2:54
msgid "page.navbar.home"
msgstr "Startseite"
-#: fietsboek/templates/layout.jinja2:55
+#: fietsboek/templates/layout.jinja2:57
msgid "page.navbar.browse"
msgstr "Stöbern"
-#: fietsboek/templates/layout.jinja2:59
+#: fietsboek/templates/layout.jinja2:61
msgid "page.navbar.upload"
msgstr "Hochladen"
-#: fietsboek/templates/layout.jinja2:68
+#: fietsboek/templates/layout.jinja2:70
msgid "page.navbar.user"
msgstr "Nutzer"
-#: fietsboek/templates/layout.jinja2:72
+#: fietsboek/templates/layout.jinja2:74
msgid "page.navbar.welcome_user"
msgstr "Willkommen, {}!"
-#: fietsboek/templates/layout.jinja2:75
+#: fietsboek/templates/layout.jinja2:77
msgid "page.navbar.logout"
msgstr "Abmelden"
-#: fietsboek/templates/layout.jinja2:78
+#: fietsboek/templates/layout.jinja2:80
msgid "page.navbar.profile"
msgstr "Profil"
-#: fietsboek/templates/layout.jinja2:81
+#: fietsboek/templates/layout.jinja2:83
msgid "page.navbar.user_data"
msgstr "Persönliche Daten"
-#: fietsboek/templates/layout.jinja2:85
+#: fietsboek/templates/layout.jinja2:87
msgid "page.navbar.admin"
msgstr "Admin"
-#: fietsboek/templates/layout.jinja2:91
+#: fietsboek/templates/layout.jinja2:93
msgid "page.navbar.login"
msgstr "Anmelden"
-#: fietsboek/templates/layout.jinja2:95
+#: fietsboek/templates/layout.jinja2:97
msgid "page.navbar.create_account"
msgstr "Konto Erstellen"
@@ -571,6 +582,10 @@ msgstr "Anmelden"
msgid "page.login.forgot_password"
msgstr "Passwort vergessen"
+#: fietsboek/templates/login.jinja2:45
+msgid "page.login.resend_verification"
+msgstr "Bestätigungsmail erneut senden"
+
#: fietsboek/templates/password_reset.jinja2:5
msgid "page.password_reset.title"
msgstr "Passwort Zurücksetzen"
@@ -669,6 +684,23 @@ msgstr "E-Mail-Adresse"
msgid "page.request_password.request"
msgstr "Anfrage senden"
+#: fietsboek/templates/resend_verification.jinja2:5
+msgid "page.resend_verification.title"
+msgstr "Bestätigungsmail Erneut Senden"
+
+#: fietsboek/templates/resend_verification.jinja2:6
+msgid "page.resend_verification.info"
+msgstr ""
+"Hier kannst Du eine neue E-Mail zur Bestätigung Deines Kontos anfordern."
+
+#: fietsboek/templates/resend_verification.jinja2:12
+msgid "page.resend_verification.email"
+msgstr "E-Mail-Adresse"
+
+#: fietsboek/templates/resend_verification.jinja2:17
+msgid "page.resend_verification.request"
+msgstr "E-Mail anfordern"
+
#: fietsboek/templates/upload.jinja2:9
msgid "page.upload.form.gpx"
msgstr "GPX Datei"
@@ -753,26 +785,15 @@ msgstr ""
"Diese Transformation passt die Höhenangabe für Punkte an, bei denen die "
"Höhe sprunghaft steigt oder fällt."
-#: fietsboek/views/account.py:54
+#: fietsboek/views/account.py:53
msgid "flash.invalid_name"
msgstr "Ungültiger Name"
-#: fietsboek/views/account.py:59
+#: fietsboek/views/account.py:58
msgid "flash.invalid_email"
msgstr "Ungültige E-Mail-Adresse"
-#: fietsboek/views/account.py:72
-msgid "email.verify_mail.subject"
-msgstr "Fietsboek Konto Bestätigung"
-
-#: fietsboek/views/account.py:75
-msgid "email.verify.text"
-msgstr ""
-"Um Dein Fietsboek-Konto zu bestätigen, nutze diesen Link: {}\n"
-"\n"
-"Falls Du kein Konto angelegt hast, ignoriere diese E-Mail."
-
-#: fietsboek/views/account.py:86
+#: fietsboek/views/account.py:67
msgid "flash.a_confirmation_link_has_been_sent"
msgstr "Ein Bestätigungslink wurde versandt"
@@ -824,15 +845,23 @@ msgstr ""
"Falls Du keine Passwortzurücksetzung beantragt hast, dann ignoriere diese"
" E-Mail."
-#: fietsboek/views/default.py:214
+#: fietsboek/views/default.py:224
+msgid "flash.resend_verification_email_fail"
+msgstr "Ungültige E-Mail-Adresse angegeben"
+
+#: fietsboek/views/default.py:229
+msgid "flash.verification_token_generated"
+msgstr "Ein Link zur Bestätigung Deines Kontos wurde versandt"
+
+#: fietsboek/views/default.py:249
msgid "flash.token_expired"
msgstr "Der Link ist nicht mehr gültig"
-#: fietsboek/views/default.py:220
+#: fietsboek/views/default.py:255
msgid "flash.email_verified"
msgstr "E-Mail-Adresse bestätigt"
-#: fietsboek/views/default.py:234
+#: fietsboek/views/default.py:269
msgid "flash.password_updated"
msgstr "Passwort aktualisiert"
diff --git a/fietsboek/locale/en/LC_MESSAGES/messages.mo b/fietsboek/locale/en/LC_MESSAGES/messages.mo
index bfd051f..df35686 100644
--- a/fietsboek/locale/en/LC_MESSAGES/messages.mo
+++ b/fietsboek/locale/en/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/fietsboek/locale/en/LC_MESSAGES/messages.po b/fietsboek/locale/en/LC_MESSAGES/messages.po
index 2ec7b76..1cac820 100644
--- a/fietsboek/locale/en/LC_MESSAGES/messages.po
+++ b/fietsboek/locale/en/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2023-05-22 22:40+0200\n"
+"POT-Creation-Date: 2023-05-31 20:46+0200\n"
"PO-Revision-Date: 2023-04-03 20:42+0200\n"
"Last-Translator: \n"
"Language: en\n"
@@ -18,11 +18,22 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.12.1\n"
-#: fietsboek/util.py:280
+#: fietsboek/actions.py:250
+msgid "email.verify_mail.subject"
+msgstr "Fietsboek Account Verification"
+
+#: fietsboek/actions.py:253
+msgid "email.verify.text"
+msgstr ""
+"To verify your Fietsboek account, please use this link: {}\n"
+"\n"
+"If you did not create an account, ignore this email."
+
+#: fietsboek/util.py:304
msgid "password_constraint.mismatch"
msgstr "Passwords don't match"
-#: fietsboek/util.py:282
+#: fietsboek/util.py:306
msgid "password_constraint.length"
msgstr "Password not long enough"
@@ -474,70 +485,70 @@ msgstr "Upload"
msgid "page.upload.form.cancel"
msgstr "Cancel"
-#: fietsboek/templates/home.jinja2:5
+#: fietsboek/templates/home.jinja2:6
msgid "page.home.title"
msgstr "Home"
-#: fietsboek/templates/home.jinja2:8
+#: fietsboek/templates/home.jinja2:17
msgid "page.home.unfinished_uploads"
msgstr "You have unfinished uploads. Click on the links below to resume them:"
-#: fietsboek/templates/home.jinja2:27 fietsboek/templates/home.jinja2:34
-#: fietsboek/templates/home.jinja2:52
+#: fietsboek/templates/home.jinja2:31 fietsboek/templates/home.jinja2:38
+#: fietsboek/templates/home.jinja2:56
msgid "page.home.summary.track"
msgid_plural "page.home.summary.tracks"
msgstr[0] "%(num)d track"
msgstr[1] "%(num)d tracks"
-#: fietsboek/templates/home.jinja2:52
+#: fietsboek/templates/home.jinja2:56
msgid "page.home.total"
msgstr "Total"
-#: fietsboek/templates/layout.jinja2:41
+#: fietsboek/templates/layout.jinja2:43
msgid "page.navbar.toggle"
msgstr "Toggle navigation"
-#: fietsboek/templates/layout.jinja2:52
+#: fietsboek/templates/layout.jinja2:54
msgid "page.navbar.home"
msgstr "Home"
-#: fietsboek/templates/layout.jinja2:55
+#: fietsboek/templates/layout.jinja2:57
msgid "page.navbar.browse"
msgstr "Browse"
-#: fietsboek/templates/layout.jinja2:59
+#: fietsboek/templates/layout.jinja2:61
msgid "page.navbar.upload"
msgstr "Upload"
-#: fietsboek/templates/layout.jinja2:68
+#: fietsboek/templates/layout.jinja2:70
msgid "page.navbar.user"
msgstr "User"
-#: fietsboek/templates/layout.jinja2:72
+#: fietsboek/templates/layout.jinja2:74
msgid "page.navbar.welcome_user"
msgstr "Welcome, {}!"
-#: fietsboek/templates/layout.jinja2:75
+#: fietsboek/templates/layout.jinja2:77
msgid "page.navbar.logout"
msgstr "Logout"
-#: fietsboek/templates/layout.jinja2:78
+#: fietsboek/templates/layout.jinja2:80
msgid "page.navbar.profile"
msgstr "Profile"
-#: fietsboek/templates/layout.jinja2:81
+#: fietsboek/templates/layout.jinja2:83
msgid "page.navbar.user_data"
msgstr "Personal Data"
-#: fietsboek/templates/layout.jinja2:85
+#: fietsboek/templates/layout.jinja2:87
msgid "page.navbar.admin"
msgstr "Admin"
-#: fietsboek/templates/layout.jinja2:91
+#: fietsboek/templates/layout.jinja2:93
msgid "page.navbar.login"
msgstr "Login"
-#: fietsboek/templates/layout.jinja2:95
+#: fietsboek/templates/layout.jinja2:97
msgid "page.navbar.create_account"
msgstr "Create Account"
@@ -565,6 +576,10 @@ msgstr "Login"
msgid "page.login.forgot_password"
msgstr "Forgot password"
+#: fietsboek/templates/login.jinja2:45
+msgid "page.login.resend_verification"
+msgstr "Re-send verification mail"
+
#: fietsboek/templates/password_reset.jinja2:5
msgid "page.password_reset.title"
msgstr "Reset Your Password"
@@ -663,6 +678,22 @@ msgstr "Email"
msgid "page.request_password.request"
msgstr "Send request"
+#: fietsboek/templates/resend_verification.jinja2:5
+msgid "page.resend_verification.title"
+msgstr "Re-send Verification Mail"
+
+#: fietsboek/templates/resend_verification.jinja2:6
+msgid "page.resend_verification.info"
+msgstr "Here you can request a new mail to verify your account"
+
+#: fietsboek/templates/resend_verification.jinja2:12
+msgid "page.resend_verification.email"
+msgstr "Email"
+
+#: fietsboek/templates/resend_verification.jinja2:17
+msgid "page.resend_verification.request"
+msgstr "Send request"
+
#: fietsboek/templates/upload.jinja2:9
msgid "page.upload.form.gpx"
msgstr "GPX file"
@@ -743,26 +774,15 @@ msgstr "Fix elevation jumps"
msgid "transformers.fix-elevation-jumps.description"
msgstr "This transformer fixes abrupt jumps in the elevation value."
-#: fietsboek/views/account.py:54
+#: fietsboek/views/account.py:53
msgid "flash.invalid_name"
msgstr "Invalid name"
-#: fietsboek/views/account.py:59
+#: fietsboek/views/account.py:58
msgid "flash.invalid_email"
msgstr "Invalid email"
-#: fietsboek/views/account.py:72
-msgid "email.verify_mail.subject"
-msgstr "Fietsboek Account Verification"
-
-#: fietsboek/views/account.py:75
-msgid "email.verify.text"
-msgstr ""
-"To verify your Fietsboek account, please use this link: {}\n"
-"\n"
-"If you did not create an account, ignore this email."
-
-#: fietsboek/views/account.py:86
+#: fietsboek/views/account.py:67
msgid "flash.a_confirmation_link_has_been_sent"
msgstr "A confirmation link has been sent"
@@ -813,15 +833,23 @@ msgstr ""
"\n"
"If you did not request a password reset, ignore this email."
-#: fietsboek/views/default.py:214
+#: fietsboek/views/default.py:224
+msgid "flash.resend_verification_email_fail"
+msgstr "Invalid email address provided"
+
+#: fietsboek/views/default.py:229
+msgid "flash.verification_token_generated"
+msgstr "A verification email has been sent"
+
+#: fietsboek/views/default.py:249
msgid "flash.token_expired"
msgstr "The link has expired"
-#: fietsboek/views/default.py:220
+#: fietsboek/views/default.py:255
msgid "flash.email_verified"
msgstr "Your email address has been verified"
-#: fietsboek/views/default.py:234
+#: fietsboek/views/default.py:269
msgid "flash.password_updated"
msgstr "Password has been updated"
diff --git a/fietsboek/locale/fietslog.pot b/fietsboek/locale/fietslog.pot
index 113d2d1..8d43284 100644
--- a/fietsboek/locale/fietslog.pot
+++ b/fietsboek/locale/fietslog.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2023-05-22 22:40+0200\n"
+"POT-Creation-Date: 2023-05-31 20:46+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,11 +17,19 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.12.1\n"
-#: fietsboek/util.py:280
+#: fietsboek/actions.py:250
+msgid "email.verify_mail.subject"
+msgstr ""
+
+#: fietsboek/actions.py:253
+msgid "email.verify.text"
+msgstr ""
+
+#: fietsboek/util.py:304
msgid "password_constraint.mismatch"
msgstr ""
-#: fietsboek/util.py:282
+#: fietsboek/util.py:306
msgid "password_constraint.length"
msgstr ""
@@ -471,70 +479,70 @@ msgstr ""
msgid "page.upload.form.cancel"
msgstr ""
-#: fietsboek/templates/home.jinja2:5
+#: fietsboek/templates/home.jinja2:6
msgid "page.home.title"
msgstr ""
-#: fietsboek/templates/home.jinja2:8
+#: fietsboek/templates/home.jinja2:17
msgid "page.home.unfinished_uploads"
msgstr ""
-#: fietsboek/templates/home.jinja2:27 fietsboek/templates/home.jinja2:34
-#: fietsboek/templates/home.jinja2:52
+#: fietsboek/templates/home.jinja2:31 fietsboek/templates/home.jinja2:38
+#: fietsboek/templates/home.jinja2:56
msgid "page.home.summary.track"
msgid_plural "page.home.summary.tracks"
msgstr[0] ""
msgstr[1] ""
-#: fietsboek/templates/home.jinja2:52
+#: fietsboek/templates/home.jinja2:56
msgid "page.home.total"
msgstr ""
-#: fietsboek/templates/layout.jinja2:41
+#: fietsboek/templates/layout.jinja2:43
msgid "page.navbar.toggle"
msgstr ""
-#: fietsboek/templates/layout.jinja2:52
+#: fietsboek/templates/layout.jinja2:54
msgid "page.navbar.home"
msgstr ""
-#: fietsboek/templates/layout.jinja2:55
+#: fietsboek/templates/layout.jinja2:57
msgid "page.navbar.browse"
msgstr ""
-#: fietsboek/templates/layout.jinja2:59
+#: fietsboek/templates/layout.jinja2:61
msgid "page.navbar.upload"
msgstr ""
-#: fietsboek/templates/layout.jinja2:68
+#: fietsboek/templates/layout.jinja2:70
msgid "page.navbar.user"
msgstr ""
-#: fietsboek/templates/layout.jinja2:72
+#: fietsboek/templates/layout.jinja2:74
msgid "page.navbar.welcome_user"
msgstr ""
-#: fietsboek/templates/layout.jinja2:75
+#: fietsboek/templates/layout.jinja2:77
msgid "page.navbar.logout"
msgstr ""
-#: fietsboek/templates/layout.jinja2:78
+#: fietsboek/templates/layout.jinja2:80
msgid "page.navbar.profile"
msgstr ""
-#: fietsboek/templates/layout.jinja2:81
+#: fietsboek/templates/layout.jinja2:83
msgid "page.navbar.user_data"
msgstr ""
-#: fietsboek/templates/layout.jinja2:85
+#: fietsboek/templates/layout.jinja2:87
msgid "page.navbar.admin"
msgstr ""
-#: fietsboek/templates/layout.jinja2:91
+#: fietsboek/templates/layout.jinja2:93
msgid "page.navbar.login"
msgstr ""
-#: fietsboek/templates/layout.jinja2:95
+#: fietsboek/templates/layout.jinja2:97
msgid "page.navbar.create_account"
msgstr ""
@@ -562,6 +570,10 @@ msgstr ""
msgid "page.login.forgot_password"
msgstr ""
+#: fietsboek/templates/login.jinja2:45
+msgid "page.login.resend_verification"
+msgstr ""
+
#: fietsboek/templates/password_reset.jinja2:5
msgid "page.password_reset.title"
msgstr ""
@@ -658,6 +670,22 @@ msgstr ""
msgid "page.request_password.request"
msgstr ""
+#: fietsboek/templates/resend_verification.jinja2:5
+msgid "page.resend_verification.title"
+msgstr ""
+
+#: fietsboek/templates/resend_verification.jinja2:6
+msgid "page.resend_verification.info"
+msgstr ""
+
+#: fietsboek/templates/resend_verification.jinja2:12
+msgid "page.resend_verification.email"
+msgstr ""
+
+#: fietsboek/templates/resend_verification.jinja2:17
+msgid "page.resend_verification.request"
+msgstr ""
+
#: fietsboek/templates/upload.jinja2:9
msgid "page.upload.form.gpx"
msgstr ""
@@ -738,23 +766,15 @@ msgstr ""
msgid "transformers.fix-elevation-jumps.description"
msgstr ""
-#: fietsboek/views/account.py:54
+#: fietsboek/views/account.py:53
msgid "flash.invalid_name"
msgstr ""
-#: fietsboek/views/account.py:59
+#: fietsboek/views/account.py:58
msgid "flash.invalid_email"
msgstr ""
-#: fietsboek/views/account.py:72
-msgid "email.verify_mail.subject"
-msgstr ""
-
-#: fietsboek/views/account.py:75
-msgid "email.verify.text"
-msgstr ""
-
-#: fietsboek/views/account.py:86
+#: fietsboek/views/account.py:67
msgid "flash.a_confirmation_link_has_been_sent"
msgstr ""
@@ -802,15 +822,23 @@ msgstr ""
msgid "page.password_reset.email.body"
msgstr ""
-#: fietsboek/views/default.py:214
+#: fietsboek/views/default.py:224
+msgid "flash.resend_verification_email_fail"
+msgstr ""
+
+#: fietsboek/views/default.py:229
+msgid "flash.verification_token_generated"
+msgstr ""
+
+#: fietsboek/views/default.py:249
msgid "flash.token_expired"
msgstr ""
-#: fietsboek/views/default.py:220
+#: fietsboek/views/default.py:255
msgid "flash.email_verified"
msgstr ""
-#: fietsboek/views/default.py:234
+#: fietsboek/views/default.py:269
msgid "flash.password_updated"
msgstr ""
diff --git a/fietsboek/routes.py b/fietsboek/routes.py
index 8f109d9..60a566d 100644
--- a/fietsboek/routes.py
+++ b/fietsboek/routes.py
@@ -14,6 +14,7 @@ def includeme(config):
config.add_route("track-archive", "/track/archive")
config.add_route("password-reset", "/password-reset")
+ config.add_route("resend-verification", "/resend-verification")
config.add_route("use-token", "/token/{uuid}")
config.add_route("create-account", "/create-account")
diff --git a/fietsboek/templates/login.jinja2 b/fietsboek/templates/login.jinja2
index 7a9bf07..bdf2c99 100644
--- a/fietsboek/templates/login.jinja2
+++ b/fietsboek/templates/login.jinja2
@@ -41,6 +41,8 @@
<div class="row justify-content-center">
<div class="col-auto mb-3">
<a href="{{ request.route_url("password-reset") }}">{{ _("page.login.forgot_password") }}</a>
+ &bull;
+ <a href="{{ request.route_url("resend-verification") }}">{{ _("page.login.resend_verification") }}</a>
</div>
</div>
</form>
diff --git a/fietsboek/templates/resend_verification.jinja2 b/fietsboek/templates/resend_verification.jinja2
new file mode 100644
index 0000000..cc56854
--- /dev/null
+++ b/fietsboek/templates/resend_verification.jinja2
@@ -0,0 +1,22 @@
+{% extends "layout.jinja2" %}
+{% import "util.jinja2" as util with context %}
+{% block content %}
+<div class="container">
+ <h1>{{ _("page.resend_verification.title") }}</h1>
+ <p>{{ _("page.resend_verification.info") }}</p>
+ <form method="POST">
+ <div class="row align-items-center">
+ <div class="col-lg-5">
+ <div class="form-floating">
+ <input type="email" id="resendEmail" name="email" class="form-control" placeholder="x">
+ <label for="resendEmail">{{ _("page.resend_verification.email") }}</label>
+ </div>
+ </div>
+ {{ util.hidden_csrf_input() }}
+ <div class="col-lg-4">
+ <button class="btn btn-primary">{{ _("page.resend_verification.request") }}</button>
+ </div>
+ </div>
+ </form>
+</div>
+{% endblock %}
diff --git a/fietsboek/views/account.py b/fietsboek/views/account.py
index 39a62e5..5400f0a 100644
--- a/fietsboek/views/account.py
+++ b/fietsboek/views/account.py
@@ -3,8 +3,7 @@ from pyramid.httpexceptions import HTTPForbidden, HTTPFound
from pyramid.i18n import TranslationString as _
from pyramid.view import view_config
-from .. import email, models, util
-from ..models.user import TokenType
+from .. import actions, models, util
@view_config(
@@ -63,25 +62,7 @@ def do_create_account(request):
user.set_password(password)
request.dbsession.add(user)
- token = models.Token.generate(user, TokenType.VERIFY_EMAIL)
- request.dbsession.add(token)
-
- message = email.prepare_message(
- request.config.email_from,
- user.email,
- request.localizer.translate(_("email.verify_mail.subject")),
- )
- message.set_content(
- request.localizer.translate(_("email.verify.text")).format(
- request.route_url("use-token", uuid=token.uuid)
- )
- )
- email.send_message(
- request.config.email_smtp_url,
- request.config.email_username,
- request.config.email_password.get_secret_value(),
- message,
- )
+ actions.send_verification_token(request, user)
request.session.flash(request.localizer.translate(_("flash.a_confirmation_link_has_been_sent")))
return HTTPFound(request.route_url("login"))
diff --git a/fietsboek/views/default.py b/fietsboek/views/default.py
index b8b1835..f9942c3 100644
--- a/fietsboek/views/default.py
+++ b/fietsboek/views/default.py
@@ -12,7 +12,7 @@ from sqlalchemy import select
from sqlalchemy.exc import NoResultFound
from sqlalchemy.orm import aliased
-from .. import email, models, summaries, util
+from .. import actions, email, models, summaries, util
from ..models.track import TrackType, TrackWithMetadata
from ..models.user import TOKEN_LIFETIME, PasswordMismatch, TokenType
@@ -196,6 +196,41 @@ def do_password_reset(request: Request) -> Response:
return HTTPFound(request.route_url("password-reset"))
+@view_config(
+ route_name="resend-verification",
+ request_method="GET",
+ renderer="fietsboek:templates/resend_verification.jinja2",
+)
+def resend_verification(_request: Request) -> Response:
+ """Form to request a new verification mail.
+
+ :param request: The Pyramid request.
+ :return: The HTTP response.
+ """
+ return {}
+
+
+@view_config(route_name="resend-verification", request_method="POST")
+def do_resend_verification(request: Request) -> Response:
+ """Endpoint for the verification resend form.
+
+ :param request: The Pyramid request.
+ :return: The HTTP response.
+ """
+ query = models.User.query_by_email(request.params["email"])
+ user = request.dbsession.execute(query).scalar_one_or_none()
+ if user is None or user.is_verified:
+ request.session.flash(
+ request.localizer.translate(_("flash.resend_verification_email_fail"))
+ )
+ return HTTPFound(request.route_url("resend-verification"))
+
+ actions.send_verification_token(request, user)
+ request.session.flash(request.localizer.translate(_("flash.verification_token_generated")))
+
+ return HTTPFound(request.route_url("login"))
+
+
@view_config(route_name="use-token")
def use_token(request: Request) -> Response:
"""Endpoint with which a user can use a token for a password reset or email
diff --git a/tests/integration/test_register.py b/tests/integration/test_register.py
index af1e313..dc7b00a 100644
--- a/tests/integration/test_register.py
+++ b/tests/integration/test_register.py
@@ -1,19 +1,28 @@
import re
+import pytest
+
import fietsboek.email
from fietsboek import models
VERIFICATION_LINK_PATTERN = re.compile("http://example.com(/token/[A-Za-z0-9-]+)")
-def test_registration_working(testapp, dbsession, route_path, monkeypatch):
- """Ensures that a user can register, including using the verification link."""
+@pytest.fixture
+def mailcatcher(monkeypatch):
+ """Monkeypatches the send mail functionality.
+
+ Returns the list of mails sent.
+ """
mails = []
- def send_message(server_url, username, password, message):
+ def send_message(_server_url, _username, _passwords, message):
mails.append(message)
-
monkeypatch.setattr(fietsboek.email, "send_message", send_message)
+ yield mails
+
+def test_registration_working(testapp, dbsession, route_path, mailcatcher):
+ """Ensures that a user can register, including using the verification link."""
registration = testapp.get(route_path('create-account'))
form = registration.form
form['email'] = 'foo-new@bar.com'
@@ -23,18 +32,37 @@ def test_registration_working(testapp, dbsession, route_path, monkeypatch):
response = form.submit().maybe_follow()
assert b'A confirmation link has been sent' in response.body
- assert len(mails) == 1
+ assert len(mailcatcher) == 1
user = dbsession.execute(models.User.query_by_email('foo-new@bar.com')).scalar_one()
assert not user.is_verified
- body = mails[0].get_body().get_content()
+ body = mailcatcher[0].get_body().get_content()
token_path = VERIFICATION_LINK_PATTERN.search(body).group(1)
testapp.get(token_path)
assert user.is_verified
+def test_resend_verification_mail(testapp, dbsession, route_path, mailcatcher):
+ """Ensures that the verification link re-sending works."""
+ registration = testapp.get(route_path('create-account'))
+ form = registration.form
+ form['email'] = 'foo-new@bar.com'
+ form['name'] = 'The new Foo'
+ form['password'] = 'foobarpassword'
+ form['repeat-password'] = 'foobarpassword'
+ form.submit().maybe_follow()
+
+ req = testapp.get(route_path('resend-verification'))
+ req.form['email'] = 'foo-new@bar.com'
+ req.form.submit().maybe_follow()
+
+ assert len(mailcatcher) == 2
+ assert VERIFICATION_LINK_PATTERN.search(mailcatcher[0].get_body().get_content())
+ assert VERIFICATION_LINK_PATTERN.search(mailcatcher[1].get_body().get_content())
+
+
def test_registration_short_password(testapp, route_path):
"""Ensures that passwords that are too short are rejected."""
registration = testapp.get(route_path('create-account'))