to drop into any POST
* - csrf_verify(): abort with HTTP 403 if the POSTed token is missing/invalid */ // Defensive: callers normally start the session themselves, but make sure we // have one to store the token in. if (session_status() !== PHP_SESSION_ACTIVE) { session_start(); } // Generate the token once per session. if (empty($_SESSION['_csrf_token'])) { $_SESSION['_csrf_token'] = bin2hex(random_bytes(32)); } if (!function_exists('csrf_token')) { /** * Return the current CSRF token as a hex string. */ function csrf_token(): string { return $_SESSION['_csrf_token'] ?? ''; } /** * Emit a ready-to-use hidden for any POST in a template. * Usage in a .tpl: */ function csrf_field(): string { return ''; } /** * Render a clean, self-contained error page and stop execution (issue #299). * * The admin Mods are POSTed to directly; when the admin session is missing * (e.g. it was destroyed by a game logout that shares the same PHP session, * or the form was served from a mobile cache with a stale/empty token) the * old code stopped with a bare die('

Access Denied

') fragment, which * shows up as an essentially blank page. This renders a proper, styled error * with a way back into the panel instead. */ function admin_deny(string $message, string $title = 'Access Denied', int $httpCode = 403): void { if (!headers_sent()) { http_response_code($httpCode); header('Content-Type: text/html; charset=UTF-8'); // Never let this error page be cached (it is session-dependent). header('Cache-Control: no-store, no-cache, must-revalidate'); } $safeTitle = htmlspecialchars($title, ENT_QUOTES, 'UTF-8'); $safeMsg = htmlspecialchars($message, ENT_QUOTES, 'UTF-8'); echo '' . '' . '' . $safeTitle . '
' . '

' . $safeTitle . '

' . '

' . $safeMsg . '

' . 'Return to Admin Panel' . '
'; exit; } /** * Verify the CSRF token of a POST request. * Stops execution with HTTP 403 if the token is missing or does not match. * Uses hash_equals() instead of === to prevent timing attacks. */ function csrf_verify(): void { $submitted = isset($_POST['_csrf_token']) ? (string)$_POST['_csrf_token'] : ''; $stored = csrf_token(); if ($stored === '' || !hash_equals($stored, $submitted)) { // Generic message — does not reveal details about the mechanism. admin_deny( 'Your session may have expired or this page was loaded from cache. ' . 'Please reload the admin panel and try again.', 'Security Check Failed', 403 ); } } }