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);
})();