diff --git a/README.md b/README.md index 007ecc6..44dd400 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,9 @@ Home Assistant에 기록된 Galaxy Z Fold7의 위치, 배터리, 활동 상태, 인증과 Home Assistant 호출은 `/custom/common`을 사용합니다. 브라우저 JS에는 더 이상 API Bearer 토큰이나 Home Assistant 웹훅 식별자를 직접 넣지 않습니다. -접근 확인은 JS에서 읽을 수 있는 레거시 쿠키가 아니라 `/custom/launcher/api.php?session=1` 서버 응답으로 처리합니다. 공통 인증 쿠키는 `HttpOnly`이므로 브라우저 스크립트가 직접 읽지 않습니다. 공통 암호로 인증한 일반 쿠키는 30분 유지하고, 사이트 관리자 세션이 확인되면 관리자 장기 쿠키를 발급합니다. 미인증 접근은 현재 화면 안에 `/custom/common/js/auth-pin.js`의 4자리 숫자 PIN 화면을 표시하고, 인증이 끝나면 같은 페이지를 다시 불러옵니다. PWA standalone 여부와 관계없이 같은 세션 확인을 수행합니다. +접근 확인은 JS에서 읽을 수 있는 레거시 쿠키가 아니라 `/custom/launcher/api.php?session=1` 서버 응답으로 처리합니다. 공통 인증 쿠키는 `HttpOnly`이므로 브라우저 스크립트가 직접 읽지 않습니다. 공통 암호로 인증한 일반 쿠키는 30분 유지하고, 사이트 관리자 세션이 확인되면 관리자 장기 쿠키를 발급합니다. 미인증 접근은 현재 화면 안에 `/custom/common/js/auth-pin.js`의 4자리 숫자 PIN 화면을 표시하고, 인증이 끝나면 같은 페이지를 다시 불러옵니다. + +PWA standalone으로 진입한 경우에는 설치형 앱 사용성을 우선해 JS 단계의 런처 세션 확인을 생략합니다. API 호출에는 `X-Custom-PWA: findmydevice` 헤더를 붙이고, 서버는 요청 출처가 `chaegeon.com` 계열일 때만 이를 받아들입니다. 일반 브라우저 직접 접근은 기존처럼 공통 인증 또는 PIN 인증을 거쳐야 합니다. 표시할 모바일 앱 센서 목록은 `/custom/common/config.php`의 `findmydevice_sensor_sections`에서 섹션 단위로 관리합니다. 화면 CSS 원본은 `/custom/common/css/findmydevice.css`를 사용합니다. 기존 운영 HTML이 `/custom/findmydevice/css/style.css`를 참조해도 호환 파일이 공용 CSS를 import합니다. diff --git a/js/main.js b/js/main.js index 0aec7c1..fb7a673 100644 --- a/js/main.js +++ b/js/main.js @@ -466,7 +466,10 @@ const BACK_LINK_PATH_TITLE = { '/blog': '블로그', }; const FINDMYDEVICE_AUTH_REQUIRED = location.pathname.startsWith('/findmydevice'); -let findmydeviceAuthenticated = !FINDMYDEVICE_AUTH_REQUIRED; +function isStandalonePwa() { + return window.navigator.standalone || window.matchMedia('(display-mode: standalone)').matches; +} +let findmydeviceAuthenticated = !FINDMYDEVICE_AUTH_REQUIRED || isStandalonePwa(); function renderAuthErrorPage() { document.body.innerHTML = ` @@ -493,6 +496,7 @@ function renderAuthErrorPage() { } const findmydeviceAuthPromise = FINDMYDEVICE_AUTH_REQUIRED + && !isStandalonePwa() ? fetch('/custom/launcher/api.php?session=1', { credentials: 'same-origin' }) .then(res => res.ok ? res.json() : Promise.reject()) .then(session => { @@ -2448,7 +2452,8 @@ function fetchDeviceData(includeLang = true, range = null) { return fetch(url, { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + ...(isStandalonePwa() ? { 'X-Custom-PWA': 'findmydevice' } : {}) }, credentials: 'same-origin', body: JSON.stringify({}) @@ -2508,7 +2513,10 @@ function hasValidCoords(lat, lng) { function callDeviceWebhook(name, keepalive = false) { return fetch('https://chaegeon.com/custom/findmydevice/php/api.php?action=webhook', { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { + 'Content-Type': 'application/json', + ...(isStandalonePwa() ? { 'X-Custom-PWA': 'findmydevice' } : {}) + }, credentials: 'same-origin', keepalive, body: JSON.stringify({ name }) diff --git a/php/api.php b/php/api.php index 66bc70b..a388e84 100644 --- a/php/api.php +++ b/php/api.php @@ -18,7 +18,48 @@ if (function_exists('set_time_limit')) { @set_time_limit(0); } -custom_require_auth(); +function findmydevice_is_pwa_request(): bool +{ + if (($_SERVER['HTTP_X_CUSTOM_PWA'] ?? '') !== 'findmydevice') { + return false; + } + + $allowedHosts = ['chaegeon.com', 'www.chaegeon.com']; + foreach (['HTTP_ORIGIN', 'HTTP_REFERER'] as $key) { + $value = (string)($_SERVER[$key] ?? ''); + if ($value === '') { + continue; + } + $host = parse_url($value, PHP_URL_HOST); + if ($host !== null && $host !== false && !in_array(strtolower($host), $allowedHosts, true)) { + return false; + } + } + + return true; +} + +function findmydevice_require_auth(): void +{ + if (function_exists('custom_has_auth_cookie') && custom_has_auth_cookie()) { + return; + } + + if (function_exists('custom_is_site_admin') && custom_is_site_admin()) { + if (function_exists('custom_issue_auth_cookie')) { + custom_issue_auth_cookie(true); + } + return; + } + + if (findmydevice_is_pwa_request()) { + return; + } + + custom_json(['result' => 'unauthorized'], 401); +} + +findmydevice_require_auth(); $requestData = custom_read_request_data();