Settings: add dedicated DAV/Pushover tabs, fix CalDAV/CardDAV bugs
- Add admin DAV tab (rename from CalDAV/CardDAV) and Pushover tab
- Add per-user Pushover tab (User Key only; App Token stays admin-managed)
- Remove system-wide CalDAV/CardDAV fallback — per-user config only
- Rewrite contacts_tool.py using httpx directly (caldav 2.x dropped AddressBook)
- Fix CardDAV REPORT/PROPFIND using SOGo URL pattern
- Fix CalDAV/CardDAV test endpoints (POST method, URL scheme normalization)
- Fix Show Password button — API now returns actual credential values
- Convert Credentials tab to generic key-value store; dedicated keys
(CalDAV, Pushover, trusted_proxy) excluded via _DEDICATED_CRED_KEYS
This commit is contained in:
@@ -42,6 +42,8 @@ from .context_vars import current_user as _current_user_var
|
||||
from .database import close_db, credential_store, init_db
|
||||
from .inbox.listener import inbox_listener
|
||||
from .mcp import create_mcp_app, _session_manager
|
||||
from .monitors.page_monitor import page_monitor
|
||||
from .monitors.rss_monitor import rss_monitor
|
||||
from .telegram.listener import telegram_listener
|
||||
from .tools import build_registry
|
||||
from .users import assign_existing_data_to_admin, create_user, get_user_by_username, user_count
|
||||
@@ -190,6 +192,11 @@ async def lifespan(app: FastAPI):
|
||||
print("[aide] Agent ready.")
|
||||
agent_runner.init(_agent)
|
||||
await agent_runner.start()
|
||||
# Wire monitors into the shared scheduler after it starts
|
||||
page_monitor.init(agent_runner.scheduler)
|
||||
rss_monitor.init(agent_runner.scheduler)
|
||||
await page_monitor.start_all()
|
||||
await rss_monitor.start_all()
|
||||
await _migrate_email_accounts()
|
||||
await inbox_listener.start_all()
|
||||
telegram_listener.start()
|
||||
@@ -260,7 +267,7 @@ import time as _time
|
||||
|
||||
_USER_COOKIE = "aide_user"
|
||||
_EXEMPT_PATHS = frozenset({"/login", "/login/mfa", "/logout", "/setup", "/health"})
|
||||
_EXEMPT_PREFIXES = ("/static/", "/brain-mcp/", "/docs", "/redoc", "/openapi.json")
|
||||
_EXEMPT_PREFIXES = ("/static/", "/brain-mcp/", "/docs", "/redoc", "/openapi.json", "/webhook/")
|
||||
_EXEMPT_API_PATHS = frozenset({"/api/settings/api-key"})
|
||||
|
||||
|
||||
@@ -380,6 +387,87 @@ app.include_router(api_router, prefix="/api")
|
||||
app.mount("/brain-mcp", create_mcp_app())
|
||||
|
||||
|
||||
# ── Public webhook trigger endpoints ─────────────────────────────────────────
|
||||
# These live outside /api and outside the auth middleware (token = auth).
|
||||
|
||||
_webhook_logger = logging.getLogger("server.webhook")
|
||||
|
||||
|
||||
async def _handle_webhook_trigger(token: str, message: str, wait: bool = False) -> JSONResponse:
|
||||
"""Shared logic for GET and POST webhook triggers."""
|
||||
from .webhooks.endpoints import get_by_token, record_trigger
|
||||
from .security import sanitize_external_content
|
||||
|
||||
ep = await get_by_token(token)
|
||||
if ep is None:
|
||||
return JSONResponse({"error": "Not found"}, status_code=404)
|
||||
|
||||
agent_id = ep.get("agent_id")
|
||||
if not agent_id:
|
||||
return JSONResponse({"error": "No agent configured for this webhook"}, status_code=422)
|
||||
|
||||
message = (message or "").strip()
|
||||
if not message:
|
||||
message = "Webhook triggered"
|
||||
|
||||
message = await sanitize_external_content(message, source="webhook")
|
||||
|
||||
from .audit import audit_log
|
||||
ep_id = str(ep["id"])
|
||||
session_id = f"webhook:{ep_id}"
|
||||
|
||||
await audit_log.record(
|
||||
tool_name="webhook",
|
||||
arguments={"endpoint": ep.get("name"), "message": message[:200]},
|
||||
session_id=session_id,
|
||||
)
|
||||
|
||||
if wait:
|
||||
result = await agent_runner.run_agent_and_wait(
|
||||
agent_id=agent_id,
|
||||
override_message=message,
|
||||
session_id=session_id,
|
||||
)
|
||||
await record_trigger(ep_id)
|
||||
return JSONResponse({"ok": True, "result": result})
|
||||
else:
|
||||
run = await agent_runner.run_agent_now(agent_id=agent_id, override_message=message)
|
||||
await record_trigger(ep_id)
|
||||
run_id = run.get("id") or run.get("error", "error")
|
||||
return JSONResponse({"ok": True, "run_id": run_id})
|
||||
|
||||
|
||||
@app.get("/webhook/{token}")
|
||||
async def webhook_trigger_get(token: str, q: str = "", wait: bool = False):
|
||||
"""iOS Shortcuts / simple GET trigger. Message via ?q= query param."""
|
||||
ep = await _get_webhook_endpoint_for_get(token)
|
||||
if ep is None:
|
||||
return JSONResponse({"error": "Not found"}, status_code=404)
|
||||
return await _handle_webhook_trigger(token, q, wait=wait)
|
||||
|
||||
|
||||
@app.post("/webhook/{token}")
|
||||
async def webhook_trigger_post(token: str, request: Request):
|
||||
"""External service POST trigger. Message via JSON body {"message": "..."}."""
|
||||
try:
|
||||
body = await request.json()
|
||||
message = body.get("message", "")
|
||||
wait = bool(body.get("wait", False))
|
||||
except Exception:
|
||||
message = ""
|
||||
wait = False
|
||||
return await _handle_webhook_trigger(token, message, wait=wait)
|
||||
|
||||
|
||||
async def _get_webhook_endpoint_for_get(token: str) -> dict | None:
|
||||
"""Return endpoint only if allow_get is True."""
|
||||
from .webhooks.endpoints import get_by_token
|
||||
ep = await get_by_token(token)
|
||||
if ep and not ep.get("allow_get", True):
|
||||
return None
|
||||
return ep
|
||||
|
||||
|
||||
|
||||
# ── Auth helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -682,6 +770,11 @@ async def audit_page(request: Request):
|
||||
return templates.TemplateResponse("audit.html", await _ctx(request))
|
||||
|
||||
|
||||
@app.get("/monitors", response_class=HTMLResponse)
|
||||
async def monitors_page(request: Request):
|
||||
return templates.TemplateResponse("monitors.html", await _ctx(request))
|
||||
|
||||
|
||||
@app.get("/help", response_class=HTMLResponse)
|
||||
async def help_page(request: Request):
|
||||
return templates.TemplateResponse("help.html", await _ctx(request))
|
||||
|
||||
Reference in New Issue
Block a user