Require auth on all data API routes, not just /api/auth/me

get_current_user was only wired into one of 75 routes. Everything
else - chat, kb crud, uploads, projects, jobs, mcp management - had
no auth dependency at all, so setting CLAUDE_OS_EMAIL/PASSWORD only
gated the frontend's login screen while the API itself stayed fully
open to anyone who could reach it directly.

Added Depends(get_current_user) to all 72 remaining data routes,
left /api/auth/login, /api/auth/status, and /health public (has to
be reachable pre-login / for health checks). get_current_user already
returns a guest user when auth isn't configured, so this is a no-op
for anyone who hasn't set credentials.

Moved the mcp_server.auth import to the top of the file - it was
previously imported ~3250 lines down, after most of these routes'
def statements would have already tried to evaluate
Depends(get_current_user) as a default argument.
This commit is contained in:
Rosie Val 2026-08-22 15:43:16 -04:00
parent 6a23cf80d5
commit d999d6af4a

View file

@ -14,6 +14,12 @@ sys.path.insert(0, str(Path(__file__).parent.parent))
from fastapi import FastAPI, Request, UploadFile, File, HTTPException, Depends, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from mcp_server.auth import (
authenticate_user,
create_access_token,
is_auth_enabled,
get_current_user
)
from fastapi.responses import JSONResponse
from pydantic import BaseModel
import uvicorn
@ -564,14 +570,14 @@ class SearchAllRequest(BaseModel):
# REST API Endpoints for UI
@app.get("/api/kb")
async def api_list_knowledge_bases():
async def api_list_knowledge_bases(current_user: dict = Depends(get_current_user)):
"""REST API: List all knowledge bases."""
kbs = await list_knowledge_bases()
return {"knowledge_bases": kbs}
@app.post("/api/kb")
async def api_create_knowledge_base(request: CreateKBRequest):
async def api_create_knowledge_base(request: CreateKBRequest, current_user: dict = Depends(get_current_user)):
"""REST API: Create a new knowledge base."""
result = await create_knowledge_base(
name=request.name,
@ -585,7 +591,7 @@ async def api_create_knowledge_base(request: CreateKBRequest):
@app.post("/api/kb/search-all")
@limiter.limit("10/minute")
async def api_search_all(request: Request, body: SearchAllRequest):
async def api_search_all(request: Request, body: SearchAllRequest, current_user: dict = Depends(get_current_user)):
"""REST API: Search across multiple knowledge bases."""
try:
from llama_index.embeddings.ollama import OllamaEmbedding
@ -634,7 +640,7 @@ async def api_search_all(request: Request, body: SearchAllRequest):
@app.delete("/api/kb/{kb_name}")
async def api_delete_knowledge_base(kb_name: str):
async def api_delete_knowledge_base(kb_name: str, current_user: dict = Depends(get_current_user)):
"""REST API: Delete a knowledge base."""
try:
db_manager = get_sqlite_manager()
@ -654,7 +660,7 @@ async def api_delete_knowledge_base(kb_name: str):
@app.get("/api/kb/{kb_name}/stats")
async def api_get_kb_stats(kb_name: str):
async def api_get_kb_stats(kb_name: str, current_user: dict = Depends(get_current_user)):
"""REST API: Get knowledge base statistics."""
stats = await get_kb_stats(kb_name)
if "error" in stats:
@ -663,7 +669,7 @@ async def api_get_kb_stats(kb_name: str):
@app.get("/api/kb/{kb_name}/documents")
async def api_list_documents(kb_name: str):
async def api_list_documents(kb_name: str, current_user: dict = Depends(get_current_user)):
"""REST API: List documents in a knowledge base."""
docs = await list_documents(kb_name)
return {"documents": docs}
@ -671,7 +677,7 @@ async def api_list_documents(kb_name: str):
@app.post("/api/kb/{kb_name}/chat")
@limiter.limit("20/minute") # Rate limit: 20 queries per minute per IP
async def api_chat(request: Request, kb_name: str, chat_request: ChatRequest):
async def api_chat(request: Request, kb_name: str, chat_request: ChatRequest, current_user: dict = Depends(get_current_user)):
"""REST API: Chat with a knowledge base."""
result = await search_knowledge_base(
kb_name=kb_name,
@ -686,7 +692,7 @@ async def api_chat(request: Request, kb_name: str, chat_request: ChatRequest):
@app.post("/api/kb/{kb_name}/upload")
async def api_upload_document(kb_name: str, file: UploadFile = File(...)):
async def api_upload_document(kb_name: str, file: UploadFile = File(...), current_user: dict = Depends(get_current_user)):
"""REST API: Upload a single document to a knowledge base."""
import tempfile
import os
@ -733,7 +739,7 @@ class ContentUploadRequest(BaseModel):
@app.post("/api/kb/{kb_name}/documents/content")
async def api_upload_content(kb_name: str, request: ContentUploadRequest):
async def api_upload_content(kb_name: str, request: ContentUploadRequest, current_user: dict = Depends(get_current_user)):
"""REST API: Upload raw text content directly to a knowledge base (no file needed)."""
import tempfile
import os as _os
@ -777,7 +783,7 @@ async def api_upload_content(kb_name: str, request: ContentUploadRequest):
@app.post("/api/kb/{kb_name}/import")
async def api_import_directory(kb_name: str, directory_path: str):
async def api_import_directory(kb_name: str, directory_path: str, current_user: dict = Depends(get_current_user)):
"""REST API: Import all files from a directory into a knowledge base."""
from app.core.ingestion import ingest_directory
@ -810,7 +816,7 @@ async def api_import_directory(kb_name: str, directory_path: str):
@app.delete("/api/kb/{kb_name}/documents/{filename}")
async def api_delete_document(kb_name: str, filename: str):
async def api_delete_document(kb_name: str, filename: str, current_user: dict = Depends(get_current_user)):
"""REST API: Delete a document from a knowledge base by filename."""
db_manager = get_sqlite_manager()
@ -886,7 +892,7 @@ class SemanticIndexRequest(BaseModel):
@app.post("/api/kb/{kb_name}/index-structural")
async def api_index_structural(kb_name: str, request: StructuralIndexRequest):
async def api_index_structural(kb_name: str, request: StructuralIndexRequest, current_user: dict = Depends(get_current_user)):
"""
REST API: Fast structural indexing using tree-sitter.
Phase 1 of hybrid indexing - completes in ~30 seconds for 10k files.
@ -1145,7 +1151,7 @@ def _run_semantic_indexing_sync(kb_name: str, project_path: str, selective: bool
@app.post("/api/kb/{kb_name}/index-semantic")
async def api_index_semantic(kb_name: str, request: SemanticIndexRequest, background_tasks: BackgroundTasks):
async def api_index_semantic(kb_name: str, request: SemanticIndexRequest, background_tasks: BackgroundTasks, current_user: dict = Depends(get_current_user)):
"""
REST API: Semantic indexing with selective embedding.
Phase 2 of hybrid indexing.
@ -1224,7 +1230,7 @@ async def api_index_semantic(kb_name: str, request: SemanticIndexRequest, backgr
@app.get("/api/jobs/{job_id}")
async def api_get_job_status(job_id: str):
async def api_get_job_status(job_id: str, current_user: dict = Depends(get_current_user)):
"""Get the status of a background indexing job."""
with INDEXING_JOBS_LOCK:
if job_id not in INDEXING_JOBS:
@ -1233,7 +1239,7 @@ async def api_get_job_status(job_id: str):
@app.get("/api/jobs")
async def api_list_jobs():
async def api_list_jobs(current_user: dict = Depends(get_current_user)):
"""List all indexing jobs (recent first)."""
with INDEXING_JOBS_LOCK:
jobs = list(INDEXING_JOBS.values())
@ -1248,7 +1254,7 @@ async def api_get_repo_map(
token_budget: int = 1024,
project_path: Optional[str] = None,
personalization: Optional[str] = None # JSON string
):
, current_user: dict = Depends(get_current_user)):
"""
REST API: Get compact repo map for Claude's prompt context.
Returns the most important symbols fitting within token budget.
@ -1361,7 +1367,7 @@ class ProjectDocumentIngestRequest(BaseModel):
@app.get("/api/projects")
async def api_list_projects():
async def api_list_projects(current_user: dict = Depends(get_current_user)):
"""REST API: List all projects."""
try:
db_manager = get_sqlite_manager()
@ -1373,7 +1379,7 @@ async def api_list_projects():
@app.post("/api/projects")
async def api_create_project(request: ProjectRequest):
async def api_create_project(request: ProjectRequest, current_user: dict = Depends(get_current_user)):
"""REST API: Create a new project with 4 required MCPs."""
try:
db_manager = get_sqlite_manager()
@ -1429,7 +1435,7 @@ async def api_create_project(request: ProjectRequest):
@app.get("/api/projects/{project_id}")
async def api_get_project(project_id: int):
async def api_get_project(project_id: int, current_user: dict = Depends(get_current_user)):
"""REST API: Get project details with MCP assignments."""
try:
db_manager = get_sqlite_manager()
@ -1456,7 +1462,7 @@ async def api_get_project(project_id: int):
@app.get("/api/projects/{project_id}/mcps")
async def api_get_project_mcps(project_id: int):
async def api_get_project_mcps(project_id: int, current_user: dict = Depends(get_current_user)):
"""REST API: Get detailed MCP info for a project (KB names and IDs)."""
try:
db_manager = get_sqlite_manager()
@ -1495,7 +1501,7 @@ async def api_get_project_mcps(project_id: int):
@app.post("/api/projects/{project_id}/folders")
async def api_set_kb_folder(project_id: int, request: ProjectKBFolderRequest):
async def api_set_kb_folder(project_id: int, request: ProjectKBFolderRequest, current_user: dict = Depends(get_current_user)):
"""REST API: Set the folder path for a project's MCP KB."""
try:
db_manager = get_sqlite_manager()
@ -1580,7 +1586,7 @@ async def api_set_kb_folder(project_id: int, request: ProjectKBFolderRequest):
@app.get("/api/projects/{project_id}/folders")
async def api_get_kb_folders(project_id: int):
async def api_get_kb_folders(project_id: int, current_user: dict = Depends(get_current_user)):
"""REST API: Get folder configurations for a project's MCPs."""
try:
db_manager = get_sqlite_manager()
@ -1606,7 +1612,7 @@ async def api_get_kb_folders(project_id: int):
@app.post("/api/projects/{project_id}/ingest-document")
async def api_ingest_document(project_id: int, request: ProjectDocumentIngestRequest):
async def api_ingest_document(project_id: int, request: ProjectDocumentIngestRequest, current_user: dict = Depends(get_current_user)):
"""REST API: Ingest a document directly into a project's MCP KB."""
try:
db_manager = get_sqlite_manager()
@ -1669,7 +1675,7 @@ async def api_ingest_document(project_id: int, request: ProjectDocumentIngestReq
@app.delete("/api/projects/{project_id}")
async def api_delete_project(project_id: int):
async def api_delete_project(project_id: int, current_user: dict = Depends(get_current_user)):
"""REST API: Delete a project and its associated KBs."""
try:
db_manager = get_sqlite_manager()
@ -1734,7 +1740,7 @@ class SyncRequest(BaseModel):
@app.post("/api/projects/{project_id}/hooks/{mcp_type}/enable")
async def api_enable_hook(project_id: int, mcp_type: str, request: HookRequest):
async def api_enable_hook(project_id: int, mcp_type: str, request: HookRequest, current_user: dict = Depends(get_current_user)):
"""Enable automatic KB synchronization for a folder."""
try:
hook = get_project_hook(project_id)
@ -1759,7 +1765,7 @@ async def api_enable_hook(project_id: int, mcp_type: str, request: HookRequest):
@app.post("/api/projects/{project_id}/hooks/{mcp_type}/disable")
async def api_disable_hook(project_id: int, mcp_type: str):
async def api_disable_hook(project_id: int, mcp_type: str, current_user: dict = Depends(get_current_user)):
"""Disable automatic KB synchronization."""
try:
hook = get_project_hook(project_id)
@ -1777,7 +1783,7 @@ async def api_disable_hook(project_id: int, mcp_type: str):
@app.post("/api/projects/{project_id}/hooks/sync")
async def api_sync_hooks(project_id: int, request: Optional[SyncRequest] = None):
async def api_sync_hooks(project_id: int, request: Optional[SyncRequest] = None, current_user: dict = Depends(get_current_user)):
"""Manually sync KB folders for a project."""
try:
hook = get_project_hook(project_id)
@ -1803,7 +1809,7 @@ async def api_sync_hooks(project_id: int, request: Optional[SyncRequest] = None)
@app.get("/api/projects/{project_id}/hooks")
async def api_get_hooks_status(project_id: int):
async def api_get_hooks_status(project_id: int, current_user: dict = Depends(get_current_user)):
"""Get hook status for a project."""
try:
hook = get_project_hook(project_id)
@ -1824,7 +1830,7 @@ async def api_get_hooks_status(project_id: int):
# ============================================================================
@app.post("/api/watcher/start/{project_id}")
async def api_start_watcher(project_id: int):
async def api_start_watcher(project_id: int, current_user: dict = Depends(get_current_user)):
"""Start file watcher for a project."""
try:
watcher = get_global_watcher()
@ -1842,7 +1848,7 @@ async def api_start_watcher(project_id: int):
@app.post("/api/watcher/stop/{project_id}")
async def api_stop_watcher(project_id: int):
async def api_stop_watcher(project_id: int, current_user: dict = Depends(get_current_user)):
"""Stop file watcher for a project."""
try:
watcher = get_global_watcher()
@ -1860,7 +1866,7 @@ async def api_stop_watcher(project_id: int):
@app.post("/api/watcher/restart/{project_id}")
async def api_restart_watcher(project_id: int):
async def api_restart_watcher(project_id: int, current_user: dict = Depends(get_current_user)):
"""Restart file watcher for a project."""
try:
watcher = get_global_watcher()
@ -1878,7 +1884,7 @@ async def api_restart_watcher(project_id: int):
@app.get("/api/watcher/status")
async def api_watcher_status():
async def api_watcher_status(current_user: dict = Depends(get_current_user)):
"""Get file watcher status."""
try:
watcher = get_global_watcher()
@ -1893,7 +1899,7 @@ async def api_watcher_status():
# ============================================================================
@app.post("/api/spec-watcher/start/{project_id}")
async def api_start_spec_watcher(project_id: int):
async def api_start_spec_watcher(project_id: int, current_user: dict = Depends(get_current_user)):
"""Start spec watcher for a project."""
try:
from app.core.spec_watcher import get_global_spec_watcher
@ -1918,7 +1924,7 @@ async def api_start_spec_watcher(project_id: int):
@app.post("/api/spec-watcher/stop/{project_id}")
async def api_stop_spec_watcher(project_id: int):
async def api_stop_spec_watcher(project_id: int, current_user: dict = Depends(get_current_user)):
"""Stop spec watcher for a project."""
try:
from app.core.spec_watcher import get_global_spec_watcher
@ -1937,7 +1943,7 @@ async def api_stop_spec_watcher(project_id: int):
@app.post("/api/spec-watcher/start-all")
async def api_start_all_spec_watchers():
async def api_start_all_spec_watchers(current_user: dict = Depends(get_current_user)):
"""Start spec watchers for all projects."""
try:
from app.core.spec_watcher import get_global_spec_watcher
@ -1955,7 +1961,7 @@ async def api_start_all_spec_watchers():
@app.get("/api/spec-watcher/status")
async def api_spec_watcher_status():
async def api_spec_watcher_status(current_user: dict = Depends(get_current_user)):
"""Get spec watcher status."""
try:
from app.core.spec_watcher import get_global_spec_watcher
@ -1972,7 +1978,7 @@ async def api_spec_watcher_status():
# ============================================================================
@app.get("/api/projects/{project_id}/specs")
async def api_get_project_specs(project_id: int):
async def api_get_project_specs(project_id: int, current_user: dict = Depends(get_current_user)):
"""Get all specs for a project."""
try:
manager = SpecManager()
@ -1984,7 +1990,7 @@ async def api_get_project_specs(project_id: int):
@app.get("/api/specs/{spec_id}/tasks")
async def api_get_spec_tasks(spec_id: int):
async def api_get_spec_tasks(spec_id: int, current_user: dict = Depends(get_current_user)):
"""Get all tasks for a spec."""
try:
manager = SpecManager()
@ -2001,7 +2007,7 @@ class TaskStatusUpdate(BaseModel):
@app.patch("/api/tasks/{task_id}/status")
async def api_update_task_status(task_id: int, update: TaskStatusUpdate):
async def api_update_task_status(task_id: int, update: TaskStatusUpdate, current_user: dict = Depends(get_current_user)):
"""Update a task's status."""
try:
manager = SpecManager()
@ -2023,7 +2029,7 @@ async def api_update_task_status(task_id: int, update: TaskStatusUpdate):
@app.post("/api/projects/{project_id}/specs/sync")
async def api_sync_project_specs(project_id: int):
async def api_sync_project_specs(project_id: int, current_user: dict = Depends(get_current_user)):
"""Sync specs from project's agent-os folder."""
try:
# Get project path
@ -2049,7 +2055,7 @@ async def api_sync_project_specs(project_id: int):
@app.get("/api/projects/{project_id}/kanban")
async def api_get_kanban_view(project_id: int, include_archived: bool = False):
async def api_get_kanban_view(project_id: int, include_archived: bool = False, current_user: dict = Depends(get_current_user)):
"""Get Kanban board view for a project."""
try:
manager = SpecManager()
@ -2100,7 +2106,7 @@ async def api_get_kanban_view(project_id: int, include_archived: bool = False):
@app.post("/api/specs/{spec_id}/archive")
async def api_archive_spec(spec_id: int):
async def api_archive_spec(spec_id: int, current_user: dict = Depends(get_current_user)):
"""Archive a spec."""
try:
manager = SpecManager()
@ -2112,7 +2118,7 @@ async def api_archive_spec(spec_id: int):
@app.post("/api/specs/{spec_id}/unarchive")
async def api_unarchive_spec(spec_id: int):
async def api_unarchive_spec(spec_id: int, current_user: dict = Depends(get_current_user)):
"""Unarchive a spec."""
try:
manager = SpecManager()
@ -2147,7 +2153,7 @@ class SessionExtractRequest(BaseModel):
@app.get("/api/sessions/list")
async def api_list_sessions(project_path: str, limit: int = 50):
async def api_list_sessions(project_path: str, limit: int = 50, current_user: dict = Depends(get_current_user)):
"""
REST API: List Claude Code session files for a project.
@ -2177,7 +2183,7 @@ async def api_list_sessions(project_path: str, limit: int = 50):
@app.post("/api/sessions/extract")
async def api_extract_session_insights(request: SessionExtractRequest):
async def api_extract_session_insights(request: SessionExtractRequest, current_user: dict = Depends(get_current_user)):
"""
REST API: Extract insights from a Claude Code session file.
@ -2325,7 +2331,7 @@ class SkillInstallRequest(BaseModel):
@app.get("/api/skills")
async def api_list_skills(project_path: Optional[str] = None, include_content: bool = False):
async def api_list_skills(project_path: Optional[str] = None, include_content: bool = False, current_user: dict = Depends(get_current_user)):
"""
REST API: List all skills (global and project).
@ -2352,7 +2358,7 @@ async def api_list_skills(project_path: Optional[str] = None, include_content: b
@app.get("/api/skills/templates")
async def api_list_skill_templates(category: Optional[str] = None):
async def api_list_skill_templates(category: Optional[str] = None, current_user: dict = Depends(get_current_user)):
"""
REST API: List available skill templates.
@ -2378,7 +2384,7 @@ async def api_list_skill_templates(category: Optional[str] = None):
@app.get("/api/skills/{scope}/{name}")
async def api_get_skill(scope: str, name: str, project_path: Optional[str] = None):
async def api_get_skill(scope: str, name: str, project_path: Optional[str] = None, current_user: dict = Depends(get_current_user)):
"""
REST API: Get skill details including content.
@ -2412,7 +2418,7 @@ async def api_get_skill(scope: str, name: str, project_path: Optional[str] = Non
@app.post("/api/skills/install")
async def api_install_skill_template(request: SkillInstallRequest, project_path: str):
async def api_install_skill_template(request: SkillInstallRequest, project_path: str, current_user: dict = Depends(get_current_user)):
"""
REST API: Install a skill template to a project.
@ -2446,7 +2452,7 @@ async def api_install_skill_template(request: SkillInstallRequest, project_path:
@app.post("/api/skills")
async def api_create_skill(request: SkillCreateRequest, project_path: str):
async def api_create_skill(request: SkillCreateRequest, project_path: str, current_user: dict = Depends(get_current_user)):
"""
REST API: Create a custom skill for a project.
@ -2483,7 +2489,7 @@ async def api_create_skill(request: SkillCreateRequest, project_path: str):
@app.put("/api/skills/{name}")
async def api_update_skill(name: str, request: SkillUpdateRequest, project_path: str):
async def api_update_skill(name: str, request: SkillUpdateRequest, project_path: str, current_user: dict = Depends(get_current_user)):
"""
REST API: Update an existing project skill.
@ -2520,7 +2526,7 @@ async def api_update_skill(name: str, request: SkillUpdateRequest, project_path:
@app.delete("/api/skills/{name}")
async def api_delete_skill(name: str, project_path: str):
async def api_delete_skill(name: str, project_path: str, current_user: dict = Depends(get_current_user)):
"""
REST API: Delete a project skill.
@ -2562,7 +2568,7 @@ class CommunitySkillInstallRequest(BaseModel):
@app.get("/api/skills/community/sources")
async def api_list_community_sources():
async def api_list_community_sources(current_user: dict = Depends(get_current_user)):
"""
REST API: List available community skill sources.
@ -2582,7 +2588,7 @@ async def api_list_community_sources():
@app.get("/api/skills/community")
async def api_list_community_skills(source: Optional[str] = None):
async def api_list_community_skills(source: Optional[str] = None, current_user: dict = Depends(get_current_user)):
"""
REST API: List skills from community repositories.
@ -2620,7 +2626,7 @@ async def api_list_community_skills(source: Optional[str] = None):
async def api_install_community_skill(
request: CommunitySkillInstallRequest,
project_path: str
):
, current_user: dict = Depends(get_current_user)):
"""
REST API: Install a community skill to a project.
@ -2665,7 +2671,7 @@ async def api_install_community_skill(
# ============================================================================
@app.get("/api/ollama/models")
async def api_list_ollama_models():
async def api_list_ollama_models(current_user: dict = Depends(get_current_user)):
"""REST API: List available Ollama models."""
try:
response = requests.get(f"{Config.OLLAMA_HOST}/api/tags", timeout=2)
@ -2767,7 +2773,7 @@ def check_port_listening(port: int) -> bool:
@app.get("/api/services/status")
async def api_get_services_status():
async def api_get_services_status(current_user: dict = Depends(get_current_user)):
"""
Get status of all Claude OS services.
@ -2905,7 +2911,7 @@ async def api_get_services_status():
@app.post("/api/services/{service_type}/control")
async def api_control_service(service_type: str, request: ServiceControlRequest):
async def api_control_service(service_type: str, request: ServiceControlRequest, current_user: dict = Depends(get_current_user)):
"""
Control a service (start/stop/restart).
@ -2966,7 +2972,7 @@ async def api_control_service(service_type: str, request: ServiceControlRequest)
@app.get("/api/browse-directory")
async def api_browse_directory(path: str = None):
async def api_browse_directory(path: str = None, current_user: dict = Depends(get_current_user)):
"""REST API: Browse directories and return subdirectories."""
try:
from pathlib import Path
@ -3052,19 +3058,19 @@ async def api_browse_directory(path: str = None):
# MCP Protocol Endpoints
@app.post("/")
async def mcp_root(request: Request):
async def mcp_root(request: Request, current_user: dict = Depends(get_current_user)):
"""Main MCP endpoint - handles JSON-RPC requests."""
return await handle_mcp_request(request)
@app.post("/mcp")
async def mcp_endpoint(request: Request):
async def mcp_endpoint(request: Request, current_user: dict = Depends(get_current_user)):
"""Global MCP endpoint - exposes all KBs."""
return await handle_mcp_request(request, kb_filter=None)
@app.post("/mcp/kb/{kb_slug}")
async def mcp_kb_endpoint(kb_slug: str, request: Request):
async def mcp_kb_endpoint(kb_slug: str, request: Request, current_user: dict = Depends(get_current_user)):
"""KB-specific MCP endpoint - only exposes tools for this KB."""
# Get KB name from slug
db_manager = get_sqlite_manager()
@ -3263,12 +3269,6 @@ async def handle_mcp_request(request: Request, kb_filter: Optional[str] = None)
# Authentication Endpoints
# ============================================================================
from mcp_server.auth import (
authenticate_user,
create_access_token,
is_auth_enabled,
get_current_user
)
from datetime import timedelta
@ -3489,7 +3489,7 @@ def _run_consolidation_background(job_id: str, kb_name: str, doc_ids: list, new_
@app.post("/api/kb/{kb_name}/lifecycle/dedup-scan")
async def api_lifecycle_dedup_scan(kb_name: str, request: DedupScanRequest, background_tasks: BackgroundTasks):
async def api_lifecycle_dedup_scan(kb_name: str, request: DedupScanRequest, background_tasks: BackgroundTasks, current_user: dict = Depends(get_current_user)):
"""Scan KB for duplicate/near-duplicate documents."""
import uuid
@ -3520,7 +3520,7 @@ async def api_lifecycle_dedup_scan(kb_name: str, request: DedupScanRequest, back
@app.post("/api/kb/{kb_name}/lifecycle/dedup-merge")
async def api_lifecycle_dedup_merge(kb_name: str, request: DedupMergeRequest):
async def api_lifecycle_dedup_merge(kb_name: str, request: DedupMergeRequest, current_user: dict = Depends(get_current_user)):
"""Merge duplicate documents by keeping one and removing the rest."""
db_manager = get_sqlite_manager()
if not db_manager.collection_exists(kb_name):
@ -3532,7 +3532,7 @@ async def api_lifecycle_dedup_merge(kb_name: str, request: DedupMergeRequest):
@app.post("/api/kb/{kb_name}/lifecycle/consolidate")
async def api_lifecycle_consolidate(kb_name: str, request: ConsolidateRequest, background_tasks: BackgroundTasks):
async def api_lifecycle_consolidate(kb_name: str, request: ConsolidateRequest, background_tasks: BackgroundTasks, current_user: dict = Depends(get_current_user)):
"""Consolidate related documents into a single merged document using LLM."""
import uuid
@ -3559,7 +3559,7 @@ async def api_lifecycle_consolidate(kb_name: str, request: ConsolidateRequest, b
@app.get("/api/kb/{kb_name}/lifecycle/health")
async def api_lifecycle_health(kb_name: str):
async def api_lifecycle_health(kb_name: str, current_user: dict = Depends(get_current_user)):
"""Get comprehensive health report for a knowledge base."""
db_manager = get_sqlite_manager()
if not db_manager.collection_exists(kb_name):
@ -3570,7 +3570,7 @@ async def api_lifecycle_health(kb_name: str):
@app.get("/api/kb/{kb_name}/lifecycle/growth")
async def api_lifecycle_growth(kb_name: str, granularity: str = "month"):
async def api_lifecycle_growth(kb_name: str, granularity: str = "month", current_user: dict = Depends(get_current_user)):
"""Get document growth timeline for a knowledge base."""
db_manager = get_sqlite_manager()
if not db_manager.collection_exists(kb_name):
@ -3581,7 +3581,7 @@ async def api_lifecycle_growth(kb_name: str, granularity: str = "month"):
@app.post("/api/kb/{kb_name}/lifecycle/archive")
async def api_lifecycle_archive(kb_name: str, request: ArchiveRequest):
async def api_lifecycle_archive(kb_name: str, request: ArchiveRequest, current_user: dict = Depends(get_current_user)):
"""Archive documents in a knowledge base."""
db_manager = get_sqlite_manager()
if not db_manager.collection_exists(kb_name):
@ -3592,7 +3592,7 @@ async def api_lifecycle_archive(kb_name: str, request: ArchiveRequest):
@app.post("/api/kb/{kb_name}/lifecycle/restore")
async def api_lifecycle_restore(kb_name: str, request: RestoreRequest):
async def api_lifecycle_restore(kb_name: str, request: RestoreRequest, current_user: dict = Depends(get_current_user)):
"""Restore archived documents in a knowledge base."""
db_manager = get_sqlite_manager()
if not db_manager.collection_exists(kb_name):
@ -3603,7 +3603,7 @@ async def api_lifecycle_restore(kb_name: str, request: RestoreRequest):
@app.get("/api/kb/{kb_name}/lifecycle/archived")
async def api_lifecycle_archived(kb_name: str):
async def api_lifecycle_archived(kb_name: str, current_user: dict = Depends(get_current_user)):
"""List archived documents in a knowledge base."""
db_manager = get_sqlite_manager()
if not db_manager.collection_exists(kb_name):
@ -3614,7 +3614,7 @@ async def api_lifecycle_archived(kb_name: str):
@app.get("/api/kb/{kb_name}/lifecycle/stale")
async def api_lifecycle_stale(kb_name: str, stale_days: int = 90):
async def api_lifecycle_stale(kb_name: str, stale_days: int = 90, current_user: dict = Depends(get_current_user)):
"""Find stale documents older than the specified threshold."""
db_manager = get_sqlite_manager()
if not db_manager.collection_exists(kb_name):
@ -3625,7 +3625,7 @@ async def api_lifecycle_stale(kb_name: str, stale_days: int = 90):
@app.get("/api/kb/{kb_name}/lifecycle/logs")
async def api_lifecycle_logs(kb_name: str, operation_type: Optional[str] = None, limit: int = 50):
async def api_lifecycle_logs(kb_name: str, operation_type: Optional[str] = None, limit: int = 50, current_user: dict = Depends(get_current_user)):
"""Get lifecycle operation logs for a knowledge base."""
db_manager = get_sqlite_manager()
if not db_manager.collection_exists(kb_name):