Replace three divergent 2FA indicator styles (cyan x-badge in users list,
custom inline-flex chip in settings header, plain mono text in sidebar) with a
single x-two-factor-badge component using the cyan x-badge + shield icon style
and the canonical settings.two_factor_on/off lang keys throughout.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Optional host-level WireGuard gate for the Clusev panel: install.sh installs
wireguard-tools (off), a 'clusev wg setup/up/down/status/add-peer/remove-peer'
host CLI sets up wg0 + gates TCP 80/443 to the WG subnet via a dedicated
CLUSEV-WG-GATE iptables chain off DOCKER-USER, with 'clusev wg down' as the
escape hatch and SSH/WG-UDP never gated. Hardened after an adversarial review:
exact ordered firewall rules, persistence skips if wg0 is down (no reboot
brick), down isolates Clusev's chain, setup idempotency, precise install
wiring (CLUSEV_DIR exec + install_host_watchers). SP1 of 2; dashboard
status/traffic is SP2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
App-level persistent IP ban for Clusev's own login (distinct from the remote-fleet
Fail2banService): BannedIp model, BruteforceGuard service, guests-only BlockBannedIp
middleware, Login/2FA hooks + audit, a new Anmeldeschutz settings tab, clusev:unban CLI.
Hardened after an adversarial multi-lens review: documented IP trust model (Caddy sole
proxy, prod-only trustProxies), guests-only enforcement so an authed operator is never
locked out and can self-unban, current-IP self-whitelist, concrete cache strategy,
inet_pton CIDR matching, R5 confirm modals, errors.php for the 403 strings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- backup field: autocomplete=off -> one-time-code (backup codes ARE one-time codes;
matches the main challenge field, the rest of the app, and the spec; restores
password-manager OTP autofill that the earlier polish broke)
- both verify() use the trait's memoised $this->pendingUser() instead of a fresh
User::find(), saving a redundant query on the failed-attempt re-render (Pint dropped
the now-unused User import in TwoFactorBackup)
- spec: align trait backing-state visibility note to the implemented 'private'
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mount now redirects key-only users without a secure context straight to
the dedicated backup view. The main challenge form renders only for TOTP
users; a subordinate button links to two-factor.challenge.backup instead
of embedding the field inline.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Task-by-task TDD plan for Spec 1: shared CompletesTwoFactorChallenge trait,
new TwoFactorBackup component + guest route + view, main-view redesign
(form gated to TOTP users, backup button, mount->backup redirect for
key-only-no-secure-context), i18n keys, and full R12/R15 verification.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Spec 1 of 2 for the 2FA challenge rework. Moves the backup (recovery) code
off the combined challenge screen into a dedicated /two-factor-challenge/backup
route + view, behind a 'Backup-Code verwenden' button; the main screen shows
only the primary factor. A key-only user without secure context (http + bare IP)
is redirected straight to the backup view. Shared rate-limit/login logic is
extracted into a CompletesTwoFactorChallenge trait so both views hit one bucket
set. IP-vs-domain factor routing is already correct (WebauthnService::available)
and unchanged. Spec 2 (auth-failure -> fail2ban hard IP ban) is out of scope here.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A checkout path containing shell syntax ($(), quotes) could otherwise inject
code into /usr/local/bin/clusev, which runs as root via 'sudo clusev update'.
Render the path through printf %q into an unquoted assignment; add a test that
a hostile path does not execute injected code.
New /help full-page Livewire component with left topic nav (like Settings),
bilingual content via per-locale Blade partials, sidebar + command-palette
entries (g h). First topic: Überblick / Overview.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Spec for a bilingual /help page (left topic nav like Settings) covering all
settings, a generic reverse-proxy setup guide, and the 2FA access-path note
(security key only over the HTTPS domain; backup code on the bare-IP/HTTP path;
TOTP works everywhere). Also generalize the external-TLS hint copy (no product
name).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With one CSS + one JS entry the <link rel=preload> sits next to the matching
stylesheet/script and adds no value, but it triggers Chrome's 'preloaded but
not used' console warning (amplified by DevTools 'Disable cache', which
double-fetches the CSS). Disable preload tag generation; stylesheet and script
still load normally.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Even with authenticatorAttachment=cross-platform, Bitwarden's extension
intercepted navigator.credentials.create and offered to save a passkey. Add the
WebAuthn L3 'hints: [security-key]' to both registration and assertion options;
browsers and passkey managers that honor hints step aside and let the hardware
key prompt through. Injected into the client payload only (the session copy used
for server validation is unchanged).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Registration set no authenticatorSelection, so the browser offered the
passkey-manager 'save passkey' flow (Bitwarden) instead of prompting for the
YubiKey. Now request a cross-platform, non-resident, presence-only credential:
authenticatorAttachment=cross-platform (roaming key, not a platform/manager
passkey), residentKey=discouraged (non-discoverable second factor — what keeps
passkey managers out), userVerification=discouraged (single touch, no PIN).
Login assertion also uses discouraged so it is: insert key, one tap, done.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>