210 Commits

Author SHA1 Message Date
Timothy Jaeryang Baek ce3c175e26 refac 2026-08-10 23:13:10 -06:00
Timothy Jaeryang Baek c2107e5bb3 refac 2026-08-10 22:36:42 -06:00
Classic298 5462c02af0 fix: OIDC login fails when the provider adds a private JOSE header (#28065)
Logging in through CyberArk Identity dies at the callback with "Unsupported {'app_id'} in header" and the user sees "The email or password provided is incorrect". Any provider that puts a vendor-specific parameter in the ID token header hits this; CAS was already patched by name, CyberArk is the next one.

Authlib 1.7 verifies ID tokens with joserfc, which rejects header parameters it does not recognise. The old fix registered `client_id` so CAS would work, which only ever fixes one provider at a time. This turns off the unknown-header rejection instead, so any private header parameter is ignored rather than fatal. Signature verification, the algorithm allowlist, `crit` handling and value validation of registered headers all still run, so nothing that actually protects the token is relaxed.

Fixes #28062
2026-08-10 20:06:31 -06:00
Classic298 2d18727ab8 perf: build info log messages lazily so raising the log level actually saves work (#27837)
Raising GLOBAL_LOG_LEVEL to WARNING buys quieter output but not less work: 241 INFO call sites interpolate their payload into an f-string before the logging call gets to drop it. The heaviest is get_doc, which logs every chunk id and metadata dict in a collection, so on the full-context retrieval path that is the entire knowledge base, once per chat request.

That one line at WARNING, CPython 3.12:

| knowledge base | payload | before   | after   |
| -------------- | ------- | -------- | ------- |
| top-k of 3     | 1.2 kB  | 3.8 us   | 0.07 us |
| 500 chunks     | 201 kB  | 583.6 us | 0.08 us |
| 5000 chunks    | 2.0 MB  | 5.8 ms   | 0.15 us |

The lazy form log.info('query_doc:result %s %s', result.ids, result.metadatas) hands the payload to record.getMessage(), which the InterceptHandler only reaches once a record has passed the level check. Output at INFO is byte-identical. Two sites that already built their message eagerly, one str concat and one % operator, move to the same lazy form.
2026-08-02 15:39:10 -05:00
Classic298 52cfb02c72 perf: build debug log messages lazily so disabled debug logs cost nothing (#27834)
GLOBAL_LOG_LEVEL defaults to INFO, so every log.debug(...) in the backend is discarded, but the message is built first: 187 call sites interpolate their payload into an f-string before the logging call runs, so the work happens on every request and the result is thrown away. The worst one sits in process_chat_payload and stringifies the whole request body, full conversation history included, once per chat completion.

That one line with DEBUG disabled, CPython 3.12:

| conversation | payload | before   | after   |
| ------------ | ------- | -------- | ------- |
| 4 messages   | 1.2 kB  | 3.4 us   | 0.07 us |
| 20 messages  | 17 kB   | 24.8 us  | 0.07 us |
| 60 messages  | 123 kB  | 216.6 us | 0.07 us |

The lazy form log.debug('form_data: %s', form_data) hands the payload to record.getMessage(), which the InterceptHandler only reaches once a record has passed the level check. With DEBUG enabled the emitted lines are byte-identical, f'{x=}' sites included: those map to %r. MistralLoader._debug_log callers get the same treatment, since that wrapper already forwards *args.
2026-07-31 19:09:01 -05:00
Timothy Jaeryang Baek bb0f898b43 refac 2026-07-31 17:41:14 -04:00
Timothy Jaeryang Baek 810378c0b8 refac 2026-07-27 19:39:36 -04:00
Timothy Jaeryang Baek 98656b7c5e refac 2026-07-27 02:28:21 -04:00
Naytip 3fff80ad2f fix: resolve MCP OAuth authorize_url discovery and fail clearly (#26647) (#26654) 2026-07-27 02:15:28 -04:00
Classic298 5dcca59aee fix: route OAuth profile-picture fetch through the SSRF-safe session (#26699)
_process_picture_url validated the picture URL with validate_url() but then fetched it with a plain aiohttp session that resolves the hostname again at connect time, leaving a DNS-rebinding TOCTOU window (the same gap already closed for the RAG loader, the content probe, the image fetches and webhook delivery). Routing the fetch through get_ssrf_safe_session() pins the connect-time resolution via _SSRFSafeResolver and rejects non-global addresses, so a rebinding host can no longer redirect the fetch to loopback, RFC1918 or cloud-metadata endpoints. It also stops the forwarded OAuth access_token from leaking to a rebound internal target.
2026-07-27 00:56:24 -04:00
Classic298 c055203f29 fix: refresh OAuth session before the id_token expires (#27520)
`_normalize_token_expiry()` derived the session expiry from the access token alone, and that value is what `oauth_session.expires_at` stores and what both `get_oauth_token()` implementations check to decide whether to refresh five minutes ahead. Providers that issue a shorter-lived id_token than access token (Microsoft Entra ID: roughly 60 minutes against 75) therefore left a window where the session still looked valid while the id_token had already expired, so pipes and tools reading `__oauth_token__["id_token"]` forwarded a dead JWT and downstream services rejected it with 401.

The stored expiry is now capped at the id_token's `exp` claim whenever that JWT expires first, which moves the refresh ahead of the earliest expiring token in the set. This is applied in the single function every session write already passes through, so it covers both the SSO manager and the MCP client manager on their callback and refresh paths alike. Sessions without an id_token, with an opaque one, or with no `exp` claim are unaffected.

Fixes #27066
2026-07-26 18:56:18 -04:00
G30 71f8b6d5b4 feat: add a master OAuth / OIDC enable toggle in Authentication settings (#26988)
The OAuth / OIDC section in Admin Settings > Authentication had no
enable/disable switch, unlike the LDAP section above it. Add one that
persists via the existing Save flow and actually gates OAuth sign-in,
mirroring how the LDAP toggle works.

- config: new ENABLE_OAUTH persistent config ('oauth.enable'), defaulting
  to True so existing deployments with a provider configured keep working.
- oauth: expose ENABLE_OAUTH via the OAuth runtime config and reject the
  login and callback handlers with 404 when it is disabled.
- /api/config: report no OAuth providers when disabled so the login page
  hides the OAuth buttons (and cannot auto-redirect), without clearing the
  admin's provider configuration.
- auths: expose ENABLE_OAUTH through the admin OAuth config get/update
  endpoints (OAuthConfigForm + OAUTH_CONFIG_KEYS).
- Authentication.svelte: bind the OAuth / OIDC header Switch to the
  persisted oauthConfig.ENABLE_OAUTH and collapse the section when off,
  matching the LDAP header (size, weight, alignment).
2026-07-26 18:43:21 -04:00
G30 4f823774ab fix: migrate deprecated authlib.jose import to joserfc in oauth utils (#27310) 2026-07-23 21:54:59 -04:00
Timothy Jaeryang Baek 9562f036f8 refac 2026-07-01 02:57:29 -05:00
Timothy Jaeryang Baek 517cd8d102 refac 2026-06-29 13:03:14 -05:00
Timothy Jaeryang Baek eb53281c9a refac
Co-Authored-By: Classic298 <27028174+Classic298@users.noreply.github.com>
2026-06-29 06:08:31 -05:00
Timothy Jaeryang Baek cdd7b88bec refac 2026-06-29 05:14:57 -05:00
Timothy Jaeryang Baek 45fea34bd0 refac
Co-Authored-By: Pedro Machado <45520277+pedrofcm@users.noreply.github.com>
2026-06-29 04:46:56 -05:00
Alberto de la Cruz 718b226177 fix: discover MCP Protected Resource Metadata when server does not return 401 (#25980)
get_protected_resource_metadata() only attempted RFC 9728 discovery when
the anonymous `initialize` probe returned 401 with a WWW-Authenticate
header. Some remote MCP servers — notably Google's Gmail/Drive/Calendar
MCPs (gmailmcp.googleapis.com, etc.) — answer 200 to an anonymous
initialize, so OAuth scope and authorization-server discovery silently
failed and connections to them could not be established.

Run the discovery regardless of the probe's HTTP status: prefer the
resource_metadata URL from WWW-Authenticate when present, and otherwise
fall back to the RFC 9728 §4.2 well-known URIs. The trade-off is a couple
of extra well-known GETs during MCP connection setup for servers that
expose no PRM document; behavior for 401-responding servers is unchanged.
2026-06-29 04:34:36 -05:00
Timothy Jaeryang Baek 7be009649a refac 2026-06-29 02:57:58 -05:00
G30 6fdf9b4340 perf(auth): make password hashing non-blocking and batch CSV user import (#25804)
Co-authored-by: Tim Baek <tim@openwebui.com>
2026-06-29 02:45:39 -05:00
G30 6ea591491e perf(images): offload validate_url() DNS resolution with asyncio.to_thread (#25825)
validate_url() calls socket.getaddrinfo() for SSRF protection, which
blocks the event loop for 100-700ms per DNS lookup. This affects:

- Image generation (get_image_data) — every external image URL
- Image editing (load_url_image) — every external image URL
- OAuth profile pictures (_process_picture_url) — every login
- Webhook delivery (post_webhook) — every notification
- Image base64 conversion (get_image_base64_from_url) — chat images

Wrap all 5 async call sites in asyncio.to_thread() so DNS resolution
runs in the thread pool. The event loop remains free to serve other
requests during the lookup.

Benchmark (3 domains, 3 trials averaged):
- BEFORE: max jitter 479ms, 1 blocked ping per trial
- AFTER:  max jitter 1ms, 0 blocked pings (324x improvement)

Co-authored-by: Tim Baek <tim@openwebui.com>
2026-06-29 02:31:24 -05:00
Timothy Jaeryang Baek 5576e6ed8a refac 2026-06-25 14:34:22 +01:00
Timothy Jaeryang Baek b5c43968db refac 2026-06-25 03:31:45 +01:00
Timothy Jaeryang Baek 88901bfa04 refac 2026-06-22 14:42:23 +02:00
Timothy Jaeryang Baek 5cdcdbaeec refac 2026-06-17 02:52:35 +02:00
Jamie Lin ec86ce5cf7 fix(oauth): use Protected Resource Metadata scopes in MCP DCR flow (#25958)
* fix(oauth): use Protected Resource Metadata scopes in MCP DCR flow

The Dynamic Client Registration flow seeded the registration request
`scope` from the Authorization Server's `scopes_supported` (RFC 8414),
which is a full catalog of every scope the AS can grant across all
resources. Per RFC 9728 and the MCP Scope Selection Strategy, the
resource-specific Protected Resource Metadata `scopes_supported` is the
correct, least-privilege source.

The PRM is already fetched in this function; this change prefers its
`scopes_supported` and keeps the AS `scopes_supported` only as a
fallback when the PRM advertises none. Mirrors the static-credentials
fix in #24690.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(oauth): trim DCR scope comment per review feedback

Shortens the inline comment per review feedback on #25958.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:53:38 +02:00
Timothy Jaeryang Baek 6fce92aa12 chore: format 2026-06-01 13:56:55 -07:00
Justin Williams 478bc9e3f1 fix(oauth): use Protected Resource Metadata scopes in static OAuth 2.1 flow (#24690)
The static credentials OAuth flow currently sets scope=None, relying on
the OAuth provider's default scopes. This breaks providers like GitHub
that default to minimal/public-only access when no scope is requested.

This change reads scopes_supported from the Protected Resource Metadata
document (RFC 9728) and uses them in the authorization request. Unlike
the Authorization Server's scopes_supported (a full catalog of every
scope the AS can grant), the PRM scopes_supported represents what the
specific resource requires — making it safe to request without breaking
providers like Entra ID that reject broad scope requests.

Fixes the regression introduced in 349ea4ea where all scope handling was
removed from the static flow.
2026-06-01 13:52:18 -07:00
Timothy Jaeryang Baek c8eb8edca4 refac 2026-06-01 13:38:40 -07:00
Timothy Jaeryang Baek 07cbc91a8e refac
Co-Authored-By: Boris Rybalkin <ribalkin@gmail.com>
2026-06-01 10:16:01 -07:00
Classic298 d07fd7d6d8 fix: disable redirect following in OAuth picture fetch (SSRF) (#24809)
_process_picture_url validated the initial picture URL with validate_url()
but then aiohttp followed 3xx redirects without re-validating the target,
so a validate_url-passing public URL could 302 to an internal address and
the body was base64-stored in the user's profile_image_url. This is the
sixth call site of the CVE-2026-45401 redirect-bypass cohort; the other
five already pass allow_redirects=AIOHTTP_CLIENT_ALLOW_REDIRECTS. Apply
the same.
2026-05-19 23:57:38 +04:00
Timothy Jaeryang Baek 6d0295588e refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
Timothy Jaeryang Baek df42d96c95 refac 2026-05-09 21:05:49 +09:00
Timothy Jaeryang Baek 6116c6dca0 refac 2026-05-09 16:06:09 +09:00
Timothy Jaeryang Baek 93931efaa7 refac 2026-05-09 16:05:21 +09:00
Timothy Jaeryang Baek cde72dab71 refac 2026-05-09 02:54:09 +09:00
Timothy Jaeryang Baek 5774ab4984 refac 2026-04-24 16:26:34 +09:00
Timothy Jaeryang Baek 116eb7fc55 refac 2026-04-21 16:05:26 +09:00
Timothy Jaeryang Baek 349ea4ea9e refac 2026-04-17 12:25:43 +09:00
Timothy Jaeryang Baek 5dae600ce7 chore: format 2026-04-14 17:27:31 -05:00
Timothy Jaeryang Baek c767bcaa73 refac 2026-04-13 18:20:46 -05:00
Timothy Jaeryang Baek 31406caa79 refac 2026-04-13 15:13:14 -05:00
Timothy Jaeryang Baek 050c4b97a9 refac 2026-04-13 14:13:03 -05:00
Timothy Jaeryang Baek 498ff8cdc3 refac 2026-04-12 19:05:25 -05:00
Timothy Jaeryang Baek facb194a07 refac 2026-04-12 19:02:51 -05:00
Timothy Jaeryang Baek 25898116ea chore: format 2026-04-12 18:12:59 -05:00
Timothy Jaeryang Baek 27169124f2 refac: async db 2026-04-12 14:22:11 -05:00
Classic298 96a0b3239b fix: prevent first-user admin race in LDAP and OAuth registration (#23626)
Both LDAP and OAuth registration checked user count before insert to determine whether to assign admin role.  With multiple workers, concurrent first-user registrations could each see zero users and both create admin accounts.

Applies the insert-first-check-after pattern already used by signup_handler: insert with DEFAULT_USER_ROLE, then atomically check get_num_users()==1 and promote only the sole user to admin.
2026-04-12 11:28:41 -05:00
Classic298 435efa31ce fix: add SSRF protection to OAuth profile picture URL fetching (#23356) 2026-04-08 13:10:35 -07:00