신호 관리자 티켓 발급
This commit is contained in:
1 parent
f7b802c856
commit
65664ba598
2 files changed
+63
-3
No files matched your search
@@ -45,6 +45,8 @@ Rhymix 사이트 관리자 로그인 상태에서는 첫 줄에 나의 찾기,
|
|||||||
|
|
||||||
공통 암호로 로그인한 일반 인증 쿠키는 2시간 유지합니다. 사이트 관리자 세션이 확인되어 발급된 관리자 인증 쿠키는 반영구 운영값으로 10년 유지합니다. `/custom/launcher/api.php?session=1`은 현재 브라우저가 가진 공통 쿠키 또는 사이트 관리자 세션을 확인하고, `/custom/launcher/api.php?adminSession=1`은 관리 버튼 노출처럼 실제 Rhymix 관리자 세션이 필요한 화면에서만 사용합니다. `adminSession=1`은 일반 공통 쿠키를 인증으로 섞지 않고 실제 관리자 여부만 내려줍니다. 런처가 관리자 아이콘을 표시한 뒤에도 브라우저 포커스가 돌아오거나 탭이 다시 보이면 관리자 세션을 재확인하고, 이미 관리자 로그인이 풀렸으면 일반 사용자 화면으로 다시 그립니다.
|
공통 암호로 로그인한 일반 인증 쿠키는 2시간 유지합니다. 사이트 관리자 세션이 확인되어 발급된 관리자 인증 쿠키는 반영구 운영값으로 10년 유지합니다. `/custom/launcher/api.php?session=1`은 현재 브라우저가 가진 공통 쿠키 또는 사이트 관리자 세션을 확인하고, `/custom/launcher/api.php?adminSession=1`은 관리 버튼 노출처럼 실제 Rhymix 관리자 세션이 필요한 화면에서만 사용합니다. `adminSession=1`은 일반 공통 쿠키를 인증으로 섞지 않고 실제 관리자 여부만 내려줍니다. 런처가 관리자 아이콘을 표시한 뒤에도 브라우저 포커스가 돌아오거나 탭이 다시 보이면 관리자 세션을 재확인하고, 이미 관리자 로그인이 풀렸으면 일반 사용자 화면으로 다시 그립니다.
|
||||||
|
|
||||||
|
`signal.chaegeon.com`은 Rhymix 관리자 세션을 확인할 때만 런처 API를 CORS로 호출합니다. 이때 `adminSession=1&adminTicket=traffic-signal-pass` 요청에서 실제 사이트 관리자 세션이 확인되면 5분짜리 서명 티켓을 내려주고, 교차로 신호 서버는 이 티켓을 `validateAdminTicket=1`로 다시 검증한 뒤 위치 기록 관리자 화면을 엽니다. 클라이언트의 관리자 표시값만으로 위치 기록을 열지 않습니다.
|
||||||
|
|
||||||
Rhymix 로그인 실패 차단 세션이 살아 있으면 `session=1` 응답이 `loginBlocked`를 내려줍니다. 이때 런처는 아이콘, 관리 버튼, PIN 입력을 숨기고 빨간 제한 안내만 표시합니다.
|
Rhymix 로그인 실패 차단 세션이 살아 있으면 `session=1` 응답이 `loginBlocked`를 내려줍니다. 이때 런처는 아이콘, 관리 버튼, PIN 입력을 숨기고 빨간 제한 안내만 표시합니다.
|
||||||
|
|
||||||
암호는 숫자 4자리만 허용합니다. 로그인과 암호 변경 입력창은 `inputmode=numeric`, `[0-9]*` 패턴, 커스텀 숫자 키패드를 사용하며 Android/iPhone에서 문자 키보드가 뜨지 않도록 `readonly` 입력창에 화면 키패드로 값을 넣습니다. 서버 API도 4자리 숫자가 아닌 암호를 거부합니다.
|
암호는 숫자 4자리만 허용합니다. 로그인과 암호 변경 입력창은 `inputmode=numeric`, `[0-9]*` 패턴, 커스텀 숫자 키패드를 사용하며 Android/iPhone에서 문자 키보드가 뜨지 않도록 `readonly` 입력창에 화면 키패드로 값을 넣습니다. 서버 API도 4자리 숫자가 아닌 암호를 거부합니다.
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ header('Content-Type: application/json; charset=utf-8');
|
|||||||
header('Cache-Control: no-store');
|
header('Cache-Control: no-store');
|
||||||
|
|
||||||
$origin = (string)($_SERVER['HTTP_ORIGIN'] ?? '');
|
$origin = (string)($_SERVER['HTTP_ORIGIN'] ?? '');
|
||||||
if ($origin === 'https://chaegeon.com') {
|
$allowedOrigins = [
|
||||||
|
'https://chaegeon.com',
|
||||||
|
'https://signal.chaegeon.com',
|
||||||
|
];
|
||||||
|
if (in_array($origin, $allowedOrigins, true)) {
|
||||||
header('Access-Control-Allow-Origin: ' . $origin);
|
header('Access-Control-Allow-Origin: ' . $origin);
|
||||||
header('Access-Control-Allow-Credentials: true');
|
header('Access-Control-Allow-Credentials: true');
|
||||||
header('Access-Control-Allow-Headers: Content-Type, X-Requested-With');
|
header('Access-Control-Allow-Headers: Content-Type, X-Requested-With');
|
||||||
@@ -27,6 +31,44 @@ function launcher_is_digit_password(string $password): bool
|
|||||||
return preg_match('/^\d{4}$/', $password) === 1;
|
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') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
custom_block_login_denied(true);
|
custom_block_login_denied(true);
|
||||||
|
|
||||||
@@ -98,6 +140,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
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'])) {
|
if (isset($_GET['session'])) {
|
||||||
$denied = custom_login_denied_state();
|
$denied = custom_login_denied_state();
|
||||||
$blockedType = (string)($denied['type'] ?? '');
|
$blockedType = (string)($denied['type'] ?? '');
|
||||||
@@ -151,10 +203,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|||||||
if ($isAdmin) {
|
if ($isAdmin) {
|
||||||
custom_issue_auth_cookie(true);
|
custom_issue_auth_cookie(true);
|
||||||
}
|
}
|
||||||
custom_json([
|
$response = [
|
||||||
'admin' => $isAdmin,
|
'admin' => $isAdmin,
|
||||||
'authenticated' => $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'])) {
|
if (isset($_GET['failMessages'])) {
|
||||||
|
|||||||
Reference in new issue
Block a user