Commit Graph

10 Commits (82d56447677b26149efe1fa5dd2359d40ade797d)

Author SHA1 Message Date
boban 7afa50d253 feat(webauthn): use a security key at the login challenge
When the panel runs on a domain+HTTPS and the user has a registered key, the 2FA
challenge offers "Mit Security-Key anmelden" alongside the TOTP/backup-code field.
A verified assertion completes login exactly like a TOTP success; rate-limit +
session gate reused. TOTP and backup codes remain fully usable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 18:26:58 +02:00
boban 197e6f7e49 fix(auth): show the reset-success status on the login page
Both reset paths flash a `status` and redirect to login, but it was never
rendered. Show it as a success banner so the user gets confirmation their
password changed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 17:10:24 +02:00
boban abaed12b0b feat(auth): email reset-link path (gated on a configured mailer)
ForgotPassword gains sendResetLink() + a ResetPassword component at
/reset-password/{token} using Laravel's password broker. The email option is
shown only when mail.default is a real mailer (hidden while MAIL_MAILER=log), so
it activates automatically once SMTP is configured. Tested with Notification::fake.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 16:57:05 +02:00
boban cb8b906aa8 feat(auth): forgot-password reset via 2FA or backup code
"Passwort vergessen?" on login -> a reset view that proves identity with a TOTP
or one-time backup code (no email needed), then sets a new password under the
min(12) mixedCase numbers policy. Rate-limited, generic messages (no account
enumeration), audited.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 16:55:01 +02:00
boban 764e1e5e53 feat(auth): show + download + regenerate 2FA backup codes
2FA confirm now generates 8 backup codes and redirects to a show-once recovery
view (download as .txt, regenerate). Reachable again from Settings. Codes are
shown before the onboarding gate so a fresh enrollee can save them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 16:52:23 +02:00
boban b2cb94bb32 feat(auth): accept a 2FA backup code at the login challenge
The challenge now falls back to consuming a one-time recovery code when the TOTP
check fails. Relaxed the code input (was numeric, maxlength 6) so a backup code
is typable, and added a hint. Bilingual.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 16:50:36 +02:00
boban 5691051ad8 feat(i18n): bilingual foundation (DE/EN) + localize auth/dashboard/servers/fleet/account
Adds user-selectable German (default) + English, per new rule R16.

Foundation:
- config locale=de + fallback=de; User.locale column (+migration); SetLocale middleware
  (user → session → default; SUPPORTED=[de,en]); public /locale/{code} route; DE/EN
  switcher (x-lang-switch) in topbar + auth layout; <html lang> dynamic.
- Translation structure: lang/{de,en}/<group>.php (one group per feature + shared common),
  identical keys across both languages, :placeholder interpolation. Documented as R16 in
  rules.md + CLAUDE.md (R9 reconciled).
- Removed the "Warum Caddy" rationale block from the System page (per feedback).

Localized this pass (views + Livewire components, keys de↔en at parity):
- common, auth, dashboard, servers, services, files, audit, settings, system, versions.
- #[Title] attributes → dynamic title() (attributes can't call __()).

Remaining (next commit): modals components + a few modal views, shell (command palette,
nav-item, server-item, toaster), and backend service messages (app/Services/*, OsProfile).
App is fully functional in German throughout; English covers the groups above.

Boot-verified: /login HTTP 200 in DE + EN, titles + <html lang> localized, locale switch works.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 22:36:16 +02:00
boban aa7a7b1db8 fix(ux): editor click bug (@js→index), self-hosted fonts, split-brand auth, R14
- Fix Alpine "Invalid or unexpected token" on file/service/key actions: @js()
  does NOT compile inside a Blade component (x-btn) attribute, so a literal
  @js(...) reached the DOM and Alpine choked on it. Switched all 10 wire:click
  arguments to loop-index lookups (files: open/go/download/edit/confirmDelete;
  services: confirm; keys: confirmKeyRemoval) — the component resolves the value
  server-side by index. {{ $loop->index }} compiles in component attributes; @js
  does not.
- Self-host fonts (R14): Chakra Petch / Space Grotesk / JetBrains Mono as local
  .woff2 in resources/fonts/, declared via @font-face in app.css, Vite-bundled
  with relative urls so they resolve in dev AND the prod build. No Google Fonts
  / CDN link or @import. New rule R14 added to rules.md + CLAUDE.md.
- Auth redesign (split-brand, per reference): new auth layout with a brand panel
  (faux terminal + glow mesh, hidden below lg) + redesigned login,
  two-factor-challenge, two-factor-setup, password-change forms.
- x-btn: add size="lg" (h-11, >=44px touch target) for the full-width auth CTA;
  one shared button component, no bespoke styles.
- Remove unused Laravel welcome.blade.php (was full of raw hex — R3 cleanup).

Verified (R12): /login 200 / 0 console errors, fonts load (Chakra+Grotesk+JB),
brand panel visible @1440; file-editor REAL click -> modal opens, textarea
renders, 0 Alpine errors; editor load/save roundtrip (/etc/hostname='debian');
all 7 routes 200 / 0 console errors. Full rules audit R1-R14 clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 01:16:31 +02:00
boban 696dbc9d04 fix(2fa): render the setup QR as <img> (getQRCodeInline returns a data URI)
getQRCodeInline() returns a base64 data URI (data:image/svg+xml;base64,…), not raw
SVG — so {!! !!} dumped it as text. Render it via <img src> with a light quiet-zone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:06:56 +02:00
boban b3e5d3a024 feat: auth + 2FA (login wall + TOTP + forced onboarding)
- Custom Livewire auth (full-page, R1/R2): Login (rate-limited), TwoFactorChallenge,
  TwoFactorSetup (QR via pragmarx/google2fa-qrcode), PasswordChange.
- Guard: panel routes behind auth + EnsureSecurityOnboarded middleware — forces
  password rotation, then 2FA enrolment, before the panel unlocks.
- User: two_factor_secret (encrypted), two_factor_confirmed_at, must_change_password.
- Centered auth layout, POST logout, dynamic sidebar user + 2FA status.
- Seeded admin with a one-time password + must_change_password.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 13:57:02 +02:00