diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index 02891d6810..b18ba46658 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -557,6 +557,12 @@ OAUTH_SESSION_TOKEN_ENCRYPTION_KEY = os.environ.get( "OAUTH_SESSION_TOKEN_ENCRYPTION_KEY", WEBUI_SECRET_KEY ) +# Maximum number of concurrent OAuth sessions per user per provider +# This prevents unbounded session growth while allowing multi-device usage +OAUTH_MAX_SESSIONS_PER_USER = int( + os.environ.get("OAUTH_MAX_SESSIONS_PER_USER", "10") +) + # Token Exchange Configuration # Allows external apps to exchange OAuth tokens for OpenWebUI tokens ENABLE_OAUTH_TOKEN_EXCHANGE = ( diff --git a/backend/open_webui/models/oauth_sessions.py b/backend/open_webui/models/oauth_sessions.py index 538937483f..fbcd763f34 100644 --- a/backend/open_webui/models/oauth_sessions.py +++ b/backend/open_webui/models/oauth_sessions.py @@ -188,6 +188,7 @@ class OAuthSessionTable: session = ( db.query(OAuthSession) .filter_by(provider=provider, user_id=user_id) + .order_by(OAuthSession.created_at.desc()) .first() ) if session: diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index c849eb25a8..2cb552860c 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -69,6 +69,7 @@ from open_webui.env import ( ENABLE_OAUTH_ID_TOKEN_COOKIE, ENABLE_OAUTH_EMAIL_FALLBACK, OAUTH_CLIENT_INFO_ENCRYPTION_KEY, + OAUTH_MAX_SESSIONS_PER_USER, ) from open_webui.utils.misc import parse_duration from open_webui.utils.auth import get_password_hash, create_token @@ -1679,11 +1680,23 @@ class OAuthManager: if "expires_in" in token and "expires_at" not in token: token["expires_at"] = datetime.now().timestamp() + token["expires_in"] - # Clean up any existing sessions for this user/provider first + # Enforce max concurrent sessions per user/provider to prevent + # unbounded growth while allowing multi-device usage sessions = OAuthSessions.get_sessions_by_user_id(user.id, db=db) - for session in sessions: - if session.provider == provider: - OAuthSessions.delete_session_by_id(session.id, db=db) + provider_sessions = sorted( + [ + for session in sessions + if session.provider == provider + ], + key=lambda session: session.created_at, + reverse=True, + ) + # Keep the newest sessions up to the limit, prune the rest + if len(provider_sessions) >= OAUTH_MAX_SESSIONS_PER_USER: + for old_session in provider_sessions[ + OAUTH_MAX_SESSIONS_PER_USER - 1 : + ]: + OAuthSessions.delete_session_by_id(old_session.id, db=db) session = OAuthSessions.create_session( user_id=user.id,