차량 모니터를 런처 인증과 연결
This commit is contained in:
@@ -113,6 +113,8 @@ Worker 교체나 예외 종료로 `processing`, `ui_timeout_pending` 상태가
|
|||||||
## 보안
|
## 보안
|
||||||
|
|
||||||
- 차량 API는 API token 또는 허용 IP 정책을 사용합니다.
|
- 차량 API는 API token 또는 허용 IP 정책을 사용합니다.
|
||||||
|
- 차량 모니터 화면과 `monitor.php?mode=ajax`, `monitor.php?mode=usage`는 런처 공통 인증 쿠키가 유효할 때만 응답합니다.
|
||||||
|
- 런처 공통 인증은 `https://chaegeon.com/custom/launcher/api.php?session=1` 응답으로 확인하며, `/mnt` 아래 NFS 마운트 경로를 런타임 fallback으로 사용하지 않습니다.
|
||||||
- 차량 제어 명령은 허용된 명령 코드로 제한합니다.
|
- 차량 제어 명령은 허용된 명령 코드로 제한합니다.
|
||||||
- Secret 파일은 저장소 밖에서 제한된 권한으로 유지합니다.
|
- Secret 파일은 저장소 밖에서 제한된 권한으로 유지합니다.
|
||||||
- 실제 제어 명령은 최신 상태 조회와 명령 검증 이후에만 전송합니다.
|
- 실제 제어 명령은 최신 상태 조회와 명령 검증 이후에만 전송합니다.
|
||||||
|
|||||||
+104
@@ -540,7 +540,107 @@ function json_response(array $payload, int $status = 200): void
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function car_monitor_launcher_session(): array
|
||||||
|
{
|
||||||
|
static $session = null;
|
||||||
|
|
||||||
|
if (is_array($session)) {
|
||||||
|
return $session;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cookieHeader = trim((string)($_SERVER['HTTP_COOKIE'] ?? ''));
|
||||||
|
if ($cookieHeader === '') {
|
||||||
|
$session = ['authenticated' => false, 'admin' => false];
|
||||||
|
return $session;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = 'https://chaegeon.com/custom/launcher/api.php?session=1';
|
||||||
|
$body = false;
|
||||||
|
|
||||||
|
if (function_exists('curl_init')) {
|
||||||
|
$ch = curl_init($url);
|
||||||
|
if ($ch !== false) {
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 2,
|
||||||
|
CURLOPT_TIMEOUT => 4,
|
||||||
|
CURLOPT_HTTPHEADER => ['Cookie: ' . $cookieHeader],
|
||||||
|
CURLOPT_USERAGENT => 'car-monitor-auth/1.0',
|
||||||
|
]);
|
||||||
|
$body = curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($body === false) {
|
||||||
|
$context = stream_context_create([
|
||||||
|
'http' => [
|
||||||
|
'method' => 'GET',
|
||||||
|
'timeout' => 4,
|
||||||
|
'header' => "Cookie: {$cookieHeader}\r\nUser-Agent: car-monitor-auth/1.0\r\n",
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$body = @file_get_contents($url, false, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = is_string($body) ? json_decode($body, true) : null;
|
||||||
|
$session = is_array($data) ? $data : ['authenticated' => false, 'admin' => false];
|
||||||
|
|
||||||
|
return $session;
|
||||||
|
}
|
||||||
|
|
||||||
|
function car_monitor_has_access(): bool
|
||||||
|
{
|
||||||
|
$session = car_monitor_launcher_session();
|
||||||
|
return !empty($session['authenticated']);
|
||||||
|
}
|
||||||
|
|
||||||
|
function car_monitor_require_access(bool $json = false): void
|
||||||
|
{
|
||||||
|
if (car_monitor_has_access()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($json) {
|
||||||
|
json_response([
|
||||||
|
'status' => 'unauthorized',
|
||||||
|
'message' => '런처 인증이 필요합니다.',
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(401);
|
||||||
|
header('Content-Type: text/html; charset=utf-8');
|
||||||
|
header('Cache-Control: no-cache, no-store, must-revalidate');
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>차량 모니터 인증</title>
|
||||||
|
<style>
|
||||||
|
body{margin:0;min-height:100vh;display:grid;place-items:center;background:#f1f5f9;color:#334155;font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
||||||
|
.box{width:min(360px,calc(100vw - 40px));padding:28px;border:1px solid #dbe4f0;border-radius:18px;background:#fff;box-shadow:0 18px 50px rgba(15,23,42,.12);text-align:center}
|
||||||
|
h1{margin:0 0 10px;font-size:22px}
|
||||||
|
p{margin:0 0 20px;line-height:1.6;color:#64748b}
|
||||||
|
a{display:inline-flex;align-items:center;justify-content:center;min-height:44px;padding:0 20px;border-radius:999px;background:#2563eb;color:#fff;text-decoration:none;font-weight:700}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="box">
|
||||||
|
<h1>인증이 필요합니다</h1>
|
||||||
|
<p>런처에서 관리자 세션 또는 공통 비밀번호 인증을 완료한 뒤 차량 모니터에 접속할 수 있습니다.</p>
|
||||||
|
<a href="https://chaegeon.com/blog">런처로 이동</a>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<?php
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($_GET['mode']) && $_GET['mode'] === 'ajax') {
|
if (isset($_GET['mode']) && $_GET['mode'] === 'ajax') {
|
||||||
|
car_monitor_require_access(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$pdo = car_db();
|
$pdo = car_db();
|
||||||
|
|
||||||
@@ -625,6 +725,8 @@ if (isset($_GET['mode']) && $_GET['mode'] === 'ajax') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($_GET['mode']) && $_GET['mode'] === 'usage') {
|
if (isset($_GET['mode']) && $_GET['mode'] === 'usage') {
|
||||||
|
car_monitor_require_access(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
json_response([
|
json_response([
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
@@ -637,6 +739,8 @@ if (isset($_GET['mode']) && $_GET['mode'] === 'usage') {
|
|||||||
], 500);
|
], 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
car_monitor_require_access(false);
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="ko" data-bs-theme="light">
|
<html lang="ko" data-bs-theme="light">
|
||||||
|
|||||||
Reference in New Issue
Block a user