PWA 나의 찾기 인증 흐름 정리

This commit is contained in:
seo
2026-07-03 14:18:07 +09:00
parent d518b4179c
commit 2cc75b167a
3 changed files with 56 additions and 5 deletions
+3 -1
View File
@@ -38,7 +38,9 @@ Home Assistant에 기록된 Galaxy Z Fold7의 위치, 배터리, 활동 상태,
인증과 Home Assistant 호출은 `/custom/common`을 사용합니다. 브라우저 JS에는 더 이상 API Bearer 토큰이나 Home Assistant 웹훅 식별자를 직접 넣지 않습니다. 인증과 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합니다. 표시할 모바일 앱 센서 목록은 `/custom/common/config.php``findmydevice_sensor_sections`에서 섹션 단위로 관리합니다. 화면 CSS 원본은 `/custom/common/css/findmydevice.css`를 사용합니다. 기존 운영 HTML이 `/custom/findmydevice/css/style.css`를 참조해도 호환 파일이 공용 CSS를 import합니다.
+11 -3
View File
@@ -466,7 +466,10 @@ const BACK_LINK_PATH_TITLE = {
'/blog': '블로그', '/blog': '블로그',
}; };
const FINDMYDEVICE_AUTH_REQUIRED = location.pathname.startsWith('/findmydevice'); 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() { function renderAuthErrorPage() {
document.body.innerHTML = ` document.body.innerHTML = `
@@ -493,6 +496,7 @@ function renderAuthErrorPage() {
} }
const findmydeviceAuthPromise = FINDMYDEVICE_AUTH_REQUIRED const findmydeviceAuthPromise = FINDMYDEVICE_AUTH_REQUIRED
&& !isStandalonePwa()
? fetch('/custom/launcher/api.php?session=1', { credentials: 'same-origin' }) ? fetch('/custom/launcher/api.php?session=1', { credentials: 'same-origin' })
.then(res => res.ok ? res.json() : Promise.reject()) .then(res => res.ok ? res.json() : Promise.reject())
.then(session => { .then(session => {
@@ -2448,7 +2452,8 @@ function fetchDeviceData(includeLang = true, range = null) {
return fetch(url, { return fetch(url, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
...(isStandalonePwa() ? { 'X-Custom-PWA': 'findmydevice' } : {})
}, },
credentials: 'same-origin', credentials: 'same-origin',
body: JSON.stringify({}) body: JSON.stringify({})
@@ -2508,7 +2513,10 @@ function hasValidCoords(lat, lng) {
function callDeviceWebhook(name, keepalive = false) { function callDeviceWebhook(name, keepalive = false) {
return fetch('https://chaegeon.com/custom/findmydevice/php/api.php?action=webhook', { return fetch('https://chaegeon.com/custom/findmydevice/php/api.php?action=webhook', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: {
'Content-Type': 'application/json',
...(isStandalonePwa() ? { 'X-Custom-PWA': 'findmydevice' } : {})
},
credentials: 'same-origin', credentials: 'same-origin',
keepalive, keepalive,
body: JSON.stringify({ name }) body: JSON.stringify({ name })
+42 -1
View File
@@ -18,7 +18,48 @@ if (function_exists('set_time_limit')) {
@set_time_limit(0); @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(); $requestData = custom_read_request_data();