aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fietsboek/locale/de/LC_MESSAGES/messages.po28
-rw-r--r--fietsboek/locale/en/LC_MESSAGES/messages.po28
-rw-r--r--fietsboek/locale/fietslog.pot28
-rw-r--r--fietsboek/models/user.py7
-rw-r--r--fietsboek/scripts/fietscron.py12
-rw-r--r--fietsboek/views/default.py42
6 files changed, 83 insertions, 62 deletions
diff --git a/fietsboek/locale/de/LC_MESSAGES/messages.po b/fietsboek/locale/de/LC_MESSAGES/messages.po
index 5b6c654..f5528c9 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-15 20:20+0200\n"
+"POT-Creation-Date: 2023-05-22 22:40+0200\n"
"PO-Revision-Date: 2022-07-02 17:35+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
@@ -788,35 +788,35 @@ msgstr "Wappen bearbeitet"
msgid "flash.badge_deleted"
msgstr "Wappen gelöscht"
-#: fietsboek/views/default.py:121
+#: fietsboek/views/default.py:115
msgid "flash.invalid_credentials"
msgstr "Ungültige Nutzerdaten"
-#: fietsboek/views/default.py:125
+#: fietsboek/views/default.py:119
msgid "flash.account_not_verified"
msgstr "Konto noch nicht bestätigt"
-#: fietsboek/views/default.py:128
+#: fietsboek/views/default.py:122
msgid "flash.logged_in"
msgstr "Du bist nun angemeldet"
-#: fietsboek/views/default.py:150
+#: fietsboek/views/default.py:142
msgid "flash.logged_out"
msgstr "Du bist nun abgemeldet"
-#: fietsboek/views/default.py:184
+#: fietsboek/views/default.py:172
msgid "flash.reset_invalid_email"
msgstr "Ungültige E-Mail-Adresse angegeben"
-#: fietsboek/views/default.py:189
+#: fietsboek/views/default.py:177
msgid "flash.password_token_generated"
msgstr "Ein Link zum Zurücksetzen des Passworts wurde versandt"
-#: fietsboek/views/default.py:194
+#: fietsboek/views/default.py:182
msgid "page.password_reset.email.subject"
msgstr "Fietsboek Passwortzurücksetzung"
-#: fietsboek/views/default.py:197
+#: fietsboek/views/default.py:185
msgid "page.password_reset.email.body"
msgstr ""
"Du kannst Dein Fietsboek-Passwort hier zurücksetzen: {}\n"
@@ -824,15 +824,19 @@ msgstr ""
"Falls Du keine Passwortzurücksetzung beantragt hast, dann ignoriere diese"
" E-Mail."
-#: fietsboek/views/default.py:230
+#: fietsboek/views/default.py:214
+msgid "flash.token_expired"
+msgstr "Der Link ist nicht mehr gültig"
+
+#: fietsboek/views/default.py:220
msgid "flash.email_verified"
msgstr "E-Mail-Adresse bestätigt"
-#: fietsboek/views/default.py:244
+#: fietsboek/views/default.py:234
msgid "flash.password_updated"
msgstr "Passwort aktualisiert"
-#: fietsboek/views/detail.py:140
+#: fietsboek/views/detail.py:155
msgid "flash.track_deleted"
msgstr "Strecke gelöscht"
diff --git a/fietsboek/locale/en/LC_MESSAGES/messages.po b/fietsboek/locale/en/LC_MESSAGES/messages.po
index d1a8e8d..2ec7b76 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-15 20:20+0200\n"
+"POT-Creation-Date: 2023-05-22 22:40+0200\n"
"PO-Revision-Date: 2023-04-03 20:42+0200\n"
"Last-Translator: \n"
"Language: en\n"
@@ -778,50 +778,54 @@ msgstr "Badge has been modified"
msgid "flash.badge_deleted"
msgstr "Badge has been deleted"
-#: fietsboek/views/default.py:121
+#: fietsboek/views/default.py:115
msgid "flash.invalid_credentials"
msgstr "Invalid login credentials"
-#: fietsboek/views/default.py:125
+#: fietsboek/views/default.py:119
msgid "flash.account_not_verified"
msgstr "Your account is not verified yet"
-#: fietsboek/views/default.py:128
+#: fietsboek/views/default.py:122
msgid "flash.logged_in"
msgstr "You are now logged in"
-#: fietsboek/views/default.py:150
+#: fietsboek/views/default.py:142
msgid "flash.logged_out"
msgstr "You have been logged out"
-#: fietsboek/views/default.py:184
+#: fietsboek/views/default.py:172
msgid "flash.reset_invalid_email"
msgstr "Invalid email address provided"
-#: fietsboek/views/default.py:189
+#: fietsboek/views/default.py:177
msgid "flash.password_token_generated"
msgstr "A password reset email has been sent"
-#: fietsboek/views/default.py:194
+#: fietsboek/views/default.py:182
msgid "page.password_reset.email.subject"
msgstr "Fietsboek Password Reset"
-#: fietsboek/views/default.py:197
+#: fietsboek/views/default.py:185
msgid "page.password_reset.email.body"
msgstr ""
"You can reset your Fietsboek password here: {}\n"
"\n"
"If you did not request a password reset, ignore this email."
-#: fietsboek/views/default.py:230
+#: fietsboek/views/default.py:214
+msgid "flash.token_expired"
+msgstr "The link has expired"
+
+#: fietsboek/views/default.py:220
msgid "flash.email_verified"
msgstr "Your email address has been verified"
-#: fietsboek/views/default.py:244
+#: fietsboek/views/default.py:234
msgid "flash.password_updated"
msgstr "Password has been updated"
-#: fietsboek/views/detail.py:140
+#: fietsboek/views/detail.py:155
msgid "flash.track_deleted"
msgstr "Track has been deleted"
diff --git a/fietsboek/locale/fietslog.pot b/fietsboek/locale/fietslog.pot
index 7e3d440..113d2d1 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-15 20:20+0200\n"
+"POT-Creation-Date: 2023-05-22 22:40+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"
@@ -770,47 +770,51 @@ msgstr ""
msgid "flash.badge_deleted"
msgstr ""
-#: fietsboek/views/default.py:121
+#: fietsboek/views/default.py:115
msgid "flash.invalid_credentials"
msgstr ""
-#: fietsboek/views/default.py:125
+#: fietsboek/views/default.py:119
msgid "flash.account_not_verified"
msgstr ""
-#: fietsboek/views/default.py:128
+#: fietsboek/views/default.py:122
msgid "flash.logged_in"
msgstr ""
-#: fietsboek/views/default.py:150
+#: fietsboek/views/default.py:142
msgid "flash.logged_out"
msgstr ""
-#: fietsboek/views/default.py:184
+#: fietsboek/views/default.py:172
msgid "flash.reset_invalid_email"
msgstr ""
-#: fietsboek/views/default.py:189
+#: fietsboek/views/default.py:177
msgid "flash.password_token_generated"
msgstr ""
-#: fietsboek/views/default.py:194
+#: fietsboek/views/default.py:182
msgid "page.password_reset.email.subject"
msgstr ""
-#: fietsboek/views/default.py:197
+#: fietsboek/views/default.py:185
msgid "page.password_reset.email.body"
msgstr ""
-#: fietsboek/views/default.py:230
+#: fietsboek/views/default.py:214
+msgid "flash.token_expired"
+msgstr ""
+
+#: fietsboek/views/default.py:220
msgid "flash.email_verified"
msgstr ""
-#: fietsboek/views/default.py:244
+#: fietsboek/views/default.py:234
msgid "flash.password_updated"
msgstr ""
-#: fietsboek/views/detail.py:140
+#: fietsboek/views/detail.py:155
msgid "flash.track_deleted"
msgstr ""
diff --git a/fietsboek/models/user.py b/fietsboek/models/user.py
index e1b4841..4201d14 100644
--- a/fietsboek/models/user.py
+++ b/fietsboek/models/user.py
@@ -53,6 +53,9 @@ SCRYPT_PARAMETERS = {
}
SALT_LENGTH = 32
+TOKEN_LIFETIME = datetime.timedelta(hours=24)
+"""Maximum validity time of a token."""
+
friends_assoc = Table(
"friends_assoc",
@@ -457,5 +460,9 @@ class Token(Base):
now = datetime.datetime.utcnow()
return cls(user=user, uuid=token_uuid, date=now, token_type=token_type)
+ def age(self) -> datetime.timedelta:
+ """Returns the age of the token."""
+ return abs(datetime.datetime.utcnow() - self.date)
+
Index("idx_token_uuid", Token.uuid, unique=True)
diff --git a/fietsboek/scripts/fietscron.py b/fietsboek/scripts/fietscron.py
index 8116204..417b188 100644
--- a/fietsboek/scripts/fietscron.py
+++ b/fietsboek/scripts/fietscron.py
@@ -16,6 +16,7 @@ from .. import config as mod_config
from .. import hittekaart, models
from ..config import Config
from ..data import DataManager
+from ..models.user import TOKEN_LIFETIME
from . import config_option
LOGGER = logging.getLogger(__name__)
@@ -48,6 +49,7 @@ def cli(config):
LOGGER.debug("Starting maintenance tasks")
remove_old_uploads(engine)
+ remove_old_tokens(engine)
rebuild_cache(engine, data_manager)
if config.hittekaart_autogenerate:
@@ -65,6 +67,16 @@ def remove_old_uploads(engine: Engine):
session.commit()
+def remove_old_tokens(engine: Engine):
+ """Removes old tokens from the database."""
+ LOGGER.info("Deleting old tokens")
+ limit = datetime.datetime.utcnow() - TOKEN_LIFETIME
+ session = Session(engine)
+ stmt = delete(models.Token).where(models.Token.date < limit)
+ session.execute(stmt)
+ session.commit()
+
+
def rebuild_cache(engine: Engine, data_manager: DataManager):
"""Rebuilds the cache entries that are currently missing."""
LOGGER.debug("Rebuilding caches")
diff --git a/fietsboek/views/default.py b/fietsboek/views/default.py
index 08f32ce..b8b1835 100644
--- a/fietsboek/views/default.py
+++ b/fietsboek/views/default.py
@@ -4,6 +4,8 @@ from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from pyramid.i18n import TranslationString as _
from pyramid.interfaces import ISecurityPolicy
from pyramid.renderers import render_to_response
+from pyramid.request import Request
+from pyramid.response import Response
from pyramid.security import forget, remember
from pyramid.view import view_config
from sqlalchemy import select
@@ -12,17 +14,15 @@ from sqlalchemy.orm import aliased
from .. import email, models, summaries, util
from ..models.track import TrackType, TrackWithMetadata
-from ..models.user import PasswordMismatch, TokenType
+from ..models.user import TOKEN_LIFETIME, PasswordMismatch, TokenType
@view_config(route_name="home", renderer="fietsboek:templates/home.jinja2")
-def home(request):
+def home(request: Request) -> Response:
"""Renders the home page.
:param request: The Pyramid request.
- :type request: pyramid.request.Request
:return: The HTTP response.
- :rtype: pyramid.response.Response
"""
if not request.identity:
# See if the admin set a custom home page
@@ -73,13 +73,11 @@ def home(request):
@view_config(route_name="static-page", renderer="fietsboek:templates/static-page.jinja2")
-def static_page(request):
+def static_page(request: Request) -> Response:
"""Renders a static page.
:param request: The Pyramid request.
- :type request: pyramid.request.Request
:return: The HTTP response.
- :rtype: pyramid.response.Response
"""
page = request.pages.find(request.matchdict["slug"], request)
if page is None:
@@ -92,26 +90,22 @@ def static_page(request):
@view_config(route_name="login", renderer="fietsboek:templates/login.jinja2", request_method="GET")
-def login(request):
+def login(request: Request) -> Response:
"""Renders the login page.
:param request: The Pyramid request.
- :type request: pyramid.request.Request
:return: The HTTP response.
- :rtype: pyramid.response.Response
"""
# pylint: disable=unused-argument
return {}
@view_config(route_name="login", request_method="POST")
-def do_login(request):
+def do_login(request: Request) -> Response:
"""Endpoint for the login form.
:param request: The Pyramid request.
- :type request: pyramid.request.Request
:return: The HTTP response.
- :rtype: pyramid.response.Response
"""
query = models.User.query_by_email(request.params["email"])
try:
@@ -139,13 +133,11 @@ def do_login(request):
@view_config(route_name="logout")
-def logout(request):
+def logout(request: Request) -> Response:
"""Logs the user out.
:param request: The Pyramid request.
- :type request: pyramid.request.Request
:return: The HTTP response.
- :rtype: pyramid.response.Response
"""
request.session.flash(request.localizer.translate(_("flash.logged_out")))
headers = forget(request)
@@ -157,26 +149,22 @@ def logout(request):
request_method="GET",
renderer="fietsboek:templates/request_password.jinja2",
)
-def password_reset(request):
+def password_reset(request: Request) -> Response:
"""Form to request a new password.
:param request: The Pyramid request.
- :type request: pyramid.request.Request
:return: The HTTP response.
- :rtype: pyramid.response.Response
"""
# pylint: disable=unused-argument
return {}
@view_config(route_name="password-reset", request_method="POST")
-def do_password_reset(request):
+def do_password_reset(request: Request) -> Response:
"""Endpoint for the password request form.
:param request: The Pyramid request.
- :type request: pyramid.request.Request
:return: The HTTP response.
- :rtype: pyramid.response.Response
"""
query = models.User.query_by_email(request.params["email"])
user = request.dbsession.execute(query).scalar_one_or_none()
@@ -209,14 +197,12 @@ def do_password_reset(request):
@view_config(route_name="use-token")
-def use_token(request):
+def use_token(request: Request) -> Response:
"""Endpoint with which a user can use a token for a password reset or email
verification.
:param request: The Pyramid request.
- :type request: pyramid.request.Request
:return: The HTTP response.
- :rtype: pyramid.response.Response
"""
token = request.dbsession.execute(
select(models.Token).filter_by(uuid=request.matchdict["uuid"])
@@ -224,6 +210,10 @@ def use_token(request):
if token is None:
return HTTPNotFound()
+ if token.age() > TOKEN_LIFETIME:
+ request.session.flash(request.localizer.translate(_("flash.token_expired")))
+ return HTTPFound(request.route_url("home"))
+
if token.token_type == TokenType.VERIFY_EMAIL:
token.user.is_verified = True
request.dbsession.delete(token)
@@ -243,4 +233,4 @@ def use_token(request):
request.dbsession.delete(token)
request.session.flash(request.localizer.translate(_("flash.password_updated")))
return HTTPFound(request.route_url("login"))
- return None
+ raise NotImplementedError("No matching action found")