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.
- 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.
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.
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.
- 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.
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.
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.
- 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.