diff --git a/README.md b/README.md index eaf389e..f39f1e2 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ Rhymix 사이트 관리자 로그인 상태에서는 나의 찾기, 콕 찌르 공통 암호로 로그인한 일반 인증 쿠키는 30분만 유지합니다. 사이트 관리자 세션이 확인되어 발급된 관리자 인증 쿠키는 반영구 운영값으로 10년 유지합니다. `/custom/launcher/api.php?session=1`은 현재 브라우저가 가진 공통 쿠키 또는 사이트 관리자 세션을 확인하고, `/custom/launcher/api.php?adminSession=1`은 관리 버튼 노출처럼 실제 Rhymix 관리자 세션이 필요한 화면에서만 사용합니다. +Rhymix 로그인 실패 차단 세션이 살아 있으면 `session=1` 응답이 `loginBlocked`를 내려줍니다. 이때 런처는 아이콘, 관리 버튼, PIN 입력을 숨기고 빨간 제한 안내만 표시합니다. + 암호는 숫자 4자리만 허용합니다. 로그인과 암호 변경 입력창은 `inputmode=numeric`, `[0-9]*` 패턴, 커스텀 숫자 키패드를 사용하며 Android/iPhone에서 문자 키보드가 뜨지 않도록 `readonly` 입력창에 화면 키패드로 값을 넣습니다. 서버 API도 4자리 숫자가 아닌 암호를 거부합니다. 로그인은 확인 버튼 없이 4자리가 입력되면 즉시 검증하고 성공 시 원래 선택한 미니앱으로 이동합니다. 암호 변경 화면도 변경/확인 버튼 없이 현재 암호, 새 암호, 새 암호 확인 순서로 4자리 입력이 끝날 때마다 다음 칸으로 이동하며, 마지막 칸까지 입력되면 바로 변경을 검증합니다. 이미 값이 들어간 입력창을 누르면 로그인 화면은 해당 칸을 지우고, 암호 변경 화면은 클릭한 칸부터 뒤 칸까지 지운 뒤 다시 입력받습니다. 현재 입력 중인 칸은 상태 문구와 파란 테두리로 표시합니다. diff --git a/api.php b/api.php index d63a51b..416fb7a 100644 --- a/api.php +++ b/api.php @@ -72,6 +72,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'GET') { if (isset($_GET['session'])) { + $denied = custom_login_denied_state(); + if (!empty($denied['denied'])) { + custom_json([ + 'authenticated' => false, + 'admin' => false, + 'loginBlocked' => true, + 'blockedType' => $denied['type'] ?? '', + 'blockedMessage' => $denied['message'] ?? '현재 로그인할 수 없는 상태입니다.', + ]); + } + if (custom_is_site_admin()) { custom_issue_auth_cookie(true); custom_json(['authenticated' => true, 'admin' => true]); diff --git a/js/main.js b/js/main.js index 28bc271..44dc1d4 100644 --- a/js/main.js +++ b/js/main.js @@ -77,6 +77,11 @@ const adminIconItems = [ async function renderIcons() { const session = await getSession(); + if (session.loginBlocked) { + renderLoginBlocked(session); + return; + } + const isAdmin = !!session.admin; const items = isAdmin ? adminIconItems : userIconItems; shortcut.className = `find-container ${isAdmin ? 'launcher-admin-grid' : ''}`; @@ -129,6 +134,11 @@ async function getSession() { async function requireAccess(nextAction) { try { const session = await getSession(); + if (session.loginBlocked) { + renderLoginBlocked(session); + return; + } + if (session.admin || session.authenticated) { nextAction && nextAction(); return; @@ -139,6 +149,20 @@ async function requireAccess(nextAction) { showPasswordForm(nextAction); } +function renderLoginBlocked(session = {}) { + shortcut.className = 'launcher-blocked'; + shortcut.innerHTML = ''; + + const title = document.createElement('strong'); + title.textContent = '현재 로그인할 수 없는 상태입니다.'; + + const message = document.createElement('div'); + message.textContent = session.blockedMessage || '잠시 후 다시 시도하세요.'; + + shortcut.appendChild(title); + shortcut.appendChild(message); +} + function showPasswordForm(onSuccess) { shortcut.className = 'password-container'; shortcut.innerHTML = '';