chore: complete update
This commit is contained in:
97
src/Framework/Http/Session/CsrfProtection.php
Normal file
97
src/Framework/Http/Session/CsrfProtection.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Http\Session;
|
||||
|
||||
final readonly class CsrfProtection
|
||||
{
|
||||
private const int TOKEN_LIFETIME = 3600; //1h
|
||||
private const int MAX_TOKENS_PER_FORM = 3;
|
||||
|
||||
private Session $session;
|
||||
|
||||
public function __construct(Session $session)
|
||||
{
|
||||
$this->session = $session;
|
||||
|
||||
// Sicherstellen, dass CSRF-Bereich existiert
|
||||
if (!$this->session->has(SessionKey::CSRF->value)) {
|
||||
$this->session->set(SessionKey::CSRF->value, []);
|
||||
}
|
||||
}
|
||||
|
||||
public function generateToken(string $formId): string
|
||||
{
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$csrf = $this->session->get(SessionKey::CSRF->value, []);
|
||||
|
||||
if (!isset($csrf[$formId])) {
|
||||
$csrf[$formId] = [];
|
||||
}
|
||||
|
||||
$csrf[$formId][] = [
|
||||
'token' => $token,
|
||||
'created_at' => time()
|
||||
];
|
||||
|
||||
$csrf[$formId] = $this->cleanupOldTokens($csrf[$formId]);
|
||||
|
||||
$this->session->set(SessionKey::CSRF->value, $csrf);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function validateToken(string $formId, string $token): bool
|
||||
{
|
||||
$csrf = $this->session->get(SessionKey::CSRF->value, []);
|
||||
|
||||
if (!isset($csrf[$formId])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($csrf[$formId] as $index => $tokenData) {
|
||||
if ($tokenData['token'] === $token) {
|
||||
// Token ist gültig - NICHT löschen, nur als "used" markieren
|
||||
$csrf[$formId][$index]['used_at'] = time();
|
||||
$this->session->set(SessionKey::CSRF->value, $csrf);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function cleanupOldTokens(array $tokens): array
|
||||
{
|
||||
$now = time();
|
||||
$cleaned = [];
|
||||
|
||||
foreach ($tokens as $tokenData) {
|
||||
// Behalte Token die:
|
||||
// 1. Noch nicht abgelaufen sind
|
||||
// 2. Kürzlich verwendet wurden (für Re-Submits)
|
||||
$age = $now - $tokenData['created_at'];
|
||||
$usedRecently = isset($tokenData['used_at']) &&
|
||||
($now - $tokenData['used_at']) < 300; // 5 Minuten
|
||||
|
||||
if ($age < self::TOKEN_LIFETIME || $usedRecently) {
|
||||
$cleaned[] = $tokenData;
|
||||
}
|
||||
}
|
||||
|
||||
// Behalte nur die neuesten N Tokens
|
||||
return array_slice($cleaned, -self::MAX_TOKENS_PER_FORM);
|
||||
}
|
||||
|
||||
// Diese Methode nur für spezielle Fälle behalten (z.B. AJAX)
|
||||
/*public function renderHiddenFields(string $formId): string
|
||||
{
|
||||
$token = $this->generateToken($formId);
|
||||
return sprintf(
|
||||
'<input type="hidden" name="_token" value="%s"><input type="hidden" name="_form_id" value="%s">',
|
||||
htmlspecialchars($token),
|
||||
htmlspecialchars($formId)
|
||||
);
|
||||
}*/
|
||||
}
|
||||
Reference in New Issue
Block a user