Security UI: Settings toggles for reverse-proxy mode + auth-proxy header
Config is DB-backed (metadata.app_config) — there is no config.json — so the reverse-proxy settings I added earlier had NO way to be set by a user and were effectively dead. Added them to Settings → Security, next to the launch-PIN toggle: - "Behind a reverse proxy" checkbox (security.trust_reverse_proxy) — help text notes it's for nginx/Caddy/Traefik+TLS, to leave OFF for direct/LAN http://, and that it needs a restart (applied at app init). - "Auth proxy user header" field (security.auth_proxy_header) — e.g. Remote-User, with the must-strip-client-headers warning; blank = off. Wired into the existing settings load + save; the save loop already persists every key in the security object via config_manager.set, so no backend change needed. Fixed Support/REVERSE-PROXY.md to point at Settings → Security instead of a nonexistent config.json. Off by default → zero impact for direct users. 64 script-split integrity tests pass.
This commit is contained in:
parent
86d0a0dd62
commit
fb8c8a71c6
3 changed files with 30 additions and 14 deletions
|
|
@ -15,15 +15,8 @@ internet. This guide covers the safe setup.
|
||||||
|
|
||||||
By default SoulSync does **not** trust proxy headers (so a direct client can't spoof
|
By default SoulSync does **not** trust proxy headers (so a direct client can't spoof
|
||||||
its IP or pretend the connection is HTTPS). If you're behind a proxy that
|
its IP or pretend the connection is HTTPS). If you're behind a proxy that
|
||||||
terminates TLS, opt in by setting this in your `config.json`:
|
terminates TLS, turn on **Settings → Security → "Behind a reverse proxy"** and
|
||||||
|
**restart SoulSync** (this option applies at startup).
|
||||||
```json
|
|
||||||
{
|
|
||||||
"security": {
|
|
||||||
"trust_reverse_proxy": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
When enabled, SoulSync:
|
When enabled, SoulSync:
|
||||||
- trusts `X-Forwarded-For/Proto/Host/Port` from **one** proxy hop (correct client
|
- trusts `X-Forwarded-For/Proto/Host/Port` from **one** proxy hop (correct client
|
||||||
|
|
@ -116,11 +109,8 @@ Pick one:
|
||||||
option for internet exposure.
|
option for internet exposure.
|
||||||
|
|
||||||
SoulSync can **trust the proxy's authenticated-user header** so the launch PIN is
|
SoulSync can **trust the proxy's authenticated-user header** so the launch PIN is
|
||||||
skipped once the proxy has logged you in. Set the header name in `config.json`:
|
skipped once the proxy has logged you in. Set the header name in **Settings →
|
||||||
|
Security → "Auth proxy user header"** (e.g. `Remote-User`).
|
||||||
```json
|
|
||||||
{ "security": { "auth_proxy_header": "Remote-User" } }
|
|
||||||
```
|
|
||||||
|
|
||||||
> ⚠️ **Only enable this behind a proxy you control that STRIPS any client-supplied
|
> ⚠️ **Only enable this behind a proxy you control that STRIPS any client-supplied
|
||||||
> copy of that header.** Otherwise a direct visitor could send `Remote-User: admin`
|
> copy of that header.** Otherwise a direct visitor could send `Remote-User: admin`
|
||||||
|
|
|
||||||
|
|
@ -5993,6 +5993,24 @@
|
||||||
Origins (full URL, no trailing slash) allowed to open WebSocket connections to this instance — one per line, or comma-separated. Leave empty for same-origin only (the secure default; works for direct access and most reverse-proxy setups). Add your public domain here if you reach SoulSync via a reverse proxy or custom domain and the WebSocket fails to connect. Use <code>*</code> on its own line to allow any origin (insecure — only do this if you understand why you need it).
|
Origins (full URL, no trailing slash) allowed to open WebSocket connections to this instance — one per line, or comma-separated. Leave empty for same-origin only (the secure default; works for direct access and most reverse-proxy setups). Add your public domain here if you reach SoulSync via a reverse proxy or custom domain and the WebSocket fails to connect. Use <code>*</code> on its own line to allow any origin (insecure — only do this if you understand why you need it).
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="toggle-label">
|
||||||
|
<input type="checkbox" id="security-trust-proxy">
|
||||||
|
<span>Behind a reverse proxy</span>
|
||||||
|
</label>
|
||||||
|
<div class="setting-help-text">
|
||||||
|
Enable only if SoulSync runs behind nginx / Caddy / Traefik that terminates HTTPS. Trusts the proxy's <code>X-Forwarded-*</code> headers, marks the session cookie HTTPS-only, and adds security headers. <strong>Leave OFF for direct or LAN access over http://</strong> — turning it on would make the session cookie HTTPS-only and break plain-HTTP access. <strong>Takes effect after a restart.</strong> See <code>Support/REVERSE-PROXY.md</code>.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="security-auth-proxy-header">Auth proxy user header <span style="opacity:0.6">(optional)</span>:</label>
|
||||||
|
<input type="text" id="security-auth-proxy-header" placeholder="Remote-User" autocomplete="off" style="width: 100%; font-family: monospace; font-size: 12px;">
|
||||||
|
<div class="setting-help-text">
|
||||||
|
If an auth proxy (Authelia / Authentik / oauth2-proxy) logs users in <em>in front of</em> SoulSync, enter the header it sets (e.g. <code>Remote-User</code>) and SoulSync will skip the launch PIN for already-authenticated requests. <strong>Only set this behind a proxy that strips any client-supplied copy of the header</strong> — otherwise it can be spoofed. Leave blank to disable (the default).
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Discovery Settings -->
|
<!-- Discovery Settings -->
|
||||||
|
|
|
||||||
|
|
@ -1398,6 +1398,12 @@ async function loadSettingsData() {
|
||||||
const corsField = document.getElementById('security-cors-origins');
|
const corsField = document.getElementById('security-cors-origins');
|
||||||
if (corsField) corsField.value = corsOrigins;
|
if (corsField) corsField.value = corsOrigins;
|
||||||
|
|
||||||
|
// Reverse-proxy mode + auth-proxy header (default off / empty).
|
||||||
|
const trustProxy = document.getElementById('security-trust-proxy');
|
||||||
|
if (trustProxy) trustProxy.checked = settings.security?.trust_reverse_proxy || false;
|
||||||
|
const authHeader = document.getElementById('security-auth-proxy-header');
|
||||||
|
if (authHeader) authHeader.value = settings.security?.auth_proxy_header || '';
|
||||||
|
|
||||||
// Check if admin has a PIN set
|
// Check if admin has a PIN set
|
||||||
const profilesRes = await fetch('/api/profiles');
|
const profilesRes = await fetch('/api/profiles');
|
||||||
const profilesData = await profilesRes.json();
|
const profilesData = await profilesRes.json();
|
||||||
|
|
@ -3146,6 +3152,8 @@ async function saveSettings(quiet = false) {
|
||||||
security: {
|
security: {
|
||||||
require_pin_on_launch: document.getElementById('security-require-pin')?.checked || false,
|
require_pin_on_launch: document.getElementById('security-require-pin')?.checked || false,
|
||||||
cors_origins: document.getElementById('security-cors-origins')?.value?.trim() || '',
|
cors_origins: document.getElementById('security-cors-origins')?.value?.trim() || '',
|
||||||
|
trust_reverse_proxy: document.getElementById('security-trust-proxy')?.checked || false,
|
||||||
|
auth_proxy_header: document.getElementById('security-auth-proxy-header')?.value?.trim() || '',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue