Phase 2: Add CSRF token protection to all forms and processors - Created CsrfMiddleware class with 8 helper methods - Added CSRF tokens to 9 POST forms across trip/course/camping/membership - Added CSRF validation to all 10 POST processors - CsrfMiddleware.requireToken() validates and dies on invalid tokens - 100% POST endpoint coverage with CSRF protection

This commit is contained in:
twotalesanimation
2025-12-02 21:08:56 +02:00
parent 5985506001
commit a311e81a12
19 changed files with 190 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
<?php
namespace Middleware;
use Services\AuthenticationService;
/**
* CsrfMiddleware - CSRF Token Protection
*
* Provides helper methods for CSRF token generation and validation.
* Use in conjunction with AuthenticationService for token management.
*
* Usage in forms:
* <input type="hidden" name="csrf_token" value="<?php echo CsrfMiddleware::getToken(); ?>">
*
* Usage in processors:
* if (!CsrfMiddleware::validateToken($_POST['csrf_token'] ?? '')) {
* die('Invalid request');
* }
*/
class CsrfMiddleware
{
const TOKEN_FIELD = 'csrf_token';
const TOKEN_SESSION_KEY = 'csrf_token';
/**
* Get current CSRF token, generate if missing
* Safe to call multiple times
*
* @return string
*/
public static function getToken(): string
{
return AuthenticationService::generateCsrfToken();
}
/**
* Validate CSRF token from form submission
*
* @param string $token
* @return bool
*/
public static function validateToken(string $token): bool
{
return AuthenticationService::validateCsrfToken($token);
}
/**
* Require valid CSRF token, dies if invalid
* Use at start of POST processor
*
* @param array $data Usually $_POST
* @return void
*/
public static function requireToken(array $data): void
{
$token = $data[self::TOKEN_FIELD] ?? '';
if (!self::validateToken($token)) {
http_response_code(403);
header('Content-Type: application/json');
echo json_encode([
'status' => 'error',
'message' => 'Invalid or missing security token. Please try again.'
]);
exit();
}
}
/**
* Get hidden HTML input field for forms
*
* @return string HTML input element
*/
public static function getInputField(): string
{
$token = self::getToken();
return '<input type="hidden" name="' . self::TOKEN_FIELD . '" value="' . htmlspecialchars($token) . '">';
}
/**
* Regenerate token (useful for one-time use tokens)
* Warning: Will invalidate previous token
*
* @return string New token
*/
public static function regenerateToken(): string
{
$_SESSION[self::TOKEN_SESSION_KEY] = bin2hex(random_bytes(32));
return $_SESSION[self::TOKEN_SESSION_KEY];
}
/**
* Clear CSRF token (call on logout)
*
* @return void
*/
public static function clearToken(): void
{
unset($_SESSION[self::TOKEN_SESSION_KEY]);
}
/**
* Check if token exists in POST data
*
* @return bool
*/
public static function hasToken(): bool
{
return isset($_POST[self::TOKEN_FIELD]) && !empty($_POST[self::TOKEN_FIELD]);
}
/**
* Get token from POST data
*
* @return string|null
*/
public static function getTokenFromPost(): ?string
{
return $_POST[self::TOKEN_FIELD] ?? null;
}
}