9 Commits
Author SHA1 Message Date
dsh 96b5fb6ed0 refactor: move logout/change-password UI from floating widget to settings
The injected floating buttons overlapped the mobile composer send button.
Replace the tapIndex-injected widget with a proper client plugin
(panel-auth-ui, ui/ package): registers an account & security section in
the panel settings via the settings.section slot, with change-password
form and logout action. Host plugin keeps only the /panel-auth/change-
password and /panel-auth/logout endpoints.

- ui/: ModuleLoader-format client bundle (dsh.client declaration,
  exports ./client and ./package.json), host no-op stub, bare-package
  symlink required in the profile node_modules (documented in README).
- Host: renderAuthWidget/injectWidget/tapIndex injection removed.
- Tests: widget tests removed; change-password flows unchanged.
2026-08-16 03:28:31 -04:00
dsh f9883150e0 feat: in-panel logout and change-password widget
- webServer.tapIndex injects a floating widget (logout + change-password
  buttons and a modal) into the panel's index.html; scoped styles, vanilla
  JS, endpoints embedded safely.
- New /panel-auth/change-password endpoint: requires a valid session,
  verifies the old password (failures count toward lockout), enforces
  length/novelty policy, generates a fresh scrypt hash, writes it back to
  the profile patch file, and hot-applies it to the running loader entry.
- Audit: password-change events with per-step results; old-password
  mismatches logged as login-fail/password-change-old-mismatch.
- Config: changePasswordPath, patchFilePath.
- Tests: widget rendering/injection, anonymous rejection, old-password
  mismatch, policy rejection, success flow and credential switch.
2026-08-16 03:00:47 -04:00
dsh 873b712774 docs: reverse proxy must also strip Origin upstream
The browser-trust fence verifies Origin == Host after the loopback pin;
with Host presented as loopback the browser's public Origin can never
match (403 on every /api POST). Strip the Origin header in the proxy so
the fence falls back to its Sec-Fetch-Site cross-site check, while
panel-auth's cookie auth remains the real gate.
2026-08-16 02:21:22 -04:00
dsh ddb32ce129 fix: tolerate loopback Host behind reverse proxy, document Caddy setup
DSH pins privileged /api methods (settings.*, credentials.*, agentPreset.*,
host.pickDirectory, ...) to loopback hosts by design. When a reverse proxy
fronts the panel with the public Host header, those methods return 403.

The supported deployment shape is forwarding Host as loopback upstream
(header_up Host 127.0.0.1 in Caddy). This change:
- skips the login origin check when the incoming Host is loopback (proxy
  context), while still rejecting real cross-site posts on public hosts;
- documents the reverse-proxy requirement in the README;
- extends tests with raw-request coverage for non-loopback cross-site
  rejection, port tolerance, and loopback-Host skip.
2026-08-16 02:18:07 -04:00
dsh 8823049b66 feat: brute-force protection with per-IP escalating lockout
- Failures counted per client IP (X-Forwarded-For last hop behind the
  proxy); after maxFailures within the window the IP is locked out,
  doubling per repeat up to lockoutMaxSeconds.
- Locked IPs get 429 + Retry-After (login page / JSON for API), and the
  scrypt verification is skipped entirely while locked (no CPU burn).
- Fixed failedLoginDelayMs delay on every bad credential attempt.
- Basic-auth path counts and clears identically; success resets the IP.
- All thresholds configurable; in-memory state only.
- Tests: lockout, expiry restore, basic-path counting, XFF last-hop key.
2026-08-16 01:48:36 -04:00
dsh 000ca41501 fix: treat Origin: null as absent instead of rejecting login
The audit diagnostics showed the real browser posting the login form with
Origin: null (opaque context — sandboxed iframe, privacy proxy, or browser
isolation product), which is not an attack signal. Skip the origin check
for the literal "null" value; real cross-site posts with a concrete
foreign origin are still rejected.

Tests: Origin:null POST tolerated, foreign origin still 403.
2026-08-16 01:43:13 -04:00
dsh 1794fe98c2 fix: relax login origin check to hostname level, log origin/host on reject
Real browsers behind proxies/alternate listeners can produce port or case
mismatches between the Origin header and the incoming Host header, which
the strict host-level comparison rejected ("非法请求来源"). Compare
hostnames instead (still blocking foreign sites) and record the raw origin
and host values in the audit entry for future diagnostics.

Tests: cross-origin POST rejection, same-hostname different-port tolerance,
audit origin/host field assertions.
2026-08-16 01:40:52 -04:00
dsh dad6e07b73 feat: styled login page + structured JSONL audit logging
- Browser navigation now gets a self-contained login page (no JS, CSP
  hardened, XSS-escaped, cross-origin POST rejected) instead of the native
  Basic dialog; API clients keep 401 + WWW-Authenticate.
- Login/logout endpoints (/panel-auth/login, /panel-auth/logout) issue and
  revoke the signed cookie, then 303 back to the original target.
- Structured audit log (login-ok/login-fail/logout/challenge/reject with
  username, IP, UA, reason), default $DSH_HOME/panel-auth-audit.jsonl,
  5MB rotation; X-Forwarded-For honored behind the reverse proxy.
- Function-plugin form, per-request config, fail-open when unconfigured.
- Tests extended to 15 flows including login page, login POST, logout,
  audit assertions and XSS escaping.
2026-08-16 01:32:37 -04:00
dsh dde1431180 panel-auth: DSH web panel password gate plugin (Basic auth + signed cookie, fail-open) 2026-08-16 01:18:21 -04:00