From 5e898d3e04efd2c3340701ce4a90aa4e209b721e Mon Sep 17 00:00:00 2001 From: dsyoon Date: Sat, 7 Feb 2026 17:58:17 +0900 Subject: [PATCH] Add in-page login config modal Allow setting Auth0 domain/clientId and allowed emails via a modal saved to localStorage to enable login testing without redeploying. Co-authored-by: Cursor --- README.md | 5 +++ index.html | 36 ++++++++++++++++ script.js | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 155 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5f8cdae..6167266 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,11 @@ python3 -m http.server 8000 - Allowed Logout URLs: 사이트 주소 (예: `https://example.com/`) 4. `allowedEmails`에 관리 허용 이메일 목록을 입력 +팁: + +- 서버에 바로 반영하기 전 테스트가 필요하면, 페이지 상단의 **로그인**을 누르면 뜨는 **로그인 설정 모달**에서 + `domain/clientId/allowedEmails`를 입력하면 브라우저에 저장되어 즉시 테스트할 수 있습니다. + ## 데이터 저장 - 기본 링크: `links.json` diff --git a/index.html b/index.html index 1b2d379..a4ce7a6 100644 --- a/index.html +++ b/index.html @@ -175,6 +175,42 @@ + + + diff --git a/script.js b/script.js index fb96560..f23d684 100644 --- a/script.js +++ b/script.js @@ -4,6 +4,7 @@ const STORAGE_KEY = "links_home_v1"; const THEME_KEY = "links_home_theme_v1"; const AUTH_TOAST_ONCE_KEY = "links_home_auth_toast_once_v1"; + const AUTH_OVERRIDE_KEY = "links_home_auth_override_v1"; const el = { subtitle: document.getElementById("subtitle"), @@ -21,6 +22,13 @@ userText: document.getElementById("userText"), btnLogin: document.getElementById("btnLogin"), btnLogout: document.getElementById("btnLogout"), + authModal: document.getElementById("authModal"), + btnAuthClose: document.getElementById("btnAuthClose"), + authForm: document.getElementById("authForm"), + authDomain: document.getElementById("authDomain"), + authClientId: document.getElementById("authClientId"), + authAllowedEmails: document.getElementById("authAllowedEmails"), + btnAuthReset: document.getElementById("btnAuthReset"), modal: document.getElementById("modal"), btnClose: document.getElementById("btnClose"), btnCancel: document.getElementById("btnCancel"), @@ -339,10 +347,12 @@ toast(msg); } - function getAuthConfig() { - const cfg = globalThis.AUTH_CONFIG && typeof globalThis.AUTH_CONFIG === "object" ? globalThis.AUTH_CONFIG : {}; - const auth0 = cfg.auth0 && typeof cfg.auth0 === "object" ? cfg.auth0 : {}; - const allowedEmails = Array.isArray(cfg.allowedEmails) ? cfg.allowedEmails : []; + function loadAuthOverride() { + const raw = localStorage.getItem(AUTH_OVERRIDE_KEY); + const data = raw ? safeJsonParse(raw, null) : null; + if (!data || typeof data !== "object") return null; + const auth0 = data.auth0 && typeof data.auth0 === "object" ? data.auth0 : {}; + const allowedEmails = Array.isArray(data.allowedEmails) ? data.allowedEmails : []; return { auth0: { domain: String(auth0.domain || "").trim(), @@ -352,6 +362,37 @@ }; } + function saveAuthOverride(cfg) { + localStorage.setItem(AUTH_OVERRIDE_KEY, JSON.stringify(cfg)); + } + + function clearAuthOverride() { + localStorage.removeItem(AUTH_OVERRIDE_KEY); + } + + function getAuthConfig() { + const cfg = globalThis.AUTH_CONFIG && typeof globalThis.AUTH_CONFIG === "object" ? globalThis.AUTH_CONFIG : {}; + const auth0 = cfg.auth0 && typeof cfg.auth0 === "object" ? cfg.auth0 : {}; + const allowedEmails = Array.isArray(cfg.allowedEmails) ? cfg.allowedEmails : []; + const base = { + auth0: { + domain: String(auth0.domain || "").trim(), + clientId: String(auth0.clientId || "").trim(), + }, + allowedEmails: allowedEmails.map((e) => String(e).trim().toLowerCase()).filter(Boolean), + }; + const override = loadAuthOverride(); + if (!override) return base; + // override가 있으면 우선 적용 (서버 재배포 없이 테스트 가능) + return { + auth0: { + domain: override.auth0.domain || base.auth0.domain, + clientId: override.auth0.clientId || base.auth0.clientId, + }, + allowedEmails: override.allowedEmails.length ? override.allowedEmails : base.allowedEmails, + }; + } + function currentUrlNoQuery() { // Auth0 callback 후 URL 정리용 const u = new URL(location.href); @@ -491,7 +532,7 @@ async function login() { if (auth.mode !== "enabled" || !auth.client) { - toast("로그인 설정이 필요합니다. index.html의 AUTH_CONFIG(auth0.domain/clientId, allowedEmails)를 확인하세요."); + openAuthModal(); return; } await auth.client.loginWithRedirect(); @@ -510,6 +551,28 @@ }); } + function openAuthModal() { + if (!el.authModal || !el.authForm) { + toast("로그인 설정 UI를 찾지 못했습니다. 새로고침 후 다시 시도하세요."); + return; + } + + const cfg = getAuthConfig(); + el.authDomain.value = cfg.auth0.domain || ""; + el.authClientId.value = cfg.auth0.clientId || ""; + el.authAllowedEmails.value = (cfg.allowedEmails || []).join(", "); + + el.authModal.hidden = false; + document.body.style.overflow = "hidden"; + setTimeout(() => el.authDomain.focus(), 0); + } + + function closeAuthModal() { + if (!el.authModal) return; + el.authModal.hidden = true; + document.body.style.overflow = ""; + } + async function copyText(text) { try { await navigator.clipboard.writeText(text); @@ -820,6 +883,52 @@ if (el.btnLogin) el.btnLogin.addEventListener("click", () => login().catch(() => toast("로그인에 실패했습니다."))); if (el.btnLogout) el.btnLogout.addEventListener("click", () => logout().catch(() => toast("로그아웃에 실패했습니다."))); + + if (el.btnAuthClose) el.btnAuthClose.addEventListener("click", closeAuthModal); + if (el.authModal) { + el.authModal.addEventListener("click", (e) => { + const close = e.target && e.target.getAttribute && e.target.getAttribute("data-auth-close"); + if (close) closeAuthModal(); + }); + } + document.addEventListener("keydown", (e) => { + if (e.key === "Escape" && el.authModal && !el.authModal.hidden) closeAuthModal(); + }); + + if (el.btnAuthReset) { + el.btnAuthReset.addEventListener("click", () => { + clearAuthOverride(); + toast("로그인 설정을 초기화했습니다."); + closeAuthModal(); + // 초기화 후 재로딩(상태 정리) + setTimeout(() => location.reload(), 200); + }); + } + + if (el.authForm) { + el.authForm.addEventListener("submit", (e) => { + e.preventDefault(); + const domain = String(el.authDomain.value || "").trim(); + const clientId = String(el.authClientId.value || "").trim(); + const emails = String(el.authAllowedEmails.value || "") + .split(",") + .map((s) => s.trim().toLowerCase()) + .filter(Boolean); + + if (!domain || !clientId) { + toast("Domain과 Client ID를 입력하세요."); + return; + } + + saveAuthOverride({ + auth0: { domain, clientId }, + allowedEmails: emails, + }); + toast("저장했습니다. 페이지를 새로고침합니다."); + closeAuthModal(); + setTimeout(() => location.reload(), 200); + }); + } } async function main() {