This commit is contained in:
Timothy Jaeryang Baek
2026-08-10 22:36:42 -06:00
parent b7de04da14
commit c2107e5bb3
4 changed files with 81 additions and 7 deletions
+1 -3
View File
@@ -2649,7 +2649,7 @@ async def oauth_client_authorize(
detail='OAuth client registration is still invalid after re-registration',
)
return await oauth_client_manager.handle_authorize(request, client_id=client_id)
return await oauth_client_manager.handle_authorize(request, client_id=client_id, user_id=user.id)
@app.get('/oauth/clients/{client_id}/callback')
@@ -2657,12 +2657,10 @@ async def oauth_client_callback(
client_id: str,
request: Request,
response: Response,
user=Depends(get_verified_user),
):
return await oauth_client_manager.handle_callback(
request,
client_id=client_id,
user_id=user.id if user else None,
response=response,
)
+5
View File
@@ -966,6 +966,11 @@ async def signout(request: Request, response: Response, db: AsyncSession = Depen
)
response.delete_cookie('token')
try:
request.session.clear()
except Exception:
pass
response.delete_cookie('owui-session')
response.delete_cookie('oui-session')
response.delete_cookie('oauth_id_token')
+33
View File
@@ -519,6 +519,39 @@ async def get_verified_user_by_token(token: str, redis=None):
return user
async def get_verified_user_by_id(user_id: str | None):
if not user_id:
return None
user = await Users.get_user_by_id(user_id)
if user is None or user.role not in VERIFIED_USER_ROLES:
return None
return user
async def get_optional_verified_user_from_request(request: Request):
token = None
auth_token = get_http_authorization_cred(request.headers.get('Authorization'))
if auth_token:
token = auth_token.credentials
if token is None:
token = request.cookies.get('token')
if token is None and getattr(request.state, 'token', None):
token = request.state.token.credentials
if not token:
return None
try:
if token.startswith('sk-'):
user = await get_current_user_by_api_key(request, token)
return user if user.role in VERIFIED_USER_ROLES else None
return await get_verified_user_by_token(token, getattr(request.app.state, 'redis', None))
except HTTPException:
return None
def get_admin_user(user=Depends(get_current_user)):
if user.role != 'admin':
raise HTTPException(
+42 -4
View File
@@ -84,7 +84,12 @@ from open_webui.models.groups import GroupForm, GroupModel, Groups, GroupUpdateF
from open_webui.models.oauth_sessions import OAuthSessions
from open_webui.models.users import Users
from open_webui.retrieval.web.utils import get_ssrf_safe_session, validate_url
from open_webui.utils.auth import create_token, get_password_hash
from open_webui.utils.auth import (
create_token,
get_password_hash,
get_optional_verified_user_from_request,
get_verified_user_by_id,
)
from open_webui.utils.groups import apply_default_group_assignment
from open_webui.utils.misc import parse_duration
from open_webui.utils.validate import validate_profile_image_url
@@ -1167,7 +1172,7 @@ class OAuthClientManager:
log.error(f'Exception during token refresh for client_id {client_id}: {e}')
return None
async def handle_authorize(self, request, client_id: str) -> RedirectResponse:
async def handle_authorize(self, request, client_id: str, user_id: str) -> RedirectResponse:
client = await self.get_client(client_id)
if client is None:
raise HTTPException(404)
@@ -1183,7 +1188,15 @@ class OAuthClientManager:
# Pass explicit scope/resource parameters for providers that require them.
kwargs = build_oauth_request_params(client_info)
try:
return await client.authorize_redirect(request, redirect_uri_str, **kwargs)
auth_data = await client.create_authorization_url(redirect_uri_str, **kwargs)
if not auth_data.get('state'):
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail='OAuth authorization state was not generated',
)
auth_data['user_id'] = user_id
await client.save_authorize_data(request, redirect_uri=redirect_uri_str, **auth_data)
return RedirectResponse(auth_data['url'], status_code=302)
except RuntimeError as e:
# authlib raises RuntimeError('Missing "authorize_url" value') when the
# authorization endpoint could not be resolved from server metadata.
@@ -1199,14 +1212,36 @@ class OAuthClientManager:
),
)
async def handle_callback(self, request, client_id: str, user_id: str, response):
async def handle_callback(self, request, client_id: str, response):
client = await self.get_client(client_id)
if client is None:
raise HTTPException(404)
error_message = None
state = request.query_params.get('state')
user_id = None
try:
client_info = await self.get_client_info(client_id)
state_data = await client.framework.get_state_data(request.session, state) if state else None
user_id = state_data.get('user_id') if state_data else None
if not user_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='OAuth callback state is invalid or expired',
)
if not await get_verified_user_by_id(user_id):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail='OAuth callback user is not authorized',
)
request_user = await get_optional_verified_user_from_request(request)
if request_user and request_user.id != user_id:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail='OAuth callback user does not match authenticated session',
)
# Note: Do NOT pass client_id/client_secret explicitly here.
# The Authlib client already has these configured during add_client().
@@ -1257,6 +1292,9 @@ class OAuthClientManager:
error_message,
exc_info=True,
)
finally:
if state and client is not None:
await client.framework.clear_state_data(request.session, state)
webui_url = await Config.get('webui.url')
redirect_url = (str(webui_url or request.base_url)).rstrip('/')