yangdx commited on
Commit
9564069
·
1 Parent(s): 3a62f77

Refactor auth and version checks for improved reliability.

Browse files

- Prevent duplicate version checks in Vite dev mode
- Simplify protected route handling
- Add session flags for version tracking

lightrag_webui/src/App.tsx CHANGED
@@ -1,13 +1,13 @@
1
- import { useState, useCallback } from 'react'
2
  import ThemeProvider from '@/components/ThemeProvider'
3
  import TabVisibilityProvider from '@/contexts/TabVisibilityProvider'
4
  import MessageAlert from '@/components/MessageAlert'
5
  import ApiKeyAlert from '@/components/ApiKeyAlert'
6
  import StatusIndicator from '@/components/graph/StatusIndicator'
7
  import { healthCheckInterval } from '@/lib/constants'
8
- import { useBackendState } from '@/stores/state'
9
  import { useSettingsStore } from '@/stores/settings'
10
- import { useEffect } from 'react'
11
  import SiteHeader from '@/features/SiteHeader'
12
  import { InvalidApiKeyError, RequireApiKeError } from '@/api/lightrag'
13
 
@@ -23,17 +23,63 @@ function App() {
23
  const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
24
  const currentTab = useSettingsStore.use.currentTab()
25
  const [apiKeyInvalid, setApiKeyInvalid] = useState(false)
 
26
 
27
- // Health check
28
  useEffect(() => {
29
- // Check immediately
30
- useBackendState.getState().check()
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
- const interval = setInterval(async () => {
33
- await useBackendState.getState().check()
34
- }, healthCheckInterval * 1000)
35
- return () => clearInterval(interval)
36
- }, [enableHealthCheck])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
  const handleTabChange = useCallback(
39
  (tab: string) => useSettingsStore.getState().setCurrentTab(tab as any),
 
1
+ import { useState, useCallback, useEffect, useRef } from 'react'
2
  import ThemeProvider from '@/components/ThemeProvider'
3
  import TabVisibilityProvider from '@/contexts/TabVisibilityProvider'
4
  import MessageAlert from '@/components/MessageAlert'
5
  import ApiKeyAlert from '@/components/ApiKeyAlert'
6
  import StatusIndicator from '@/components/graph/StatusIndicator'
7
  import { healthCheckInterval } from '@/lib/constants'
8
+ import { useBackendState, useAuthStore } from '@/stores/state'
9
  import { useSettingsStore } from '@/stores/settings'
10
+ import { getAuthStatus } from '@/api/lightrag'
11
  import SiteHeader from '@/features/SiteHeader'
12
  import { InvalidApiKeyError, RequireApiKeError } from '@/api/lightrag'
13
 
 
23
  const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
24
  const currentTab = useSettingsStore.use.currentTab()
25
  const [apiKeyInvalid, setApiKeyInvalid] = useState(false)
26
+ const versionCheckRef = useRef(false); // Prevent duplicate calls in Vite dev mode
27
 
28
+ // Health check - can be disabled
29
  useEffect(() => {
30
+ // Only execute if health check is enabled
31
+ if (!enableHealthCheck) return;
32
+
33
+ // Health check function
34
+ const performHealthCheck = async () => {
35
+ await useBackendState.getState().check();
36
+ };
37
+
38
+ // Execute immediately
39
+ performHealthCheck();
40
+
41
+ // Set interval for periodic execution
42
+ const interval = setInterval(performHealthCheck, healthCheckInterval * 1000);
43
+ return () => clearInterval(interval);
44
+ }, [enableHealthCheck]);
45
 
46
+ // Version check - independent and executed only once
47
+ useEffect(() => {
48
+ const checkVersion = async () => {
49
+ // Prevent duplicate calls in Vite dev mode
50
+ if (versionCheckRef.current) return;
51
+ versionCheckRef.current = true;
52
+
53
+ // Check if version info was already obtained in login page
54
+ const versionCheckedFromLogin = sessionStorage.getItem('VERSION_CHECKED_FROM_LOGIN') === 'true';
55
+ if (versionCheckedFromLogin) return;
56
+
57
+ // Get version info
58
+ const token = localStorage.getItem('LIGHTRAG-API-TOKEN');
59
+ if (!token) return;
60
+
61
+ try {
62
+ const status = await getAuthStatus();
63
+ if (status.core_version || status.api_version) {
64
+ // Update version info while maintaining login state
65
+ useAuthStore.getState().login(
66
+ token,
67
+ useAuthStore.getState().isGuestMode,
68
+ status.core_version,
69
+ status.api_version
70
+ );
71
+
72
+ // Set flag to indicate version info has been checked
73
+ sessionStorage.setItem('VERSION_CHECKED_FROM_LOGIN', 'true');
74
+ }
75
+ } catch (error) {
76
+ console.error('Failed to get version info:', error);
77
+ }
78
+ };
79
+
80
+ // Execute version check
81
+ checkVersion();
82
+ }, []); // Empty dependency array ensures it only runs once on mount
83
 
84
  const handleTabChange = useCallback(
85
  (tab: string) => useSettingsStore.getState().setCurrentTab(tab as any),
lightrag_webui/src/AppRouter.tsx CHANGED
@@ -2,98 +2,11 @@ import { HashRouter as Router, Routes, Route, useNavigate } from 'react-router-d
2
  import { useEffect, useState } from 'react'
3
  import { useAuthStore } from '@/stores/state'
4
  import { navigationService } from '@/services/navigation'
5
- import { getAuthStatus } from '@/api/lightrag'
6
- import { toast } from 'sonner'
7
  import { Toaster } from 'sonner'
8
  import App from './App'
9
  import LoginPage from '@/features/LoginPage'
10
  import ThemeProvider from '@/components/ThemeProvider'
11
 
12
- interface ProtectedRouteProps {
13
- children: React.ReactNode
14
- }
15
-
16
- const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
17
- const { isAuthenticated } = useAuthStore()
18
- const [isChecking, setIsChecking] = useState(true)
19
- const navigate = useNavigate()
20
-
21
- // Set navigate function for navigation service
22
- useEffect(() => {
23
- navigationService.setNavigate(navigate)
24
- }, [navigate])
25
-
26
- useEffect(() => {
27
- let isMounted = true; // Flag to prevent state updates after unmount
28
-
29
- // This effect will run when the component mounts
30
- // and will check if authentication is required
31
- const checkAuthStatus = async () => {
32
- try {
33
- // Skip check if already authenticated
34
- if (isAuthenticated) {
35
- if (isMounted) setIsChecking(false);
36
- return;
37
- }
38
-
39
- const status = await getAuthStatus()
40
-
41
- // Only proceed if component is still mounted
42
- if (!isMounted) return;
43
-
44
- if (!status.auth_configured && status.access_token) {
45
- // If auth is not configured, use the guest token
46
- useAuthStore.getState().login(status.access_token, true, status.core_version, status.api_version)
47
- if (status.message) {
48
- toast.info(status.message)
49
- }
50
- }
51
- } catch (error) {
52
- console.error('Failed to check auth status:', error)
53
- } finally {
54
- // Only update state if component is still mounted
55
- if (isMounted) {
56
- setIsChecking(false)
57
- }
58
- }
59
- }
60
-
61
- // Execute immediately
62
- checkAuthStatus()
63
-
64
- // Cleanup function to prevent state updates after unmount
65
- return () => {
66
- isMounted = false;
67
- }
68
- }, [isAuthenticated])
69
-
70
- // Handle navigation when authentication status changes
71
- useEffect(() => {
72
- if (!isChecking && !isAuthenticated) {
73
- const currentPath = window.location.hash.slice(1); // Remove the '#' from hash
74
- const isLoginPage = currentPath === '/login';
75
-
76
- if (!isLoginPage) {
77
- // Use navigation service for redirection
78
- console.log('Not authenticated, redirecting to login');
79
- navigationService.navigateToLogin();
80
- }
81
- }
82
- }, [isChecking, isAuthenticated]);
83
-
84
- // Show nothing while checking auth status or when not authenticated on login page
85
- if (isChecking || (!isAuthenticated && window.location.hash.slice(1) === '/login')) {
86
- return null;
87
- }
88
-
89
- // Show children only when authenticated
90
- if (!isAuthenticated) {
91
- return null;
92
- }
93
-
94
- return <>{children}</>;
95
- }
96
-
97
  const AppContent = () => {
98
  const [initializing, setInitializing] = useState(true)
99
  const { isAuthenticated } = useAuthStore()
@@ -104,34 +17,20 @@ const AppContent = () => {
104
  navigationService.setNavigate(navigate)
105
  }, [navigate])
106
 
107
- // Check token validity and auth configuration on app initialization
108
  useEffect(() => {
109
- let isMounted = true; // Flag to prevent state updates after unmount
110
 
111
  const checkAuth = async () => {
112
  try {
113
  const token = localStorage.getItem('LIGHTRAG-API-TOKEN')
114
 
115
- // If we have a token, we're already authenticated
116
  if (token && isAuthenticated) {
117
  if (isMounted) setInitializing(false);
118
  return;
119
  }
120
 
121
- // If no token or not authenticated, check if auth is configured
122
- const status = await getAuthStatus()
123
-
124
- // Only proceed if component is still mounted
125
- if (!isMounted) return;
126
-
127
- if (!status.auth_configured && status.access_token) {
128
- // If auth is not configured, use the guest token
129
- useAuthStore.getState().login(status.access_token, true, status.core_version, status.api_version)
130
- if (status.message) {
131
- toast.info(status.message)
132
- }
133
- } else if (!token) {
134
- // Only logout if we don't have a token
135
  useAuthStore.getState().logout()
136
  }
137
  } catch (error) {
@@ -140,22 +39,30 @@ const AppContent = () => {
140
  useAuthStore.getState().logout()
141
  }
142
  } finally {
143
- // Only update state if component is still mounted
144
  if (isMounted) {
145
  setInitializing(false)
146
  }
147
  }
148
  }
149
 
150
- // Execute immediately
151
  checkAuth()
152
 
153
- // Cleanup function to prevent state updates after unmount
154
  return () => {
155
  isMounted = false;
156
  }
157
  }, [isAuthenticated])
158
 
 
 
 
 
 
 
 
 
 
 
 
159
  // Show nothing while initializing
160
  if (initializing) {
161
  return null
@@ -166,11 +73,7 @@ const AppContent = () => {
166
  <Route path="/login" element={<LoginPage />} />
167
  <Route
168
  path="/*"
169
- element={
170
- <ProtectedRoute>
171
- <App />
172
- </ProtectedRoute>
173
- }
174
  />
175
  </Routes>
176
  )
 
2
  import { useEffect, useState } from 'react'
3
  import { useAuthStore } from '@/stores/state'
4
  import { navigationService } from '@/services/navigation'
 
 
5
  import { Toaster } from 'sonner'
6
  import App from './App'
7
  import LoginPage from '@/features/LoginPage'
8
  import ThemeProvider from '@/components/ThemeProvider'
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  const AppContent = () => {
11
  const [initializing, setInitializing] = useState(true)
12
  const { isAuthenticated } = useAuthStore()
 
17
  navigationService.setNavigate(navigate)
18
  }, [navigate])
19
 
20
+ // Token validity check
21
  useEffect(() => {
22
+ let isMounted = true;
23
 
24
  const checkAuth = async () => {
25
  try {
26
  const token = localStorage.getItem('LIGHTRAG-API-TOKEN')
27
 
 
28
  if (token && isAuthenticated) {
29
  if (isMounted) setInitializing(false);
30
  return;
31
  }
32
 
33
+ if (!token) {
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  useAuthStore.getState().logout()
35
  }
36
  } catch (error) {
 
39
  useAuthStore.getState().logout()
40
  }
41
  } finally {
 
42
  if (isMounted) {
43
  setInitializing(false)
44
  }
45
  }
46
  }
47
 
 
48
  checkAuth()
49
 
 
50
  return () => {
51
  isMounted = false;
52
  }
53
  }, [isAuthenticated])
54
 
55
+ // Redirect effect for protected routes
56
+ useEffect(() => {
57
+ if (!initializing && !isAuthenticated) {
58
+ const currentPath = window.location.hash.slice(1);
59
+ if (currentPath !== '/login') {
60
+ console.log('Not authenticated, redirecting to login');
61
+ navigate('/login');
62
+ }
63
+ }
64
+ }, [initializing, isAuthenticated, navigate]);
65
+
66
  // Show nothing while initializing
67
  if (initializing) {
68
  return null
 
73
  <Route path="/login" element={<LoginPage />} />
74
  <Route
75
  path="/*"
76
+ element={isAuthenticated ? <App /> : null}
 
 
 
 
77
  />
78
  </Routes>
79
  )
lightrag_webui/src/features/LoginPage.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { useState, useEffect } from 'react'
2
  import { useNavigate } from 'react-router-dom'
3
  import { useAuthStore } from '@/stores/state'
4
  import { loginToServer, getAuthStatus } from '@/api/lightrag'
@@ -18,6 +18,7 @@ const LoginPage = () => {
18
  const [username, setUsername] = useState('')
19
  const [password, setPassword] = useState('')
20
  const [checkingAuth, setCheckingAuth] = useState(true)
 
21
 
22
  useEffect(() => {
23
  console.log('LoginPage mounted')
@@ -28,6 +29,13 @@ const LoginPage = () => {
28
  let isMounted = true; // Flag to prevent state updates after unmount
29
 
30
  const checkAuthConfig = async () => {
 
 
 
 
 
 
 
31
  try {
32
  // If already authenticated, redirect to home
33
  if (isAuthenticated) {
@@ -37,6 +45,17 @@ const LoginPage = () => {
37
 
38
  // Check auth status
39
  const status = await getAuthStatus()
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  // Only proceed if component is still mounted
42
  if (!isMounted) return;
@@ -48,16 +67,16 @@ const LoginPage = () => {
48
  toast.info(status.message)
49
  }
50
  navigate('/')
51
- return // Exit early, no need to set checkingAuth to false
52
  }
53
  } catch (error) {
54
  console.error('Failed to check auth configuration:', error)
55
- } finally {
56
- // Only update state if component is still mounted
57
  if (isMounted) {
58
- setCheckingAuth(false)
59
  }
60
  }
 
61
  }
62
 
63
  // Execute immediately
@@ -88,6 +107,11 @@ const LoginPage = () => {
88
  // Check authentication mode
89
  const isGuestMode = response.auth_mode === 'disabled'
90
  login(response.access_token, isGuestMode, response.core_version, response.api_version)
 
 
 
 
 
91
 
92
  if (isGuestMode) {
93
  // Show authentication disabled notification
 
1
+ import { useState, useEffect, useRef } from 'react'
2
  import { useNavigate } from 'react-router-dom'
3
  import { useAuthStore } from '@/stores/state'
4
  import { loginToServer, getAuthStatus } from '@/api/lightrag'
 
18
  const [username, setUsername] = useState('')
19
  const [password, setPassword] = useState('')
20
  const [checkingAuth, setCheckingAuth] = useState(true)
21
+ const authCheckRef = useRef(false); // Prevent duplicate calls in Vite dev mode
22
 
23
  useEffect(() => {
24
  console.log('LoginPage mounted')
 
29
  let isMounted = true; // Flag to prevent state updates after unmount
30
 
31
  const checkAuthConfig = async () => {
32
+ // Prevent duplicate calls in Vite dev mode
33
+ if (authCheckRef.current) {
34
+ if (isMounted) setCheckingAuth(false);
35
+ return;
36
+ }
37
+ authCheckRef.current = true;
38
+
39
  try {
40
  // If already authenticated, redirect to home
41
  if (isAuthenticated) {
 
45
 
46
  // Check auth status
47
  const status = await getAuthStatus()
48
+
49
+ // Set checkingAuth to false immediately after getAuthStatus
50
+ // This allows the login page to render while other processing continues
51
+ if (isMounted) {
52
+ setCheckingAuth(false);
53
+ }
54
+
55
+ // Set session flag for version check to avoid duplicate checks in App component
56
+ if (isMounted && (status.core_version || status.api_version)) {
57
+ sessionStorage.setItem('VERSION_CHECKED_FROM_LOGIN', 'true');
58
+ }
59
 
60
  // Only proceed if component is still mounted
61
  if (!isMounted) return;
 
67
  toast.info(status.message)
68
  }
69
  navigate('/')
70
+ return
71
  }
72
  } catch (error) {
73
  console.error('Failed to check auth configuration:', error)
74
+ // Also set checkingAuth to false in case of error
 
75
  if (isMounted) {
76
+ setCheckingAuth(false);
77
  }
78
  }
79
+ // Removed finally block as we're setting checkingAuth earlier
80
  }
81
 
82
  // Execute immediately
 
107
  // Check authentication mode
108
  const isGuestMode = response.auth_mode === 'disabled'
109
  login(response.access_token, isGuestMode, response.core_version, response.api_version)
110
+
111
+ // Set session flag for version check
112
+ if (response.core_version || response.api_version) {
113
+ sessionStorage.setItem('VERSION_CHECKED_FROM_LOGIN', 'true');
114
+ }
115
 
116
  if (isGuestMode) {
117
  // Show authentication disabled notification