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() {