diff options
| -rw-r--r-- | fietsboek/locale/de/LC_MESSAGES/messages.po | 28 | ||||
| -rw-r--r-- | fietsboek/locale/en/LC_MESSAGES/messages.po | 28 | ||||
| -rw-r--r-- | fietsboek/locale/fietslog.pot | 28 | ||||
| -rw-r--r-- | fietsboek/models/user.py | 7 | ||||
| -rw-r--r-- | fietsboek/scripts/fietscron.py | 12 | ||||
| -rw-r--r-- | fietsboek/views/default.py | 42 | 
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")  | 
