Zwei-Faktor-Anmeldung per TOTP

app/totp.php implementiert RFC 6238 selbst statt per Bibliothek: der
Algorithmus ist ein HMAC plus eine Truncation, und ein zweiter Faktor ist
die letzte Stelle fuer ungepruefte Abhaengigkeiten. Der QR-Code entsteht
aus dem ohnehin vorhandenen TCPDF, damit kein externer Dienst das
Geheimnis sieht.

Beim Login wird die Anmeldung bei aktivem zweitem Faktor nicht
abgeschlossen; der Zwischenzustand gewaehrt keinerlei Zugriff und ist
byte-identisch zu einem unangemeldeten Aufruf. users.totp_last_step
verhindert die Wiederverwendung eines abgefangenen Codes innerhalb seines
Gueltigkeitsfensters. Abschalten verlangt Passwort und Code.

APP_REQUIRE_2FA_FOR_ADMINS macht den Faktor fuer Platform-Admins
verbindlich, per Weiterleitung auf die Einrichtung statt als harte Sperre
- sonst koennte der Schalter den einzigen Admin aussperren. Standard aus.

check-konto-und-mandantenwechsel erwartete beim Login noch das entfernte
Kundenkuerzel-Feld und damit einen direkten Sprung aufs Dashboard; der
Check bildet jetzt den tatsaechlichen Weg ueber die Mandantenauswahl ab.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-23 23:51:45 +02:00
co-authored by Claude Opus 5
parent 86334c2752
commit a2dcc4df65
14 changed files with 1335 additions and 5 deletions
+11 -5
View File
@@ -169,13 +169,21 @@ try {
// ---------------------------------------------------------------
$cookies = [];
$csrf = konto_csrf("{$baseUrl}/login.php", $cookies);
// Das Login-Formular kennt kein Kundenkuerzel mehr: wer bei mehreren
// Mandanten Mitglied ist, waehlt nach dem Passwort per Klarnamen aus.
$login = konto_request("{$baseUrl}/login.php", $cookies, 'POST', http_build_query([
'csrf_token' => $csrf,
'email' => "kontocheck-{$suffix}@test.local",
'password' => $altesPasswort,
'tenant_slug' => "kontocheck-a-{$suffix}",
]));
pruefe('Login leitet auf index.php statt konto.php', $login['location'] === 'index.php');
pruefe('Login mit mehreren Mandanten fuehrt zur Mandantenauswahl', $login['location'] === 'mandant-auswahl.php');
$csrf = konto_csrf("{$baseUrl}/mandant-auswahl.php", $cookies);
$ersteWahl = konto_request("{$baseUrl}/mandant-auswahl.php", $cookies, 'POST', http_build_query([
'csrf_token' => $csrf,
'tenant_id' => (string)$tenantAId,
]));
pruefe('Mandantenwahl nach dem Login leitet auf index.php statt konto.php', $ersteWahl['location'] === 'index.php');
// ---------------------------------------------------------------
// 3. Verifikation wird fuer Einladungen erzwungen
@@ -281,7 +289,6 @@ try {
'csrf_token' => $csrf,
'email' => "kontocheck-{$suffix}@test.local",
'password' => $altesPasswort,
'tenant_slug' => "kontocheck-a-{$suffix}",
]));
pruefe('Login mit altem Passwort schlaegt fehl', $altLogin['location'] === null);
@@ -291,9 +298,8 @@ try {
'csrf_token' => $csrf,
'email' => "kontocheck-{$suffix}@test.local",
'password' => $neuesPasswort,
'tenant_slug' => "kontocheck-a-{$suffix}",
]));
pruefe('Login mit neuem Passwort funktioniert', $neuLogin['location'] === 'index.php');
pruefe('Login mit neuem Passwort funktioniert', $neuLogin['location'] === 'mandant-auswahl.php');
// ---------------------------------------------------------------
// Reset-Link bestaetigt die Adresse mit