diff --git a/Changes.md b/Changes.md index b0da1a6..c3da3b1 100644 --- a/Changes.md +++ b/Changes.md @@ -1,5 +1,19 @@ # Änderungen +## Einstellungen: Reiter "Profil" zum Ändern des eigenen Passworts (20.08.2026) + +Bislang ließ sich das eigene Passwort nur direkt über den Datenbestand ändern (Feature #46). +Der Einstellungsdialog bekommt dafür einen neuen Reiter `Profil`, der den angemeldeten +Benutzernamen anzeigt und ein eigenes Formular für die Passwortänderung enthält – getrennt +vom generischen Speichern-Formular der übrigen Reiter, weil hier kein Einstellungswert, +sondern der Anmeldedatensatz geschrieben wird. + +`EinstellungenController::passwortAendern()` (Route `POST /einstellungen/passwort`) verlangt +das aktuelle Passwort (geprüft über `Benutzer::pruefeAnmeldung()`), ein neues mit mindestens +10 Zeichen – dieselbe Grenze wie bei der Ersteinrichtung – und dessen Wiederholung. Bei Erfolg +schreibt `Benutzer::passwortSetzen()` den neuen Hash; ein Eintrag landet wie bei An- und +Abmeldung im Debug-Log. + ## CONTRIBUTE.md und Setup-Skript für Mitwirkende (19.08.2026) `CONTRIBUTE.md` beschreibt, wie Beiträge ankommen (Issue, PR, E-Mail), welche diff --git a/public/index.php b/public/index.php index b0c1dc9..a104e6e 100644 --- a/public/index.php +++ b/public/index.php @@ -68,6 +68,7 @@ $router->post('/cockpit/vermoegen/konten', fn() => (new CockpitController())->ko $router->get('/einstellungen', fn() => (new EinstellungenController())->index()); $router->post('/einstellungen', fn() => (new EinstellungenController())->speichern()); $router->post('/einstellungen/log-leeren', fn() => (new EinstellungenController())->logLeeren()); +$router->post('/einstellungen/passwort', fn() => (new EinstellungenController())->passwortAendern()); // Konten. Die festen Pfade stehen vor den Mustern mit {id}, sonst schluckt // "/konten/{id}/…" die Route "/konten/neu". diff --git a/src/Controllers/EinstellungenController.php b/src/Controllers/EinstellungenController.php index e334fed..b8b9887 100644 --- a/src/Controllers/EinstellungenController.php +++ b/src/Controllers/EinstellungenController.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Finanzmanager\Controllers; +use Finanzmanager\Auth; +use Finanzmanager\Models\Benutzer; use Finanzmanager\Models\Einstellung; use Finanzmanager\Models\Kategorie; use Finanzmanager\Services\Cockpit; @@ -90,6 +92,39 @@ class EinstellungenController extends BaseController ); } + /** Eigenes Passwort ändern, Reiter "Profil". Passt nicht in felderFuer()/speichern(), + * weil es keinen Einstellungswert schreibt, sondern die Anmeldedaten des Benutzers. */ + public function passwortAendern(): void + { + $this->verlangeAnmeldung(); + + $benutzer = Auth::benutzer(); + if ($benutzer === null) { + View::redirect('/login'); + } + + $altesPasswort = (string)($_POST['passwort_alt'] ?? ''); + $neuesPasswort = (string)($_POST['passwort_neu'] ?? ''); + $wiederholung = (string)($_POST['passwort_neu_wdh'] ?? ''); + + $fehler = match (true) { + Benutzer::pruefeAnmeldung($benutzer['benutzername'], $altesPasswort) === null + => 'Das aktuelle Passwort ist falsch.', + mb_strlen($neuesPasswort) < 10 => 'Das neue Passwort muss mindestens 10 Zeichen lang sein.', + $neuesPasswort !== $wiederholung => 'Die beiden neuen Passwörter stimmen nicht überein.', + default => null, + }; + + if ($fehler !== null) { + View::redirect('/einstellungen?bereich=profil', $fehler, 'fehler'); + } + + Benutzer::passwortSetzen((int)$benutzer['id'], $neuesPasswort); + DebugLog::schreibe('auth', 'Passwort geändert', ['benutzer_id' => $benutzer['id']]); + + View::redirect('/einstellungen?bereich=profil', 'Passwort geändert.'); + } + /** * Welche Felder zu welchem Reiter gehören, inklusive Typ. * Bereiche ohne Einträge sind Platzhalter für spätere Phasen. diff --git a/src/Models/Einstellung.php b/src/Models/Einstellung.php index e4bfd8b..90ad34f 100644 --- a/src/Models/Einstellung.php +++ b/src/Models/Einstellung.php @@ -21,6 +21,7 @@ class Einstellung 'import' => 'Import/Export', 'wertpapiere' => 'Wertpapiere', 'diagnose' => 'Diagnose', + 'profil' => 'Profil', ]; /** diff --git a/src/Views/einstellungen/index.php b/src/Views/einstellungen/index.php index 2910b8a..72daab2 100644 --- a/src/Views/einstellungen/index.php +++ b/src/Views/einstellungen/index.php @@ -13,6 +13,7 @@ use Finanzmanager\View; /** @var list $kacheln */ /** @var list> $kategorien */ /** @var string $csrfToken */ +/** @var array|null $benutzer */ $wert = static fn(string $k, string $standard = ''): string => (string)($werte[$k] ?? $standard); @@ -266,6 +267,11 @@ include __DIR__ . '/../layout/kopf.php'; + +

+ Angemeldet als . +

+

Dieser Bereich füllt sich in einer späteren Phase: @@ -280,6 +286,28 @@ include __DIR__ . '/../layout/kopf.php'; + +

+ + + + + + + +

Mindestens 10 Zeichen.

+ + + + +
+ +
+
+ +
diff --git a/tests/36_ProfilPasswortTest.php b/tests/36_ProfilPasswortTest.php new file mode 100644 index 0000000..7ae8a05 --- /dev/null +++ b/tests/36_ProfilPasswortTest.php @@ -0,0 +1,42 @@ +passwortWirdGeaendert(); + $this->altesPasswortWirdUngueltig(); + } + + private function passwortWirdGeaendert(): void + { + $name = 'test_' . bin2hex(random_bytes(4)); + $id = Benutzer::anlegen($name, 'altespasswort123'); + + Benutzer::passwortSetzen($id, 'neuespasswort456'); + + $this->assertNotEmpty( + Benutzer::pruefeAnmeldung($name, 'neuespasswort456'), + 'Anmeldung mit dem neuen Passwort gelingt' + ); + } + + private function altesPasswortWirdUngueltig(): void + { + $name = 'test_' . bin2hex(random_bytes(4)); + $id = Benutzer::anlegen($name, 'altespasswort123'); + + Benutzer::passwortSetzen($id, 'neuespasswort456'); + + $this->assertNull( + Benutzer::pruefeAnmeldung($name, 'altespasswort123'), + 'Anmeldung mit dem alten Passwort schlägt danach fehl' + ); + } +}