차량 모니터 설정 인증 관리자 세션으로 정리

This commit is contained in:
seo committed 2026-08-17 22:08:51 +09:00
1 parent 5d75ad81bd
commit 11bd70b65f
2 files changed
+51 -209

No files matched your search

+49 -208
View File
@@ -21,10 +21,7 @@ define('DB_CHARSET', (string)($carDbConfig['charset'] ?? 'utf8mb4'));
const LOG_LIMIT = 300;
const CHART_LIMIT = 500;
const CAR_MONITOR_SETTINGS_COOKIE = 'car_monitor_settings_access';
const CAR_MONITOR_SETTINGS_TTL = 900;
const CAR_MONITOR_SETTINGS_PASSWORD = '1026!)Corjs';
const DATA_MONTHLY_INCLUDED_BYTES = 104857600; // 100MB
const DATA_MONTHLY_INCLUDED_BYTES = 104857600; // 100MB
const DATA_BILLING_UNIT_BYTES = 512; // 0.5KB
const DATA_BILLING_UNIT_KRW = 0.011;
const DATA_MONTHLY_BASE_FEE_KRW = 5500;
@@ -862,79 +859,6 @@ 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' => '/custom/car/',
'secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'),
'httponly' => true,
'samesite' => 'Strict',
];
}
function car_monitor_settings_cookie_value(int $expires): string
{
$body = base64_encode(json_encode([
'aud' => 'car-monitor-settings',
'exp' => $expires,
'iat' => time(),
], JSON_UNESCAPED_SLASHES));
$sig = hash_hmac('sha256', $body, car_monitor_cookie_secret());
return $body . '.' . $sig;
}
function car_monitor_issue_settings_cookie(): void
{
$expires = time() + CAR_MONITOR_SETTINGS_TTL;
setcookie(CAR_MONITOR_SETTINGS_COOKIE, car_monitor_settings_cookie_value($expires), car_monitor_cookie_options($expires));
}
function car_monitor_has_settings_access(): bool
{
$cookie = $_COOKIE[CAR_MONITOR_SETTINGS_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);
$payload = $decoded !== false ? json_decode($decoded, true) : null;
return is_array($payload)
&& ($payload['aud'] ?? '') === 'car-monitor-settings'
&& (int)($payload['exp'] ?? 0) >= time();
}
function car_monitor_require_settings_access(): void
{
if (car_monitor_has_settings_access()) {
return;
}
json_response([
'status' => 'forbidden',
'message' => '관리자 비밀번호가 필요합니다.',
], 403);
}
function car_monitor_launcher_session(): array
{
static $session = null;
@@ -943,8 +867,7 @@ function car_monitor_launcher_session(): array
return $session;
}
if (custom_is_site_admin()) {
custom_issue_auth_cookie(true);
if (custom_has_admin_auth()) {
$session = ['authenticated' => true, 'admin' => true];
return $session;
}
@@ -961,6 +884,23 @@ function car_monitor_launcher_session(): array
return $session;
}
function car_monitor_require_admin_access(): void
{
if (function_exists('custom_block_login_denied')) {
custom_block_login_denied(true);
}
$session = car_monitor_launcher_session();
if (!empty($session['admin'])) {
return;
}
json_response([
'status' => 'forbidden',
'message' => '관리자 권한이 필요합니다.',
], 403);
}
function car_monitor_has_access(): bool
{
$session = car_monitor_launcher_session();
@@ -1063,40 +1003,11 @@ function car_monitor_auth_api(): void
json_response(['result' => 'fail'], 401);
}
function car_monitor_settings_auth_api(): void
{
car_monitor_require_access(true);
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
json_response(['error' => 'method_not_allowed'], 405);
}
$data = request_payload();
$password = (string)($data['password'] ?? '');
if (!hash_equals(CAR_MONITOR_SETTINGS_PASSWORD, $password)) {
json_response([
'status' => 'forbidden',
'message' => '관리자 비밀번호가 올바르지 않습니다.',
], 403);
}
car_monitor_issue_settings_cookie();
json_response([
'status' => 'success',
'settings_authenticated' => true,
'expires_in' => CAR_MONITOR_SETTINGS_TTL,
]);
}
if (isset($_GET['mode']) && $_GET['mode'] === 'auth-login') {
car_monitor_auth_api();
}
if (isset($_GET['mode']) && $_GET['mode'] === 'settings-auth') {
car_monitor_settings_auth_api();
}
car_monitor_issue_pwa_cookie_if_requested();
if (isset($_GET['mode']) && $_GET['mode'] === 'auth-login') {
car_monitor_auth_api();
}
car_monitor_issue_pwa_cookie_if_requested();
if (isset($_GET['mode']) && $_GET['mode'] === 'ajax') {
car_monitor_require_access(true);
@@ -1200,11 +1111,11 @@ if (isset($_GET['mode']) && $_GET['mode'] === 'usage') {
}
}
if (isset($_GET['mode']) && $_GET['mode'] === 'usage-calibration') {
car_monitor_require_access(true);
car_monitor_require_settings_access();
try {
if (isset($_GET['mode']) && $_GET['mode'] === 'usage-calibration') {
car_monitor_require_access(true);
car_monitor_require_admin_access();
try {
$pdo = car_db();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$saved = save_usage_calibration($pdo, request_payload());
@@ -2388,12 +2299,11 @@ $carMonitorSession = car_monitor_launcher_session();
let wakeLockSentinel = null;
let wakeLockWanted = false;
let latestDashboardData = null;
let latestDashboardMeta = null;
let latestUsageData = null;
let calibrationModalOpen = false;
let calibrationSaveInFlight = false;
let calibrationSettingsAuthenticated = false;
const FALLBACK_BACK_TARGET = 'https://chaegeon.com/';
let latestDashboardMeta = null;
let latestUsageData = null;
let calibrationModalOpen = false;
let calibrationSaveInFlight = false;
const FALLBACK_BACK_TARGET = 'https://chaegeon.com/';
const BACK_HISTORY_KEY = 'chaegeon.backHistory.v1';
const BACK_LINK_PATH_TITLE = {
'/': '채건닷컴',
@@ -2903,90 +2813,21 @@ $carMonitorSession = car_monitor_launcher_session();
});
}
function initCalibrationSettings() {
showCalibrationSettings();
}
async function ensureCalibrationSettingsAuth() {
if (calibrationSettingsAuthenticated) return true;
const password = await promptCalibrationSettingsPassword();
if (password === null) return false;
try {
const res = await fetch('?mode=settings-auth', {
method: 'POST',
cache: 'no-store',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password }),
});
const payload = await res.json();
if (!res.ok || payload.status !== 'success') {
alert(payload.message || '관리자 비밀번호가 올바르지 않습니다.');
return false;
}
calibrationSettingsAuthenticated = true;
return true;
} catch (_) {
alert('관리자 인증에 실패했습니다.');
return false;
}
}
function promptCalibrationSettingsPassword() {
return new Promise(resolve => {
const overlay = document.createElement('div');
overlay.className = 'custom-modal-overlay';
overlay.innerHTML = `
<div class="custom-modal-panel" role="dialog" aria-modal="true" aria-labelledby="settings-auth-title">
<div class="custom-modal-header">
<div>
<div id="settings-auth-title" class="fw-bold">관리자 확인</div>
<div class="small text-muted">설정 변경 권한을 확인합니다.</div>
</div>
<button type="button" class="btn-icon-toggle" data-settings-auth-cancel title="닫기">
<i class="fas fa-xmark"></i>
</button>
</div>
<div class="custom-modal-body">
<div class="calibration-field">
<label for="settings-auth-password">관리자 비밀번호</label>
<input id="settings-auth-password" type="password" autocomplete="current-password" autofocus>
</div>
<div class="calibration-message text-muted" data-settings-auth-message></div>
</div>
<div class="custom-modal-footer">
<button type="button" class="btn btn-light rounded-pill px-4" data-settings-auth-cancel>닫기</button>
<button type="button" class="btn btn-primary rounded-pill px-4" data-settings-auth-ok>확인</button>
</div>
</div>
`;
const done = value => {
document.body.style.overflow = '';
overlay.remove();
resolve(value);
};
const input = overlay.querySelector('#settings-auth-password');
overlay.querySelectorAll('[data-settings-auth-cancel]').forEach(btn => {
btn.addEventListener('click', () => done(null));
});
overlay.querySelector('[data-settings-auth-ok]')?.addEventListener('click', () => done(input?.value || ''));
input?.addEventListener('keydown', event => {
if (event.key === 'Enter') done(input.value || '');
if (event.key === 'Escape') done(null);
});
document.body.appendChild(overlay);
document.body.style.overflow = 'hidden';
setTimeout(() => input?.focus(), 30);
});
}
async function openCalibrationModal() {
if (!await ensureCalibrationSettingsAuth()) return;
const overlay = document.getElementById('calibration-overlay');
if (!overlay) return;
function initCalibrationSettings() {
if (!CAR_MONITOR_SESSION || CAR_MONITOR_SESSION.admin !== true) {
return;
}
showCalibrationSettings();
}
function openCalibrationModal() {
if (!CAR_MONITOR_SESSION || CAR_MONITOR_SESSION.admin !== true) {
return;
}
const overlay = document.getElementById('calibration-overlay');
if (!overlay) return;
calibrationModalOpen = true;
overlay.hidden = false;