diff --git a/monitor.php b/monitor.php index 1df9a01..8fb41a5 100644 --- a/monitor.php +++ b/monitor.php @@ -17,6 +17,8 @@ define('DB_CHARSET', (string)($carDbConfig['charset'] ?? 'utf8mb4')); const LOG_LIMIT = 300; const CHART_LIMIT = 500; +const CAR_MONITOR_COOKIE = 'car_monitor_access'; +const CAR_MONITOR_AUTH_TTL = 2592000; const DATA_MONTHLY_INCLUDED_BYTES = 104857600; // 100MB const DATA_BILLING_UNIT_BYTES = 512; // 0.5KB const DATA_BILLING_UNIT_KRW = 0.011; @@ -874,6 +876,72 @@ function json_response(array $payload, int $status = 200): void exit; } +function car_monitor_cookie_secret(): string +{ + global $carSecretConfig; + + $secret = (string)($carSecretConfig['monitor_auth']['secret'] ?? $carSecretConfig['auth_token'] ?? ''); + if ($secret === '') { + throw new RuntimeException('Car monitor auth secret is missing.'); + } + + return $secret; +} + +function car_monitor_cookie_options(int $expires): array +{ + return [ + 'expires' => $expires, + 'path' => '/car/', + 'secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'), + 'httponly' => true, + 'samesite' => 'Strict', + ]; +} + +function car_monitor_cookie_value(int $expires): string +{ + $body = base64_encode(json_encode([ + 'exp' => $expires, + 'iat' => time(), + ], JSON_UNESCAPED_SLASHES)); + $sig = hash_hmac('sha256', $body, car_monitor_cookie_secret()); + + return $body . '.' . $sig; +} + +function car_monitor_issue_cookie(): void +{ + $expires = time() + CAR_MONITOR_AUTH_TTL; + setcookie(CAR_MONITOR_COOKIE, car_monitor_cookie_value($expires), car_monitor_cookie_options($expires)); +} + +function car_monitor_has_cookie(): bool +{ + $cookie = $_COOKIE[CAR_MONITOR_COOKIE] ?? ''; + if (!is_string($cookie) || strpos($cookie, '.') === false) { + return false; + } + + [$body, $sig] = explode('.', $cookie, 2); + $expected = hash_hmac('sha256', $body, car_monitor_cookie_secret()); + if (!hash_equals($expected, $sig)) { + return false; + } + + $decoded = base64_decode($body, true); + if ($decoded === false) { + return false; + } + + $payload = json_decode($decoded, true); + if (!is_array($payload)) { + return false; + } + + return (int)($payload['exp'] ?? 0) >= time(); +} + function car_monitor_launcher_session(): array { static $session = null; @@ -886,11 +954,19 @@ function car_monitor_launcher_session(): array if ($cookieHeader !== '') { $remoteSession = car_monitor_launcher_api_request('GET', 'https://chaegeon.com/custom/launcher/api.php?session=1', null, $cookieHeader); if (is_array($remoteSession)) { + if (!empty($remoteSession['authenticated'])) { + car_monitor_issue_cookie(); + } $session = $remoteSession; return $session; } } + if (car_monitor_has_cookie()) { + $session = ['authenticated' => true, 'admin' => false]; + return $session; + } + $session = ['authenticated' => false, 'admin' => false]; return $session; } @@ -978,7 +1054,7 @@ function car_monitor_require_access(bool $json = false): void
- +