Kontofunktionen vor dem Go-Live und Landingpage-Kuerzung
Vier Luecken geschlossen, die im Alltag sofort aufgefallen waeren: - Passwort aendern war im eingeloggten Zustand gar nicht moeglich; es gab nur den Reset per Mail-Link. saas_change_password() prueft das aktuelle Passwort, verlangt ein tatsaechlich anderes und erneuert danach die Session-ID. Formular in konto.php. - mandant-auswahl.php war nur direkt nach dem Login erreichbar. Wer bei mehreren Mandanten Mitglied ist, musste sich zum Wechseln abmelden. Die Seite bedient jetzt beide Wege, die Mandantenpruefung bleibt unveraendert ueber saas_identity_for_user_tenant(). Menuepunkt ab zwei Mitgliedschaften. - email_verified_at wurde nirgends geprueft, nur angezeigt - bei offener Selbstregistrierung konnte sich jemand mit fremder Adresse anmelden und alles nutzen. Erzwungen wird jetzt gezielt dort, wo eine Aktion nach aussen wirkt: Einladung, Info-Mail, Jahresabschluss. Der Login selbst bleibt bewusst frei, sonst waeren alle migrierten Bestandsnutzer mit NULL-Verifikation ausgesperrt. Zusaetzlich setzt der Passwort-Reset die Verifikation mit, weil der Mail-Link den Postfachzugriff nachweist - sonst blieben eingeladene Mitglieder dauerhaft unbestaetigt. - Login landete auf konto.php statt auf dem Dashboard. Landingpage: die gruene Vertrauenszeile auf "DSGVO-konform" gekuerzt, die Eintraege zu Paragraf 19 UStG und "Bestehende Ablaeufe bleiben" entfernt. Geprueft: neues scripts/check-konto-und-mandantenwechsel.php mit 18 Assertions gruen, Passwortformular zusaetzlich manuell inkl. CSRF (419). Bestehende Suiten unveraendert gruen: HTTP-Smoke 34 Seiten, Rollenmatrix 55, Mandanten-Isolation 12, M3-Auth 9, M3-Settings 15. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+102
-2
@@ -760,8 +760,17 @@ function saas_reset_password_with_token(PDO $pdo, string $token, string $passwor
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
$pdo->prepare('UPDATE users SET password_hash = ? WHERE id = ?')
|
||||
->execute([password_hash($password, PASSWORD_DEFAULT), (int)$tokenRow['user_id']]);
|
||||
// Wer den per Mail zugestellten Link oeffnen konnte, hat den Zugriff
|
||||
// auf das Postfach nachgewiesen - fachlich dasselbe wie eine
|
||||
// Verifikation. Das betrifft vor allem eingeladene Mitglieder: die
|
||||
// Zugangsvergabe laeuft ueber genau diesen Reset-Link, ohne dass je
|
||||
// eine separate Verifikationsmail verschickt wird.
|
||||
$pdo->prepare(
|
||||
'UPDATE users
|
||||
SET password_hash = ?,
|
||||
email_verified_at = COALESCE(email_verified_at, NOW())
|
||||
WHERE id = ?'
|
||||
)->execute([password_hash($password, PASSWORD_DEFAULT), (int)$tokenRow['user_id']]);
|
||||
$pdo->prepare('UPDATE user_auth_tokens SET consumed_at = NOW() WHERE id = ?')
|
||||
->execute([(int)$tokenRow['id']]);
|
||||
$pdo->commit();
|
||||
@@ -779,6 +788,97 @@ function saas_reset_password_with_token(PDO $pdo, string $token, string $passwor
|
||||
return ['ok' => true, 'errors' => []];
|
||||
}
|
||||
|
||||
/**
|
||||
* Passwortwechsel fuer den bereits angemeldeten Nutzer. Bewusst getrennt vom
|
||||
* Token-Reset: hier weist das aktuelle Passwort die Identitaet nach, nicht
|
||||
* ein Mail-Link. Setzt anders als der Reset keine E-Mail-Verifikation, weil
|
||||
* dabei nichts ueber das Postfach nachgewiesen wird.
|
||||
*/
|
||||
function saas_change_password(
|
||||
PDO $pdo,
|
||||
int $userId,
|
||||
string $currentPassword,
|
||||
string $password,
|
||||
string $passwordConfirm
|
||||
): array {
|
||||
$errors = [];
|
||||
if (strlen($password) < 8) {
|
||||
$errors[] = 'Das neue Passwort muss mindestens 8 Zeichen lang sein.';
|
||||
}
|
||||
if ($password !== $passwordConfirm) {
|
||||
$errors[] = 'Die Passwort-Wiederholung stimmt nicht.';
|
||||
}
|
||||
if ($currentPassword !== '' && $password === $currentPassword) {
|
||||
$errors[] = 'Das neue Passwort muss sich vom bisherigen unterscheiden.';
|
||||
}
|
||||
|
||||
if ($errors !== []) {
|
||||
return ['ok' => false, 'errors' => $errors];
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('SELECT password_hash, status FROM users WHERE id = ? LIMIT 1');
|
||||
$stmt->execute([$userId]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user === false || (string)$user['status'] !== 'active') {
|
||||
return ['ok' => false, 'errors' => ['Dieses Benutzerkonto ist nicht aktiv.']];
|
||||
}
|
||||
|
||||
$hash = (string)($user['password_hash'] ?? '');
|
||||
if ($hash === '' || !password_verify($currentPassword, $hash)) {
|
||||
return ['ok' => false, 'errors' => ['Das aktuelle Passwort ist nicht korrekt.']];
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo->prepare('UPDATE users SET password_hash = ? WHERE id = ?')
|
||||
->execute([password_hash($password, PASSWORD_DEFAULT), $userId]);
|
||||
} catch (Throwable $e) {
|
||||
return ['ok' => false, 'errors' => ['Das Passwort konnte nicht gespeichert werden.']];
|
||||
}
|
||||
|
||||
// Die eigene Session bleibt bestehen, bekommt aber eine neue ID, damit ein
|
||||
// eventuell mitgelesener Session-Bezeichner nach dem Wechsel wertlos ist.
|
||||
if (PHP_SAPI !== 'cli' && session_status() === PHP_SESSION_ACTIVE) {
|
||||
session_regenerate_id(true);
|
||||
}
|
||||
|
||||
return ['ok' => true, 'errors' => []];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hat der Nutzer seine E-Mail-Adresse nachgewiesen? Wird fuer Aktionen
|
||||
* geprueft, die Mails an Dritte ausloesen oder fremde Konten anlegen -
|
||||
* siehe saas_require_verified_email().
|
||||
*/
|
||||
function saas_email_verified(?array $user = null): bool
|
||||
{
|
||||
$user = $user ?? saas_current_user();
|
||||
|
||||
return $user !== null && ($user['email_verified_at'] ?? null) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sperrt Aktionen, die aus dem Konto heraus nach aussen wirken (Mitglieder
|
||||
* einladen, Massenmail, Jahresabschluss-Mails), solange die eigene Adresse
|
||||
* nicht bestaetigt ist. Bewusst nicht auf den gesamten Login angewendet: die
|
||||
* eigene Kaffeeliste darf man auch unbestaetigt fuehren, nur nicht im Namen
|
||||
* einer moeglicherweise fremden Adresse Mails an Dritte ausloesen.
|
||||
*/
|
||||
function saas_require_verified_email(?array $user = null): array
|
||||
{
|
||||
$user = $user ?? saas_require_login();
|
||||
|
||||
if (saas_email_verified($user)) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
http_response_code(403);
|
||||
die(
|
||||
'Für diese Aktion muss die eigene E-Mail-Adresse bestätigt sein. '
|
||||
. 'Den Bestätigungslink kannst du unter "Kundenkonto" erneut anfordern.'
|
||||
);
|
||||
}
|
||||
|
||||
function saas_request_email_verification(PDO $pdo, int $userId, ?int $tenantId = null): array
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
|
||||
Reference in New Issue
Block a user