From f69b9febf4fbfb7feddec69afd58f47cc05d9772 Mon Sep 17 00:00:00 2001 From: seo Date: Tue, 28 Jul 2026 23:01:03 +0900 Subject: [PATCH] =?UTF-8?q?=EC=B0=A8=EB=9F=89=20=EB=AA=A8=EB=8B=88?= =?UTF-8?q?=ED=84=B0=20=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=BF=A0=ED=82=A4=20?= =?UTF-8?q?=EC=8A=B9=EA=B2=A9=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- monitor.php | 103 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 90 insertions(+), 13 deletions(-) diff --git a/monitor.php b/monitor.php index 4add473..fba17fe 100644 --- a/monitor.php +++ b/monitor.php @@ -888,6 +888,18 @@ function car_monitor_cookie_secret(): string return $secret; } +function car_monitor_admin_assertion_secret(): string +{ + global $carSecretConfig; + + $secret = (string)($carSecretConfig['monitor_auth']['admin_assertion_secret'] ?? ''); + if ($secret === '') { + throw new RuntimeException('Car monitor admin assertion secret is missing.'); + } + + return $secret; +} + function car_monitor_cookie_options(int $expires): array { return [ @@ -899,47 +911,76 @@ function car_monitor_cookie_options(int $expires): array ]; } -function car_monitor_cookie_value(int $expires): string +function car_monitor_cookie_value(int $expires, bool $admin = false): string { $body = base64_encode(json_encode([ 'exp' => $expires, 'iat' => time(), + 'admin' => $admin, ], JSON_UNESCAPED_SLASHES)); $sig = hash_hmac('sha256', $body, car_monitor_cookie_secret()); return $body . '.' . $sig; } -function car_monitor_issue_cookie(): void +function car_monitor_issue_cookie(bool $admin = false): void { $expires = time() + CAR_MONITOR_AUTH_TTL; - setcookie(CAR_MONITOR_COOKIE, car_monitor_cookie_value($expires), car_monitor_cookie_options($expires)); + setcookie(CAR_MONITOR_COOKIE, car_monitor_cookie_value($expires, $admin), car_monitor_cookie_options($expires)); } -function car_monitor_has_cookie(): bool +function car_monitor_cookie_payload(): ?array { $cookie = $_COOKIE[CAR_MONITOR_COOKIE] ?? ''; if (!is_string($cookie) || strpos($cookie, '.') === false) { - return false; + return null; } [$body, $sig] = explode('.', $cookie, 2); $expected = hash_hmac('sha256', $body, car_monitor_cookie_secret()); if (!hash_equals($expected, $sig)) { - return false; + return null; } $decoded = base64_decode($body, true); if ($decoded === false) { - return false; + return null; } $payload = json_decode($decoded, true); if (!is_array($payload)) { + return null; + } + + return (int)($payload['exp'] ?? 0) >= time() ? $payload : null; +} + +function car_monitor_has_cookie(): bool +{ + return is_array(car_monitor_cookie_payload()); +} + +function car_monitor_verify_admin_assertion(string $assertion): bool +{ + if (strpos($assertion, '.') === false) { return false; } - return (int)($payload['exp'] ?? 0) >= time(); + [$body, $sig] = explode('.', $assertion, 2); + $expected = hash_hmac('sha256', $body, car_monitor_admin_assertion_secret()); + if (!hash_equals($expected, $sig)) { + return false; + } + + $decoded = base64_decode($body, true); + $payload = $decoded !== false ? json_decode($decoded, true) : null; + if (!is_array($payload)) { + return false; + } + + return ($payload['aud'] ?? '') === 'car-monitor' + && !empty($payload['admin']) + && (int)($payload['exp'] ?? 0) >= time(); } function car_monitor_launcher_session(): array @@ -954,21 +995,22 @@ function car_monitor_launcher_session(): array if ($cookieHeader !== '') { $adminSession = car_monitor_launcher_api_request('GET', 'https://chaegeon.com/custom/launcher/api.php?adminSession=1', null, $cookieHeader); if (is_array($adminSession) && !empty($adminSession['authenticated']) && !empty($adminSession['admin'])) { - car_monitor_issue_cookie(); + car_monitor_issue_cookie(true); $session = ['authenticated' => true, 'admin' => true]; return $session; } $remoteSession = car_monitor_launcher_api_request('GET', 'https://chaegeon.com/custom/launcher/api.php?session=1', null, $cookieHeader); if (is_array($remoteSession) && !empty($remoteSession['authenticated'])) { - car_monitor_issue_cookie(); + car_monitor_issue_cookie(!empty($remoteSession['admin'])); $session = $remoteSession; return $session; } } - if (car_monitor_has_cookie()) { - $session = ['authenticated' => true, 'admin' => false]; + $cookiePayload = car_monitor_cookie_payload(); + if (is_array($cookiePayload)) { + $session = ['authenticated' => true, 'admin' => !empty($cookiePayload['admin'])]; return $session; } @@ -1132,10 +1174,37 @@ function car_monitor_auth_api(): void json_response(['result' => 'fail'], 401); } +function car_monitor_admin_assert_api(): void +{ + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + json_response(['error' => 'method_not_allowed'], 405); + } + + $data = request_payload(); + $assertion = (string)($data['assertion'] ?? ''); + if (!car_monitor_verify_admin_assertion($assertion)) { + json_response([ + 'status' => 'forbidden', + 'message' => '관리자 권한이 필요합니다.', + ], 403); + } + + car_monitor_issue_cookie(true); + json_response([ + 'status' => 'success', + 'authenticated' => true, + 'admin' => true, + ]); +} + if (isset($_GET['mode']) && $_GET['mode'] === 'auth-login') { car_monitor_auth_api(); } +if (isset($_GET['mode']) && $_GET['mode'] === 'admin-assert') { + car_monitor_admin_assert_api(); +} + car_monitor_issue_pwa_cookie_if_requested(); if (isset($_GET['mode']) && $_GET['mode'] === 'ajax') { @@ -2954,7 +3023,15 @@ $carMonitorSession = car_monitor_launcher_session(); cache: 'no-store', }); const data = await res.json(); - if (res.ok && data?.authenticated && data?.admin) { + if (res.ok && data?.authenticated && data?.admin && data?.assertion) { + const localRes = await fetch('?mode=admin-assert', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + cache: 'no-store', + body: JSON.stringify({ assertion: data.assertion }), + }); + if (!localRes.ok) return; + CAR_MONITOR_SESSION = { ...CAR_MONITOR_SESSION, authenticated: true, admin: true }; showCalibrationSettings(); }