275 lines
9.2 KiB
PHP
275 lines
9.2 KiB
PHP
<?php
|
|
|
|
require_once __DIR__ . '/../common/auth.php';
|
|
require_once __DIR__ . '/../common/ha.php';
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
header('Cache-Control: no-store');
|
|
|
|
$origin = (string)($_SERVER['HTTP_ORIGIN'] ?? '');
|
|
$allowedOrigins = [
|
|
'https://chaegeon.com',
|
|
'https://eodiya.net',
|
|
];
|
|
if (in_array($origin, $allowedOrigins, true)) {
|
|
header('Access-Control-Allow-Origin: ' . $origin);
|
|
header('Access-Control-Allow-Credentials: true');
|
|
header('Access-Control-Allow-Headers: Content-Type, X-Requested-With');
|
|
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
|
header('Vary: Origin');
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
|
http_response_code(204);
|
|
exit;
|
|
}
|
|
|
|
$config = custom_config();
|
|
|
|
function launcher_is_digit_password(string $password): bool
|
|
{
|
|
return preg_match('/^\d{4}$/', $password) === 1;
|
|
}
|
|
|
|
function launcher_admin_ticket_audience(string $audience): string
|
|
{
|
|
$audience = trim($audience);
|
|
if ($audience === '' || preg_match('/^[a-zA-Z0-9_.:-]{1,80}$/', $audience) !== 1) {
|
|
return '';
|
|
}
|
|
return $audience;
|
|
}
|
|
|
|
function launcher_issue_admin_ticket(string $audience): string
|
|
{
|
|
return custom_signed_cookie_payload([
|
|
'scope' => 'launcher_admin_ticket',
|
|
'aud' => $audience,
|
|
'admin' => true,
|
|
'iat' => time(),
|
|
'exp' => time() + 300,
|
|
]);
|
|
}
|
|
|
|
function launcher_valid_admin_ticket(string $ticket, string $audience): bool
|
|
{
|
|
if ($ticket === '' || strlen($ticket) > 4096 || $audience === '') {
|
|
return false;
|
|
}
|
|
|
|
$payload = custom_verify_signed_cookie_payload($ticket);
|
|
if (!is_array($payload)) {
|
|
return false;
|
|
}
|
|
|
|
return ($payload['scope'] ?? '') === 'launcher_admin_ticket'
|
|
&& ($payload['aud'] ?? '') === $audience
|
|
&& !empty($payload['admin'])
|
|
&& (int)($payload['iat'] ?? 0) <= time() + 30
|
|
&& (int)($payload['exp'] ?? 0) >= time();
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
custom_block_login_denied(true);
|
|
|
|
$data = custom_read_request_data();
|
|
$action = $data['action'] ?? 'login';
|
|
|
|
if ($action === 'login') {
|
|
$password = (string)($data['password'] ?? '');
|
|
if (!launcher_is_digit_password($password)) {
|
|
custom_json(['result' => 'fail', 'message' => 'password_digits_only'], 401);
|
|
}
|
|
|
|
if (custom_verify_password($password)) {
|
|
custom_issue_auth_cookie(false);
|
|
$pwaService = (string)($data['pwaService'] ?? $_GET['pwaService'] ?? '');
|
|
$pwaIssued = $pwaService !== '' && function_exists('custom_issue_pwa_auth_cookie')
|
|
? custom_issue_pwa_auth_cookie($pwaService)
|
|
: false;
|
|
custom_json(['result' => 'ok', 'pwaIssued' => $pwaIssued]);
|
|
}
|
|
|
|
custom_json(['result' => 'fail'], 401);
|
|
}
|
|
|
|
if ($action === 'logout') {
|
|
$scope = (string)($data['scope'] ?? '');
|
|
if ($scope === 'admin') {
|
|
custom_clear_auth_cookie();
|
|
} elseif (function_exists('custom_clear_non_admin_auth_cookie')) {
|
|
custom_clear_non_admin_auth_cookie(false);
|
|
} else {
|
|
custom_clear_auth_cookie();
|
|
}
|
|
custom_json(['result' => 'ok']);
|
|
}
|
|
|
|
if ($action === 'changePassword') {
|
|
$current = (string)($data['currentPassword'] ?? '');
|
|
$next = (string)($data['newPassword'] ?? '');
|
|
|
|
if (!launcher_is_digit_password($current) || !launcher_is_digit_password($next)) {
|
|
custom_json(['result' => 'fail', 'message' => 'password_digits_only'], 422);
|
|
}
|
|
|
|
if (!custom_verify_password($current)) {
|
|
custom_json(['result' => 'fail', 'message' => 'current_password_invalid'], 401);
|
|
}
|
|
|
|
if (!custom_save_password_hash(password_hash($next, PASSWORD_DEFAULT))) {
|
|
custom_json(['result' => 'fail', 'message' => 'password_save_failed'], 500);
|
|
}
|
|
|
|
custom_issue_auth_cookie(false);
|
|
custom_json(['result' => 'ok']);
|
|
}
|
|
|
|
if ($action === 'poke') {
|
|
$sender = trim((string)($data['sender'] ?? ''));
|
|
$message = trim((string)($data['message'] ?? ''));
|
|
$ok = custom_ha_webhook('poke', [
|
|
'sender' => $sender !== '' ? $sender : '익명',
|
|
'message' => $message,
|
|
]);
|
|
|
|
custom_json(['result' => $ok ? 'ok' : 'fail'], $ok ? 200 : 502);
|
|
}
|
|
|
|
custom_json(['error' => 'not_found'], 404);
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|
if (isset($_GET['adminBridge'])) {
|
|
$audience = launcher_admin_ticket_audience((string)($_GET['adminBridge'] ?? ''));
|
|
$returnUrl = trim((string)($_GET['return'] ?? ''));
|
|
$returnParts = parse_url($returnUrl);
|
|
$validReturn = $audience !== ''
|
|
&& is_array($returnParts)
|
|
&& strtolower((string)($returnParts['scheme'] ?? '')) === 'https'
|
|
&& strtolower((string)($returnParts['host'] ?? '')) === 'eodiya.net'
|
|
&& !isset($returnParts['port'])
|
|
&& !isset($returnParts['user'])
|
|
&& !isset($returnParts['pass'])
|
|
&& !isset($returnParts['fragment']);
|
|
if (!$validReturn) {
|
|
custom_json(['error' => 'invalid_admin_bridge_return'], 400);
|
|
}
|
|
|
|
$isAdmin = custom_is_site_admin();
|
|
if ($isAdmin) {
|
|
custom_issue_auth_cookie(true);
|
|
}
|
|
$fragment = [
|
|
'rhymixAdmin' => $isAdmin ? '1' : '0',
|
|
];
|
|
if ($isAdmin) {
|
|
$fragment['rhymixAdminTicket'] = launcher_issue_admin_ticket($audience);
|
|
$fragment['rhymixAdminTicketExpiresIn'] = '300';
|
|
}
|
|
header('Location: ' . $returnUrl . '#' . http_build_query($fragment, '', '&', PHP_QUERY_RFC3986), true, 303);
|
|
exit;
|
|
}
|
|
|
|
if (isset($_GET['validateAdminTicket'])) {
|
|
$audience = launcher_admin_ticket_audience((string)($_GET['aud'] ?? ''));
|
|
$ticket = (string)($_GET['ticket'] ?? '');
|
|
$valid = launcher_valid_admin_ticket($ticket, $audience);
|
|
custom_json([
|
|
'valid' => $valid,
|
|
'admin' => $valid,
|
|
]);
|
|
}
|
|
|
|
if (isset($_GET['session'])) {
|
|
$denied = custom_login_denied_state();
|
|
$blockedType = (string)($denied['type'] ?? '');
|
|
if (!empty($denied['denied']) && !($blockedType !== 'ip' && custom_is_site_admin())) {
|
|
if ($blockedType === 'ip') {
|
|
custom_clear_auth_cookie();
|
|
} elseif (function_exists('custom_clear_non_admin_auth_cookie')) {
|
|
custom_clear_non_admin_auth_cookie(true);
|
|
} else {
|
|
custom_clear_auth_cookie();
|
|
}
|
|
custom_json([
|
|
'authenticated' => false,
|
|
'admin' => false,
|
|
'loginBlocked' => true,
|
|
'blockedType' => $blockedType,
|
|
'blockedMessage' => $denied['message'] ?? '현재 로그인할 수 없는 상태입니다.',
|
|
]);
|
|
}
|
|
|
|
if (custom_is_site_admin()) {
|
|
custom_issue_auth_cookie(true);
|
|
$pwaService = (string)($_GET['pwaService'] ?? '');
|
|
custom_json([
|
|
'authenticated' => true,
|
|
'admin' => true,
|
|
'pwaAuthenticated' => $pwaService !== '' && function_exists('custom_has_pwa_auth_cookie') && custom_has_pwa_auth_cookie($pwaService),
|
|
]);
|
|
}
|
|
|
|
if (custom_has_auth_cookie()) {
|
|
$pwaService = (string)($_GET['pwaService'] ?? '');
|
|
custom_json([
|
|
'authenticated' => true,
|
|
'admin' => false,
|
|
'pwaAuthenticated' => $pwaService !== '' && function_exists('custom_has_pwa_auth_cookie') && custom_has_pwa_auth_cookie($pwaService),
|
|
]);
|
|
}
|
|
|
|
$pwaService = (string)($_GET['pwaService'] ?? '');
|
|
$pwaAuthenticated = $pwaService !== '' && function_exists('custom_has_pwa_auth_cookie') && custom_has_pwa_auth_cookie($pwaService);
|
|
custom_json([
|
|
'authenticated' => false,
|
|
'admin' => false,
|
|
'pwaAuthenticated' => $pwaAuthenticated,
|
|
]);
|
|
}
|
|
|
|
if (isset($_GET['adminSession'])) {
|
|
$isAdmin = custom_is_site_admin();
|
|
if ($isAdmin) {
|
|
custom_issue_auth_cookie(true);
|
|
}
|
|
$response = [
|
|
'admin' => $isAdmin,
|
|
'authenticated' => $isAdmin,
|
|
];
|
|
$audience = launcher_admin_ticket_audience((string)($_GET['adminTicket'] ?? ''));
|
|
if ($isAdmin && $audience !== '') {
|
|
$response['adminTicket'] = launcher_issue_admin_ticket($audience);
|
|
$response['adminTicketExpiresIn'] = 300;
|
|
}
|
|
custom_json($response);
|
|
}
|
|
|
|
if (isset($_GET['failMessages'])) {
|
|
custom_json([
|
|
'failMessages' => [
|
|
'비밀번호가 틀립니다.',
|
|
],
|
|
]);
|
|
}
|
|
|
|
if (isset($_GET['pokeCount'])) {
|
|
$state = custom_ha_state('mokpo', $config['entities']['poke_count']);
|
|
$count = $state['state'] ?? null;
|
|
|
|
if ($count === null) {
|
|
custom_json(['ok' => false, 'error' => 'failed_to_fetch_from_ha'], 502);
|
|
}
|
|
|
|
custom_json([
|
|
'ok' => true,
|
|
'count' => is_numeric($count) ? (int)$count : $count,
|
|
]);
|
|
}
|
|
|
|
custom_json(['error' => 'not_found'], 404);
|
|
}
|
|
|
|
custom_json(['error' => 'method_not_allowed'], 405);
|