Refactor navigation and authentication flow
- Move navigation setup to AppRouter - Prevent protected route logic to handle login 401
This commit is contained in:
@@ -5,11 +5,9 @@ import MessageAlert from '@/components/MessageAlert'
|
|||||||
import ApiKeyAlert from '@/components/ApiKeyAlert'
|
import ApiKeyAlert from '@/components/ApiKeyAlert'
|
||||||
import StatusIndicator from '@/components/graph/StatusIndicator'
|
import StatusIndicator from '@/components/graph/StatusIndicator'
|
||||||
import { healthCheckInterval } from '@/lib/constants'
|
import { healthCheckInterval } from '@/lib/constants'
|
||||||
import { useBackendState, useAuthStore } from '@/stores/state'
|
import { useBackendState } from '@/stores/state'
|
||||||
import { useSettingsStore } from '@/stores/settings'
|
import { useSettingsStore } from '@/stores/settings'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
|
||||||
import { navigationService } from '@/services/navigation'
|
|
||||||
import SiteHeader from '@/features/SiteHeader'
|
import SiteHeader from '@/features/SiteHeader'
|
||||||
import { InvalidApiKeyError, RequireApiKeError } from '@/api/lightrag'
|
import { InvalidApiKeyError, RequireApiKeError } from '@/api/lightrag'
|
||||||
|
|
||||||
@@ -21,22 +19,13 @@ import ApiSite from '@/features/ApiSite'
|
|||||||
import { Tabs, TabsContent } from '@/components/ui/Tabs'
|
import { Tabs, TabsContent } from '@/components/ui/Tabs'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const navigate = useNavigate();
|
|
||||||
const message = useBackendState.use.message()
|
const message = useBackendState.use.message()
|
||||||
|
|
||||||
// Initialize navigation service
|
|
||||||
useEffect(() => {
|
|
||||||
navigationService.setNavigate(navigate);
|
|
||||||
}, [navigate]);
|
|
||||||
const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
|
const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
|
||||||
const currentTab = useSettingsStore.use.currentTab()
|
const currentTab = useSettingsStore.use.currentTab()
|
||||||
const [apiKeyInvalid, setApiKeyInvalid] = useState(false)
|
const [apiKeyInvalid, setApiKeyInvalid] = useState(false)
|
||||||
|
|
||||||
// Health check
|
// Health check
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { isAuthenticated } = useAuthStore.getState();
|
|
||||||
if (!enableHealthCheck || !isAuthenticated) return
|
|
||||||
|
|
||||||
// Check immediately
|
// Check immediately
|
||||||
useBackendState.getState().check()
|
useBackendState.getState().check()
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { HashRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
|
import { HashRouter as Router, Routes, Route, useNavigate } from 'react-router-dom'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useAuthStore } from '@/stores/state'
|
import { useAuthStore } from '@/stores/state'
|
||||||
import { navigationService } from '@/services/navigation'
|
import { navigationService } from '@/services/navigation'
|
||||||
@@ -16,6 +16,12 @@ interface ProtectedRouteProps {
|
|||||||
const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
|
const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
|
||||||
const { isAuthenticated } = useAuthStore()
|
const { isAuthenticated } = useAuthStore()
|
||||||
const [isChecking, setIsChecking] = useState(true)
|
const [isChecking, setIsChecking] = useState(true)
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
// Set navigate function for navigation service
|
||||||
|
useEffect(() => {
|
||||||
|
navigationService.setNavigate(navigate)
|
||||||
|
}, [navigate])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isMounted = true; // Flag to prevent state updates after unmount
|
let isMounted = true; // Flag to prevent state updates after unmount
|
||||||
@@ -71,29 +77,33 @@ const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
|
|||||||
// Get current path and check if it's a direct access
|
// Get current path and check if it's a direct access
|
||||||
const currentPath = window.location.hash.slice(1); // Remove the '#' from hash
|
const currentPath = window.location.hash.slice(1); // Remove the '#' from hash
|
||||||
const isLoginPage = currentPath === '/login';
|
const isLoginPage = currentPath === '/login';
|
||||||
const isDirectAccess = !document.referrer;
|
|
||||||
|
|
||||||
// Handle direct access to root path
|
|
||||||
if (isDirectAccess && currentPath === '/') {
|
|
||||||
navigationService.resetAllApplicationState();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip redirect if already on login page
|
// Skip redirect if already on login page
|
||||||
if (isLoginPage) {
|
if (isLoginPage) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use React Router's Navigate for redirection
|
// For non-login pages, handle state reset and navigation
|
||||||
console.log('Not authenticated, redirecting to login');
|
if (!isLoginPage) {
|
||||||
return <Navigate to="/login" replace />;
|
// Use navigation service for redirection
|
||||||
|
console.log('Not authenticated, redirecting to login');
|
||||||
|
navigationService.navigateToLogin();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>{children}</>
|
return <>{children}</>
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppRouter = () => {
|
const AppContent = () => {
|
||||||
const [initializing, setInitializing] = useState(true)
|
const [initializing, setInitializing] = useState(true)
|
||||||
const { isAuthenticated } = useAuthStore()
|
const { isAuthenticated } = useAuthStore()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
// Set navigate function for navigation service
|
||||||
|
useEffect(() => {
|
||||||
|
navigationService.setNavigate(navigate)
|
||||||
|
}, [navigate])
|
||||||
|
|
||||||
// Check token validity and auth configuration on app initialization
|
// Check token validity and auth configuration on app initialization
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -152,21 +162,27 @@ const AppRouter = () => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Routes>
|
||||||
|
<Route path="/login" element={<LoginPage />} />
|
||||||
|
<Route
|
||||||
|
path="/*"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<App />
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppRouter = () => {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<AppContent />
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Toaster position="bottom-center" />
|
||||||
<Route
|
|
||||||
path="/*"
|
|
||||||
element={
|
|
||||||
<ProtectedRoute>
|
|
||||||
<App />
|
|
||||||
</ProtectedRoute>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Routes>
|
|
||||||
<Toaster position="top-center" />
|
|
||||||
</Router>
|
</Router>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
)
|
)
|
||||||
|
@@ -173,9 +173,12 @@ axiosInstance.interceptors.response.use(
|
|||||||
(error: AxiosError) => {
|
(error: AxiosError) => {
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
// Use navigation service to handle redirection
|
// For login API, throw error directly
|
||||||
|
if (error.config?.url?.includes('/login')) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
// For other APIs, navigate to login page
|
||||||
navigationService.navigateToLogin();
|
navigationService.navigateToLogin();
|
||||||
|
|
||||||
// Return a never-resolving promise to prevent further execution
|
// Return a never-resolving promise to prevent further execution
|
||||||
return new Promise(() => {});
|
return new Promise(() => {});
|
||||||
}
|
}
|
||||||
|
@@ -48,7 +48,7 @@ const LoginPage = () => {
|
|||||||
toast.info(status.message)
|
toast.info(status.message)
|
||||||
}
|
}
|
||||||
navigate('/')
|
navigate('/')
|
||||||
return; // Exit early, no need to set checkingAuth to false
|
return // Exit early, no need to set checkingAuth to false
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to check auth configuration:', error)
|
console.error('Failed to check auth configuration:', error)
|
||||||
@@ -96,10 +96,16 @@ const LoginPage = () => {
|
|||||||
toast.success(t('login.successMessage'))
|
toast.success(t('login.successMessage'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Navigate to home page after successful login
|
||||||
navigate('/')
|
navigate('/')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Login failed...', error)
|
console.error('Login failed...', error)
|
||||||
toast.error(t('login.errorInvalidCredentials'))
|
toast.error(t('login.errorInvalidCredentials'))
|
||||||
|
|
||||||
|
// Clear any existing auth state
|
||||||
|
useAuthStore.getState().logout()
|
||||||
|
// Clear local storage
|
||||||
|
localStorage.removeItem('LIGHTRAG-API-TOKEN')
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
|
@@ -61,13 +61,28 @@ class NavigationService {
|
|||||||
* @param skipReset whether to skip state reset (used for direct access scenario where reset is already handled)
|
* @param skipReset whether to skip state reset (used for direct access scenario where reset is already handled)
|
||||||
*/
|
*/
|
||||||
navigateToLogin() {
|
navigateToLogin() {
|
||||||
|
if (!this.navigate) {
|
||||||
this.resetAllApplicationState();
|
console.error('Navigation function not set');
|
||||||
useAuthStore.getState().logout();
|
return;
|
||||||
|
|
||||||
if (this.navigate) {
|
|
||||||
this.navigate('/login');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First navigate to login page
|
||||||
|
this.navigate('/login');
|
||||||
|
|
||||||
|
// Then reset state after navigation
|
||||||
|
setTimeout(() => {
|
||||||
|
this.resetAllApplicationState();
|
||||||
|
useAuthStore.getState().logout();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateToHome() {
|
||||||
|
if (!this.navigate) {
|
||||||
|
console.error('Navigation function not set');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.navigate('/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user