LibreChat/client/src/routes/Layouts/Startup.tsx
Vamsi Konakanchi e978a934fc
📍 feat: Preserve Deep Link Destinations Through the Auth Redirect Flow (#10275)
* added support for url query param persistance

* refactor: authentication redirect handling

- Introduced utility functions for managing login redirects, including `persistRedirectToSession`, `buildLoginRedirectUrl`, and `getPostLoginRedirect`.
- Updated `Login` and `AuthContextProvider` components to utilize these utilities for improved redirect logic.
- Refactored `useAuthRedirect` to streamline navigation to the login page while preserving intended destinations.
- Cleaned up the `StartupLayout` to remove unnecessary redirect handling, ensuring a more straightforward navigation flow.
- Added a new `redirect.ts` file to encapsulate redirect-related logic, enhancing code organization and maintainability.

* fix: enhance safe redirect validation logic

- Updated the `isSafeRedirect` function to improve validation of redirect URLs.
- Ensured that only safe relative paths are accepted, specifically excluding paths that lead to the login page.
- Refactored the logic to streamline the checks for valid redirect targets.

* test: add unit tests for redirect utility functions

- Introduced comprehensive tests for `isSafeRedirect`, `buildLoginRedirectUrl`, `getPostLoginRedirect`, and `persistRedirectToSession` functions.
- Validated various scenarios including safe and unsafe redirects, URL encoding, and session storage behavior.
- Enhanced test coverage to ensure robust handling of redirect logic and prevent potential security issues.

* chore: streamline authentication and redirect handling

- Removed unused `useLocation` import from `AuthContextProvider` and replaced its usage with `window.location` for better clarity.
- Updated `StartupLayout` to check for pending redirects before navigating to the new chat page, ensuring users are directed appropriately based on their session state.
- Enhanced unit tests for `useAuthRedirect` to verify correct handling of redirect parameters, including encoding of the current path and query parameters.

* test: add unit tests for StartupLayout redirect behavior

- Introduced a new test suite for the StartupLayout component to validate redirect logic based on authentication status and session storage.
- Implemented tests to ensure correct navigation to the new conversation page when authenticated without pending redirects, and to prevent navigation when a redirect URL parameter or session storage redirect is present.
- Enhanced coverage for scenarios where users are not authenticated, ensuring robust handling of redirect conditions.

---------

Co-authored-by: Vamsi Konakanchi <vamsi.k@trackmind.com>
Co-authored-by: Danny Avila <danny@librechat.ai>
2026-02-25 22:21:19 -05:00

77 lines
2.3 KiB
TypeScript

import { useEffect, useState } from 'react';
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
import type { TStartupConfig } from 'librechat-data-provider';
import { TranslationKeys, useLocalize } from '~/hooks';
import { useGetStartupConfig } from '~/data-provider';
import AuthLayout from '~/components/Auth/AuthLayout';
import { REDIRECT_PARAM, SESSION_KEY } from '~/utils';
const headerMap: Record<string, TranslationKeys> = {
'/login': 'com_auth_welcome_back',
'/register': 'com_auth_create_account',
'/forgot-password': 'com_auth_reset_password',
'/reset-password': 'com_auth_reset_password',
'/login/2fa': 'com_auth_verify_your_identity',
};
export default function StartupLayout({ isAuthenticated }: { isAuthenticated?: boolean }) {
const [error, setError] = useState<TranslationKeys | null>(null);
const [headerText, setHeaderText] = useState<TranslationKeys | null>(null);
const [startupConfig, setStartupConfig] = useState<TStartupConfig | null>(null);
const {
data,
isFetching,
error: startupConfigError,
} = useGetStartupConfig({
enabled: isAuthenticated ? startupConfig === null : true,
});
const localize = useLocalize();
const navigate = useNavigate();
const location = useLocation();
useEffect(() => {
if (isAuthenticated) {
const hasPendingRedirect =
new URLSearchParams(window.location.search).has(REDIRECT_PARAM) ||
sessionStorage.getItem(SESSION_KEY) != null;
if (!hasPendingRedirect) {
navigate('/c/new', { replace: true });
}
}
if (data) {
setStartupConfig(data);
}
}, [isAuthenticated, navigate, data]);
useEffect(() => {
document.title = startupConfig?.appTitle || 'LibreChat';
}, [startupConfig?.appTitle]);
useEffect(() => {
setError(null);
setHeaderText(null);
}, [location.pathname]);
const contextValue = {
error,
setError,
headerText,
setHeaderText,
startupConfigError,
startupConfig,
isFetching,
};
return (
<AuthLayout
header={headerText ? localize(headerText) : localize(headerMap[location.pathname])}
isFetching={isFetching}
startupConfig={startupConfig}
startupConfigError={startupConfigError}
pathname={location.pathname}
error={error}
>
<Outlet context={contextValue} />
</AuthLayout>
);
}