fix: invalidate stale Socket.IO sessions on role change and user deletion (#23642)
SESSION_POOL caches user.role at connection time and never refreshes it. When an admin demotes or deletes a user, their socket sessions retain the old cached role until voluntary disconnect, allowing continued use of admin-gated socket features (ydoc editing, channel access). Adds disconnect_user_sessions() helper that disconnects all sockets for a user ID. Called from update_user_by_id (on role change) and delete_user_by_id. The client auto-reconnects and re-authenticates with fresh DB data.
This commit is contained in:
@@ -40,6 +40,7 @@ from open_webui.utils.auth import (
|
||||
validate_password,
|
||||
)
|
||||
from open_webui.utils.access_control import get_permissions, has_permission
|
||||
from open_webui.socket.main import disconnect_user_sessions
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -620,6 +621,10 @@ async def update_user_by_id(
|
||||
)
|
||||
|
||||
if updated_user:
|
||||
# If the role changed, disconnect all socket sessions so stale
|
||||
# privileges cached in SESSION_POOL are invalidated.
|
||||
if updated_user.role != user.role:
|
||||
await disconnect_user_sessions(user_id)
|
||||
return updated_user
|
||||
|
||||
raise HTTPException(
|
||||
@@ -659,6 +664,7 @@ async def delete_user_by_id(user_id: str, user=Depends(get_admin_user), db: Asyn
|
||||
result = await Auths.delete_auth_by_id(user_id, db=db)
|
||||
|
||||
if result:
|
||||
await disconnect_user_sessions(user_id)
|
||||
return True
|
||||
|
||||
raise HTTPException(
|
||||
|
||||
@@ -312,6 +312,24 @@ async def enter_room_for_users(room: str, user_ids: list[str]):
|
||||
log.debug(f'Failed to make users {user_ids} join room {room}: {e}')
|
||||
|
||||
|
||||
async def disconnect_user_sessions(user_id: str):
|
||||
"""Disconnect all Socket.IO sessions belonging to a user.
|
||||
|
||||
Call this when a user's role is changed or the user is deleted so that
|
||||
stale role/permission data cached in SESSION_POOL is invalidated.
|
||||
The client will automatically reconnect and re-authenticate with
|
||||
fresh data from the database.
|
||||
"""
|
||||
try:
|
||||
session_ids = get_session_ids_from_room(f'user:{user_id}')
|
||||
for sid in session_ids:
|
||||
await sio.disconnect(sid)
|
||||
if session_ids:
|
||||
log.info(f'Disconnected {len(session_ids)} session(s) for user {user_id}')
|
||||
except Exception as e:
|
||||
log.warning(f'Failed to disconnect sessions for user {user_id}: {e}')
|
||||
|
||||
|
||||
@sio.on('usage')
|
||||
async def usage(sid, data):
|
||||
if sid in SESSION_POOL:
|
||||
|
||||
Reference in New Issue
Block a user