diff --git a/index.html b/index.html index a7eb62b..6a583c6 100644 --- a/index.html +++ b/index.html @@ -22,10 +22,18 @@ // 2) Allowed Callback URLs / Allowed Logout URLs에 현재 사이트 주소를 등록하세요. // 예: https://drive.daewoongai.com/apps/dashboard/ window.AUTH_CONFIG = { + // end-user가 설정 모달을 사용하는지 여부(기본: false) + allowEndUserConfig: false, auth0: { domain: "", clientId: "", }, + // Auth0 connection 이름(선택). 예: google-oauth2 / kakao / naver + connections: { + google: "", + kakao: "", + naver: "", + }, // 관리 허용 이메일(대소문자 무시) allowedEmails: [], }; @@ -383,6 +391,7 @@ const STORAGE_KEY = "links_home_v1"; const THEME_KEY = "links_home_theme_v1"; + const AUTH_OVERRIDE_KEY = "links_home_auth_override_v1"; const el = { q: document.getElementById("q"), @@ -395,6 +404,13 @@ btnImport: document.getElementById("btnImport"), btnExport: document.getElementById("btnExport"), btnTheme: document.getElementById("btnTheme"), + snsLogin: document.getElementById("snsLogin"), + btnNaver: document.getElementById("btnNaver"), + btnKakao: document.getElementById("btnKakao"), + btnGoogle: document.getElementById("btnGoogle"), + btnLogout: document.getElementById("btnLogout"), + user: document.getElementById("user"), + userText: document.getElementById("userText"), toast: document.getElementById("toast"), modal: document.getElementById("modal"), btnClose: document.getElementById("btnClose"), @@ -662,6 +678,138 @@ applyTheme(prefersLight ? "light" : "dark"); })(); + // Auth (fallback): wire SNS quick login buttons + const auth = { + client: null, + user: null, + ready: false, + }; + + 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 connections = data.connections && typeof data.connections === "object" ? data.connections : {}; + const allowedEmails = Array.isArray(data.allowedEmails) ? data.allowedEmails : []; + return { + auth0: { + domain: String(auth0.domain || "").trim(), + clientId: String(auth0.clientId || "").trim(), + }, + connections: { + google: String(connections.google || "").trim(), + kakao: String(connections.kakao || "").trim(), + naver: String(connections.naver || "").trim(), + }, + allowedEmails: allowedEmails.map((e) => String(e).trim().toLowerCase()).filter(Boolean), + }; + } + + function getAuthConfig() { + const cfg = window.AUTH_CONFIG && typeof window.AUTH_CONFIG === "object" ? window.AUTH_CONFIG : {}; + const auth0 = cfg.auth0 && typeof cfg.auth0 === "object" ? cfg.auth0 : {}; + const connections = cfg.connections && typeof cfg.connections === "object" ? cfg.connections : {}; + const allowedEmails = Array.isArray(cfg.allowedEmails) ? cfg.allowedEmails : []; + const base = { + auth0: { + domain: String(auth0.domain || "").trim(), + clientId: String(auth0.clientId || "").trim(), + }, + connections: { + google: String(connections.google || "").trim(), + kakao: String(connections.kakao || "").trim(), + naver: String(connections.naver || "").trim(), + }, + allowedEmails: allowedEmails.map((e) => String(e).trim().toLowerCase()).filter(Boolean), + }; + const over = loadAuthOverride(); + if (!over) return base; + return { + auth0: { + domain: over.auth0.domain || base.auth0.domain, + clientId: over.auth0.clientId || base.auth0.clientId, + }, + connections: { + google: over.connections.google || base.connections.google, + kakao: over.connections.kakao || base.connections.kakao, + naver: over.connections.naver || base.connections.naver, + }, + allowedEmails: over.allowedEmails.length ? over.allowedEmails : base.allowedEmails, + }; + } + + function currentUrlNoQuery() { + const u = new URL(location.href); + u.searchParams.delete("code"); + u.searchParams.delete("state"); + return u.toString(); + } + + async function ensureAuthClient() { + if (auth.client) return auth.client; + const cfg = getAuthConfig(); + if (!cfg.auth0.domain || !cfg.auth0.clientId) return null; + if (typeof window.createAuth0Client !== "function") return null; + auth.client = await window.createAuth0Client({ + domain: cfg.auth0.domain, + clientId: cfg.auth0.clientId, + authorizationParams: { + redirect_uri: location.origin === "null" ? location.href : location.origin + location.pathname, + }, + cacheLocation: "localstorage", + useRefreshTokens: true, + }); + return auth.client; + } + + async function initAuth() { + auth.ready = true; + const client = await ensureAuthClient(); + if (!client) { + // No config: keep buttons visible but disabled style + return; + } + const u = new URL(location.href); + const isCallback = u.searchParams.has("code") && u.searchParams.has("state"); + if (isCallback) { + try { + await client.handleRedirectCallback(); + } finally { + history.replaceState({}, document.title, currentUrlNoQuery()); + } + } + const isAuthed = await client.isAuthenticated(); + auth.user = isAuthed ? await client.getUser() : null; + if (el.btnLogout) el.btnLogout.hidden = !auth.user; + if (el.snsLogin) el.snsLogin.hidden = Boolean(auth.user); + if (el.user) el.user.hidden = !auth.user; + if (el.userText && auth.user) el.userText.textContent = auth.user.email || auth.user.name || "로그인됨"; + } + + async function loginWithConnection(provider) { + const cfg = getAuthConfig(); + const client = await ensureAuthClient(); + if (!client) { + toast("간편로그인은 관리자 설정 후 사용 가능합니다."); + return; + } + const conn = cfg.connections[provider] || ""; + if (!conn) { + toast("관리자가 Auth0 connection 이름을 설정해야 합니다."); + return; + } + await client.loginWithRedirect({ authorizationParams: { connection: conn } }); + } + + async function logout() { + const client = await ensureAuthClient(); + if (!client) return; + await client.logout({ + logoutParams: { returnTo: location.origin === "null" ? location.href : location.origin + location.pathname }, + }); + } + // Wire events if (el.q) el.q.addEventListener("input", () => { @@ -777,7 +925,13 @@ applyTheme(cur === "dark" ? "light" : "dark"); }); + if (el.btnNaver) el.btnNaver.addEventListener("click", () => loginWithConnection("naver").catch(() => toast("로그인에 실패했습니다."))); + if (el.btnKakao) el.btnKakao.addEventListener("click", () => loginWithConnection("kakao").catch(() => toast("로그인에 실패했습니다."))); + if (el.btnGoogle) el.btnGoogle.addEventListener("click", () => loginWithConnection("google").catch(() => toast("로그인에 실패했습니다."))); + if (el.btnLogout) el.btnLogout.addEventListener("click", () => logout().catch(() => toast("로그아웃에 실패했습니다."))); + render(); + initAuth().catch(() => {}); toast("스크립트 로딩 문제로 폴백 모드로 실행 중입니다."); }, 200); })();