diff --git a/cortex/routers/files.py b/cortex/routers/files.py index 9bc3d71..4c24eb5 100644 --- a/cortex/routers/files.py +++ b/cortex/routers/files.py @@ -6,6 +6,7 @@ import re from fastapi import APIRouter, HTTPException, Query from pydantic import BaseModel from persona import persona_path, set_context, validate as validate_persona +from config import settings as _settings router = APIRouter() @@ -22,6 +23,9 @@ ALLOWED = { "HELP.md", } +# Files served from home/{user}/ instead of persona path +USER_FILES = {"email_allowlist.json"} + def _resolve(user: str, persona: str) -> None: """Validate and set context from query params. Raises HTTPException on bad input.""" @@ -32,7 +36,11 @@ def _resolve(user: str, persona: str) -> None: raise HTTPException(status_code=404, detail=str(e)) -def _path(filename: str): +def _path(filename: str, user: str = ""): + if filename in USER_FILES: + if not user: + raise HTTPException(status_code=400, detail="user param required for this file") + return _settings.home_root() / user / filename if filename not in ALLOWED: raise HTTPException(status_code=404, detail=f"File not found: {filename}") return persona_path() / filename @@ -55,6 +63,16 @@ async def list_files( "size": st.st_size if st else 0, "modified": st.st_mtime if st else None, }) + for name in sorted(USER_FILES): + p = _settings.home_root() / user / name + st = p.stat() if p.exists() else None + files.append({ + "name": name, + "exists": p.exists(), + "size": st.st_size if st else 0, + "modified": st.st_mtime if st else None, + "scope": "user", + }) return {"files": files} @@ -65,7 +83,7 @@ async def get_file( persona: str = Query("inara"), ) -> dict: _resolve(user, persona) - p = _path(filename) + p = _path(filename, user=user) if not p.exists(): raise HTTPException(status_code=404, detail=f"{filename} does not exist") return {"name": filename, "content": p.read_text()} @@ -83,7 +101,7 @@ async def save_file( persona: str = Query("inara"), ) -> dict: _resolve(user, persona) - p = _path(filename) + p = _path(filename, user=user) p.write_text(req.content) return {"ok": True, "name": filename, "size": len(req.content)} diff --git a/cortex/routers/settings.py b/cortex/routers/settings.py index 1b58a1d..a01424c 100644 --- a/cortex/routers/settings.py +++ b/cortex/routers/settings.py @@ -8,6 +8,8 @@ Routes: POST /settings/persona/rename → rename a persona directory """ +import html as _html +import json import logging import re from pathlib import Path @@ -63,6 +65,14 @@ def _settings_page(username: str, personas: list[str], back_persona: str = "", s role = auth_data.get("role", "user") html = html.replace("{{ user_role }}", role) + al_path = app_settings.home_root() / username / "email_allowlist.json" + try: + patterns = json.loads(al_path.read_text()) + allowlist_text = _html.escape("\n".join(str(p) for p in patterns if str(p).strip())) + except Exception: + allowlist_text = "" + html = html.replace("{{ email_allowlist }}", allowlist_text) + persona_items = "\n".join( f'''
+ One regex pattern per line. The email_send
+ tool will only send to addresses that match at least one pattern.
+ Leave blank to block all outbound email.
+