로그인 차단 상태 런처 제한 표시

This commit is contained in:
seo
2026-07-10 21:11:39 +09:00
parent 6054479cee
commit 2106830a47
3 changed files with 37 additions and 0 deletions
+2
View File
@@ -45,6 +45,8 @@ Rhymix 사이트 관리자 로그인 상태에서는 나의 찾기, 콕 찌르
공통 암호로 로그인한 일반 인증 쿠키는 30분만 유지합니다. 사이트 관리자 세션이 확인되어 발급된 관리자 인증 쿠키는 반영구 운영값으로 10년 유지합니다. `/custom/launcher/api.php?session=1`은 현재 브라우저가 가진 공통 쿠키 또는 사이트 관리자 세션을 확인하고, `/custom/launcher/api.php?adminSession=1`은 관리 버튼 노출처럼 실제 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자리만 허용합니다. 로그인과 암호 변경 입력창은 `inputmode=numeric`, `[0-9]*` 패턴, 커스텀 숫자 키패드를 사용하며 Android/iPhone에서 문자 키보드가 뜨지 않도록 `readonly` 입력창에 화면 키패드로 값을 넣습니다. 서버 API도 4자리 숫자가 아닌 암호를 거부합니다.
로그인은 확인 버튼 없이 4자리가 입력되면 즉시 검증하고 성공 시 원래 선택한 미니앱으로 이동합니다. 암호 변경 화면도 변경/확인 버튼 없이 현재 암호, 새 암호, 새 암호 확인 순서로 4자리 입력이 끝날 때마다 다음 칸으로 이동하며, 마지막 칸까지 입력되면 바로 변경을 검증합니다. 이미 값이 들어간 입력창을 누르면 로그인 화면은 해당 칸을 지우고, 암호 변경 화면은 클릭한 칸부터 뒤 칸까지 지운 뒤 다시 입력받습니다. 현재 입력 중인 칸은 상태 문구와 파란 테두리로 표시합니다. 로그인은 확인 버튼 없이 4자리가 입력되면 즉시 검증하고 성공 시 원래 선택한 미니앱으로 이동합니다. 암호 변경 화면도 변경/확인 버튼 없이 현재 암호, 새 암호, 새 암호 확인 순서로 4자리 입력이 끝날 때마다 다음 칸으로 이동하며, 마지막 칸까지 입력되면 바로 변경을 검증합니다. 이미 값이 들어간 입력창을 누르면 로그인 화면은 해당 칸을 지우고, 암호 변경 화면은 클릭한 칸부터 뒤 칸까지 지운 뒤 다시 입력받습니다. 현재 입력 중인 칸은 상태 문구와 파란 테두리로 표시합니다.
+11
View File
@@ -72,6 +72,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($_SERVER['REQUEST_METHOD'] === 'GET') { if ($_SERVER['REQUEST_METHOD'] === 'GET') {
if (isset($_GET['session'])) { 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()) { if (custom_is_site_admin()) {
custom_issue_auth_cookie(true); custom_issue_auth_cookie(true);
custom_json(['authenticated' => true, 'admin' => true]); custom_json(['authenticated' => true, 'admin' => true]);
+24
View File
@@ -77,6 +77,11 @@ const adminIconItems = [
async function renderIcons() { async function renderIcons() {
const session = await getSession(); const session = await getSession();
if (session.loginBlocked) {
renderLoginBlocked(session);
return;
}
const isAdmin = !!session.admin; const isAdmin = !!session.admin;
const items = isAdmin ? adminIconItems : userIconItems; const items = isAdmin ? adminIconItems : userIconItems;
shortcut.className = `find-container ${isAdmin ? 'launcher-admin-grid' : ''}`; shortcut.className = `find-container ${isAdmin ? 'launcher-admin-grid' : ''}`;
@@ -129,6 +134,11 @@ async function getSession() {
async function requireAccess(nextAction) { async function requireAccess(nextAction) {
try { try {
const session = await getSession(); const session = await getSession();
if (session.loginBlocked) {
renderLoginBlocked(session);
return;
}
if (session.admin || session.authenticated) { if (session.admin || session.authenticated) {
nextAction && nextAction(); nextAction && nextAction();
return; return;
@@ -139,6 +149,20 @@ async function requireAccess(nextAction) {
showPasswordForm(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) { function showPasswordForm(onSuccess) {
shortcut.className = 'password-container'; shortcut.className = 'password-container';
shortcut.innerHTML = ''; shortcut.innerHTML = '';