fix: tighten API P0 auth and route handling

This commit is contained in:
2026-05-28 12:42:42 +01:00
parent 550d405935
commit 54760083b5
6 changed files with 309 additions and 96 deletions
@@ -60,6 +60,33 @@ def _resolve_institute(
return None, db, teacher_uuid
def _allowed_neo4j_dbs(user_id: str, user_email: str) -> set[str]:
"""Return Neo4j databases this user may request via lazy graph APIs."""
allowed = {f"cc.users.teacher.{user_id.replace('-', '')}"} if user_id else set()
if user_id or user_email:
_, institute_db, _ = _resolve_institute(user_id, user_email)
if institute_db:
allowed.add(institute_db)
allowed.add(f"{institute_db}.curriculum")
return allowed
def _require_allowed_neo4j_db(neo4j_db_name: str, node_type: str, section_id: str, user_id: str, user_email: str) -> None:
"""Reject arbitrary DB traversal from /graph/node/children query params."""
if not neo4j_db_name:
raise HTTPException(status_code=400, detail="neo4j_db_name is required")
if neo4j_db_name == "classroomcopilot":
if node_type.startswith("Calendar") or section_id == "calendar":
return
raise HTTPException(status_code=403, detail="Requested graph database is not allowed for this node")
if neo4j_db_name in _allowed_neo4j_dbs(user_id, user_email):
return
raise HTTPException(status_code=403, detail="Requested graph database is outside the authenticated user's scope")
def _find_teacher_institute(user_email: str) -> Tuple[Optional[str], Optional[str]]:
"""Return (institute_db_name, teacher_uuid) by matching worker_email in all institute DBs."""
if not user_email:
@@ -821,7 +848,11 @@ async def get_node_children(
section_id: str = "",
credentials: dict = Depends(SupabaseBearer()),
) -> Dict[str, Any]:
user_id = credentials.get("sub", "")
user_email = credentials.get("email", "")
if not user_id:
raise HTTPException(status_code=403, detail="Could not extract user_id from token")
_require_allowed_neo4j_db(neo4j_db_name, node_type, section_id, user_id, user_email)
children = _get_children_for_node(neo4j_node_id, neo4j_db_name, node_type, section_id, user_email)
return {"status": "success", "children": children}