diff --git a/config/configtemplate.json b/config/configtemplate.json index b60a98d..80f343d 100644 --- a/config/configtemplate.json +++ b/config/configtemplate.json @@ -21,6 +21,7 @@ }, "session": { "session_expiry_seconds": 86400, + "session_refresh_seconds": 43200, "max_session_count": 5, "auto_cookie": true, "auto_cookie_name": "session", diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 4186754..78d1a62 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -41,6 +41,7 @@ Make sure that all parameters are set correctly before starting the service. | Parameter | Description | |------------|-------------| | `session.session_expiry_seconds` | **Datatype:** Integer
**Default:** `86400`
The time in seconds until a login session expires. Expires on Client (Browser) and on the Server (Database). | +| `session.session_refresh_seconds` | **Datatype:** Integer
**Default:** `43200`
The session get's automatically extended if user performs **any authentication requiring action** within session_refresh_seconds (43200) before expiry. Be careful with this parameter, because setting it too close to session_expiry_seconds, may cause unwanted server and database load due to more frequent session expiry update actions. | | `session.max_session_count` | **Datatype:** Integer
**Default:** `5`
Maximum amount of sessions for one User. | | `session.auto_cookie` | **Datatype:** Boolean
**Default:** `true`
Specifies if the API should automatically return a `Set-Cookie` header to potentially automatically set the Session Token for the client. May simplify upcoming requests to this API. | | `session.auto_cookie_name` | **Datatype:** String
**Default:** `"session"`
The name of the cookie which will be set by the API. | diff --git a/src/api/dependencies/authenticated.py b/src/api/dependencies/authenticated.py index 6632bbf..f5cc263 100644 --- a/src/api/dependencies/authenticated.py +++ b/src/api/dependencies/authenticated.py @@ -1,10 +1,14 @@ from fastapi import HTTPException, Cookie from tools import SessionConfig -from tools import sessions_collection from crud.user import get_public_user, get_user, get_dangerous_user +from crud.sessions import extend_session, get_session import logging +def session_token_used(session: str): + extend_session(session) + + async def get_pub_user_dep( session_token: str = Cookie(default=None, alias=SessionConfig.auto_cookie_name) ): @@ -24,7 +28,7 @@ async def get_pub_user_dep( if not session_token: logging.debug("No session token") raise HTTPException(status_code=401) - session = sessions_collection.find_one({"session_token": session_token}) + session = get_session(session_token) if not session: logging.debug("No session found") raise HTTPException(status_code=401) @@ -32,6 +36,7 @@ async def get_pub_user_dep( if not user: logging.debug("No user for session found") raise HTTPException(status_code=401) + session_token_used(session) return user @@ -54,7 +59,7 @@ async def get_user_dep( if not session_token: logging.debug("No session token") raise HTTPException(status_code=401) - session = sessions_collection.find_one({"session_token": session_token}) + session = get_session(session_token) if not session: logging.debug("No session found") raise HTTPException(status_code=401) @@ -62,6 +67,7 @@ async def get_user_dep( if not user: logging.debug("No user for session found") raise HTTPException(status_code=401) + session_token_used(session) return user @@ -84,7 +90,7 @@ async def get_dangerous_user_dep( if not session_token: logging.debug("No session token") raise HTTPException(status_code=401) - session = sessions_collection.find_one({"session_token": session_token}) + session = get_session(session_token) if not session: logging.debug("No session found") raise HTTPException(status_code=401) @@ -92,4 +98,5 @@ async def get_dangerous_user_dep( if not user: logging.debug("No user for session found") raise HTTPException(status_code=401) + session_token_used(session) return user diff --git a/src/api/helpers/extension_loader.py b/src/api/helpers/extension_loader.py index 0e0328a..14eeb56 100644 --- a/src/api/helpers/extension_loader.py +++ b/src/api/helpers/extension_loader.py @@ -21,7 +21,7 @@ def load_extensions(app: FastAPI): readme_file = None try: readme_file = open(os.path.join(item_path, "README.md"), "r").read() - except: + except FileNotFoundError: logger.error(f"Extension {item} is missing README.md") spec = importlib.util.spec_from_file_location(item, init_file) module = importlib.util.module_from_spec(spec) diff --git a/src/crud/sessions.py b/src/crud/sessions.py index 77b0afe..eba03cc 100644 --- a/src/crud/sessions.py +++ b/src/crud/sessions.py @@ -143,3 +143,25 @@ def count_sessions() -> int: int: Amount of Sessions """ return sessions_collection.count_documents({}) + + +def extend_session(session: dict) -> None: + """ + Extend the session expiration time, depending on configuration. + + Args: + session_token (str): Session Token + """ + expiry = session["createdAt"] + datetime.timedelta( + seconds=SessionConfig.session_expiry_seconds + ) + + start_extending_period = expiry - datetime.timedelta( + seconds=SessionConfig.session_refresh_seconds + ) + + if datetime.datetime.now() >= start_extending_period: + sessions_collection.update_one( + {"session_token": session["session_token"]}, + {"$set": {"createdAt": datetime.datetime.now()}}, + ) diff --git a/src/tools/conf/SessionConfig.py b/src/tools/conf/SessionConfig.py index 93e19a6..fc5d12b 100644 --- a/src/tools/conf/SessionConfig.py +++ b/src/tools/conf/SessionConfig.py @@ -3,6 +3,7 @@ class SessionConfig: session_expiry_seconds: int = config["session"]["session_expiry_seconds"] + session_refresh_seconds: int = config["session"]["session_refresh_seconds"] max_session_count: int = config["session"]["max_session_count"] auto_cookie: bool = config["session"]["auto_cookie"] auto_cookie_name: str = config["session"]["auto_cookie_name"] @@ -17,6 +18,12 @@ def validate_types(self) -> bool: type(self.session_expiry_seconds) ) ) + if not isinstance(self.session_refresh_seconds, int): + raise ValueError( + "session.session_refresh_seconds must be an integer (got type {})".format( + type(self.session_refresh_seconds) + ) + ) if not isinstance(self.max_session_count, int): raise ValueError( "session.max_session_count must be an integer (got type {})".format( @@ -56,6 +63,16 @@ def validate_values(self) -> bool: self.session_expiry_seconds ) ) + if not self.session_refresh_seconds > 0: + raise ValueError( + "session.session_refresh_seconds must be a positive integer (got {})".format( + self.session_refresh_seconds + ) + ) + if self.session_refresh_seconds > self.session_expiry_seconds: + raise ValueError( + "session.session_refresh_seconds must be less or equal than session.session_expiry_seconds" + ) if not self.max_session_count > 0: raise ValueError( "session.max_session_count must be a positive integer (got {})".format( diff --git a/src/tools/conf/testing_config.json b/src/tools/conf/testing_config.json index fe33b68..966f2e4 100644 --- a/src/tools/conf/testing_config.json +++ b/src/tools/conf/testing_config.json @@ -21,6 +21,7 @@ }, "session": { "session_expiry_seconds": 86400, + "session_refresh_seconds": 43200, "max_session_count": 5, "auto_cookie": true, "auto_cookie_name": "session",