WOL 전용 토큰 API 추가

This commit is contained in:
seo
2026-07-22 01:17:39 +09:00
parent 8fe2726c70
commit 01cc393ebe
3 changed files with 71 additions and 4 deletions
+4 -1
View File
@@ -41,6 +41,8 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를
- `public/api.php?action=fan`: 팬 모드와 PWM 저장/적용
- `public/api.php?action=wifi`: 허용된 WiFi/DHCP service 제어
- `public/api.php?action=wifi_alias`: MAC별 WiFi 호스트명 저장
- `public/api.php?action=wake_lan_status`: LAN 클라이언트 WOL 대상 상태 조회
- `public/api.php?action=wake_lan`: LAN 클라이언트에 WOL magic packet 전송
- `public/api.php?action=settings`: 설정 가능한 Control 동작값 조회
- `public/api.php?action=settings_save`: 설정 모달 값 저장
- `public/api.php?action=settings_reset`: 설정 모달 값을 기본값으로 초기화
@@ -74,7 +76,7 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를
- `wifi_observed_sessions`: 5G WiFi/LAN client의 최초/마지막 감지 시간
- `wifi_client_aliases`: MAC별 수동 WiFi 호스트명
- `app_settings`: 설정 모달에서 저장한 Control 동작값 오버라이드
- `/home/seo/secret/control.php`: 앱 비밀번호, DB 설정, 선택적 배터리 설정
- `/home/seo/secret/control.php`: 앱 비밀번호, DB 설정, 선택적 배터리 설정, WOL 전용 API 토큰
- UPS/배터리 기능은 `/home/seo/secret/control.php``battery.enabled`가 명시적으로 true일 때만 동작합니다. 현재 운영 기준은 UPS 제거 상태이므로 기본값은 false입니다.
## UPS/배터리 상태
@@ -216,6 +218,7 @@ control-wifi-observe.service
## 보안
- 로그인 세션과 CSRF token을 사용합니다.
- `/custom/wakeonlan` 서버 프록시가 쓰는 `wake_lan`, `wake_lan_status`만 전용 API 토큰으로 호출할 수 있습니다. 이 토큰은 해당 두 액션에만 유효하며, 일반 POST 제어는 기존 로그인 세션과 CSRF token을 계속 요구합니다.
- Rhymix 사이트 관리자 세션 또는 `/custom/common` 관리자 인증 쿠키가 확인되면 Control 내부 로그인 세션을 발급합니다.
- Control 로그아웃은 Control remember token과 세션을 지우고, `/custom/common` 공통 인증 쿠키도 함께 만료합니다.
- POST 조작 API는 CSRF 검증을 통과해야 합니다.
+20
View File
@@ -696,6 +696,26 @@ function signed_in(): bool
return auto_login_from_cookie();
}
function control_api_token_valid(string $scope): bool
{
global $controlSecretConfig;
$provided = (string)(
$_SERVER['HTTP_X_CONTROL_API_TOKEN']
?? $_POST['api_token']
?? ''
);
if ($provided === '') {
return false;
}
$tokens = is_array($controlSecretConfig['api_tokens'] ?? null) ? $controlSecretConfig['api_tokens'] : [];
$expected = (string)($tokens[$scope] ?? '');
return $expected !== '' && hash_equals($expected, $provided);
}
function control_admin_session_from_rhymix_or_custom(): bool
{
static $checked = null;
+47 -3
View File
@@ -9,7 +9,11 @@ if (!$controlApiLibrary && session_status() !== PHP_SESSION_ACTIVE) {
require_once __DIR__ . '/../config/config.php';
if (!$controlApiLibrary && !signed_in()) {
$controlApiAction = $_GET['action'] ?? $_POST['action'] ?? 'status';
$controlApiTokenScope = in_array($controlApiAction, ['wake_lan', 'wake_lan_status'], true) ? 'wake_lan' : '';
$controlApiTokenAuthenticated = $controlApiTokenScope !== '' && control_api_token_valid($controlApiTokenScope);
if (!$controlApiLibrary && !$controlApiTokenAuthenticated && !signed_in()) {
json_out([
'ok' => false,
'error' => 'login_required',
@@ -2862,6 +2866,35 @@ function send_wake_on_lan(string $mac, string $broadcast = '192.168.50.255', int
];
}
function wake_lan_status(string $mac): array
{
$mac = strtolower(trim($mac));
if (!wifi_valid_mac($mac)) {
throw new InvalidArgumentException('bad_mac');
}
$leases = dnsmasq_leases();
$activeLanMacs = array_fill_keys(lan_fdb_macs(), true);
$observedLanMacs = array_fill_keys(observed_lan_macs($leases), true);
$known = isset($leases[$mac]) || isset($activeLanMacs[$mac]) || isset($observedLanMacs[$mac]);
if (!$known) {
throw new InvalidArgumentException('unknown_dhcp_client');
}
$lease = $leases[$mac] ?? [];
$isOnline = isset($activeLanMacs[$mac]);
return [
'mac' => $mac,
'ip' => $lease['ip'] ?? null,
'hostname' => $lease['hostname'] ?? null,
'state' => $isOnline ? 'on' : 'off',
'presence' => $isOnline ? 'fdb' : (isset($observedLanMacs[$mac]) ? 'observed' : 'lease'),
'wol_available' => !$isOnline,
];
}
function wifi_time_ms(string $value): int
{
if (preg_match('/(\d+)/', $value, $m)) {
@@ -3630,9 +3663,11 @@ function collect_snapshot(bool $applyFan = true): array
function control_api_dispatch(): void
{
$action = $_GET['action'] ?? $_POST['action'] ?? 'status';
global $controlApiAction, $controlApiTokenAuthenticated;
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
$action = $controlApiAction;
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST' && !$controlApiTokenAuthenticated) {
require_csrf();
}
@@ -3839,6 +3874,15 @@ function control_api_dispatch(): void
]);
}
if ($action === 'wake_lan_status') {
json_out([
'ok' => true,
'data' => [
'wake' => wake_lan_status((string)($_GET['mac'] ?? $_POST['mac'] ?? '')),
],
]);
}
if ($action === 'collect') {
json_out([
'ok' => true,