diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b9f413f..767a1a4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ TBD - Fix user creation bug where it would fail when a role was selected ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/763)) - Security improvements for general user creations on roles page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/763)) - Security improvements for general user creations on user page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/763)) +- Use UTC for tokens_valid_from in user config, to resolve token invalidation on instance TZ change ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/765)) ### Tweaks - Add info note to default creds file ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/760)) - Remove navigation label from sidebar ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/766)) diff --git a/app/classes/models/users.py b/app/classes/models/users.py index 3f96e651..6f6a6bde 100644 --- a/app/classes/models/users.py +++ b/app/classes/models/users.py @@ -38,7 +38,7 @@ class Users(BaseModel): superuser = BooleanField(default=False) lang = CharField(default="en_EN") support_logs = CharField(default="") - valid_tokens_from = DateTimeField(default=datetime.datetime.now) + valid_tokens_from = DateTimeField(default=Helpers.get_utc_now) server_order = CharField(default="") preparing = BooleanField(default=False) hints = BooleanField(default=True) @@ -119,7 +119,6 @@ class HelperUsers: @staticmethod def get_user_total(): count = Users.select().where(Users.username != "system").count() - print(count) return count @staticmethod diff --git a/app/classes/shared/authentication.py b/app/classes/shared/authentication.py index fad8b730..94db5532 100644 --- a/app/classes/shared/authentication.py +++ b/app/classes/shared/authentication.py @@ -1,5 +1,6 @@ import logging import time +from datetime import datetime from typing import Optional, Dict, Any, Tuple import jwt from jwt import PyJWTError @@ -62,7 +63,17 @@ class Authentication: user = HelperUsers.get_user(user_id) # TODO: Have a cache or something so we don't constantly # have to query the database - if int(user.get("valid_tokens_from").timestamp()) < iat: + valid_tokens_from_str = user.get("valid_tokens_from") + # It's possible this will be a string or a dt coming from the DB + # We need to account for that + try: + valid_tokens_from_dt = datetime.strptime( + valid_tokens_from_str, "%Y-%m-%d %H:%M:%S.%f%z" + ) + except TypeError: + valid_tokens_from_dt = valid_tokens_from_str + # Convert the string to a datetime object + if int(valid_tokens_from_dt.timestamp()) < iat: # Success! return key, data, user return None diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py index 55a588fc..64d4e1d1 100644 --- a/app/classes/shared/helpers.py +++ b/app/classes/shared/helpers.py @@ -19,7 +19,7 @@ import shutil import shlex import subprocess import itertools -from datetime import datetime +from datetime import datetime, timezone from socket import gethostname from contextlib import redirect_stderr, suppress import libgravatar @@ -640,6 +640,10 @@ class Helpers: version = f"{major}.{minor}.{sub}" return str(version) + @staticmethod + def get_utc_now() -> datetime: + return datetime.fromtimestamp(time.time(), tz=timezone.utc) + def encode_pass(self, password): return self.passhasher.hash(password) diff --git a/app/classes/web/routes/api/auth/invalidate_tokens.py b/app/classes/web/routes/api/auth/invalidate_tokens.py index f15bf60d..9e38670a 100644 --- a/app/classes/web/routes/api/auth/invalidate_tokens.py +++ b/app/classes/web/routes/api/auth/invalidate_tokens.py @@ -1,6 +1,6 @@ -import datetime import logging from app.classes.web.base_api_handler import BaseApiHandler +from app.classes.shared.helpers import Helpers logger = logging.getLogger(__name__) @@ -13,7 +13,7 @@ class ApiAuthInvalidateTokensHandler(BaseApiHandler): logger.debug(f"Invalidate tokens for user {auth_data[4]['user_id']}") self.controller.users.raw_update_user( - auth_data[4]["user_id"], {"valid_tokens_from": datetime.datetime.now()} + auth_data[4]["user_id"], {"valid_tokens_from": Helpers.get_utc_now()} ) self.finish_json(200, {"status": "ok"}) diff --git a/app/migrations/20211120221511_api_keys.py b/app/migrations/20211120221511_api_keys.py index bede2c92..f5dd1e46 100644 --- a/app/migrations/20211120221511_api_keys.py +++ b/app/migrations/20211120221511_api_keys.py @@ -1,10 +1,11 @@ import peewee import datetime +from app.classes.shared.helpers import Helpers def migrate(migrator, database, **kwargs): migrator.add_columns( - "users", valid_tokens_from=peewee.DateTimeField(default=datetime.datetime.now) + "users", valid_tokens_from=peewee.DateTimeField(default=Helpers.get_utc_now) ) migrator.drop_columns("users", ["api_token"])