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:
parent
6a23cf80d5
commit
d999d6af4a
1 changed files with 78 additions and 78 deletions
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue