Konsolidierung: Security-Härtung + .gitignore
- TOTP-Secret wird verschlüsselt-at-rest gespeichert (Crypto, Klartext-Fallback) - forgot_password.php: Throttle (3/15min je Session) gegen Reset-Spam - .gitignore für vendor/, composer.lock, phpunit-Cache, Updater-Laufzeitdaten - Verifiziert: PHPUnit 15/15 grün, PHPStan ohne Fehler
This commit is contained in:
parent
c63c5e78f0
commit
546accc185
3 changed files with 26 additions and 3 deletions
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Dev-/Test-Abhängigkeiten (Laufzeit braucht KEIN composer)
|
||||
/vendor/
|
||||
composer.lock
|
||||
.phpunit.result.cache
|
||||
.phpunit.cache/
|
||||
|
||||
# Updater-Laufzeitdaten
|
||||
/updater/storage/.version
|
||||
/updater/storage/.maintenance
|
||||
/updater/storage/.update-progress
|
||||
/updater/storage/.update.zip
|
||||
/updater/storage/.update-staging/
|
||||
/updater/storage/.migrations-lock
|
||||
/updater/storage/updater-settings.json
|
||||
|
|
@ -24,9 +24,16 @@ $success = '';
|
|||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
// Einfacher Throttle: max. 3 Anfragen pro 15 Minuten je Session (gegen Spam)
|
||||
$now = time();
|
||||
$rl = array_values(array_filter($_SESSION['pwreset_times'] ?? [], fn($t) => ($now - $t) < 900));
|
||||
if (count($rl) >= 3) {
|
||||
$error = 'Zu viele Anfragen. Bitte warten Sie einige Minuten.';
|
||||
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$error = __('error_email_invalid');
|
||||
} else {
|
||||
$rl[] = $now;
|
||||
$_SESSION['pwreset_times'] = $rl;
|
||||
$user = $db->fetchOne("SELECT * FROM users WHERE email = ? AND is_active = 1 AND password_hash IS NOT NULL", [$email]);
|
||||
|
||||
// Always show success (don't reveal whether email exists)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
require_once __DIR__ . '/Totp.php';
|
||||
require_once __DIR__ . '/Notifier.php';
|
||||
require_once __DIR__ . '/Crypto.php';
|
||||
|
||||
class Auth {
|
||||
private $db;
|
||||
|
|
@ -117,7 +118,8 @@ class Auth {
|
|||
return false;
|
||||
}
|
||||
// Entweder gueltiger TOTP-Code ODER ein Recovery-/Backup-Code
|
||||
$ok = Totp::verify($user['totp_secret'], $code);
|
||||
// (Secret wird verschluesselt gespeichert; Klartext-Fallback via decrypt)
|
||||
$ok = Totp::verify(Crypto::decrypt($user['totp_secret']), $code);
|
||||
if (!$ok && $this->consumeBackupCode($user, $code)) {
|
||||
$ok = true;
|
||||
$this->writeAuditLog($user['id'], 'user_login_backup_code', 'user', $user['id'], 'Login per Recovery-Code');
|
||||
|
|
@ -143,7 +145,7 @@ class Auth {
|
|||
$hashes = array_map(function ($c) { return hash('sha256', $c); }, $codes);
|
||||
$this->db->query(
|
||||
"UPDATE users SET totp_secret = ?, totp_enabled = 1, totp_backup_codes = ? WHERE id = ?",
|
||||
[$secret, json_encode($hashes), $userId]
|
||||
[Crypto::encrypt($secret), json_encode($hashes), $userId]
|
||||
);
|
||||
$this->writeAuditLog($userId, 'totp_enabled', 'user', $userId, '2FA aktiviert');
|
||||
return $codes;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue