<?php if (!defined('DASHBOARD_ROOT')) { http_response_code(403); exit; } class EphemeralTokens { private const TOKEN_KEY = '_eph_token'; private const COUNTER_KEY = '_eph_counter'; private static function getSecret(): string { static $secret = null; if ($secret === null) { $secret = ''; if (file_exists(PRIVATE_DIR . '/.env')) { $lines = file(PRIVATE_DIR . '/.env', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); foreach ($lines as $line) { if (str_starts_with(trim($line), 'sk=')) { $secret = trim(substr(trim($line), 3)); break; } } } } return $secret; } public static function rotate(): string { $adminId = $_SESSION['admin_id'] ?? 0; $counter = ($_SESSION[self::COUNTER_KEY] ?? 0) + 1; $time = microtime(true); $material = implode('|', [ $adminId, $counter, $time, session_id(), $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0', ]); $token = hash_hmac('sha256', $material, self::getSecret() . session_id()); $_SESSION[self::TOKEN_KEY] = $token; $_SESSION[self::COUNTER_KEY] = $counter; $_SESSION['_eph_generated'] = time(); return $token; } public static function validate(string $submitted): bool { $stored = $_SESSION[self::TOKEN_KEY] ?? ''; if ($stored === '' || $submitted === '') { return false; } unset($_SESSION[self::TOKEN_KEY]); if (!hash_equals($stored, $submitted)) { Events::record(Events::CAT_SECURITY, 'ephemeral.replay_attempt', 'auth', null, [ 'admin_id' => $_SESSION['admin_id'] ?? null, 'ip' => $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0', 'counter' => $_SESSION[self::COUNTER_KEY] ?? 0, ]); return false; } self::rotate(); return true; } public static function current(): string { if (empty($_SESSION[self::TOKEN_KEY])) { return self::rotate(); } return $_SESSION[self::TOKEN_KEY]; } public static function counter(): int { return $_SESSION[self::COUNTER_KEY] ?? 0; } public static function isActive(): bool { return isset($_SESSION[self::TOKEN_KEY]) && isset($_SESSION[self::COUNTER_KEY]); } public static function field(): string { return '<input type="hidden" name="_eph" value="' . self::current() . '">'; } public static function status(): array { return [ 'active' => self::isActive(), 'counter' => self::counter(), 'generated' => $_SESSION['_eph_generated'] ?? null, 'has_token' => !empty($_SESSION[self::TOKEN_KEY]), ]; } } 