* 🧊 fix: Inline-refresh OpenID session tokens at MCP OBO call time
Resolves the walk-away failure mode where MCP tool calls using OBO auth
fail with "No valid OpenID access token is available for OBO exchange"
after a user idles past their access-token lifetime. The strategy-time
snapshot on `user.federatedTokens` could expire mid-stream before
`resolveOboToken` ran, while `req.session.openidTokens` carried a still-
valid (or refreshable) token that nothing read.
- New OpenIDSessionRefresh service: per-user single-flighted closure that
reads `req.session.openidTokens` at OBO time and inline-refreshes via
`openid-client.refreshTokenGrant` when expired (30s skew), persisting
via `req.session.save()`. No cookie writes (headers already flushed).
- `resolveOboToken` gains a required UpstreamTokenProvider parameter
(typed as `() => Promise<OIDCTokens | null>`, reusing the shared shape
from @librechat/data-schemas). Compile-time guarantee that every call
site is updated.
- New `session_refresh_failed` OboTokenResolutionReason distinguishes
"session expired and IdP rejected refresh" from "no upstream token
ever existed."
- `req` threaded through createMCPTool/createMCPTools/createToolInstance
to construct the closure with captured request, plus fail-closed
guards in MCPConnectionFactory.getOboTokens and MCPManager.callTool
when the closure isn't plumbed.
- Startup warning in MCPServersInitializer when OBO is configured but
OPENID_REUSE_TOKENS is unset (the strategy populating
user.federatedTokens is only registered under reuse, so OBO would
fail every call without it).
Tests: 16 new in OpenIDSessionRefresh.spec.js; obo.spec.ts extended
for the new param + error reason; wiring smoke tests in MCPManager,
MCPConnectionFactory, MCPServersInitializer, and MCP.spec.js.
* 🛡️ fix: Harden OBO inline-refresh against token type and session edge cases
- Token-preference asymmetry: live-token reuse and expires_at derivation
now strictly gate on the access_token, not the id_token. Added a
required `tokenPreference` parameter on isLiveSessionTokenStillValid,
buildOIDCTokensFromSession, and createOpenIDSessionTokenProvider
so every call site is explicit. Dropped the bogus id_token-exp
fallback in performIdpRefresh — id_token TTL is governed by IdP
session policy and would mark a short-lived access_token reusable
past its real lifetime.
- Missing req in /reinitialize route: the manual reconnect
endpoint now forwards req into reinitMCPServer, so OBO servers can
build a session-aware upstream-token closure instead of failing with
missing_upstream_token.
- Single-flight key collisions: composed key as
tenantId:openidIssuer:openidId:sessionId via getSingleFlightKey.
Concurrent calls in the same session still coalesce; separate sessions
never share an in-flight refresh, preventing refresh-token rotation
from breaking sibling sessions and preventing cross-tenant token
crossover when distinct users share an IdP sub.
- Opaque access token reuse): persist accessTokenExpiresAt
(unix seconds, from tokenset.expires_in) on each refresh AND on initial
login / SPA refresh in setOpenIDAuthTokens. New getAccessTokenExp
helper falls back to it when the access token isn't a JWT, avoiding
redundant inline refreshes for Microsoft Graph and Auth0 default
audiences.
- Log hygiene: the single-flight key (containing sessionId,
openidId, openidIssuer, tenantId) is now SHA-256-hashed in the
"Joining in-flight refresh" debug log. Preserves cross-line correlation
via a 12-char prefix without leaking credential or PII material.
Documented req.session.openidTokens shape contract via JSDoc typedef so
the new accessTokenExpiresAt field has a discoverable home alongside the
existing accessToken/idToken/refreshToken/expiresAt/lastRefreshedAt.
Tests: OpenIDSessionRefresh.spec.js up to 30 passing (added coverage for
opaque-token reuse, JWT-access-token-exp fallback, no-id_token-fallback
regression, cross-session no-coalesce, persistence on refresh, and a
guard against stale accessTokenExpiresAt carryover). AuthService.spec.js
adds two cases covering accessTokenExpiresAt persistence on login.
mcp.spec.js (route) gains a regression test asserting req flows into
reinitMCPServer.
* 🔍 fix: Detect OBO-only MCP admin config overrides
Admin Config overlays for YAML-defined MCP servers compare only
ADMIN_CONFIGURABLE_FIELDS to decide whether to lazy-init a config-tier override.
The OBO config field was added after that fingerprint list, so an override that
only added or changed `obo` was treated as unchanged YAML and skipped.
Include `obo` in the admin-configurable field list and add a regression test for
an OBO-only override.
* 🔊 fix: Mock MCP OAuth timeout in SDK integration test
MCPConnectionFactory.attemptToConnect reads mcpConfig.OAUTH_HANDLING_TIMEOUT
when building the OAuth connection timeout. The SDK OAuth integration test
mocked mcpConfig without that field, which made the timeout calculation produce
NaN and caused the test to fail before the OAuth refresh/start path completed.
Add OAUTH_HANDLING_TIMEOUT to the test mock.
* ♻️ refactor: Pass OBO upstream-token closure into MCP instead of req
Build the OpenID upstream-token provider at the request boundary and thread
only the closure through MCP handling, so the MCP service layer no longer
receives the raw Express request. The closure still reads/refreshes the live
session at tool-call time, preserving the walk-away recovery.
- Drop `req`/`capturedReq` from createMCPTools, createMCPTool, reconnectServer,
createToolInstance, and reinitMCPServer; forward `upstreamTokenProvider`
instead. Closure is constructed in loadTools, loadToolDefinitionsWrapper, and
the reinitialize route, where req/res are in scope.
- OBO: fall back to user.federatedTokens when the provider yields no live
session, so OIDC remote-agent calls (verified bearer, no session) still work.
- Inline refresh: mirror a rotated refresh token to the refreshToken cookie via
a shared setRefreshTokenCookie helper, guarded by !res.headersSent (no-op on
the streaming path; session copy stays authoritative).
- Single-flight: hydrate a joining request's own session from the resolved
tokens so a later OBO call doesn't replay a rotated-away refresh token.
Addresses owner feedback and three review findings.
* 🔒 fix: Recover OIDC refresh-token rotation after SSE OBO refresh
When an inline OBO refresh rotates the OpenID refresh token after SSE headers
have already been sent, the browser refreshToken cookie cannot be updated. Store
a short-lived encrypted bridge from the stale cookie token to the rotated token
so /api/auth/refresh can recover after express-session loss.
Use the signed openid_user_id cookie to load user context for bridge validation,
retry only on invalid_grant, and delete the bridge only after the bridged refresh
succeeds.
* 🔨 fix: hydrate joined OIDC refresh sessions with stable refresh tokens
Update single-flight OIDC refresh joiners whenever refreshed access token
state changes, even if the IdP keeps the refresh token unchanged.
This prevents joined requests from retaining stale accessToken or
accessTokenExpiresAt values and redundantly refreshing later in the same run.
* 🌉 Persist OIDC refresh-token recovery bridges in MongoDB
Store SSE OBO refresh-token recovery bridges in MongoDB instead of
process-local memory so /api/auth/refresh can recover after worker
restarts or cross-worker routing.
Derive bridge expiry from REFRESH_TOKEN_EXPIRY so the recovery window
matches the stale refreshToken cookie it repairs, and delete bridges
after successful recovery.
* 🤝 Coordinate OIDC inline refreshes across workers
Add a short-lived Mongo-backed refresh-flight record so concurrent
OBO refreshes for the same OpenID session do not redeem the same
rotating refresh token on different workers.
The winning worker performs the IdP refresh and stores an encrypted
result; joiners wait for that result, hydrate their request session,
and return without calling the IdP.
* ⚓ Keep OpenID marker cookies aligned on inline refresh
Refresh token_provider and openid_user_id with the same expiry as the
rotated refreshToken cookie when an inline OBO refresh can still write
headers.
Share the marker-cookie writer with the normal OpenID auth refresh path
so the fallback /api/auth/refresh branch continues to recognize valid
OpenID refresh tokens after session expiry.
* 🔑 fix: include refresh token in OIDC local refresh flight key
Key the process-local OIDC refresh coalescing by the current session
refresh token, matching the Mongo-backed flight key. This prevents a
request with a newly rotated token from joining an older pending refresh
and inheriting its failure/result.
* 🌉 fix: store OIDC refresh bridge without cookie response
Treat missing or non-cookie responses like headers-sent streaming
responses during inline OIDC refresh. When the IdP rotates the refresh
token and cookies cannot be written, persist a recovery bridge so a later
/auth/refresh can recover after session expiry.
* 🫙 fix: preserve stale OIDC cookie bridge key
Track the refresh token last written to the browser cookie separately
from the current session refresh token. When inline OIDC refreshes rotate
tokens without a writable response, keep bridging from the browser-stale
token directly to the latest session token.
* 🙌 fix: keep OIDC bridge recovery success on cleanup failure
Make refresh-token bridge cleanup best-effort after a bridged OIDC
refresh succeeds. A transient delete failure now logs a warning but does
not convert the already-refreshed session and cookies into a 403 response.
* 📦 test: Exclude RefreshTokenBridge from tenant-isolation coverage
Add RefreshTokenBridge to the tenant-isolation coverage allowlist because
refresh bridge lookups run during unauthenticated OpenID refresh recovery.
The controller first recovers user context from the signed OpenID marker
cookie, then the bridge methods apply explicit user and tenant filters.
Ambient tenant isolation would bind this recovery path to request-local
tenant context that is not available at the point the stale cookie is being
resolved
* ⚡ Fix OpenID refresh flight retry and marker hydration
Allow failed OpenID refresh flights to be reclaimed immediately instead of pinning transient errors.
Preserve the browser refresh-token marker when joined refreshes hydrate session tokens from a shared flight result.
Stabilize AuthService tests by isolating mocked module imports from prior suites.
* 🛠️ fix: centralize OBO identity scoping
Add shared auth identity helpers for app user ids, OpenID subjects,
tenant ids, and normalized OpenID issuers.
Thread a non-placeholder-visible OBO identity context from the real
request user through MCP connection, tool-call, reinit, and refresh
paths. Keep tenantId and openidIssuer out of createSafeUser so MCP
user placeholders do not expose those fields.
Scope OBO token cache and in-flight exchange keys by tenant, issuer,
OpenID subject, scopes, and a SHA-256 hash of the upstream assertion.
This prevents cross-tenant/cross-issuer collisions and avoids reusing
tokens minted from stale rotated assertions.
Use the shared identity helpers for OpenID refresh-flight keys and
refresh-token bridge recovery records so related OBO refresh paths share
the same identity normalization rules.
The helper is intended for auth-boundary and credential-cache code, not
as a blanket replacement for ordinary app user id ownership checks.
* 🛠️ fix: preserve OIDC refresh-token sync on save failures
Sync OpenID refresh-token cookie/bridge state before persisting the
session so a transient session-store failure cannot lose an IdP-rotated
refresh token.
Also trigger sync when the session refresh token differs from the
browser refresh-token marker, not only when the current grant rotates
the token. This lets later writable refreshes repair stale browser
cookies left behind by SSE refreshes.
Route refresh bridge identity through the shared identity helper with
the threaded OBO identity context, falling back to request/user context
when needed.
Add regression coverage for session-save failures, stale browser cookie
repair, non-writable bridge storage, and shared-helper identity fallback.
* 🛠️ fix: keep OIDC refresh bridge during recovery grace
After successful bridged refresh recovery, re-store the stale-cookie bridge
with a short grace TTL instead of deleting it immediately. This lets parallel
/api/auth/refresh requests that already sent the stale browser cookie recover
before they can observe the first response's Set-Cookie.
Retarget the bridge to the refresh token returned by the bridged retry so
B-to-C refresh-token rotation remains recoverable. The grace TTL is parsed with
math() and defaults to 60s, which shrinks the replay window from the original
REFRESH_TOKEN_EXPIRY bridge lifetime to the short recovery grace period.
Remove the now-unused explicit bridge delete path from the service and
data-schemas method surface. Add regression coverage for grace re-store,
identity symmetry, retry failure behavior, and same-key upsert replacement.
* 🛠️ fix: fail closed on OBO MCP user identity mismatch
Add an OBO-specific guard before MCP tool execution that requires the
effective invocation user and captured request user to both have ids and
to match. This prevents OBO tool calls from falling back to a separate
configurable.user_id identity after request-bound OBO context has already
been captured.
Keep the existing user id fallback behavior for non-OBO MCP calls.
Tests cover mismatched OBO users, missing user ids, and the matching-user
path ignoring a conflicting configurable.user_id.
* 🛠️ fix: Guard OpenID bridge retry user identity
Extract the shared OpenID refresh/user-resolution flow in AuthController
so the normal refresh path and bridge-recovery retry use the same grant,
claims, issuer, user lookup, and diagnostic logging code.
Preserve the existing path-specific behavior: the normal path still owns
migration updates and 401 login redirects, while the bridge retry still
falls through to the existing 403 invalid-token response.
Add a bridge-recovery guard that rejects retry results whose resolved
user id differs from the signed openid_user_id cookie before issuing
tokens or re-storing the grace bridge. Cover both the successful
matching-user recovery and the mismatched-user rejection.
* 🛠️ fix: type-safety polish on OBO data layer
Replace refresh token bridge query/update Record<string, unknown> usage
with typed Mongoose FilterQuery and UpdateQuery definitions.
Harden OpenID marker cookie JWT expiry handling by converting refresh
expiry milliseconds to integer seconds and rejecting invalid or
non-positive durations.
Add focused CSRF tests for fractional refresh expiry values and invalid
expiry configuration.
* 🛠️ fix: Bind OpenID session tokens to authenticated identity
Stamp OpenID session token state with the LibreChat user id, OpenID subject,
tenant id, and normalized issuer when tokens are stored.
Fail closed before OBO inline token reuse/refresh when the session token
identity does not match the current authenticated identity, preventing a stale
or mixed Express session from supplying another user's upstream assertion.
Also validate the normal /api/auth/refresh session-token reuse shortcut against
the signed marker-cookie user before returning cached session tokens.
Note: sessions created before this change carry no identity stamp and are
treated as a mismatch. This is self-healing — the reuse path forces a full IdP
refresh (which re-stamps the session) and the OBO path throws, surfacing as a
one-time re-authentication for active OBO users at deploy time. The session
re-stamps within one session lifetime (SESSION_EXPIRY, default 15 min).
* 🛠️ fix: Recover OpenID refresh token drift
Prefer the browser refresh-token cookie when it differs from the
server-side OpenID session state, and force a real IdP refresh in that
case instead of reusing stale session tokens.
Store a short-lived refresh-token bridge when inline OBO refresh writes
a rotated browser cookie but session persistence fails, so follow-up
refreshes can still recover from the old token.
Keep the bridge grace TTL centralized in RefreshTokenBridge so both
recovery paths use the same env-backed value.
Note: drift is measured against the last-synced browserRefreshToken
marker, so the SSE path (intentionally stale cookie, authoritative
session) does not false-positive. Sessions predating the marker have no
browserRefreshToken; for those, drift falls back to comparing the cookie
against the session refresh token and prefers the cookie on difference.
This is the same self-healing pre-change-session window as the identity
binding fix and re-syncs within one session lifetime.
Tests cover cookie/session drift selection, reusable-session bypass on
drift, bridge storage after session-save failure, and the shared bridge
constant wiring.
* 🛠️ fix: Harden OBO token caching and expiry handling
Reject malformed OBO grant responses before writing them to the exchanged-token cache so a missing access_token cannot poison the cache.
Store absolute expires_at values with cached OBO tokens and ignore legacy cache entries without usable expiry metadata. This keeps cached-token freshness based on the token’s real
remaining lifetime instead of reusing the original relative expires_in on cache hits.
Move OBO expiry normalization and skew helpers into packages/api and use them from both the JS exchange service and the TS MCP resolver. Apply a 30-second safety margin with a one-
second floor for short-lived tokens, covered by direct helper tests and caller-level regression tests.
Tests:
- packages/api: npm run build
- packages/api: npx jest src/mcp/oauth/expiry.spec.ts src/mcp/oauth/obo.spec.ts
- api: npx jest server/services/OboTokenService.spec.js
* 🛠️ fix: Harden OBO refresh-token bridge lookup and indexing
Reuse getValidOpenIDReuseUserId for the bridge-recovery user lookup in
refreshController instead of re-verifying openid_user_id inline. The shared
helper enforces the JWT_REFRESH_SECRET presence check and a strict
typeof payload.id === 'string' guard, rejecting tokens whose id claim is
present but not a string (e.g. a numeric id) that the inline check accepted.
Fail closed on issuer mismatch in getRefreshTokenBridge. Both the stored and
the expected issuer are now normalized and compared for equality, so a bridge
is recovered only when both sides agree (both absent, or both present and
equal after normalization). Previously the check was skipped whenever the
stored issuer was absent, allowing recovery across mismatched issuer context.
Drop the unused {oldRefreshTokenHash, userId, tenantId, openidIssuer} index
and the openidIssuer field on RefreshTokenBridgeQuery. The data-layer filter
only queries the 3-field {oldRefreshTokenHash, userId, tenantId} index; the
issuer is verified in application code, not the query. Hoist the repeated
model accessor into getRefreshTokenBridgeModel.
Note: issuer is now load-bearing for recovery. A bridge stored with an issuer
recovers only when the lookup supplies a matching issuer; the recovery lookup
reads user.openidIssuer via AUTH_REFRESH_USER_PROJECTION (an exclusion
projection that retains the field). If a user's persisted openidIssuer is
empty while the stored bridge has one, recovery fails closed (falls through to
normal re-authentication) until the bridge TTLs out — no security regression.
Tests cover invalid signed-cookie payloads bypassing the bridge, both
asymmetric issuer-presence cases, issuer normalization before comparison, and
an index-alignment assertion guarding against re-adding the dropped index.
* 🛠️ fix: Degrade OBO discovery on token resolution failures
Catch expected OboTokenResolutionError failures during MCP tool discovery and
fall back to unauthenticated tool listing instead of aborting discovery. This
keeps discovery aligned with the existing unauthenticated listing behavior while
preserving unexpected errors as real failures.
Also correct OBO tool-call freshness comment and tighten the OBO trust-check
permissions type to the existing role permission shape.
Tests:
- npx jest src/mcp/__tests__/MCPConnectionFactory.test.ts --runInBand --coverage=false
- npx jest src/mcp/oauth/obo.spec.ts --runInBand --coverage=false
* 🛠️ fix: tighten OBO tool-call errors, bridge logging, and flight typing
Move resolveToolCallUserId inside the tool-call try/catch so an OBO
identity mismatch surfaces with serverName/toolName context and the
standard tool-call-failed message instead of an opaque bare Error.
Raise the refresh-token bridge lookup failure log from debug to warn so
transient infrastructure failures on the unauthenticated /api/auth/refresh
path are observable, and guard the message access against non-Error values.
Replace the unknown+cast in isDuplicateKeyError with a hasErrorCode type
predicate so the duplicate-key check reads error.code without an assertion.
Preserve real math/isEnabled in the MCPConnectionFactory test mock (mock
only processMCPEnv) so mcpConfig timeouts no longer resolve to NaN, fixing
the TimeoutNaNWarning that masked slow OAuth retry behavior.
* 🧪 fix: Restore the Flight Uniqueness Index and Buffer the Graph Cache TTL
Two CI failures on the merge, both in suites this environment cannot run
(their MongoDB binary download is blocked).
`GraphApiService.spec.js` still asserted the unbuffered TTL. Graph tokens
route through the same `getTokenCacheTtlMs` as the OBO and openidStrategy
caches, so the entry now expires 30s before the credential does.
`openidRefreshFlight.spec.ts` dropped the database between tests, which takes
the indexes with it, and Mongoose builds them only once when the model is
compiled. Whether the unique `key` index survived into a test was a race with
that one-time build. Without it a second `create` inserts instead of raising a
duplicate-key error, so every worker believes it won the flight — the
mutual exclusion the file exists to prove. Indexes are now rebuilt after each
drop, which also makes the reclaim and complete cases reach those paths for
the right reason.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SxKWxwqxAGckYpRsYTqx3F
* fix: address OBO review findings
* 🔐 fix: Install Bridge Indexes and Carry OBO Through Assistant Recovery
Two findings from the Codex pass on d08c82f0d.
The refresh-token bridge relied on Mongoose auto-indexing for both of its
indexes, and `MONGO_AUTO_INDEX=false` is a supported deployment setting. A
bridge holds an encrypted refresh token and the TTL index is the only thing
that ever deletes one, so under that setting they would accumulate for the
life of the collection while concurrent upserts lost the compound uniqueness
the filter assumes. Installed before the first write, matching the flight
methods and the session and schedule methods before them.
`recoverServerTools`, the assistant create/update path that reruns
`reinitMCPServer` when a referenced server's catalog and connection snapshot
are both missing, was the last reinit site not carrying the upstream-token
closure. For an OBO server the factory rejects the connection outright, so the
assistant write failed with unavailable MCP definitions. It now builds the
provider at that request boundary like the other entry points; assistant
writes have no `res`, so a rotation there falls back to the recovery bridge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SxKWxwqxAGckYpRsYTqx3F
* fix: harden OBO refresh coordination
* ⏳ fix: Keep Elapsed Expiries Elapsed and Revoke the Superseded Session
Two of the five findings from the Codex pass on fcdc15885 — the two that are
defects in code this branch introduced rather than design questions about the
bridge.
`getSkewedTokenExpiresAtMs` floored every result at a second in the future,
including an expiry the provider had already declared elapsed. An exchange
answering `expires_in: 0` or a past `expires_at` was handed to the MCP
connection stamped valid for another second, which only moves the failure
downstream. The floor now applies to a lifetime that is still live, which is
what it was for; an elapsed one stays elapsed so the caller rejects it. Same
for the cache TTL, which falls back to the elapsed-credential floor.
Bridge recovery left the stale token's durable Session behind. Only the token
it recovered through was passed as `existingRefreshToken`, so that one's
session was replaced while the token the browser actually presented kept its
record until its original expiry. That record, with the marker cookie still
bound to it, is what authorizes local image access for OpenID users — so a
copy of the stale cookie outlived the rotation it had lost. Revoked
explicitly on successful recovery.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SxKWxwqxAGckYpRsYTqx3F
* fix: close OBO refresh review findings
* 🎟️ fix: Carry the Bridged Token Through a Non-Rotating Recovery
Bridge recovery passes the browser's stale token as `existingRefreshToken` so
the durable Session naming it is the record replaced. That also makes the
stale token the fallback the installed session and the refresh cookie use when
a tokenset carries no `refresh_token` of its own, which holds only while the
recovery grant rotates.
An IdP that answers that grant without rotating sends the browser back to the
very token the bridge exists to retire: `storeOpenIDSession` installs and
deletes the same stale record in one call, and the cookie is rewritten to a
token the IdP already rejected — a sign-out on the next refresh. The grace
bridge one line above already guards this with `|| bridgedRefreshToken`; the
resolved tokenset now does the same, so leader and followers alike publish the
recovered token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SxKWxwqxAGckYpRsYTqx3F
* 🚫 fix: Reject an OBO Exchange That Returns an Expired Credential
Preserving an elapsed expiry through the skew helper only helps if something
acts on it, and nothing did: `MCPManager.callTool` checks the access token and
nothing else before setting the Authorization header, so a credential the IdP
declared spent still went downstream to fail there. It is rejected at the
exchange now, where the reason is known, and retryably — the exchange itself
worked, so a fresh grant can succeed.
Completes the elapsed-expiry change in cb26a6f7d, which made the stamp honest
without giving anyone a reason to look at it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SxKWxwqxAGckYpRsYTqx3F
* fix: close OpenID refresh review findings
* fix: coordinate OpenID refresh entry points
* chore: sort OpenID flight imports
* fix: fence OpenID refreshes during logout
* fix: close OpenID logout publication races
* fix: narrow completed refresh flight
* test: cover bridge cleanup failure after ownership loss
The compensating delete in storeRefreshTokenBridgeWithLease swallows its own
failure so the lease error stays the one the caller sees. Nothing asserted
that, so removing the inner catch left every suite green while callers began
receiving the cleanup error instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SxKWxwqxAGckYpRsYTqx3F
* fix: compensate OpenID bridges only on proven ownership loss
The post-write lease assertion deletes the bridge it just published when it
throws, but it threw for two different reasons: a coordination record that is
no longer ours, and a coordination read that simply failed. Treating the second
as the first destroys the only mapping from the token the browser still holds
to the one the IdP already rotated to, so a transient Mongo error on the
headers-already-sent path signed the user out.
Tag the ownership error where the lease raises it and compensate only for that,
preserving the bridge whenever ownership is merely undetermined. A preserved
bridge stays behind the logout revocation fence, so the safe default costs
nothing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SxKWxwqxAGckYpRsYTqx3F
* fix: reject spent OpenID refresh results
Two ways a refresh could report success while handing back a credential
nothing can use.
An inline refresh carries the previous id_token forward when the IdP omits
one on rotation, so tokenset.id_token is not necessarily freshly issued.
setOpenIDAuthTokens applied its freshness guard only to the session copy and
took tokenset.id_token unconditionally, so /refresh returned an expired
bearer even though the grant produced a usable access token. Skip it only
when it is provably expired: an id_token whose expiry cannot be read stays
preferred, since access_token may be opaque or scoped to another audience.
normalizeExpiresIn preserves a zero or negative lifetime rather than
discarding it, so a grant declaring an already-spent access token still
published, rotating the refresh token and returning a token every freshness
check rejects. Each OBO call then repeated the grant. Reject an elapsed
lifetime before publishing; an unknown lifetime still publishes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SxKWxwqxAGckYpRsYTqx3F
* fix: harden OpenID refresh publication
* fix: keep identity and results intact through OpenID refresh cleanup
Two follow-ons from the last round's fixes.
Stripping an expired carried-forward id_token from the refresh result removed
the only identity material a rotation without id_token leaves behind. The
result is rebuilt by buildOIDCTokensFromSession, so it carries no provider
claims() either, and getTokenClaims accepts only those two — bridge recovery
failed with "no usable identity claims" before setOpenIDAuthTokens could hand
back the fresh access token. The stripped token now travels in a
non-enumerable marker, alongside the existing browser and predecessor markers,
which identity resolution reads and the authentication response never sees.
The lease drained a pending renewal by awaiting it in finally, so a transient
coordination failure there threw from finally and replaced the operation's
result. The refresh had already settled and published, so the caller saw a
failure on credentials that had rotated. Proven ownership loss is recorded on
ownershipLost and checked before the return, so the drain has nothing to add
but noise; it now absorbs and logs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SxKWxwqxAGckYpRsYTqx3F
* refactor: fence OpenID recovery publication
* fix: close OpenID publication transaction
* refactor: make OpenID publication transactional
* fix: satisfy OpenID publication type checks
* fix: fence OpenID session publication
* fix: authorize OpenID refresh publication
* fix: bind OpenID replay generations
* fix: fence OpenID response generations
* fix: authorize OpenID token delivery
* fix: linearize OpenID publication delivery
* chore: sort OpenID refresh flight imports
---------
Co-authored-by: J.C. Bartle <jcbartle@users.noreply.github.com>
Co-authored-by: jbartle <jbartle@rand.org>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: jcbartle <7274202+jcbartle@users.noreply.github.com>
The user schema lowercases emails on save, but sendVerificationEmail
stored the verification token with the raw registration email. For
mixed-case registrations, verifyEmail's lookup by the lowercased DB
email never matched the token, failing with 'No email verification
data found'. Normalize once and use it for the link, recipient, and
token. resendVerificationEmail already used the DB email and was
unaffected.
Co-authored-by: VictorEPlus <victor.ortega@eplusadvisor.com>
* fix: honor admin-panel allowedDomains override at registration
registerUser called getAppConfig({ baseOnly: true }), which short-
circuits before any DB override merge. As a result, admin-panel edits to
registration.allowedDomains were silently ignored at signup, even though
they correctly apply to SSO callbacks via checkDomainAllowed (which
calls getAppConfig() with the full resolution).
The admin panel writes registration.allowedDomains to the __base__
principal in the configs collection. That principal is unconditionally
injected by getApplicableConfigs (no user identity required), so a
fully-resolved getAppConfig call picks up the override even before any
user exists. This aligns native signup with the SSO paths and lets
admins tighten or relax the allowed list without a backend restart.
Per review feedback: pass the ALS tenantId explicitly. /api/auth runs
through preAuthTenantMiddleware, which puts a tenantId into
AsyncLocalStorage. Mongoose queries inside getApplicableConfigs are
ALS-scoped, but the per-principal merged-config cache key uses the
*explicit* tenantId parameter (see overrideCacheKey in
packages/api/src/app/service.ts). If we leave tenantId undefined while
ALS holds tenant A, the merged result caches at `__default__` — and a
later request from tenant B would hit that entry, leaking tenant A's
allowedDomains (and balance) across tenants. Reading getTenantId() and
forwarding it makes the cache key match the DB scope, so __base__
overrides apply per-tenant correctly.
Behavior when no admin override exists is unchanged (the merged config
equals the YAML config; optional chaining handles missing fields).
Tests in AuthService.spec.js:
- Regression guard that getAppConfig is called with `{}` (no baseOnly)
when ALS has no tenant — protects against reintroduction of the
short-circuit.
- New tenant-context test verifying getAppConfig({ tenantId }) when
getTenantId() returns a tenant ID — protects against cross-tenant
cache bleed.
- Behavioral test confirming a disallowed domain returns 403 before any
DB user lookup.
* test: remove unused registerSchema import after merge resolution
---------
Co-authored-by: Danny Avila <danny@librechat.ai>
* 🌩️ feat: CloudFront CDN File Strategy + signed cookies
Squashed from PR #12193:
- feat(storage): add CloudFront CDN file strategy
- feat(auth): add CloudFront signed cookie support
Note: package.json/package-lock.json dependency additions are intentionally
omitted from this commit and will be re-added via `npm install` after rebase
to avoid lock-file merge conflicts. The two new peer deps that need to be
re-installed are:
- @aws-sdk/client-cloudfront@^3.1032.0
- @aws-sdk/cloudfront-signer@^3.1012.0
Also fixes 4 missing destructured names in AuthService.spec.js
(getUserById, generateToken, generateRefreshToken, createSession) that
were referenced in tests but not imported from the mocked '~/models'.
* 📦 chore: install CloudFront SDK deps for PR #12193
Adds the two AWS CloudFront packages required by the rebased
CloudFront CDN strategy:
- @aws-sdk/client-cloudfront
- @aws-sdk/cloudfront-signer
Following the @aws-sdk/client-s3 pattern:
- api/package.json: regular dependency (runtime resolution)
- packages/api/package.json: peerDependency
Generated by `npm install` against the freshly rebased lock file
to avoid the merge conflicts that came from the original PR's
lock-file edits being made against an older base of dev.
* 🐛 fix: CI failures + review findings on CloudFront PR #12193
CI fixes
- Rename packages/data-provider/src/__tests__/cloudfront-config.test.ts
→ src/cloudfront-config.spec.ts. Jest's default testMatch picks up
__tests__/ directories even inside dist/, so the compiled .d.ts shell
was being executed as an empty test suite. Moving to .spec.ts (matching
the rest of the package) avoids the dist/ pickup.
- Add cookieExpiry: 1800 to CloudFront crud.test makeConfig: the schema
applies a default so CloudFrontFullConfig requires it.
Review findings addressed
- #1 (Codex + comprehensive): Normalize CloudFront domain with /\/+$/
regex (and key with /^\/+/ regex) in buildCloudFrontUrl, matching the
cookie code so resource policy and file URLs stay aligned even when
the configured domain has multiple trailing slashes. Added tests.
- #2: Move DEFAULT_BASE_PATH out of s3Config into shared
packages/api/src/storage/constants.ts. ImageService no longer imports
S3-specific config.
- #3: getCloudFrontConfig() returns Readonly<CloudFrontFullConfig> | null
to discourage mutation of the cached signing config.
- #4: Add cross-field refinement tests for cloudfrontConfigSchema
(invalidateOnDelete-without-distributionId,
imageSigning="cookies"-without-cookieDomain).
- #6: Revert unrelated MCP comment re-indentation in
librechat.example.yaml.
- #7: Add azure_blob to the strategy list comment.
Skipped
- #5 (extractKeyFromS3Url with CloudFront URLs): existing
deleteFileFromCloudFront tests already cover the path-equivalence
assumption; renaming the helper is real refactor work beyond this
PR's scope.
- #8, #9 (NIT, low confidence): leaving for author judgement.
* 🧹 chore: drop dead DEFAULT_BASE_PATH from s3Config test mock
After moving DEFAULT_BASE_PATH to ~/storage/constants, crud.ts no longer
reads it from s3Config — so the entry in the s3Config jest mock was
misleading dead config. The tests still pass because the unmocked real
constants module provides the value.
---------
Co-authored-by: Danny Avila <danny@librechat.ai>
* feat: add resolveAppConfigForUser utility for tenant-scoped auth config
TypeScript utility in packages/api that wraps getAppConfig in
tenantStorage.run() when the user has a tenantId, falling back to
baseOnly for new users or non-tenant deployments. Uses DI pattern
(getAppConfig passed as parameter) for testability.
Auth flows apply role-level overrides only (userId not passed)
because user/group principal resolution is deferred to post-auth.
* feat: tenant-scoped app config in auth login flows
All auth strategies (LDAP, SAML, OpenID, social login) now use a
two-phase domain check consistent with requestPasswordReset:
1. Fast-fail with base config (memory-cached, zero DB queries)
2. DB user lookup
3. Tenant-scoped re-check via resolveAppConfigForUser (only when
user has a tenantId; otherwise reuse base config)
This preserves the original fast-fail protection against globally
blocked domains while enabling tenant-specific config overrides.
OpenID error ordering preserved: AUTH_FAILED checked before domain
re-check so users with wrong providers get the correct error type.
registerUser unchanged (baseOnly, no user identity yet).
* test: add tenant-scoped config tests for auth strategies
Add resolveAppConfig.spec.ts in packages/api with 8 tests:
- baseOnly fallback for null/undefined/no-tenant users
- tenant-scoped config with role and tenantId
- ALS context propagation verified inside getAppConfig callback
- undefined role with tenantId edge case
Update strategy and AuthService tests to mock resolveAppConfigForUser
via @librechat/api. Tests verify two-phase domain check behavior:
fast-fail before DB, tenant re-check after. Non-tenant users reuse
base config without calling resolveAppConfigForUser.
* refactor: skip redundant domain re-check for non-tenant users
Guard the second isEmailDomainAllowed call with appConfig !== baseConfig
in SAML, OpenID, and social strategies. For non-tenant users the tenant
config is the same base config object, so the second check is a no-op.
Narrow eslint-disable in resolveAppConfig.spec.ts to the specific
require line instead of blanket file-level suppression.
* fix: address review findings — consistency, tests, and ordering
- Consolidate duplicate require('@librechat/api') in AuthService.js
- Add two-phase domain check to LDAP (base fast-fail before findUser),
making all strategies consistent with PR description
- Add appConfig !== baseConfig guard to requestPasswordReset second
domain check, consistent with SAML/OpenID/social strategies
- Move SAML provider check before tenant config resolution to avoid
unnecessary resolveAppConfigForUser call for wrong-provider users
- Add tenant domain rejection tests to SAML, OpenID, and social specs
verifying that tenant config restrictions actually block login
- Add error propagation tests to resolveAppConfig.spec.ts
- Remove redundant mockTenantStorage alias in resolveAppConfig.spec.ts
- Narrow eslint-disable to specific require line
* test: add tenant domain rejection test for LDAP strategy
Covers the appConfig !== baseConfig && !isEmailDomainAllowed path,
consistent with SAML, OpenID, and social strategy specs.
* refactor: rename resolveAppConfig to app/resolve per AGENTS.md
Rename resolveAppConfig.ts → resolve.ts and
resolveAppConfig.spec.ts → resolve.spec.ts to align with
the project's concise naming convention.
* fix: remove fragile reference-equality guard, add logging and docs
Remove appConfig !== baseConfig guard from all strategies and
requestPasswordReset. The guard relied on implicit cache-backend
identity semantics (in-memory Keyv returns same object reference)
that would silently break with Redis or cloned configs. The second
isEmailDomainAllowed call is a cheap synchronous check — always
running it is clearer and eliminates the coupling.
Add audit logging to requestPasswordReset domain blocks (base and
tenant), consistent with all auth strategies.
Extract duplicated error construction into makeDomainDeniedError().
Wrap resolveAppConfigForUser in requestPasswordReset with try/catch
to prevent DB errors from leaking to the client via the controller's
generic catch handler.
Document the dual tenantId propagation (ALS for DB isolation,
explicit param for cache key) in resolveAppConfigForUser JSDoc.
Add comment documenting the LDAP error-type ordering change
(cross-provider users from blocked domains now get 'domain not
allowed' instead of AUTH_FAILED).
Assert resolveAppConfigForUser is not called on LDAP provider
mismatch path.
* fix: return generic response for tenant domain block in password reset
Tenant-scoped domain rejection in requestPasswordReset now returns the
same generic "If an account with that email exists..." response instead
of an Error. This prevents user-enumeration: an attacker cannot
distinguish between "email not found" and "tenant blocks this domain"
by comparing HTTP responses.
The base-config fast-fail (pre-user-lookup) still returns an Error
since it fires before any user existence is revealed.
* docs: document phase 1 vs phase 2 domain check behavior in JSDoc
Phase 1 (base config, pre-findUser) intentionally returns Error/400
to reveal globally blocked domains without confirming user existence.
Phase 2 (tenant config, post-findUser) returns generic 200 to prevent
user-enumeration. This distinction is now explicit in the JSDoc.
* feat: add tenant context middleware for ALS-based isolation
Introduces tenantContextMiddleware that propagates req.user.tenantId
into AsyncLocalStorage, activating the Mongoose applyTenantIsolation
plugin for all downstream DB queries within a request.
- Strict mode (TENANT_ISOLATION_STRICT=true) returns 403 if no tenantId
- Non-strict mode passes through for backward compatibility
- No-op for unauthenticated requests
- Includes 6 unit tests covering all paths
* feat: register tenant middleware and wrap startup/auth in runAsSystem()
- Register tenantContextMiddleware in Express app after capability middleware
- Wrap server startup initialization in runAsSystem() for strict mode compat
- Wrap auth strategy getAppConfig() calls in runAsSystem() since they run
before user context is established (LDAP, SAML, OpenID, social login, AuthService)
* feat: thread tenantId through all getAppConfig callers
Pass tenantId from req.user to getAppConfig() across all callers that
have request context, ensuring correct per-tenant cache key resolution.
Also fixes getBaseConfig admin endpoint to scope to requesting admin's
tenant instead of returning the unscoped base config.
Files updated:
- Controllers: UserController, PluginController
- Middleware: checkDomainAllowed, balance
- Routes: config
- Services: loadConfigModels, loadDefaultModels, getEndpointsConfig, MCP
- Audio services: TTSService, STTService, getVoices, getCustomConfigSpeech
- Admin: getBaseConfig endpoint
* feat: add config cache invalidation on admin mutations
- Add clearOverrideCache(tenantId?) to flush per-principal override caches
by enumerating Keyv store keys matching _OVERRIDE_: prefix
- Add invalidateConfigCaches() helper that clears base config, override
caches, tool caches, and endpoint config cache in one call
- Wire invalidation into all 5 admin config mutation handlers
(upsert, patch, delete field, delete overrides, toggle active)
- Add strict mode warning when __default__ tenant fallback is used
- Add 3 new tests for clearOverrideCache (all/scoped/base-preserving)
* chore: update getUserPrincipals comment to reflect ALS-based tenant filtering
The TODO(#12091) about missing tenantId filtering is resolved by the
tenant context middleware + applyTenantIsolation Mongoose plugin.
Group queries are now automatically scoped by tenantId via ALS.
* fix: replace runAsSystem with baseOnly for pre-tenant code paths
App configs are tenant-owned — runAsSystem() would bypass tenant
isolation and return cross-tenant DB overrides. Instead, add
baseOnly option to getAppConfig() that returns YAML-derived config
only, with zero DB queries.
All startup code, auth strategies, and MCP initialization now use
getAppConfig({ baseOnly: true }) to get the YAML config without
touching the Config collection.
* fix: address PR review findings — middleware ordering, types, cache safety
- Chain tenantContextMiddleware inside requireJwtAuth after passport auth
instead of global app.use() where req.user is always undefined (Finding 1)
- Remove global tenantContextMiddleware registration from index.js
- Update BalanceMiddlewareOptions to include tenantId, remove redundant cast (Finding 4)
- Add warning log when clearOverrideCache cannot enumerate keys on Redis (Finding 3)
- Use startsWith instead of includes for cache key filtering (Finding 12)
- Use generator loop instead of Array.from for key enumeration (Finding 3)
- Selective barrel export — exclude _resetTenantMiddlewareStrictCache (Finding 5)
- Move isMainThread check to module level, remove per-request check (Finding 9)
- Move mid-file require to top of app.js (Finding 8)
- Parallelize invalidateConfigCaches with Promise.all (Finding 10)
- Remove clearOverrideCache from public app.js exports (internal only)
- Strengthen getUserPrincipals comment re: ALS dependency (Finding 2)
* fix: restore runAsSystem for startup DB ops, consolidate require, clarify baseOnly
- Restore runAsSystem() around performStartupChecks, updateInterfacePermissions,
initializeMCPs, and initializeOAuthReconnectManager — these make Mongoose
queries that need system context in strict tenant mode (NEW-3)
- Consolidate duplicate require('@librechat/api') in requireJwtAuth.js (NEW-1)
- Document that baseOnly ignores role/userId/tenantId in JSDoc (NEW-2)
* test: add requireJwtAuth tenant chaining + invalidateConfigCaches tests
- requireJwtAuth: 5 tests verifying ALS tenant context is set after
passport auth, isolated between concurrent requests, and not set
when user has no tenantId (Finding 6)
- invalidateConfigCaches: 4 tests verifying all four caches are cleared,
tenantId is threaded through, partial failure is handled gracefully,
and operations run in parallel via Promise.all (Finding 11)
* fix: address Copilot review — passport errors, namespaced cache keys, /base scoping
- Forward passport errors in requireJwtAuth before entering tenant
middleware — prevents silent auth failures from reaching handlers (P1)
- Account for Keyv namespace prefix in clearOverrideCache — stored keys
are namespaced as "APP_CONFIG:_OVERRIDE_:..." not "_OVERRIDE_:...",
so override caches were never actually matched/cleared (P2)
- Remove role from getBaseConfig — /base should return tenant-scoped
base config, not role-merged config that drifts per admin role (P2)
- Return tenantStorage.run() for cleaner async semantics
- Update mock cache in service.spec.ts to simulate Keyv namespacing
* fix: address second review — cache safety, code quality, test reliability
- Decouple cache invalidation from mutation response: fire-and-forget
with logging so DB mutation success is not masked by cache failures
- Extract clearEndpointConfigCache helper from inline IIFE
- Move isMainThread check to lazy once-per-process guard (no import
side effect)
- Memoize process.env read in overrideCacheKey to avoid per-request
env lookups and log flooding in strict mode
- Remove flaky timer-based parallelism assertion, use structural check
- Merge orphaned double JSDoc block on getUserPrincipals
- Fix stale [getAppConfig] log prefix → [ensureBaseConfig]
- Fix import order in tenant.spec.ts (package types before local values)
- Replace "Finding 1" reference with self-contained description
- Use real tenantStorage primitives in requireJwtAuth spec mock
* fix: move JSDoc to correct function after clearEndpointConfigCache extraction
* refactor: remove Redis SCAN from clearOverrideCache, rely on TTL expiry
Redis SCAN causes 60s+ stalls under concurrent load (see #12410).
APP_CONFIG defaults to FORCED_IN_MEMORY_CACHE_NAMESPACES, so the
in-memory store.keys() path handles the standard case. When APP_CONFIG
is Redis-backed, overrides expire naturally via overrideCacheTtl (60s
default) — an acceptable window for admin config mutations.
* fix: remove return from tenantStorage.run to satisfy void middleware signature
* fix: address second review — cache safety, code quality, test reliability
- Switch invalidateConfigCaches from Promise.all to Promise.allSettled
so partial failures are logged individually instead of producing one
undifferentiated error (Finding 3)
- Gate overrideCacheKey strict-mode warning behind a once-per-process
flag to prevent log flooding under load (Finding 4)
- Add test for passport error forwarding in requireJwtAuth — the
if (err) { return next(err) } branch now has coverage (Finding 5)
- Add test for real partial failure in invalidateConfigCaches where
clearAppConfigCache rejects (not just the swallowed endpoint error)
* chore: reorder imports in index.js and app.js for consistency
- Moved logger and runAsSystem imports to maintain a consistent import order across files.
- Improved code readability by ensuring related imports are grouped together.
The express session cookie maxAge (SESSION_EXPIRY, default 15 min) is
shorter than the OIDC token lifetime (~1 hour). When OPENID_REUSE_TOKENS
is enabled, the refresh token was stored only in the express session
(req.session.openidTokens). After the session expired, the refresh token
was lost, causing "Refresh token not provided" on the next refresh
attempt and signing the user out. Re-login via OIDC would succeed
immediately (provider session still active), masking the root cause.
The session-only storage was introduced in #11236 to avoid HTTP/2 header
size limits from large access_token/id_token JWTs (especially Azure
Entra ID with many group claims). The refresh token is a small opaque
string and does not contribute to that problem.
Move the refreshToken cookie out of the no-session fallback branch so it
is always set alongside the session storage. The refreshController
already has the fallback logic (req.session?.openidTokens?.refreshToken
|| parsedCookies.refreshToken) but previously never had a cookie to fall
back to.
Timeline before fix:
T=0 Login, session created (15 min maxAge), id_token valid ~1 hr
T=15min Session cookie expires, refresh token lost
T=15min+ Page refresh or id_token expiry triggers refresh, fails with
"Refresh token not provided", user redirected to /login
Timeline after fix:
T=0 Login, session created + refreshToken cookie (7 day expiry)
T=15min Session cookie expires
T=15min+ Refresh reads refreshToken from cookie fallback, succeeds,
restores session with fresh tokens
* fix: Convert `const` to `enum` in MCP tool schemas for Gemini/Vertex AI compatibility
Gemini/Vertex AI rejects the JSON Schema `const` keyword in function declarations
with a 400 error. Previously, the Zod conversion layer accidentally stripped `const`,
but after migrating to pass raw JSON schemas directly to providers, the unsupported
keyword now reaches Gemini verbatim.
Add `normalizeJsonSchema` to recursively convert `const: X` → `enum: [X]`, which is
semantically equivalent per the JSON Schema spec and supported by all providers.
* fix: Update secure cookie handling in AuthService to use dynamic secure flag
Replaced the static `secure: isProduction` with a call to `shouldUseSecureCookie()` in the `setOpenIDAuthTokens` function. This change ensures that the secure cookie setting is evaluated at runtime, improving cookie handling in development environments while maintaining security in production.
* refactor: Simplify MCP tool key formatting and remove unused mocks in tests
- Updated MCP test suite to replace static tool key formatting with a dynamic delimiter from Constants, enhancing consistency and maintainability.
- Removed unused mock implementations for `@langchain/core/tools` and `@librechat/agents`, streamlining the test setup.
- Adjusted related test cases to reflect the new tool key format, ensuring all tests remain functional.
* chore: import order
* fix(openid): distinguish ID tokens from access tokens in federated auth
Fix OpenID Connect token handling to properly distinguish ID tokens from access tokens. ID tokens and access tokens are now stored and propagated separately, preventing token placeholders from resolving to identical values.
- AuthService.js: Added idToken field to session storage
- openIdJwtStrategy.js: Updated to read idToken from session
- openidStrategy.js: Explicitly included id_token in federatedTokens
- Test suites: Added comprehensive test coverage for token distinction
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(openid): add separate openid_id_token cookie for ID token storage
Store the OIDC ID token in its own cookie rather than relying solely on
the access token, ensuring correct token type is used for identity
verification vs API authorization.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test(openid): add JWT strategy cookie fallback tests
Cover the token source resolution logic in openIdJwtStrategy:
session-only, cookie-only, partial session fallback, raw Bearer
fallback, and distinct id_token/access_token from cookies.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* 🔒 fix: Secure Cookie Localhost Bypass and OpenID Token Selection in AuthService
Two independent bugs in `api/server/services/AuthService.js` cause complete
authentication failure when using `OPENID_REUSE_TOKENS=true` with Microsoft
Entra ID (or Auth0) on `http://localhost` with `NODE_ENV=production`:
Bug 1: `secure: isProduction` prevents auth cookies on localhost
PR #11518 introduced `shouldUseSecureCookie()` in `socialLogins.js` to handle
the case where `NODE_ENV=production` but the server runs on `http://localhost`.
However, `AuthService.js` was not updated — it still used `secure: isProduction`
in 6 cookie locations across `setAuthTokens()` and `setOpenIDAuthTokens()`.
The `token_provider` cookie being dropped is critical: without it,
`requireJwtAuth` middleware defaults to the `jwt` strategy instead of
`openidJwt`, causing all authenticated requests to return 401.
Bug 2: `setOpenIDAuthTokens()` returns `access_token` instead of `id_token`
The `openIdJwtStrategy` validates the Bearer token via JWKS. For Entra ID
without `OPENID_AUDIENCE`, the `access_token` is a Microsoft Graph API token
(opaque or signed for a different audience), which fails JWKS validation.
The `id_token` is always a standard JWT signed by the IdP's JWKS keys with
the app's `client_id` as audience — which is what the strategy expects.
This is the same root cause as issue #8796 (Auth0 encrypted access tokens).
Changes:
- Consolidate `shouldUseSecureCookie()` into `packages/api/src/oauth/csrf.ts`
as a shared, typed utility exported from `@librechat/api`, replacing the
duplicate definitions in `AuthService.js` and `socialLogins.js`
- Move `isProduction` check inside the function body so it is evaluated at
call time rather than module load time
- Fix `packages/api/src/oauth/csrf.ts` which also used bare
`secure: isProduction` for CSRF and session cookies (same localhost bug)
- Return `tokenset.id_token || tokenset.access_token` from
`setOpenIDAuthTokens()` so JWKS validation works with standard OIDC
providers; falls back to `access_token` for backward compatibility
- Add 15 tests for `shouldUseSecureCookie()` covering production/dev modes,
localhost variants, edge cases, and a documented IPv6 bracket limitation
- Add 13 tests for `setOpenIDAuthTokens()` covering token selection,
session storage, cookie secure flag delegation, and edge cases
Refs: #8796, #11518, #11236, #9931
* chore: Adjust Import Order and Type Definitions in AgentPanel Component
- Reordered imports in `AgentPanel.tsx` for better organization and clarity.
- Updated type imports to ensure proper usage of `FieldNamesMarkedBoolean` and `TranslationKeys`.
- Removed redundant imports to streamline the codebase.
* refactor: OpenID token handling by storing tokens in session to reduce cookie size
* refactor: Improve OpenID user identification logic in logout controller
* refactor: Enhance OpenID logout flow by adding post-logout redirect URI
* refactor: Update logout process to clear additional OpenID user ID cookie
* chore: Add mathjs dependency
* refactor: Replace eval with mathjs for safer expression evaluation and improve session expiry handling to not environment variables from data-schemas package
* test: Add integration tests for math function with environment variable expressions
* refactor: Update test description for clarity on expiresIn behavior
* refactor: Update test cases to clarify default expiration behavior for token generation
* refactor: Improve error handling in math function for clearer evaluation errors
* feat: Add OpenID Connect federated provider token support
Implements support for passing federated provider tokens (Cognito, Azure AD, Auth0)
as variables in LibreChat's librechat.yaml configuration for both custom endpoints
and MCP servers.
Features:
- New LIBRECHAT_OPENID_* template variables for federated provider tokens
- JWT claims parsing from ID tokens without verification (for claim extraction)
- Token validation with expiration checking
- Support for multiple token storage locations (federatedTokens, openidTokens)
- Integration with existing template variable system
- Comprehensive test suite with Cognito-specific scenarios
- Provider-agnostic design supporting Cognito, Azure AD, Auth0, etc.
Security:
- Server-side only token processing
- Automatic token expiration validation
- Graceful fallbacks for missing/invalid tokens
- No client-side token exposure
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: Add federated token propagation to OIDC authentication strategies
Adds federatedTokens object to user during authentication to enable
federated provider token template variables in LibreChat configuration.
Changes:
- OpenID JWT Strategy: Extract raw JWT from Authorization header and
attach as federatedTokens.access_token to enable {{LIBRECHAT_OPENID_TOKEN}}
placeholder resolution
- OpenID Strategy: Attach tokenset tokens as federatedTokens object to
standardize token access across both authentication strategies
This enables proper token propagation for custom endpoints and MCP
servers that require federated provider tokens for authorization.
Resolves missing token issue reported by @ramden in PR #9931🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Denis Ramic <denis.ramic@nfon.com>
Co-Authored-By: Claude <noreply@anthropic.com>
* test: Add federatedTokens validation tests for OIDC strategies
Adds comprehensive test coverage for the federated token propagation
feature implemented in the authentication strategies.
Tests added:
- Verify federatedTokens object is attached to user with correct structure
(access_token, refresh_token, expires_at)
- Verify both tokenset and federatedTokens are present in user object
- Ensure tokens from OIDC provider are correctly propagated
Also fixes existing test suite by adding missing mocks:
- isEmailDomainAllowed function mock
- findOpenIDUser function mock
These tests validate the fix from commit 5874ba29f that enables
{{LIBRECHAT_OPENID_TOKEN}} template variable functionality.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* docs: Remove implementation documentation file
The PR description already contains all necessary implementation details.
This documentation file is redundant and was requested to be removed.
* fix: skip s256 check
* fix(openid): handle missing refresh token in Cognito token refresh response
When OPENID_REUSE_TOKENS=true, the token refresh flow was failing because
Cognito (and most OAuth providers) don't return a new refresh token in the
refresh grant response - they only return new access and ID tokens.
Changes:
- Modified setOpenIDAuthTokens() to accept optional existingRefreshToken parameter
- Updated validation to only require access_token (refresh_token now optional)
- Added logic to reuse existing refresh token when not provided in tokenset
- Updated refreshController to pass original refresh token as fallback
- Added comments explaining standard OAuth 2.0 refresh token behavior
This fixes the "Token is not present. User is not authenticated." error that
occurred during silent token refresh with Cognito as the OpenID provider.
Fixes: Authentication loop with OPENID_REUSE_TOKENS=true and AWS Cognito
* fix(openid): extract refresh token from cookies for template variable replacement
When OPENID_REUSE_TOKENS=true, the openIdJwtStrategy populates user.federatedTokens
to enable template variable replacement (e.g., {{LIBRECHAT_OPENID_ACCESS_TOKEN}}).
However, the refresh_token field was incorrectly sourced from payload.refresh_token,
which is always undefined because:
1. JWTs don't contain refresh tokens in their payload
2. The JWT itself IS the access token
3. Refresh tokens are separate opaque tokens stored in HTTP-only cookies
This caused extractOpenIDTokenInfo() to receive incomplete federatedTokens,
resulting in template variables remaining unreplaced in headers.
**Root Cause:**
- Line 90: `refresh_token: payload.refresh_token` (always undefined)
- JWTs only contain access token data in their claims
- Refresh tokens are separate, stored securely in cookies
**Solution:**
- Import `cookie` module to parse cookies from request
- Extract refresh token from `refreshToken` cookie
- Populate federatedTokens with both access token (JWT) and refresh token (from cookie)
**Impact:**
- Template variables like {{LIBRECHAT_OPENID_ACCESS_TOKEN}} now work correctly
- Headers in librechat.yaml are properly replaced with actual tokens
- MCP server authentication with federated tokens now functional
**Technical Details:**
- passReqToCallback=true in JWT strategy provides req object access
- Refresh token extracted via cookies.parse(req.headers.cookie).refreshToken
- Falls back gracefully if cookie header or refreshToken is missing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: re-resolve headers on each request to pick up fresh federatedTokens
- OpenAIClient now re-resolves headers in chatCompletion() before each API call
- This ensures template variables like {{LIBRECHAT_OPENID_TOKEN}} are replaced
with actual token values from req.user.federatedTokens
- initialize.js now stores original template headers instead of pre-resolved ones
- Fixes template variable replacement when OPENID_REUSE_TOKENS=true
The issue was that headers were only resolved once during client initialization,
before openIdJwtStrategy had populated user.federatedTokens. Now headers are
re-resolved on every request with the current user's fresh tokens.
* debug: add logging to track header resolution in OpenAIClient
* debug: log tokenset structure after refresh to diagnose missing access_token
* fix: set federatedTokens on user object after OAuth refresh
- After successful OAuth token refresh, the user object was not being
updated with federatedTokens
- This caused template variable resolution to fail on subsequent requests
- Now sets user.federatedTokens with access_token, id_token, refresh_token
and expires_at from the refreshed tokenset
- Fixes template variables like {{LIBRECHAT_OPENID_TOKEN}} not being
replaced after token refresh
- Related to PR #9931 (OpenID federated token support)
* fix(openid): pass user object through agent chain for template variable resolution
Root cause: buildAgentContext in agents/run.ts called resolveHeaders without
the user parameter, preventing OpenID federated token template variables from
being resolved in agent runtime parameters.
Changes:
- packages/api/src/agents/run.ts: Add user parameter to createRun signature
- packages/api/src/agents/run.ts: Pass user to resolveHeaders in buildAgentContext
- api/server/controllers/agents/client.js: Pass user when calling createRun
- api/server/services/Endpoints/bedrock/options.js: Add resolveHeaders call with debug logging
- api/server/services/Endpoints/custom/initialize.js: Add debug logging
- packages/api/src/utils/env.ts: Add comprehensive debug logging and stack traces
- packages/api/src/utils/oidc.ts: Fix eslint errors (unused type, explicit any)
This ensures template variables like {{LIBRECHAT_OPENID_TOKEN}} and
{{LIBRECHAT_USER_OPENIDID}} are properly resolved in both custom endpoint
headers and Bedrock AgentCore runtime parameters.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: remove debug logging from OpenID token template feature
Removed excessive debug logging that was added during development to make
the PR more suitable for upstream review:
- Removed 7 debug statements from OpenAIClient.js
- Removed all console.log statements from packages/api/src/utils/env.ts
- Removed debug logging from bedrock/options.js
- Removed debug logging from custom/initialize.js
- Removed debug statement from AuthController.js
This reduces the changeset by ~50 lines while maintaining full functionality
of the OpenID federated token template variable feature.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* test(openid): add comprehensive unit tests for template variable substitution
- Add 34 unit tests for OIDC token utilities (oidc.spec.ts)
- Test coverage for token extraction, validation, and placeholder processing
- Integration tests for full OpenID token flow
- All tests pass with comprehensive edge case coverage
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
* test: fix OpenID federated tokens test failures
- Add serverMetadata() mock to openid-client mock configuration
* Fixes TypeError in openIdJwtStrategy.js where serverMetadata() was being called
* Mock now returns jwks_uri and end_session_endpoint as expected by the code
- Update outdated initialize.spec.js test
* Remove test expecting resolveHeaders call during initialization
* Header resolution was refactored to be deferred until LLM request time
* Update test to verify options are returned correctly with useLegacyContent flag
Fixes#9931 CI failures for backend unit tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: fix package-lock.json conflict
* chore: sync package-log with upstream
* chore: cleanup
* fix: use createSafeUser
* fix: fix createSafeUser signature
* chore: remove comments
* chore: purge comments
* fix: update Jest testPathPattern to testPathPatterns for Jest 30+ compatibility
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Denis Ramic <denis.ramic@nfon.com>
Co-authored-by: kristjanaapro <kristjana@apro.is>
chore: import order and add back JSDoc for OpenID JWT callback
* chore: move domain related functions to `packages/api`
* fix: isEmailDomainAllowed for case-insensitive domain matching
- Added tests to validate case-insensitive matching for email domains in various scenarios.
- Updated isEmailDomainAllowed function to convert email domains to lowercase for consistent comparison.
- Improved handling of null/undefined entries in allowedDomains.
* ci: Mock isEmailDomainAllowed in samlStrategy tests
- Added a mock implementation for isEmailDomainAllowed to return true in samlStrategy tests, ensuring consistent behavior during test execution.
* ci: Update import of isEmailDomainAllowed in ldapStrategy tests
- Changed the import of isEmailDomainAllowed from the domains service to the api package for consistency and to reflect recent refactoring.
* refactor: Optimize Email Domain Validation in OpenID, SAML, and Social Login Strategies
- Implemented email domain validation for user authentication in OpenID and SAML strategies, ensuring only allowed domains are processed.
- Adjusted error messages for clarity and consistency across authentication methods.
- Refactored social login to validate email domains before checking for existing users, improving registration flow.
* refactor: Email Domain Validation in LDAP and Social Login Strategies
* Refactor deleteTokens to use an array of conditions for querying, ensuring only specified fields are considered for deletion.
* Add error handling to prevent accidental deletion when no query parameters are provided.
* Update AuthService to match the new deleteTokens signature by passing an object instead of a string for email.
* 🔧 fix: Enhance OpenID token handling with user ID for image path validation
* 🔧 fix: Change logger level to error for user info fetch failure and remove redundant info log in OpenID user lookup
* 🔧 refactor: Remove validateImageRequest from middleware exports and enhance validation logic in validateImageRequest.js
* Removed validateImageRequest from the middleware index.
* Improved error handling and validation checks in validateImageRequest.js, including handling of OpenID tokens, URL length, and malformed URLs.
* Updated tests in validateImages.spec.js to cover new validation scenarios and edge cases.
* 🪶 feat: Add Support for Uploading Plaintext Files
feat: delineate between OCR and text handling in fileConfig field of config file
- also adds support for passing in mimetypes as just plain file extensions
feat: add showLabel bool to support future synthetic component DynamicDropdownInput
feat: add new combination dropdown-input component in params panel to support file type token limits
refactor: move hovercard to side to align with other hovercards
chore: clean up autogenerated comments
feat: add delineation to file upload path between text and ocr configured filetypes
feat: add token limit checks during file upload
refactor: move textParsing out of ocrEnabled logic
refactor: clean up types for filetype config
refactor: finish decoupling DynamicDropdownInput from fileTokenLimits
fix: move image token cost function into file to fix circular dependency causing unittest to fail and remove unused var for linter
chore: remove out of scope code following review
refactor: make fileTokenLimit conform to existing styles
chore: remove unused localization string
chore: undo changes to DynamicInput and other strays
feat: add fileTokenLimit to all provider config panels
fix: move textParsing back into ocr tool_resource block for now so that it doesn't interfere with other upload types
* 📤 feat: Add RAG API Endpoint Support for Text Parsing (#8849)
* feat: implement RAG API integration for text parsing with fallback to native parsing
* chore: remove TODO now that placeholder and fllback are implemented
* ✈️ refactor: Migrate Text Parsing to TS (#8892)
* refactor: move generateShortLivedToken to packages/api
* refactor: move textParsing logic into packages/api
* refactor: reduce nesting and dry code with createTextFile
* fix: add proper source handling
* fix: mock new parseText and parseTextNative functions in jest file
* ci: add test coverage for textParser
* 💬 feat: Add Audio File Support to Upload as Text (#8893)
* feat: add STT support for Upload as Text
* refactor: move processAudioFile to packages/api
* refactor: move textParsing from utils to files
* fix: remove audio/mp3 from unsupported mimetypes test since it is now supported
* ✂️ feat: Configurable File Token Limits and Truncation (#8911)
* feat: add configurable fileTokenLimit default value
* fix: add stt to fileConfig merge logic
* fix: add fileTokenLimit to mergeFileConfig logic so configurable value is actually respected from yaml
* feat: add token limiting to parsed text files
* fix: add extraction logic and update tests so fileTokenLimit isnt sent to LLM providers
* fix: address comments
* refactor: rename textTokenLimiter.ts to text.ts
* chore: update form-data package to address CVE-2025-7783 and update package-lock
* feat: use default supported mime types for ocr on frontend file validation
* fix: should be using logger.debug not console.debug
* fix: mock existsSync in text.spec.ts
* fix: mock logger rather than every one of its function calls
* fix: reorganize imports and streamline file upload processing logic
* refactor: update createTextFile function to use destructured parameters and improve readability
* chore: update file validation to use EToolResources for improved type safety
* chore: update import path for types in audio processing module
* fix: update file configuration access and replace console.debug with logger.debug for improved logging
---------
Co-authored-by: Dustin Healy <dustinhealy1@gmail.com>
Co-authored-by: Dustin Healy <54083382+dustinhealy@users.noreply.github.com>
* WIP: app.locals refactoring
WIP: appConfig
fix: update memory configuration retrieval to use getAppConfig based on user role
fix: update comment for AppConfig interface to clarify purpose
🏷️ refactor: Update tests to use getAppConfig for endpoint configurations
ci: Update AppService tests to initialize app config instead of app.locals
ci: Integrate getAppConfig into remaining tests
refactor: Update multer storage destination to use promise-based getAppConfig and improve error handling in tests
refactor: Rename initializeAppConfig to setAppConfig and update related tests
ci: Mock getAppConfig in various tests to provide default configurations
refactor: Update convertMCPToolsToPlugins to use mcpManager for server configuration and adjust related tests
chore: rename `Config/getAppConfig` -> `Config/app`
fix: streamline OpenAI image tools configuration by removing direct appConfig dependency and using function parameters
chore: correct parameter documentation for imageOutputType in ToolService.js
refactor: remove `getCustomConfig` dependency in config route
refactor: update domain validation to use appConfig for allowed domains
refactor: use appConfig registration property
chore: remove app parameter from AppService invocation
refactor: update AppConfig interface to correct registration and turnstile configurations
refactor: remove getCustomConfig dependency and use getAppConfig in PluginController, multer, and MCP services
refactor: replace getCustomConfig with getAppConfig in STTService, TTSService, and related files
refactor: replace getCustomConfig with getAppConfig in Conversation and Message models, update tempChatRetention functions to use AppConfig type
refactor: update getAppConfig calls in Conversation and Message models to include user role for temporary chat expiration
ci: update related tests
refactor: update getAppConfig call in getCustomConfigSpeech to include user role
fix: update appConfig usage to access allowedDomains from actions instead of registration
refactor: enhance AppConfig to include fileStrategies and update related file strategy logic
refactor: update imports to use normalizeEndpointName from @librechat/api and remove redundant definitions
chore: remove deprecated unused RunManager
refactor: get balance config primarily from appConfig
refactor: remove customConfig dependency for appConfig and streamline loadConfigModels logic
refactor: remove getCustomConfig usage and use app config in file citations
refactor: consolidate endpoint loading logic into loadEndpoints function
refactor: update appConfig access to use endpoints structure across various services
refactor: implement custom endpoints configuration and streamline endpoint loading logic
refactor: update getAppConfig call to include user role parameter
refactor: streamline endpoint configuration and enhance appConfig usage across services
refactor: replace getMCPAuthMap with getUserMCPAuthMap and remove unused getCustomConfig file
refactor: add type annotation for loadedEndpoints in loadEndpoints function
refactor: move /services/Files/images/parse to TS API
chore: add missing FILE_CITATIONS permission to IRole interface
refactor: restructure toolkits to TS API
refactor: separate manifest logic into its own module
refactor: consolidate tool loading logic into a new tools module for startup logic
refactor: move interface config logic to TS API
refactor: migrate checkEmailConfig to TypeScript and update imports
refactor: add FunctionTool interface and availableTools to AppConfig
refactor: decouple caching and DB operations from AppService, make part of consolidated `getAppConfig`
WIP: fix tests
* fix: rebase conflicts
* refactor: remove app.locals references
* refactor: replace getBalanceConfig with getAppConfig in various strategies and middleware
* refactor: replace appConfig?.balance with getBalanceConfig in various controllers and clients
* test: add balance configuration to titleConvo method in AgentClient tests
* chore: remove unused `openai-chat-tokens` package
* chore: remove unused imports in initializeMCPs.js
* refactor: update balance configuration to use getAppConfig instead of getBalanceConfig
* refactor: integrate configMiddleware for centralized configuration handling
* refactor: optimize email domain validation by removing unnecessary async calls
* refactor: simplify multer storage configuration by removing async calls
* refactor: reorder imports for better readability in user.js
* refactor: replace getAppConfig calls with req.config for improved performance
* chore: replace getAppConfig calls with req.config in tests for centralized configuration handling
* chore: remove unused override config
* refactor: add configMiddleware to endpoint route and replace getAppConfig with req.config
* chore: remove customConfig parameter from TTSService constructor
* refactor: pass appConfig from request to processFileCitations for improved configuration handling
* refactor: remove configMiddleware from endpoint route and retrieve appConfig directly in getEndpointsConfig if not in `req.config`
* test: add mockAppConfig to processFileCitations tests for improved configuration handling
* fix: pass req.config to hasCustomUserVars and call without await after synchronous refactor
* fix: type safety in useExportConversation
* refactor: retrieve appConfig using getAppConfig in PluginController and remove configMiddleware from plugins route, to avoid always retrieving when plugins are cached
* chore: change `MongoUser` typedef to `IUser`
* fix: Add `user` and `config` fields to ServerRequest and update JSDoc type annotations from Express.Request to ServerRequest
* fix: remove unused setAppConfig mock from Server configuration tests
* chore: Update @modelcontextprotocol/sdk to version 1.12.3 in package.json and package-lock.json
- Bump version of @modelcontextprotocol/sdk to 1.12.3 to incorporate recent updates.
- Update dependencies for ajv and cross-spawn to their latest versions.
- Add ajv as a new dependency in the sdk module.
- Include json-schema-traverse as a new dependency in the sdk module.
* feat: @librechat/auth
* feat: Add crypto module exports to auth package
- Introduced a new crypto module by creating index.ts in the crypto directory.
- Updated the main index.ts of the auth package to export from the new crypto module.
* feat: Update package dependencies and build scripts for auth package
- Added @librechat/auth as a dependency in package.json and package-lock.json.
- Updated build scripts to include the auth package in both frontend and bun build processes.
- Removed unused mongoose and openid-client dependencies from package-lock.json for cleaner dependency management.
* refactor: Migrate crypto utility functions to @librechat/auth
- Replaced local crypto utility imports with the new @librechat/auth package across multiple files.
- Removed the obsolete crypto.js file and its exports.
- Updated relevant services and models to utilize the new encryption and decryption methods from @librechat/auth.
* feat: Enhance OAuth token handling and update dependencies in auth package
* chore: Remove Token model and TokenService due to restructuring of OAuth handling
- Deleted the Token.js model and TokenService.js, which were responsible for managing OAuth tokens.
- This change is part of a broader refactor to streamline OAuth token management and improve code organization.
* refactor: imports from '@librechat/auth' to '@librechat/api' and add OAuth token handling functionality
* refactor: Simplify logger usage in MCP and FlowStateManager classes
* chore: fix imports
* feat: Add OAuth configuration schema to MCP with token exchange method support
* feat: FIRST PASS Implement MCP OAuth flow with token management and error handling
- Added a new route for handling OAuth callbacks and token retrieval.
- Integrated OAuth token storage and retrieval mechanisms.
- Enhanced MCP connection to support automatic OAuth flow initiation on 401 errors.
- Implemented dynamic client registration and metadata discovery for OAuth.
- Updated MCPManager to manage OAuth tokens and handle authentication requirements.
- Introduced comprehensive logging for OAuth processes and error handling.
* refactor: Update MCPConnection and MCPManager to utilize new URL handling
- Added a `url` property to MCPConnection for better URL management.
- Refactored MCPManager to use the new `url` property instead of a deprecated method for OAuth handling.
- Changed logging from info to debug level for flow manager and token methods initialization.
- Improved comments for clarity on existing tokens and OAuth event listener setup.
* refactor: Improve connection timeout error messages in MCPConnection and MCPManager and use initTimeout for connection
- Updated the connection timeout error messages to include the duration of the timeout.
- Introduced a configurable `connectTimeout` variable in both MCPConnection and MCPManager for better flexibility.
* chore: cleanup MCP OAuth Token exchange handling; fix: erroneous use of flowsCache and remove verbose logs
* refactor: Update MCPManager and MCPTokenStorage to use TokenMethods for token management
- Removed direct token storage handling in MCPManager and replaced it with TokenMethods for better abstraction.
- Refactored MCPTokenStorage methods to accept parameters for token operations, enhancing flexibility and readability.
- Improved logging messages related to token persistence and retrieval processes.
* refactor: Update MCP OAuth handling to use static methods and improve flow management
- Refactored MCPOAuthHandler to utilize static methods for initiating and completing OAuth flows, enhancing clarity and reducing instance dependencies.
- Updated MCPManager to pass flowManager explicitly to OAuth handling methods, improving flexibility in flow state management.
- Enhanced comments and logging for better understanding of OAuth processes and flow state retrieval.
* refactor: Integrate token methods into createMCPTool for enhanced token management
* refactor: Change logging from info to debug level in MCPOAuthHandler for improved log management
* chore: clean up logging
* feat: first pass, auth URL from MCP OAuth flow
* chore: Improve logging format for OAuth authentication URL display
* chore: cleanup mcp manager comments
* feat: add connection reconnection logic in MCPManager
* refactor: reorganize token storage handling in MCP
- Moved token storage logic from MCPManager to a new MCPTokenStorage class for better separation of concerns.
- Updated imports to reflect the new token storage structure.
- Enhanced methods for storing, retrieving, updating, and deleting OAuth tokens, improving overall token management.
* chore: update comment for SYSTEM_USER_ID in MCPManager for clarity
* feat: implement refresh token functionality in MCP
- Added refresh token handling in MCPManager to support token renewal for both app-level and user-specific connections.
- Introduced a refreshTokens function to facilitate token refresh logic.
- Enhanced MCPTokenStorage to manage client information and refresh token processes.
- Updated logging for better traceability during token operations.
* chore: cleanup @librechat/auth
* feat: implement MCP server initialization in a separate service
- Added a new service to handle the initialization of MCP servers, improving code organization and readability.
- Refactored the server startup logic to utilize the new initializeMCP function.
- Removed redundant MCP initialization code from the main server file.
* fix: don't log auth url for user connections
* feat: enhance OAuth flow with success and error handling components
- Updated OAuth callback routes to redirect to new success and error pages instead of sending status messages.
- Introduced `OAuthSuccess` and `OAuthError` components to provide user feedback during authentication.
- Added localization support for success and error messages in the translation files.
- Implemented countdown functionality in the success component for a better user experience.
* fix: refresh token handling for user connections, add missing URL and methods
- add standard enum for system user id and helper for determining app-lvel vs. user-level connections
* refactor: update token handling in MCPManager and MCPTokenStorage
* fix: improve error logging in OAuth authentication handler
* fix: concurrency issues for both login url emission and concurrency of oauth flows for shared flows (same user, same server, multiple calls for same server)
* fix: properly fail shared flows for concurrent server calls and prevent duplication of tokens
* chore: remove unused auth package directory from update configuration
* ci: fix mocks in samlStrategy tests
* ci: add mcpConfig to AppService test setup
* chore: remove obsolete MCP OAuth implementation documentation
* fix: update build script for API to use correct command
* chore: bump version of @librechat/api to 1.2.4
* fix: update abort signal handling in createMCPTool function
* fix: add optional clientInfo parameter to refreshTokensFunction metadata
* refactor: replace app.locals.availableTools with getCachedTools in multiple services and controllers for improved tool management
* fix: concurrent refresh token handling issue
* refactor: add signal parameter to getUserConnection method for improved abort handling
* chore: JSDoc typing for `loadEphemeralAgent`
* refactor: update isConnectionActive method to use destructured parameters for improved readability
* feat: implement caching for MCP tools to handle app-level disconnects for loading list of tools
* ci: fix agent test
* refactor: move model definitions and database-related methods to packages/data-schemas
* ci: update tests due to new DB structure
fix: disable mocking `librechat-data-provider`
feat: Add schema exports to data-schemas package
- Introduced a new schema module that exports various schemas including action, agent, and user schemas.
- Updated index.ts to include the new schema exports for better modularity and organization.
ci: fix appleStrategy tests
fix: Agent.spec.js
ci: refactor handleTools tests to use MongoMemoryServer for in-memory database
fix: getLogStores imports
ci: update banViolation tests to use MongoMemoryServer and improve session mocking
test: refactor samlStrategy tests to improve mock configurations and user handling
ci: fix crypto mock in handleText tests for improved accuracy
ci: refactor spendTokens tests to improve model imports and setup
ci: refactor Message model tests to use MongoMemoryServer and improve database interactions
* refactor: streamline IMessage interface and move feedback properties to types/message.ts
* refactor: use exported initializeRoles from `data-schemas`, remove api workspace version (this serves as an example of future migrations that still need to happen)
* refactor: update model imports to use destructuring from `~/db/models` for consistency and clarity
* refactor: remove unused mongoose imports from model files for cleaner code
* refactor: remove unused mongoose imports from Share, Prompt, and Transaction model files for cleaner code
* refactor: remove unused import in Transaction model for cleaner code
* ci: update deploy workflow to reference new Docker Dev Branch Images Build and add new workflow for building Docker images on dev branch
* chore: cleanup imports
* feat: integrate OpenID Connect support with token reuse
- Added `jwks-rsa` and `new-openid-client` dependencies for OpenID Connect functionality.
- Implemented OpenID token refresh logic in `AuthController`.
- Enhanced `LogoutController` to handle OpenID logout and session termination.
- Updated JWT authentication middleware to support OpenID token provider.
- Modified OAuth routes to accommodate OpenID authentication and token management.
- Created `setOpenIDAuthTokens` function to manage OpenID tokens in cookies.
- Upgraded OpenID strategy with user info fetching and token exchange protocol.
- Introduced `openIdJwtLogin` strategy for handling OpenID JWT tokens.
- Added caching mechanism for exchanged OpenID tokens.
- Updated configuration to include OpenID exchanged tokens cache key.
- updated .env.example to include the new env variables needed for the feature.
* fix: update return type in downloadImage documentation for clarity and fixed openIdJwtLogin env variables
* fix: update Jest configuration and tests for OpenID strategy integration
* fix: update OpenID strategy to include callback URL in setup
* fix: fix optionalJwtAuth middleware to support OpenID token reuse and improve currentUrl method in CustomOpenIDStrategy to override the dynamic host issue related to proxy (e.g. cloudfront)
* fix: fixed code formatting
* Fix: Add mocks for openid-client and passport strategy in Jest configuration to fix unit tests
* fix eslint errors: Format mock file openid-client.
* ✨ feat: Add PKCE support for OpenID and default handling in strategy setup
---------
Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com>
Co-authored-by: Ruben Talstra <RubenTalstra1211@outlook.com>
* fix: Update Gemini 2.5 Pro Preview Model Name in Token Values
* refactor: Update DeleteButton to close menu when deletion is successful
* refactor: Add unmountOnHide prop to DropdownPopup in multiple components
* chore: linting
* chore: linting
* feat: Add `chatMenu` option for MCP Servers to control visibility in MCPSelect dropdown
* refactor: Update loadManifestTools to return combined tool manifest with MCP tools first
* chore: remove deprecated openapi plugins
* chore: linting
* chore(AgentClient): linting, remove unnecessary `checkVisionRequest` logger
* refactor(AuthService): change logoutUser logging from error to debug level
* chore: new Gemini models token values and rates
* chore(AskController): linting
* 🔒 fix: update refresh token handling to use plain token instead of hashed token
* 🔒 fix: simplify logoutUser by using plain refresh token for session lookup
* ✨ feat: Implement session management with CRUD operations and integrate into user workflows
* ✨ refactor: Update session model import paths and enhance session creation logic in AuthService
* ✨ refactor: Validate session and user ID formats in session management functions
* ✨ style: Enhance UI components with improved styling and accessibility features
* chore: Update login form tests to use getByTestId instead of getByRole, remove console.log()
* chore: Update login form tests to use getByTestId instead of getByRole
---------
Co-authored-by: Danny Avila <danny@librechat.ai>
* feat: basic invite-user script
* feat: add invite user functionality and registration validation middleware
* fix: invite user fixes
* refactor: consolidate direct model access to a central place of functions
* style(Registration): add spinner to continue button
* refactor: import ordrer
* feat: improve invite user script and error handling
* fix: merge conflict
* refactor: remove `console.log` and use `logger`
* fix: token operation and checkinvite issues
* bring back comment and remove console log
* fix: return invalid token when token is not found
* fix: getInvite fix
* refactor: Update Token.js to use async/await syntax for update and delete operations
* feat: Refactor Token.js to use async/await syntax for createToken and findToken functions
* refactor(inviteUser): define functions outside of module.exports
* Update AuthService.js
---------
Co-authored-by: Danny Avila <danny@librechat.ai>
* refactor: scrollToEnd
* fix(validateConvoAccess): search conversation by ID for proper validation
* feat: Add unique index for conversationId and user in convoSchema
* refactor: Update font sizes 1 rem -> font-size-base in style.css
* fix: Assistants map type issues
* refactor: Remove obsolete scripts
* fix: Update DropdownNoState component to handle both string and OptionType values
* refactor: Remove config/loader.js file
* fix: remove crypto.randomBytes(); refactor: Create reusable function for generating token and hash
* chore: Update prompt description placeholder text
* fix: promptsPathPattern to not include new
* feat: command input and styling change for prompt views
* fix: intended validation
* feat: prompts slash command
* chore: localizations and fix add command during creation
* refactor(PromptsCommand): better label
* feat: update `allPrompGroups` cache on all promptGroups mutations
* refactor: ensure assistants builder is first within sidepanel
* refactor: allow defining emailVerified via create-user script