LibreChat/api/server/middleware/oauthNavigation.js
Marco Beretta 146664e0d1
🔐 fix: Redirect OAuth Browser Navigations Instead of Rendering JSON (#15440)
* 🔐 fix: Redirect OAuth Browser Navigations Instead of Rendering JSON

The /oauth/* routes are reached by top-level browser navigation, so a
middleware that rejects one with a JSON body makes the browser render
that body as the page. Hitting the login rate limit during a social
sign-in dropped the user on a blank page showing:

  {"message": "Too many login attempts, please try again after 5 minutes."}

Mark those requests with markOAuthNavigation and have the two middlewares
that can reject them answer with a redirect to the login page carrying an
error code the client localizes. The login limiter stays a single instance
so the attempt budget is still shared with POST /api/auth/login, and that
route keeps its JSON 429 for the SPA.

* 🧪 fix: Address PR Review Bot Findings

chatgpt-codex-connector:
- Split the inline `type TranslationKeys` specifier out of the value
  import, per the standalone `import type` convention in CLAUDE.md. This
  also clears the Static checks import-sort failure.
- Cover the new OAuth error branches in Login.spec.tsx: each mapped
  `?error=` code raises its localized toast and the query is cleaned,
  and unmapped codes including prototype members raise nothing.
2026-08-31 21:13:47 -04:00

36 lines
1.2 KiB
JavaScript

const { redirectToAuthFailure } = require('@librechat/api');
/**
* Flags the request as a top-level browser navigation belonging to the OAuth flow.
*
* These routes are reached by the address bar (social button click, IdP redirect, SAML/Apple
* form post), never by fetch, so any middleware that rejects them with a JSON body renders that
* body as the page. Rejections must redirect back to the login page instead.
*
* @type {import('express').RequestHandler}
*/
const markOAuthNavigation = (req, _res, next) => {
req.isOAuthNavigation = true;
next();
};
/**
* @param {import('express').Request} req
* @returns {boolean} Whether the request is a top-level browser navigation in the OAuth flow.
*/
const isOAuthNavigation = (req) => req?.isOAuthNavigation === true;
/**
* Sends the browser back to the login page carrying an error code the client localizes.
*
* @param {import('express').Response} res
* @param {string} authFailedError - An `ErrorTypes` value.
*/
const redirectOAuthFailure = (res, authFailedError) =>
redirectToAuthFailure(res, { clientDomain: process.env.DOMAIN_CLIENT, authFailedError });
module.exports = {
markOAuthNavigation,
isOAuthNavigation,
redirectOAuthFailure,
};