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:
2026-04-10 12:06:23 +02:00
parent a9ca08f13d
commit 7b0a9ccc2b
25 changed files with 4011 additions and 235 deletions

View File

@@ -42,14 +42,18 @@
<li><a href="#settings-general">General</a></li>
<li><a href="#settings-whitelists">Whitelists</a></li>
<li><a href="#settings-credentials">Credentials</a></li>
<li><a href="#settings-dav">DAV</a></li>
<li><a href="#settings-pushover">Pushover</a></li>
{% endif %}
<li><a href="#settings-inbox">Inbox</a></li>
<li><a href="#settings-emailaccounts">Email Accounts</a></li>
<li><a href="#settings-telegram">Telegram</a></li>
<li><a href="#settings-profile">Profile</a></li>
{% if not (current_user and current_user.is_admin) %}
<li><a href="#settings-caldav">CalDAV</a></li>
<li><a href="#settings-caldav">CalDAV / CardDAV</a></li>
<li><a href="#settings-pushover">Pushover</a></li>
{% endif %}
<li><a href="#settings-webhooks">Webhooks</a></li>
<li><a href="#settings-profile">Profile</a></li>
<li><a href="#settings-personality">Personality</a></li>
<li><a href="#settings-brain">2nd Brain</a></li>
<li><a href="#settings-mcp">MCP Servers</a></li>
@@ -372,8 +376,28 @@ mcp = FastMCP(
<h2 id="settings-credentials">Credentials <small style="font-weight:400;font-size:12px;color:var(--text-dim)">(Admin)</small></h2>
<p>
All secrets (API keys, passwords, app settings) are stored in an AES-256-GCM encrypted PostgreSQL table. Keys use a <code>namespace:key</code> convention. See the <a href="#credentials">Credential Key Reference</a> for a full list.
A generic AES-256-GCM encrypted key-value store for API keys and other secrets. Keys use a <code>namespace:key</code> convention. Service-specific credentials (CalDAV, CardDAV, Pushover) are managed in their own dedicated tabs — they do not appear here. See the <a href="#credentials">Credential Key Reference</a> for a full list of system keys.
</p>
<h2 id="settings-dav">DAV (CalDAV &amp; CardDAV) <small style="font-weight:400;font-size:12px;color:var(--text-dim)">(Admin)</small></h2>
<p>
Configure CalDAV and CardDAV for the admin user. There is no system-wide fallback — every user configures their own credentials independently via this tab (admin) or the <strong>CalDAV / CardDAV</strong> tab (regular users).
</p>
<ul>
<li><strong>CalDAV</strong>: server URL, username, password, and calendar name. Bare hostnames (e.g. <code>mail.example.com</code>) are accepted — <code>https://</code> is prepended automatically.</li>
<li><strong>CardDAV</strong>: tick <em>Same server as CalDAV</em> to reuse the same credentials, or enter a separate URL, username, and password. The SOGo URL pattern (<code>/SOGo/dav/{user}/Contacts/personal/</code>) is built automatically.</li>
<li><strong>Allow contact writes</strong>: when enabled, agents can create, update, and delete contacts (not just read them). This is per-user — enabling it for your account does not affect other users.</li>
<li><strong>Test buttons</strong>: verify CalDAV and CardDAV connectivity without saving.</li>
</ul>
<h2 id="settings-pushover">Pushover <small style="font-weight:400;font-size:12px;color:var(--text-dim)">(Admin)</small></h2>
<p>
Pushover sends push notifications to iOS and Android devices.
</p>
<ul>
<li><strong>App Token</strong>: registered once at <a href="https://pushover.net" target="_blank">pushover.net</a> for this oAI-Web installation. Shared by all users — they cannot see or change it.</li>
<li><strong>User Key</strong>: the admin's personal Pushover user key, shown on your pushover.net dashboard. Each user sets their own User Key in <strong>Settings → Pushover</strong>.</li>
</ul>
{% endif %}
<h2 id="settings-inbox">Inbox</h2>
@@ -413,6 +437,36 @@ mcp = FastMCP(
<li><strong>Trigger Rules</strong>: same keyword-matching logic as email inbox</li>
</ul>
{% if not (current_user and current_user.is_admin) %}
<h2 id="settings-caldav">CalDAV / CardDAV</h2>
<p>
Configure your personal CalDAV and CardDAV connection. There is no system-wide fallback — if you don't configure it, the tools are unavailable to you.
</p>
<ul>
<li><strong>CalDAV</strong>: server URL, username, password, and calendar name. Bare hostnames are accepted — <code>https://</code> is added automatically.</li>
<li><strong>CardDAV</strong>: tick <em>Same server as CalDAV</em> to reuse credentials, or enter separate details.</li>
<li><strong>Allow contact writes</strong>: when enabled, agents can create, update, and delete contacts.</li>
<li><strong>Test buttons</strong>: verify connectivity before saving.</li>
</ul>
<h2 id="settings-pushover">Pushover</h2>
<p>
Set your personal <strong>User Key</strong> to receive push notifications on your Pushover-connected devices. Your User Key is shown on your <a href="https://pushover.net" target="_blank">pushover.net</a> dashboard. The App Token (the shared application credential) is managed by the admin — you only need your own User Key.
</p>
{% endif %}
<h2 id="settings-webhooks">Webhooks</h2>
<p>
Inbound webhooks let external services trigger agents via HTTP — useful for iOS Shortcuts, GitHub actions, Home Assistant automations, or any tool that can send an HTTP request.
</p>
<ul>
<li><strong>Create a webhook</strong>: assign a name, description, and target agent. The secret token is shown <strong>once</strong> at creation — copy it immediately. Use <em>Rotate Token</em> to generate a new one if it is ever compromised.</li>
<li><strong>Trigger via POST</strong>: <code>POST /webhook/{token}</code> with body <code>{"message": "..."}</code></li>
<li><strong>Trigger via GET</strong>: <code>GET /webhook/{token}?q=your+message</code> — useful for iOS Shortcuts URL actions</li>
<li><strong>Enable/disable</strong>: toggle a webhook on/off without deleting it</li>
</ul>
<p>The <strong>Outbound Targets</strong> section (same tab) manages named URLs that agents can send JSON payloads to via the <code>webhook</code> tool.</p>
<h2 id="settings-profile">Profile</h2>
<p>Available to all users. Contains:</p>
<ul>
@@ -422,16 +476,8 @@ mcp = FastMCP(
<li><strong>Two-Factor Authentication (TOTP)</strong>: enable/disable TOTP-based MFA. On setup, a QR code is shown to scan with any authenticator app (e.g. Aegis, Google Authenticator). Once enabled, every login requires a 6-digit code.</li>
<li><strong>Data Folder</strong>: shows the path of your auto-provisioned personal folder (set by admin via <code>system:users_base_folder</code>). This folder is where the Files page browses and where agent memory files are stored.</li>
<li><strong>Telegram Bot Token</strong>: per-user Telegram bot token (optional). Overrides the global token for your sessions.</li>
<li><strong>CalDAV</strong>: per-user CalDAV server, credentials, and calendar name. Overrides the global CalDAV config.</li>
</ul>
{% if not (current_user and current_user.is_admin) %}
<h2 id="settings-caldav">CalDAV</h2>
<p>
Configure your personal CalDAV connection under <strong>Settings → Profile → CalDAV</strong>. This overrides the global CalDAV config set by the admin. Fields: server URL, username, password, calendar name. Leave blank to inherit the global config.
</p>
{% endif %}
<h2 id="settings-personality">Personality</h2>
<p>
Edit <strong>SOUL.md</strong> (agent identity, values, communication style) and <strong>USER.md</strong> (owner context: name, location, preferences) directly in the browser. Changes take effect immediately — no restart required.
@@ -553,9 +599,7 @@ mcp = FastMCP(
<tr><td><code>system:security_max_subject_chars</code></td><td>Max chars of email subject (default: 200)</td></tr>
<tr><td><code>telegram:bot_token</code></td><td>Global Telegram bot API token</td></tr>
<tr><td><code>telegram:default_agent_id</code></td><td>UUID of agent for unmatched Telegram messages</td></tr>
<tr><td><code>pushover_user_key</code></td><td>Pushover user key</td></tr>
<tr><td><code>pushover_app_token</code></td><td>Pushover application token</td></tr>
<tr><td><code>caldav_calendar_name</code></td><td>Optional CalDAV calendar name override (global)</td></tr>
<tr><td><code>pushover_app_token</code></td><td>Pushover App Token — managed via <strong>Settings → Pushover</strong>, not this tab</td></tr>
<tr><td><code>brain:mcp_key</code></td><td>2nd Brain MCP authentication key</td></tr>
<tr><td><code>system:api_key_hash</code></td><td>SHA-256 hash of the external API key (raw key never stored)</td></tr>
<tr><td><code>system:api_key_created_at</code></td><td>Timestamp of last API key generation</td></tr>
@@ -632,6 +676,10 @@ mcp = FastMCP(
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/settings/branding/logo</code></td><td>Reset logo to default</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/settings/audit-retention</code></td><td>Current audit retention setting</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/settings/audit-retention</code></td><td>Update <code>{days}</code></td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/settings/caldav</code></td><td>Admin CalDAV &amp; CardDAV config (same as <code>/api/my/caldav/config</code>)</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/settings/caldav</code></td><td>Save admin CalDAV &amp; CardDAV config</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/settings/pushover</code></td><td>Current Pushover App Token and admin User Key</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/settings/pushover</code></td><td>Save App Token and admin User Key</td></tr>
</tbody>
</table>
</div>
@@ -768,14 +816,62 @@ mcp = FastMCP(
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/mfa/setup/begin</code></td><td>Start MFA setup — returns QR code PNG (base64) and provisioning URI</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/mfa/setup/confirm</code></td><td>Confirm setup with a valid TOTP code <code>{code}</code></td></tr>
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/my/mfa/disable</code></td><td>Disable MFA for the current user</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/caldav/config</code></td><td>Get per-user CalDAV config</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/caldav/config</code></td><td>Save per-user CalDAV credentials</td></tr>
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/my/caldav/config</code></td><td>Remove per-user CalDAV config (fall back to global)</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/caldav/config</code></td><td>Get per-user CalDAV &amp; CardDAV config</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/caldav/config</code></td><td>Save per-user CalDAV &amp; CardDAV credentials</td></tr>
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/my/caldav/config</code></td><td>Remove per-user CalDAV &amp; CardDAV config</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/caldav/test</code></td><td>Test CalDAV connectivity with current saved config</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/caldav/test-carddav</code></td><td>Test CardDAV connectivity with current saved config</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/pushover</code></td><td>Get current user's Pushover User Key (masked)</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/pushover</code></td><td>Save personal User Key <code>{user_key}</code></td></tr>
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/my/pushover</code></td><td>Remove personal User Key</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/telegram/whitelisted-chats</code></td><td>List Telegram chat IDs whitelisted for the current user</td></tr>
</tbody>
</table>
</div>
<h3>Webhooks</h3>
<div class="table-wrap">
<table class="help-api-table">
<thead><tr><th>Method</th><th>Path</th><th>Description</th></tr></thead>
<tbody>
<tr><td><span class="http-get">GET</span></td><td><code>/api/webhooks</code></td><td>List inbound webhook endpoints (admin)</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/webhooks</code></td><td>Create endpoint — returns token once (admin)</td></tr>
<tr><td><span class="http-put">PUT</span></td><td><code>/api/webhooks/{id}</code></td><td>Update name/description/agent/enabled (admin)</td></tr>
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/webhooks/{id}</code></td><td>Delete endpoint (admin)</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/webhooks/{id}/rotate</code></td><td>Regenerate token — returns new token once (admin)</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/webhooks</code></td><td>List current user's webhook endpoints</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/webhooks</code></td><td>Create personal webhook endpoint</td></tr>
<tr><td><span class="http-put">PUT</span></td><td><code>/api/my/webhooks/{id}</code></td><td>Update personal webhook endpoint</td></tr>
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/my/webhooks/{id}</code></td><td>Delete personal webhook endpoint</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/webhook/{token}</code></td><td>Trigger via GET — param: <code>?q=message</code> (no auth)</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/webhook/{token}</code></td><td>Trigger via POST — body: <code>{"message": "...", "async": true}</code> (no auth)</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/webhook-targets</code></td><td>List outbound webhook targets (admin)</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/webhook-targets</code></td><td>Create outbound target (admin)</td></tr>
<tr><td><span class="http-put">PUT</span></td><td><code>/api/webhook-targets/{id}</code></td><td>Update outbound target (admin)</td></tr>
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/webhook-targets/{id}</code></td><td>Delete outbound target (admin)</td></tr>
</tbody>
</table>
</div>
<h3>Monitors</h3>
<div class="table-wrap">
<table class="help-api-table">
<thead><tr><th>Method</th><th>Path</th><th>Description</th></tr></thead>
<tbody>
<tr><td><span class="http-get">GET</span></td><td><code>/api/watched-pages</code></td><td>List page-change monitors</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/watched-pages</code></td><td>Create page monitor</td></tr>
<tr><td><span class="http-put">PUT</span></td><td><code>/api/watched-pages/{id}</code></td><td>Update page monitor</td></tr>
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/watched-pages/{id}</code></td><td>Delete page monitor</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/watched-pages/{id}/check-now</code></td><td>Force an immediate check</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/rss-feeds</code></td><td>List RSS feed monitors</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/rss-feeds</code></td><td>Create RSS feed monitor</td></tr>
<tr><td><span class="http-put">PUT</span></td><td><code>/api/rss-feeds/{id}</code></td><td>Update RSS feed monitor</td></tr>
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/rss-feeds/{id}</code></td><td>Delete RSS feed monitor</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/rss-feeds/{id}/fetch-now</code></td><td>Force an immediate fetch</td></tr>
</tbody>
</table>
</div>
<h3>User Management <small style="color:var(--text-dim)">(Admin)</small></h3>
<div class="table-wrap">
<table class="help-api-table">