fix(csrf): stateless HMAC token to survive page cache
This commit is contained in:
@@ -102,6 +102,13 @@ if (!defined('ENABLE_USER_ACCOUNTS')) define('ENABLE_USER_ACCOUNTS', false);
|
||||
if (!defined('CACHE_ENABLED')) define('CACHE_ENABLED', true);
|
||||
if (!defined('CACHE_DURATION')) define('CACHE_DURATION', 3600); // En secondes (1 heure)
|
||||
|
||||
// Clé secrète utilisée pour signer les tokens CSRF stateless.
|
||||
// À remplacer impérativement dans config.local.php par une valeur aléatoire
|
||||
// propre à l'instance (ex. bin2hex(random_bytes(32))).
|
||||
if (!defined('CSRF_SECRET')) {
|
||||
define('CSRF_SECRET', 'change-me-in-config-local-php');
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// Configuration de la section Hero (bannière d'accueil)
|
||||
// =========================================
|
||||
|
||||
@@ -331,6 +331,11 @@ define('WORDPRESS_ENABLED', false);
|
||||
// define('CACHE_ENABLED', true);
|
||||
// define('CACHE_DURATION', 3600); // 1 heure recommandé
|
||||
|
||||
// Clé secrète utilisée pour signer les tokens CSRF stateless.
|
||||
// Générez une valeur aléatoire unique pour votre instance :
|
||||
// php -r "echo bin2hex(random_bytes(32)) . PHP_EOL;"
|
||||
// define('CSRF_SECRET', 'votre-cle-secrete-aleatoire');
|
||||
|
||||
// =========================================
|
||||
// Intégration Funkwhale (Musique)
|
||||
// =========================================
|
||||
|
||||
+33
-23
@@ -142,39 +142,49 @@ function validateHttpHeaders() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un token CSRF sécurisé
|
||||
*
|
||||
* @return string Token CSRF
|
||||
* Génère un token CSRF stateless (HMAC + timestamp).
|
||||
*
|
||||
* Le token ne dépend pas de la session : il reste valide même si la page
|
||||
* HTML est servie depuis un cache (Service Worker, CDN). Il expire après
|
||||
* une durée limitée.
|
||||
*
|
||||
* @return string Token CSRF au format "timestamp:hash"
|
||||
*/
|
||||
function generateCSRFToken() {
|
||||
// Démarrer la session seulement si les en-têtes n'ont pas été envoyés
|
||||
if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
return $_SESSION['csrf_token'];
|
||||
$timestamp = time();
|
||||
$hash = hash_hmac('sha256', (string) $timestamp, CSRF_SECRET);
|
||||
return $timestamp . ':' . $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide un token CSRF
|
||||
*
|
||||
* Valide un token CSRF stateless
|
||||
*
|
||||
* @param string $token Token à valider
|
||||
* @return bool True si le token est valide
|
||||
* @return bool True si le token est valide et non expiré
|
||||
*/
|
||||
function validateCSRFToken($token) {
|
||||
if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['csrf_token'])) {
|
||||
if (empty($token) || !is_string($token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hash_equals($_SESSION['csrf_token'], $token);
|
||||
|
||||
$parts = explode(':', $token, 2);
|
||||
if (count($parts) !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
[$timestamp, $hash] = $parts;
|
||||
|
||||
// Vérifier que le timestamp est numérique et pas trop ancien (1 heure)
|
||||
if (!ctype_digit($timestamp)) {
|
||||
return false;
|
||||
}
|
||||
$age = abs(time() - (int) $timestamp);
|
||||
if ($age > 3600) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$expectedHash = hash_hmac('sha256', $timestamp, CSRF_SECRET);
|
||||
return hash_equals($expectedHash, $hash);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user