Add social quick login and user sync API
Add quick provider login buttons (Auth0 connections), an API to upsert users into Postgres and gate admin via can_manage, plus schema and Node server. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
99
script.js
99
script.js
@@ -22,12 +22,18 @@
|
||||
userText: document.getElementById("userText"),
|
||||
btnLogin: document.getElementById("btnLogin"),
|
||||
btnLogout: document.getElementById("btnLogout"),
|
||||
btnGoogle: document.getElementById("btnGoogle"),
|
||||
btnKakao: document.getElementById("btnKakao"),
|
||||
btnNaver: document.getElementById("btnNaver"),
|
||||
authModal: document.getElementById("authModal"),
|
||||
btnAuthClose: document.getElementById("btnAuthClose"),
|
||||
authForm: document.getElementById("authForm"),
|
||||
authDomain: document.getElementById("authDomain"),
|
||||
authClientId: document.getElementById("authClientId"),
|
||||
authAllowedEmails: document.getElementById("authAllowedEmails"),
|
||||
authConnGoogle: document.getElementById("authConnGoogle"),
|
||||
authConnKakao: document.getElementById("authConnKakao"),
|
||||
authConnNaver: document.getElementById("authConnNaver"),
|
||||
btnAuthReset: document.getElementById("btnAuthReset"),
|
||||
modal: document.getElementById("modal"),
|
||||
btnClose: document.getElementById("btnClose"),
|
||||
@@ -65,6 +71,7 @@
|
||||
authorized: false,
|
||||
ready: false,
|
||||
mode: "disabled", // enabled | misconfigured | sdk_missing | disabled
|
||||
serverCanManage: null,
|
||||
};
|
||||
|
||||
function nowIso() {
|
||||
@@ -358,6 +365,14 @@
|
||||
domain: String(auth0.domain || "").trim(),
|
||||
clientId: String(auth0.clientId || "").trim(),
|
||||
},
|
||||
connections:
|
||||
data.connections && typeof data.connections === "object"
|
||||
? {
|
||||
google: String(data.connections.google || "").trim(),
|
||||
kakao: String(data.connections.kakao || "").trim(),
|
||||
naver: String(data.connections.naver || "").trim(),
|
||||
}
|
||||
: { google: "", kakao: "", naver: "" },
|
||||
allowedEmails: allowedEmails.map((e) => String(e).trim().toLowerCase()).filter(Boolean),
|
||||
};
|
||||
}
|
||||
@@ -379,6 +394,11 @@
|
||||
domain: String(auth0.domain || "").trim(),
|
||||
clientId: String(auth0.clientId || "").trim(),
|
||||
},
|
||||
connections: {
|
||||
google: "",
|
||||
kakao: "",
|
||||
naver: "",
|
||||
},
|
||||
allowedEmails: allowedEmails.map((e) => String(e).trim().toLowerCase()).filter(Boolean),
|
||||
};
|
||||
const override = loadAuthOverride();
|
||||
@@ -389,6 +409,11 @@
|
||||
domain: override.auth0.domain || base.auth0.domain,
|
||||
clientId: override.auth0.clientId || base.auth0.clientId,
|
||||
},
|
||||
connections: {
|
||||
google: override.connections?.google || "",
|
||||
kakao: override.connections?.kakao || "",
|
||||
naver: override.connections?.naver || "",
|
||||
},
|
||||
allowedEmails: override.allowedEmails.length ? override.allowedEmails : base.allowedEmails,
|
||||
};
|
||||
}
|
||||
@@ -427,6 +452,16 @@
|
||||
// 설정/SDK 문제 상태에서도 버튼은 "클릭 가능"하게 두고, 클릭 시 토스트로 안내합니다.
|
||||
el.btnLogin.disabled = false;
|
||||
el.btnLogout.disabled = false;
|
||||
|
||||
// 간편 로그인 버튼 노출 (connection이 설정되어 있고, 미로그인 상태)
|
||||
const cfg = getAuthConfig();
|
||||
const showQuick = enabled && !auth.user;
|
||||
const g = showQuick && Boolean(cfg.connections.google);
|
||||
const k = showQuick && Boolean(cfg.connections.kakao);
|
||||
const n = showQuick && Boolean(cfg.connections.naver);
|
||||
if (el.btnGoogle) el.btnGoogle.hidden = !g;
|
||||
if (el.btnKakao) el.btnKakao.hidden = !k;
|
||||
if (el.btnNaver) el.btnNaver.hidden = !n;
|
||||
}
|
||||
|
||||
function applyManageLock() {
|
||||
@@ -519,6 +554,35 @@
|
||||
auth.user = isAuthed ? await auth.client.getUser() : null;
|
||||
const email = auth.user && auth.user.email ? auth.user.email : "";
|
||||
auth.authorized = Boolean(auth.user) && isAllowedEmail(email);
|
||||
auth.serverCanManage = null;
|
||||
|
||||
// 로그인되었으면 서버에 사용자 upsert 및 can_manage 동기화(서버가 있을 때만)
|
||||
if (auth.user) {
|
||||
try {
|
||||
const claims = await auth.client.getIdTokenClaims();
|
||||
const raw = claims && claims.__raw ? String(claims.__raw) : "";
|
||||
if (raw) {
|
||||
const cfg = getAuthConfig();
|
||||
const r = await fetch("/api/auth/sync", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${raw}`,
|
||||
"X-Auth0-Issuer": `https://${cfg.auth0.domain}/`,
|
||||
"X-Auth0-ClientId": cfg.auth0.clientId,
|
||||
},
|
||||
});
|
||||
if (r.ok) {
|
||||
const data = await r.json();
|
||||
if (data && data.ok) {
|
||||
auth.serverCanManage = Boolean(data.canManage);
|
||||
auth.authorized = auth.serverCanManage;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore: server not running or blocked
|
||||
}
|
||||
}
|
||||
|
||||
if (auth.user && !auth.authorized) {
|
||||
toastOnce("deny", "로그인은 되었지만 허용 이메일이 아니라서 관리 기능이 잠금 상태입니다.");
|
||||
@@ -538,6 +602,16 @@
|
||||
await auth.client.loginWithRedirect();
|
||||
}
|
||||
|
||||
async function loginWithConnection(connection) {
|
||||
if (auth.mode !== "enabled" || !auth.client) {
|
||||
openAuthModal();
|
||||
return;
|
||||
}
|
||||
await auth.client.loginWithRedirect({
|
||||
authorizationParams: { connection },
|
||||
});
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
if (auth.mode !== "enabled" || !auth.client) return;
|
||||
auth.user = null;
|
||||
@@ -561,6 +635,9 @@
|
||||
el.authDomain.value = cfg.auth0.domain || "";
|
||||
el.authClientId.value = cfg.auth0.clientId || "";
|
||||
el.authAllowedEmails.value = (cfg.allowedEmails || []).join(", ");
|
||||
if (el.authConnGoogle) el.authConnGoogle.value = cfg.connections.google || "";
|
||||
if (el.authConnKakao) el.authConnKakao.value = cfg.connections.kakao || "";
|
||||
if (el.authConnNaver) el.authConnNaver.value = cfg.connections.naver || "";
|
||||
|
||||
el.authModal.hidden = false;
|
||||
document.body.style.overflow = "hidden";
|
||||
@@ -883,6 +960,24 @@
|
||||
|
||||
if (el.btnLogin) el.btnLogin.addEventListener("click", () => login().catch(() => toast("로그인에 실패했습니다.")));
|
||||
if (el.btnLogout) el.btnLogout.addEventListener("click", () => logout().catch(() => toast("로그아웃에 실패했습니다.")));
|
||||
if (el.btnGoogle)
|
||||
el.btnGoogle.addEventListener("click", () => {
|
||||
const c = getAuthConfig().connections.google;
|
||||
if (!c) return openAuthModal();
|
||||
return loginWithConnection(c).catch(() => toast("로그인에 실패했습니다."));
|
||||
});
|
||||
if (el.btnKakao)
|
||||
el.btnKakao.addEventListener("click", () => {
|
||||
const c = getAuthConfig().connections.kakao;
|
||||
if (!c) return openAuthModal();
|
||||
return loginWithConnection(c).catch(() => toast("로그인에 실패했습니다."));
|
||||
});
|
||||
if (el.btnNaver)
|
||||
el.btnNaver.addEventListener("click", () => {
|
||||
const c = getAuthConfig().connections.naver;
|
||||
if (!c) return openAuthModal();
|
||||
return loginWithConnection(c).catch(() => toast("로그인에 실패했습니다."));
|
||||
});
|
||||
|
||||
if (el.btnAuthClose) el.btnAuthClose.addEventListener("click", closeAuthModal);
|
||||
if (el.authModal) {
|
||||
@@ -914,6 +1009,9 @@
|
||||
.split(",")
|
||||
.map((s) => s.trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
const connGoogle = el.authConnGoogle ? String(el.authConnGoogle.value || "").trim() : "";
|
||||
const connKakao = el.authConnKakao ? String(el.authConnKakao.value || "").trim() : "";
|
||||
const connNaver = el.authConnNaver ? String(el.authConnNaver.value || "").trim() : "";
|
||||
|
||||
if (!domain || !clientId) {
|
||||
toast("Domain과 Client ID를 입력하세요.");
|
||||
@@ -922,6 +1020,7 @@
|
||||
|
||||
saveAuthOverride({
|
||||
auth0: { domain, clientId },
|
||||
connections: { google: connGoogle, kakao: connKakao, naver: connNaver },
|
||||
allowedEmails: emails,
|
||||
});
|
||||
toast("저장했습니다. 페이지를 새로고침합니다.");
|
||||
|
||||
Reference in New Issue
Block a user