Merge pull request #1115 from danielaskdd/main
Hot fix: Server can not startup with legacy data (json_doc_status_impl.py)
This commit is contained in:
@@ -42,45 +42,38 @@ def get_auth_dependency():
|
|||||||
request: Request,
|
request: Request,
|
||||||
token: str = Depends(OAuth2PasswordBearer(tokenUrl="login", auto_error=False)),
|
token: str = Depends(OAuth2PasswordBearer(tokenUrl="login", auto_error=False)),
|
||||||
):
|
):
|
||||||
if request.url.path in whitelist:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Check if authentication is configured
|
# Check if authentication is configured
|
||||||
auth_configured = bool(
|
auth_configured = bool(
|
||||||
os.getenv("AUTH_USERNAME") and os.getenv("AUTH_PASSWORD")
|
os.getenv("AUTH_USERNAME") and os.getenv("AUTH_PASSWORD")
|
||||||
)
|
)
|
||||||
|
|
||||||
# If authentication is not configured, accept any token including guest tokens
|
# If authentication is not configured, skip all validation
|
||||||
if not auth_configured:
|
if not auth_configured:
|
||||||
if token: # If token is provided, still validate it
|
|
||||||
try:
|
|
||||||
# Validate token but don't raise exception
|
|
||||||
token_info = auth_handler.validate_token(token)
|
|
||||||
# Check if it's a guest token
|
|
||||||
if token_info.get("role") != "guest":
|
|
||||||
# Non-guest tokens are not valid when auth is not configured
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
# Ignore validation errors but log them
|
|
||||||
print(f"Token validation error (ignored): {str(e)}")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# If authentication is configured, validate the token and reject guest tokens
|
# For configured auth, allow whitelist paths without token
|
||||||
|
if request.url.path in whitelist:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Require token for all other paths when auth is configured
|
||||||
if not token:
|
if not token:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Token required"
|
status_code=status.HTTP_401_UNAUTHORIZED, detail="Token required"
|
||||||
)
|
)
|
||||||
|
|
||||||
token_info = auth_handler.validate_token(token)
|
try:
|
||||||
|
token_info = auth_handler.validate_token(token)
|
||||||
# Reject guest tokens when authentication is configured
|
# Reject guest tokens when authentication is configured
|
||||||
if token_info.get("role") == "guest":
|
if token_info.get("role") == "guest":
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Authentication required. Guest access not allowed when authentication is configured.",
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token"
|
||||||
detail="Authentication required. Guest access not allowed when authentication is configured.",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# At this point, we have a valid non-guest token
|
|
||||||
return
|
return
|
||||||
|
|
||||||
return dependency
|
return dependency
|
||||||
|
@@ -87,6 +87,9 @@ class JsonDocStatusStorage(DocStatusStorage):
|
|||||||
# If content is missing, use content_summary as content
|
# If content is missing, use content_summary as content
|
||||||
if "content" not in data and "content_summary" in data:
|
if "content" not in data and "content_summary" in data:
|
||||||
data["content"] = data["content_summary"]
|
data["content"] = data["content_summary"]
|
||||||
|
# If file_path is not in data, use document id as file path
|
||||||
|
if "file_path" not in data:
|
||||||
|
data["file_path"] = "no-file-path"
|
||||||
result[k] = DocProcessingStatus(**data)
|
result[k] = DocProcessingStatus(**data)
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
logger.error(f"Missing required field for document {k}: {e}")
|
logger.error(f"Missing required field for document {k}: {e}")
|
||||||
|
@@ -246,7 +246,8 @@ async def _merge_nodes_then_upsert(
|
|||||||
file_path = GRAPH_FIELD_SEP.join(
|
file_path = GRAPH_FIELD_SEP.join(
|
||||||
set([dp["metadata"]["file_path"] for dp in nodes_data] + already_file_paths)
|
set([dp["metadata"]["file_path"] for dp in nodes_data] + already_file_paths)
|
||||||
)
|
)
|
||||||
print(f"file_path: {file_path}")
|
|
||||||
|
logger.debug(f"file_path: {file_path}")
|
||||||
description = await _handle_entity_relation_summary(
|
description = await _handle_entity_relation_summary(
|
||||||
entity_name, description, global_config
|
entity_name, description, global_config
|
||||||
)
|
)
|
||||||
|
@@ -8,6 +8,8 @@ import { healthCheckInterval } from '@/lib/constants'
|
|||||||
import { useBackendState, useAuthStore } from '@/stores/state'
|
import { useBackendState, useAuthStore } 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'
|
||||||
|
|
||||||
@@ -19,7 +21,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)
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
import axios, { AxiosError } from 'axios'
|
import axios, { AxiosError } from 'axios'
|
||||||
import { backendBaseUrl, webuiPrefix } from '@/lib/constants'
|
import { backendBaseUrl } from '@/lib/constants'
|
||||||
import { errorMessage } from '@/lib/utils'
|
import { errorMessage } from '@/lib/utils'
|
||||||
import { useSettingsStore } from '@/stores/settings'
|
import { useSettingsStore } from '@/stores/settings'
|
||||||
import { useAuthStore } from '@/stores/state'
|
import { useAuthStore } from '@/stores/state'
|
||||||
|
import { navigationService } from '@/services/navigation'
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
export type LightragNodeType = {
|
export type LightragNodeType = {
|
||||||
@@ -157,21 +158,13 @@ axiosInstance.interceptors.request.use((config) => {
|
|||||||
const apiKey = useSettingsStore.getState().apiKey
|
const apiKey = useSettingsStore.getState().apiKey
|
||||||
const token = localStorage.getItem('LIGHTRAG-API-TOKEN');
|
const token = localStorage.getItem('LIGHTRAG-API-TOKEN');
|
||||||
|
|
||||||
// Check authentication status for paths that require authentication
|
// Always include token if it exists, regardless of path
|
||||||
const authRequiredPaths = ['/documents', '/graphs', '/query', '/health']; // Add all paths that require authentication
|
|
||||||
const isAuthRequired = authRequiredPaths.some(path => config.url?.includes(path));
|
|
||||||
|
|
||||||
if (isAuthRequired && !token && config.url !== '/login') {
|
|
||||||
// Cancel the request and return a rejected Promise
|
|
||||||
return Promise.reject(new Error('Authentication required'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (apiKey) {
|
|
||||||
config.headers['X-API-Key'] = apiKey
|
|
||||||
}
|
|
||||||
if (token) {
|
if (token) {
|
||||||
config.headers['Authorization'] = `Bearer ${token}`
|
config.headers['Authorization'] = `Bearer ${token}`
|
||||||
}
|
}
|
||||||
|
if (apiKey) {
|
||||||
|
config.headers['X-API-Key'] = apiKey
|
||||||
|
}
|
||||||
return config
|
return config
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -185,11 +178,11 @@ axiosInstance.interceptors.response.use(
|
|||||||
sessionStorage.clear();
|
sessionStorage.clear();
|
||||||
useAuthStore.getState().logout();
|
useAuthStore.getState().logout();
|
||||||
|
|
||||||
if (window.location.pathname !== `${webuiPrefix}/#/login`) {
|
// Use navigation service to handle redirection
|
||||||
window.location.href = `${webuiPrefix}/#/login`;
|
navigationService.navigateToLogin();
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject(error);
|
// Return a never-resolving promise to prevent further execution
|
||||||
|
return new Promise(() => {});
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`${error.response.status} ${error.response.statusText}\n${JSON.stringify(
|
`${error.response.status} ${error.response.statusText}\n${JSON.stringify(
|
||||||
|
@@ -45,7 +45,6 @@ export default function DocumentManager() {
|
|||||||
} else {
|
} else {
|
||||||
setDocs(null)
|
setDocs(null)
|
||||||
}
|
}
|
||||||
// console.log(docs)
|
|
||||||
} else {
|
} else {
|
||||||
setDocs(null)
|
setDocs(null)
|
||||||
}
|
}
|
||||||
|
@@ -141,7 +141,13 @@ const fetchGraph = async (label: string, maxDepth: number, minDegree: number) =>
|
|||||||
|
|
||||||
// Create a new graph instance with the raw graph data
|
// Create a new graph instance with the raw graph data
|
||||||
const createSigmaGraph = (rawGraph: RawGraph | null) => {
|
const createSigmaGraph = (rawGraph: RawGraph | null) => {
|
||||||
// Always create a new graph instance
|
// Skip graph creation if no data or empty nodes
|
||||||
|
if (!rawGraph || !rawGraph.nodes.length) {
|
||||||
|
console.log('No graph data available, skipping sigma graph creation');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new graph instance
|
||||||
const graph = new DirectedGraph()
|
const graph = new DirectedGraph()
|
||||||
|
|
||||||
// Add nodes from raw graph data
|
// Add nodes from raw graph data
|
||||||
@@ -242,7 +248,7 @@ const useLightrangeGraph = () => {
|
|||||||
if (!isFetching && !fetchInProgressRef.current &&
|
if (!isFetching && !fetchInProgressRef.current &&
|
||||||
(paramsChanged || !useGraphStore.getState().graphDataFetchAttempted)) {
|
(paramsChanged || !useGraphStore.getState().graphDataFetchAttempted)) {
|
||||||
|
|
||||||
// Only fetch data if the Graph tab is visible
|
// Only fetch data if the Graph tab is visible and we haven't attempted a fetch yet
|
||||||
if (!isGraphTabVisible) {
|
if (!isGraphTabVisible) {
|
||||||
console.log('Graph tab not visible, skipping data fetch');
|
console.log('Graph tab not visible, skipping data fetch');
|
||||||
return;
|
return;
|
||||||
|
17
lightrag_webui/src/services/navigation.ts
Normal file
17
lightrag_webui/src/services/navigation.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { NavigateFunction } from 'react-router-dom';
|
||||||
|
|
||||||
|
class NavigationService {
|
||||||
|
private navigate: NavigateFunction | null = null;
|
||||||
|
|
||||||
|
setNavigate(navigate: NavigateFunction) {
|
||||||
|
this.navigate = navigate;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateToLogin() {
|
||||||
|
if (this.navigate) {
|
||||||
|
this.navigate('/login');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const navigationService = new NavigationService();
|
Reference in New Issue
Block a user