feat!: require explicit user on every tenant-data endpoint

Remove the implicit jpmschweitzer default tenant (DEFAULT_USER) from
src/core/multi_tenancy.py and every endpoint and request model that
inherited it (~40 endpoints across /query, /wiki, /vector, /graph,
/ingest, /volatile, /documents, /stats, /rag).

- Add validate_required_user() + RequiredUser pydantic type in
  multi_tenancy and a shared require_user FastAPI dependency
  (RequiredUserQuery) that rejects missing, empty, and whitespace-only
  users with 422, following the /maintenance/* pattern.
- Wiki page create / smart-create / dossier request models now require
  user (no fallback in wiki_service).
- /maintenance/cleanup/test-data derives the tenant from the page path
  instead of using the production tenant collection.
- Wiki.js change listener skips changes when no tenant user can be
  derived from the notification email instead of defaulting to the
  production tenant.
- Consolidation service internal helpers no longer default to the
  production tenant.
- Tool catalog marks user as required with honest descriptions.
- OpenAPI descriptions updated honestly; CHANGELOG notes that callers
  (tatlock, Scheduler ingest tasks) must now send explicit user.
- Offline tests: 422 coverage for query/body endpoints, required-user
  validator tests; updated legacy tests that assumed a default tenant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 11:06:11 +02:00
co-authored by Claude Fable 5
parent a66d801abd
commit 84e9185371
27 changed files with 481 additions and 153 deletions
+5 -5
View File
@@ -18,9 +18,9 @@ from pathlib import Path
from src.config import Settings, get_settings, __version__
from src.core.dependencies import (
verify_api_key, QdrantDep, WikiJSDep, OllamaDep, Neo4jDep, PaperlessDep
verify_api_key, QdrantDep, WikiJSDep, OllamaDep, Neo4jDep, PaperlessDep,
RequiredUserQuery
)
from src.core.multi_tenancy import DEFAULT_USER
# Configure logging
logging.basicConfig(
@@ -151,7 +151,7 @@ async def health(settings: Settings = Depends(get_settings)) -> HealthResponse:
@app.get("/stats", response_model=StatsResponse, tags=["System"])
async def stats(
user: str = Query(default=DEFAULT_USER, description="User identifier"),
user: RequiredUserQuery,
neo4j: Neo4jDep = None,
qdrant: QdrantDep = None,
wikijs: WikiJSDep = None,
@@ -290,8 +290,8 @@ async def get_repo_status(
@app.post("/query/semantic", tags=["Query"])
async def semantic_query(
user: RequiredUserQuery,
query: str = Query(..., min_length=1, description="Search query text"),
user: str = Query(default=DEFAULT_USER, description="User identifier"),
limit: int = Query(default=10, ge=1, le=100, description="Maximum results"),
score_threshold: float = Query(default=0.5, ge=0.0, le=1.0, description="Minimum similarity score"),
qdrant_client: QdrantDep = None,
@@ -331,8 +331,8 @@ async def semantic_query(
@app.post("/query/graph", tags=["Query"])
async def graph_query(
user: RequiredUserQuery,
query: str = Query(..., description="Cypher query to execute"),
user: str = Query(default=DEFAULT_USER, description="User for scoping (auto-filters results)"),
neo4j_client: Neo4jDep = None,
wiki_client: WikiJSDep = None,
api_key: str = Depends(verify_api_key)