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'))  | 
