2025-05-22 14:19:24 +02:00
|
|
|
const cookies = require('cookie');
|
2025-10-01 23:30:47 -04:00
|
|
|
const passport = require('passport');
|
🧵 feat: ALS Context Middleware, Tenant Threading, and Config Cache Invalidation (#12407)
* 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.
2026-03-26 17:35:00 -04:00
|
|
|
const { isEnabled, tenantContextMiddleware } = require('@librechat/api');
|
feat: Auth and User System (#205)
* server-side JWT auth implementation
* move oauth routes and strategies, fix bugs
* backend modifications for wiring up the frontend login and reg forms
* Add frontend data services for login and registration
* Add login and registration forms
* Implment auth context, functional client side auth
* protect routes with jwt auth
* finish local strategy (using local storage)
* Start setting up google auth
* disable token refresh, remove old auth middleware
* refactor client, add ApiErrorBoundary context
* disable google and facebook strategies
* fix: fix presets not displaying specific to user
* fix: fix issue with browser refresh
* fix: casing issue with User.js (#11)
* delete user.js to be renamed
* fix: fix casing issue with User.js
* comment out api error watcher temporarily
* fix: issue with api error watcher (#12)
* delete user.js to be renamed
* fix: fix casing issue with User.js
* comment out api error watcher temporarily
* feat: add google auth social login
* fix: make google login url dynamic based on dev/prod
* fix: bug where UI is briefly displayed before redirecting to login
* fix: fix cookie expires value for local auth
* Update README.md
* Update LOCAL_INSTALL structure
* Add local testing instructions
* Only load google strategy if client id and secret are provided
* Update .env.example files with new params
* fix issue with not redirecting to register form
* only show google login button if value is set in .env
* cleanup log messages
* Add label to button for google login on login form
* doc: fix client/server url values in .env.example
* feat: add error message details to registration failure
* Restore preventing paste on confirm password
* auto-login user after registering
* feat: forgot password (#24)
* make login/reg pages look like openai's
* add password reset data services
* new form designs similar to openai, add password reset pages
* add api's for password reset
* email utils for password reset
* remove bcrypt salt rounds from process.env
* refactor: restructure api auth code, consolidate routes (#25)
* add api's for password reset
* remove bcrypt salt rounds from process.env
* refactor: consolidate auth routes, use controller pattern
* refactor: code cleanup
* feat: migrate data to first user (#26)
* refactor: use /api for auth routes
* fix: use user id instead of username
* feat: migrate data to first user on register
* fix: fix social login routes after refactor (#27)
* refactor: use /api for auth routes
* fix: use user id instead of username
* feat: migrate data to first user on register
* fix: fix social login routes
* fix: issue with auto-login when logging out then logging in with new browser window (#28)
* refactor: use /api for auth routes
* fix: use user id instead of username
* feat: migrate data to first user on register
* fix: fix social login routes
* fix: fix issue with auto-login in new tab
* doc: Update README and .env.example files with user system information (#29)
* refactor: use /api for auth routes
* fix: use user id instead of username
* feat: migrate data to first user on register
* fix: fix social login routes
* fix: fix issue with auto-login in new tab
* doc: update README and .env.example files
* Fixup: LOCAL_INSTALL.md PS instructions (#200) (#30)
Co-authored-by: alfredo-f <alfredo.fomitchenko@mail.polimi.it>
* feat: send user with completion to protect against abuse (#31)
* Fixup: LOCAL_INSTALL.md PS instructions (#200)
* server-side JWT auth implementation
* move oauth routes and strategies, fix bugs
* backend modifications for wiring up the frontend login and reg forms
* Add frontend data services for login and registration
* Add login and registration forms
* Implment auth context, functional client side auth
* protect routes with jwt auth
* finish local strategy (using local storage)
* Start setting up google auth
* disable token refresh, remove old auth middleware
* refactor client, add ApiErrorBoundary context
* disable google and facebook strategies
* fix: fix presets not displaying specific to user
* fix: fix issue with browser refresh
* fix: casing issue with User.js (#11)
* delete user.js to be renamed
* fix: fix casing issue with User.js
* comment out api error watcher temporarily
* feat: add google auth social login
* fix: make google login url dynamic based on dev/prod
* fix: bug where UI is briefly displayed before redirecting to login
* fix: fix cookie expires value for local auth
* Only load google strategy if client id and secret are provided
* Update .env.example files with new params
* fix issue with not redirecting to register form
* only show google login button if value is set in .env
* cleanup log messages
* Add label to button for google login on login form
* doc: fix client/server url values in .env.example
* feat: add error message details to registration failure
* Restore preventing paste on confirm password
* auto-login user after registering
* feat: forgot password (#24)
* make login/reg pages look like openai's
* add password reset data services
* new form designs similar to openai, add password reset pages
* add api's for password reset
* email utils for password reset
* remove bcrypt salt rounds from process.env
* refactor: restructure api auth code, consolidate routes (#25)
* add api's for password reset
* remove bcrypt salt rounds from process.env
* refactor: consolidate auth routes, use controller pattern
* refactor: code cleanup
* feat: migrate data to first user (#26)
* refactor: use /api for auth routes
* fix: use user id instead of username
* feat: migrate data to first user on register
* fix: fix social login routes after refactor (#27)
* refactor: use /api for auth routes
* fix: use user id instead of username
* feat: migrate data to first user on register
* fix: fix social login routes
* fix: issue with auto-login when logging out then logging in with new browser window (#28)
* refactor: use /api for auth routes
* fix: use user id instead of username
* feat: migrate data to first user on register
* fix: fix social login routes
* fix: fix issue with auto-login in new tab
* doc: Update README and .env.example files with user system information (#29)
* refactor: use /api for auth routes
* fix: use user id instead of username
* feat: migrate data to first user on register
* fix: fix social login routes
* fix: fix issue with auto-login in new tab
* doc: update README and .env.example files
* Send user id to openai to protect against abuse
* add meilisearch to gitignore
* Remove webpack
---------
Co-authored-by: alfredo-f <alfredo.fomitchenko@mail.polimi.it>
---------
Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Co-authored-by: Alfredo Fomitchenko <alfredo.fomitchenko@mail.polimi.it>
2023-05-07 10:04:51 -07:00
|
|
|
|
2025-05-22 14:19:24 +02:00
|
|
|
/**
|
🧵 feat: ALS Context Middleware, Tenant Threading, and Config Cache Invalidation (#12407)
* 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.
2026-03-26 17:35:00 -04:00
|
|
|
* Custom Middleware to handle JWT authentication, with support for OpenID token reuse.
|
|
|
|
|
* Switches between JWT and OpenID authentication based on cookies and environment settings.
|
|
|
|
|
*
|
|
|
|
|
* After successful authentication (req.user populated), automatically chains into
|
|
|
|
|
* `tenantContextMiddleware` to propagate `req.user.tenantId` into AsyncLocalStorage
|
|
|
|
|
* for downstream Mongoose tenant isolation.
|
2025-05-22 14:19:24 +02:00
|
|
|
*/
|
|
|
|
|
const requireJwtAuth = (req, res, next) => {
|
|
|
|
|
const cookieHeader = req.headers.cookie;
|
|
|
|
|
const tokenProvider = cookieHeader ? cookies.parse(cookieHeader).token_provider : null;
|
|
|
|
|
|
🧵 feat: ALS Context Middleware, Tenant Threading, and Config Cache Invalidation (#12407)
* 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.
2026-03-26 17:35:00 -04:00
|
|
|
const strategy =
|
|
|
|
|
tokenProvider === 'openid' && isEnabled(process.env.OPENID_REUSE_TOKENS) ? 'openidJwt' : 'jwt';
|
2025-05-22 14:19:24 +02:00
|
|
|
|
🧵 feat: ALS Context Middleware, Tenant Threading, and Config Cache Invalidation (#12407)
* 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.
2026-03-26 17:35:00 -04:00
|
|
|
passport.authenticate(strategy, { session: false })(req, res, (err) => {
|
|
|
|
|
if (err) {
|
|
|
|
|
return next(err);
|
|
|
|
|
}
|
|
|
|
|
// req.user is now populated by passport — set up tenant ALS context
|
|
|
|
|
tenantContextMiddleware(req, res, next);
|
|
|
|
|
});
|
2025-05-22 14:19:24 +02:00
|
|
|
};
|
feat: Auth and User System (#205)
* server-side JWT auth implementation
* move oauth routes and strategies, fix bugs
* backend modifications for wiring up the frontend login and reg forms
* Add frontend data services for login and registration
* Add login and registration forms
* Implment auth context, functional client side auth
* protect routes with jwt auth
* finish local strategy (using local storage)
* Start setting up google auth
* disable token refresh, remove old auth middleware
* refactor client, add ApiErrorBoundary context
* disable google and facebook strategies
* fix: fix presets not displaying specific to user
* fix: fix issue with browser refresh
* fix: casing issue with User.js (#11)
* delete user.js to be renamed
* fix: fix casing issue with User.js
* comment out api error watcher temporarily
* fix: issue with api error watcher (#12)
* delete user.js to be renamed
* fix: fix casing issue with User.js
* comment out api error watcher temporarily
* feat: add google auth social login
* fix: make google login url dynamic based on dev/prod
* fix: bug where UI is briefly displayed before redirecting to login
* fix: fix cookie expires value for local auth
* Update README.md
* Update LOCAL_INSTALL structure
* Add local testing instructions
* Only load google strategy if client id and secret are provided
* Update .env.example files with new params
* fix issue with not redirecting to register form
* only show google login button if value is set in .env
* cleanup log messages
* Add label to button for google login on login form
* doc: fix client/server url values in .env.example
* feat: add error message details to registration failure
* Restore preventing paste on confirm password
* auto-login user after registering
* feat: forgot password (#24)
* make login/reg pages look like openai's
* add password reset data services
* new form designs similar to openai, add password reset pages
* add api's for password reset
* email utils for password reset
* remove bcrypt salt rounds from process.env
* refactor: restructure api auth code, consolidate routes (#25)
* add api's for password reset
* remove bcrypt salt rounds from process.env
* refactor: consolidate auth routes, use controller pattern
* refactor: code cleanup
* feat: migrate data to first user (#26)
* refactor: use /api for auth routes
* fix: use user id instead of username
* feat: migrate data to first user on register
* fix: fix social login routes after refactor (#27)
* refactor: use /api for auth routes
* fix: use user id instead of username
* feat: migrate data to first user on register
* fix: fix social login routes
* fix: issue with auto-login when logging out then logging in with new browser window (#28)
* refactor: use /api for auth routes
* fix: use user id instead of username
* feat: migrate data to first user on register
* fix: fix social login routes
* fix: fix issue with auto-login in new tab
* doc: Update README and .env.example files with user system information (#29)
* refactor: use /api for auth routes
* fix: use user id instead of username
* feat: migrate data to first user on register
* fix: fix social login routes
* fix: fix issue with auto-login in new tab
* doc: update README and .env.example files
* Fixup: LOCAL_INSTALL.md PS instructions (#200) (#30)
Co-authored-by: alfredo-f <alfredo.fomitchenko@mail.polimi.it>
* feat: send user with completion to protect against abuse (#31)
* Fixup: LOCAL_INSTALL.md PS instructions (#200)
* server-side JWT auth implementation
* move oauth routes and strategies, fix bugs
* backend modifications for wiring up the frontend login and reg forms
* Add frontend data services for login and registration
* Add login and registration forms
* Implment auth context, functional client side auth
* protect routes with jwt auth
* finish local strategy (using local storage)
* Start setting up google auth
* disable token refresh, remove old auth middleware
* refactor client, add ApiErrorBoundary context
* disable google and facebook strategies
* fix: fix presets not displaying specific to user
* fix: fix issue with browser refresh
* fix: casing issue with User.js (#11)
* delete user.js to be renamed
* fix: fix casing issue with User.js
* comment out api error watcher temporarily
* feat: add google auth social login
* fix: make google login url dynamic based on dev/prod
* fix: bug where UI is briefly displayed before redirecting to login
* fix: fix cookie expires value for local auth
* Only load google strategy if client id and secret are provided
* Update .env.example files with new params
* fix issue with not redirecting to register form
* only show google login button if value is set in .env
* cleanup log messages
* Add label to button for google login on login form
* doc: fix client/server url values in .env.example
* feat: add error message details to registration failure
* Restore preventing paste on confirm password
* auto-login user after registering
* feat: forgot password (#24)
* make login/reg pages look like openai's
* add password reset data services
* new form designs similar to openai, add password reset pages
* add api's for password reset
* email utils for password reset
* remove bcrypt salt rounds from process.env
* refactor: restructure api auth code, consolidate routes (#25)
* add api's for password reset
* remove bcrypt salt rounds from process.env
* refactor: consolidate auth routes, use controller pattern
* refactor: code cleanup
* feat: migrate data to first user (#26)
* refactor: use /api for auth routes
* fix: use user id instead of username
* feat: migrate data to first user on register
* fix: fix social login routes after refactor (#27)
* refactor: use /api for auth routes
* fix: use user id instead of username
* feat: migrate data to first user on register
* fix: fix social login routes
* fix: issue with auto-login when logging out then logging in with new browser window (#28)
* refactor: use /api for auth routes
* fix: use user id instead of username
* feat: migrate data to first user on register
* fix: fix social login routes
* fix: fix issue with auto-login in new tab
* doc: Update README and .env.example files with user system information (#29)
* refactor: use /api for auth routes
* fix: use user id instead of username
* feat: migrate data to first user on register
* fix: fix social login routes
* fix: fix issue with auto-login in new tab
* doc: update README and .env.example files
* Send user id to openai to protect against abuse
* add meilisearch to gitignore
* Remove webpack
---------
Co-authored-by: alfredo-f <alfredo.fomitchenko@mail.polimi.it>
---------
Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
Co-authored-by: Alfredo Fomitchenko <alfredo.fomitchenko@mail.polimi.it>
2023-05-07 10:04:51 -07:00
|
|
|
|
|
|
|
|
module.exports = requireJwtAuth;
|