diff options
-rw-r--r-- | fietsboek/actions.py | 43 | ||||
-rw-r--r-- | fietsboek/locale/de/LC_MESSAGES/messages.mo | bin | 13807 -> 14476 bytes | |||
-rw-r--r-- | fietsboek/locale/de/LC_MESSAGES/messages.po | 103 | ||||
-rw-r--r-- | fietsboek/locale/en/LC_MESSAGES/messages.mo | bin | 12916 -> 13505 bytes | |||
-rw-r--r-- | fietsboek/locale/en/LC_MESSAGES/messages.po | 102 | ||||
-rw-r--r-- | fietsboek/locale/fietslog.pot | 96 | ||||
-rw-r--r-- | fietsboek/routes.py | 1 | ||||
-rw-r--r-- | fietsboek/templates/login.jinja2 | 2 | ||||
-rw-r--r-- | fietsboek/templates/resend_verification.jinja2 | 22 | ||||
-rw-r--r-- | fietsboek/views/account.py | 23 | ||||
-rw-r--r-- | fietsboek/views/default.py | 37 | ||||
-rw-r--r-- | tests/integration/test_register.py | 40 |
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 Binary files differindex 887f9bc..10f468c 100644 --- a/fietsboek/locale/de/LC_MESSAGES/messages.mo +++ b/fietsboek/locale/de/LC_MESSAGES/messages.mo 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 Binary files differindex bfd051f..df35686 100644 --- a/fietsboek/locale/en/LC_MESSAGES/messages.mo +++ b/fietsboek/locale/en/LC_MESSAGES/messages.mo 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> + • + <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')) |