차량 모니터 관리자 쿠키 승격 처리

This commit is contained in:
seo committed 2026-07-28 23:01:03 +09:00
1 parent ce9fe85934
commit f69b9febf4
1 file changed
+90 -13
+90 -13
View File
@@ -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();
}