Merge pull request 'Fix #46: Reiter "Profil" zum Ändern des eigenen Passworts' (#47) from fds2610-bot/Finanzmanager:main into main
Reviewed-on: https://codeberg.org/fds2610/Finanzmanager/pulls/47
This commit is contained in:
commit
9022144c9e
6 changed files with 121 additions and 0 deletions
14
Changes.md
14
Changes.md
|
|
@ -1,5 +1,19 @@
|
||||||
# Änderungen
|
# Ä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 und Setup-Skript für Mitwirkende (19.08.2026)
|
||||||
|
|
||||||
`CONTRIBUTE.md` beschreibt, wie Beiträge ankommen (Issue, PR, E-Mail), welche
|
`CONTRIBUTE.md` beschreibt, wie Beiträge ankommen (Issue, PR, E-Mail), welche
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ $router->post('/cockpit/vermoegen/konten', fn() => (new CockpitController())->ko
|
||||||
$router->get('/einstellungen', fn() => (new EinstellungenController())->index());
|
$router->get('/einstellungen', fn() => (new EinstellungenController())->index());
|
||||||
$router->post('/einstellungen', fn() => (new EinstellungenController())->speichern());
|
$router->post('/einstellungen', fn() => (new EinstellungenController())->speichern());
|
||||||
$router->post('/einstellungen/log-leeren', fn() => (new EinstellungenController())->logLeeren());
|
$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. Die festen Pfade stehen vor den Mustern mit {id}, sonst schluckt
|
||||||
// "/konten/{id}/…" die Route "/konten/neu".
|
// "/konten/{id}/…" die Route "/konten/neu".
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Finanzmanager\Controllers;
|
namespace Finanzmanager\Controllers;
|
||||||
|
|
||||||
|
use Finanzmanager\Auth;
|
||||||
|
use Finanzmanager\Models\Benutzer;
|
||||||
use Finanzmanager\Models\Einstellung;
|
use Finanzmanager\Models\Einstellung;
|
||||||
use Finanzmanager\Models\Kategorie;
|
use Finanzmanager\Models\Kategorie;
|
||||||
use Finanzmanager\Services\Cockpit;
|
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.
|
* Welche Felder zu welchem Reiter gehören, inklusive Typ.
|
||||||
* Bereiche ohne Einträge sind Platzhalter für spätere Phasen.
|
* Bereiche ohne Einträge sind Platzhalter für spätere Phasen.
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ class Einstellung
|
||||||
'import' => 'Import/Export',
|
'import' => 'Import/Export',
|
||||||
'wertpapiere' => 'Wertpapiere',
|
'wertpapiere' => 'Wertpapiere',
|
||||||
'diagnose' => 'Diagnose',
|
'diagnose' => 'Diagnose',
|
||||||
|
'profil' => 'Profil',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use Finanzmanager\View;
|
||||||
/** @var list<string> $kacheln */
|
/** @var list<string> $kacheln */
|
||||||
/** @var list<array<string,mixed>> $kategorien */
|
/** @var list<array<string,mixed>> $kategorien */
|
||||||
/** @var string $csrfToken */
|
/** @var string $csrfToken */
|
||||||
|
/** @var array|null $benutzer */
|
||||||
|
|
||||||
$wert = static fn(string $k, string $standard = ''): string => (string)($werte[$k] ?? $standard);
|
$wert = static fn(string $k, string $standard = ''): string => (string)($werte[$k] ?? $standard);
|
||||||
|
|
||||||
|
|
@ -266,6 +267,11 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
</table>
|
</table>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php elseif ($bereich === 'profil'): ?>
|
||||||
|
<p class="feldhinweis">
|
||||||
|
Angemeldet als <strong><?= View::h($benutzer['benutzername'] ?? '') ?></strong>.
|
||||||
|
</p>
|
||||||
|
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<p class="platzhalter">
|
<p class="platzhalter">
|
||||||
Dieser Bereich füllt sich in einer späteren Phase:
|
Dieser Bereich füllt sich in einer späteren Phase:
|
||||||
|
|
@ -280,6 +286,28 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<?php if ($bereich === 'profil'): ?>
|
||||||
|
<form method="post" action="/einstellungen/passwort" class="formular breit">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||||||
|
|
||||||
|
<label for="passwort_alt">Aktuelles Passwort</label>
|
||||||
|
<input type="password" id="passwort_alt" name="passwort_alt" autocomplete="current-password" required>
|
||||||
|
|
||||||
|
<label for="passwort_neu">Neues Passwort</label>
|
||||||
|
<input type="password" id="passwort_neu" name="passwort_neu" autocomplete="new-password"
|
||||||
|
minlength="10" required>
|
||||||
|
<p class="feldhinweis">Mindestens 10 Zeichen.</p>
|
||||||
|
|
||||||
|
<label for="passwort_neu_wdh">Neues Passwort wiederholen</label>
|
||||||
|
<input type="password" id="passwort_neu_wdh" name="passwort_neu_wdh" autocomplete="new-password"
|
||||||
|
minlength="10" required>
|
||||||
|
|
||||||
|
<div class="knopfleiste">
|
||||||
|
<button type="submit" class="knopf primaer">Passwort ändern</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($bereich === 'diagnose' && $logDateien !== []): ?>
|
<?php if ($bereich === 'diagnose' && $logDateien !== []): ?>
|
||||||
<form method="post" action="/einstellungen/log-leeren" class="formular breit"
|
<form method="post" action="/einstellungen/log-leeren" class="formular breit"
|
||||||
data-bestaetigen="Alle Logdateien löschen?">
|
data-bestaetigen="Alle Logdateien löschen?">
|
||||||
|
|
|
||||||
42
tests/36_ProfilPasswortTest.php
Normal file
42
tests/36_ProfilPasswortTest.php
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Finanzmanager\Models\Benutzer;
|
||||||
|
|
||||||
|
class ProfilPasswortTest extends TestCase
|
||||||
|
{
|
||||||
|
protected string $testName = 'Test 36: Profil – eigenes Passwort ändern';
|
||||||
|
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$this->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'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue