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') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
$email = trim($_POST['email'] ?? '');
|
$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');
|
$error = __('error_email_invalid');
|
||||||
} else {
|
} 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]);
|
$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)
|
// Always show success (don't reveal whether email exists)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/Totp.php';
|
require_once __DIR__ . '/Totp.php';
|
||||||
require_once __DIR__ . '/Notifier.php';
|
require_once __DIR__ . '/Notifier.php';
|
||||||
|
require_once __DIR__ . '/Crypto.php';
|
||||||
|
|
||||||
class Auth {
|
class Auth {
|
||||||
private $db;
|
private $db;
|
||||||
|
|
@ -117,7 +118,8 @@ class Auth {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Entweder gueltiger TOTP-Code ODER ein Recovery-/Backup-Code
|
// 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)) {
|
if (!$ok && $this->consumeBackupCode($user, $code)) {
|
||||||
$ok = true;
|
$ok = true;
|
||||||
$this->writeAuditLog($user['id'], 'user_login_backup_code', 'user', $user['id'], 'Login per Recovery-Code');
|
$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);
|
$hashes = array_map(function ($c) { return hash('sha256', $c); }, $codes);
|
||||||
$this->db->query(
|
$this->db->query(
|
||||||
"UPDATE users SET totp_secret = ?, totp_enabled = 1, totp_backup_codes = ? WHERE id = ?",
|
"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');
|
$this->writeAuditLog($userId, 'totp_enabled', 'user', $userId, '2FA aktiviert');
|
||||||
return $codes;
|
return $codes;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue