From fb2153cbb04e4316b6f65d58cc47ed5640144956 Mon Sep 17 00:00:00 2001 From: dsyoon Date: Sat, 7 Feb 2026 21:31:52 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20UI=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20.env=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인 설정 모달(UI) 제거 - 허용 이메일 라벨을 관리자 이메일로 변경 - Auth0/관리자 이메일을 서버 .env에서 제공하고 클라이언트가 자동 로드 Co-authored-by: Cursor --- .env.example | 24 ++++++++ .gitignore | 1 + index.html | 68 ++++++---------------- script.js | 161 +++++++++++++++------------------------------------ server.js | 63 +++++++++++++++----- 5 files changed, 138 insertions(+), 179 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1631bf9 --- /dev/null +++ b/.env.example @@ -0,0 +1,24 @@ +## Database +DB_HOST=ncue.net +DB_PORT=5432 +DB_NAME=ncue +DB_USER=ncue +DB_PASSWORD=REPLACE_ME +TABLE=ncue_user + +## Auth0 (server-side) +# Auth0 Domain (without https://) +AUTH0_DOMAIN=ncue.net +# Auth0 SPA Application Client ID +AUTH0_CLIENT_ID=g5RDfax7FZkKzXYvXOgku3Ll8CxuA4IM +# Google connection name (usually google-oauth2) +AUTH0_GOOGLE_CONNECTION=google-oauth2 +# Admin emails (comma-separated) +ADMIN_EMAILS=dosangyoon@gmail.com,dsyoon@ncue.net + +## Optional +# Server port +PORT=8000 +# Optional: allow writing config via API (not required if using env) +CONFIG_TOKEN= + diff --git a/.gitignore b/.gitignore index 5fbafd6..8aaa642 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules/ .env .env.* +!.env.example diff --git a/index.html b/index.html index 4443437..491d8c5 100644 --- a/index.html +++ b/index.html @@ -22,8 +22,6 @@ // 2) Allowed Callback URLs / Allowed Logout URLs에 현재 사이트 주소를 등록하세요. // 예: https://drive.daewoongai.com/apps/dashboard/ window.AUTH_CONFIG = { - // end-user가 설정 모달을 사용하는지 여부(기본: false) - allowEndUserConfig: false, // (선택) API 서버가 다른 도메인이면 지정. 예: https://api.ncue.net apiBase: "", auth0: { @@ -34,8 +32,8 @@ connections: { google: "", }, - // 관리 허용 이메일(대소문자 무시) - allowedEmails: [], + // 관리자 이메일(대소문자 무시) + adminEmails: [], }; @@ -194,48 +192,6 @@ - - - @@ -713,7 +669,12 @@ 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 : []; + // legacy: allowedEmails -> adminEmails + const adminEmails = Array.isArray(data.adminEmails) + ? data.adminEmails + : Array.isArray(data.allowedEmails) + ? data.allowedEmails + : []; return { auth0: { domain: String(auth0.domain || "").trim(), @@ -724,7 +685,7 @@ kakao: String(connections.kakao || "").trim(), naver: String(connections.naver || "").trim(), }, - allowedEmails: allowedEmails.map((e) => String(e).trim().toLowerCase()).filter(Boolean), + adminEmails: adminEmails.map((e) => String(e).trim().toLowerCase()).filter(Boolean), }; } @@ -732,7 +693,12 @@ 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 : []; + // legacy: allowedEmails -> adminEmails + const adminEmails = Array.isArray(cfg.adminEmails) + ? cfg.adminEmails + : Array.isArray(cfg.allowedEmails) + ? cfg.allowedEmails + : []; const base = { auth0: { domain: String(auth0.domain || "").trim(), @@ -743,7 +709,7 @@ kakao: String(connections.kakao || "").trim(), naver: String(connections.naver || "").trim(), }, - allowedEmails: allowedEmails.map((e) => String(e).trim().toLowerCase()).filter(Boolean), + adminEmails: adminEmails.map((e) => String(e).trim().toLowerCase()).filter(Boolean), }; const over = loadAuthOverride(); if (!over) return base; @@ -757,7 +723,7 @@ kakao: over.connections.kakao || base.connections.kakao, naver: over.connections.naver || base.connections.naver, }, - allowedEmails: over.allowedEmails.length ? over.allowedEmails : base.allowedEmails, + adminEmails: over.adminEmails.length ? over.adminEmails : base.adminEmails, }; } diff --git a/script.js b/script.js index 71f86c8..3c729ed 100644 --- a/script.js +++ b/script.js @@ -10,7 +10,6 @@ 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 AUTH_SETUP_SHOWN_KEY = "links_home_auth_setup_shown_v1"; const AUTH_PKCE_KEY = "links_home_auth_pkce_v1"; const AUTH_TOKEN_KEY = "links_home_auth_tokens_v1"; @@ -31,14 +30,6 @@ btnLogout: document.getElementById("btnLogout"), snsLogin: document.getElementById("snsLogin"), btnGoogle: document.getElementById("btnGoogle"), - 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"), - btnAuthReset: document.getElementById("btnAuthReset"), modal: document.getElementById("modal"), btnClose: document.getElementById("btnClose"), btnCancel: document.getElementById("btnCancel"), @@ -79,7 +70,7 @@ "link-ncue-net", "dreamgirl-ncue-net", ]); - const ACCESS_ADMIN_EMAILS = new Set(["dosangyoon@gmail.com", "dsyoon@ncue.net"]); + const DEFAULT_ADMIN_EMAILS = new Set(["dosangyoon@gmail.com", "dsyoon@ncue.net"]); function getUserEmail() { const e = auth && auth.user && auth.user.email ? String(auth.user.email) : ""; @@ -88,7 +79,10 @@ function isAdminEmail(email) { const e = String(email || "").trim().toLowerCase(); - return ACCESS_ADMIN_EMAILS.has(e); + const cfg = getAuthConfig(); + const admins = Array.isArray(cfg.adminEmails) ? cfg.adminEmails : []; + if (admins.length) return admins.includes(e); + return DEFAULT_ADMIN_EMAILS.has(e); } function canAccessLink(link) { @@ -416,7 +410,12 @@ 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 : []; + // legacy: allowedEmails -> adminEmails + const adminEmails = Array.isArray(data.adminEmails) + ? data.adminEmails + : Array.isArray(data.allowedEmails) + ? data.allowedEmails + : []; return { auth0: { domain: String(auth0.domain || "").trim(), @@ -431,7 +430,7 @@ naver: String(data.connections.naver || "").trim(), } : { google: "", kakao: "", naver: "" }, - allowedEmails: allowedEmails.map((e) => String(e).trim().toLowerCase()).filter(Boolean), + adminEmails: adminEmails.map((e) => String(e).trim().toLowerCase()).filter(Boolean), }; } @@ -445,12 +444,15 @@ function getAuthConfig() { const cfg = globalThis.AUTH_CONFIG && typeof globalThis.AUTH_CONFIG === "object" ? globalThis.AUTH_CONFIG : {}; - const allowEndUserConfig = Boolean(cfg.allowEndUserConfig); const apiBase = String(cfg.apiBase || "").trim(); // optional, e.g. https://api.ncue.net const auth0 = cfg.auth0 && typeof cfg.auth0 === "object" ? cfg.auth0 : {}; - const allowedEmails = Array.isArray(cfg.allowedEmails) ? cfg.allowedEmails : []; + // legacy: allowedEmails -> adminEmails + const adminEmails = Array.isArray(cfg.adminEmails) + ? cfg.adminEmails + : Array.isArray(cfg.allowedEmails) + ? cfg.allowedEmails + : []; const base = { - allowEndUserConfig, apiBase, auth0: { domain: String(auth0.domain || "").trim(), @@ -461,13 +463,12 @@ kakao: "", naver: "", }, - allowedEmails: allowedEmails.map((e) => String(e).trim().toLowerCase()).filter(Boolean), + adminEmails: adminEmails.map((e) => String(e).trim().toLowerCase()).filter(Boolean), }; const override = loadAuthOverride(); if (!override) return base; // override가 있으면 우선 적용 (서버 재배포 없이 테스트 가능) return { - allowEndUserConfig, apiBase, auth0: { domain: override.auth0.domain || base.auth0.domain, @@ -478,7 +479,7 @@ kakao: override.connections?.kakao || "", naver: override.connections?.naver || "", }, - allowedEmails: override.allowedEmails.length ? override.allowedEmails : base.allowedEmails, + adminEmails: override.adminEmails.length ? override.adminEmails : base.adminEmails, }; } @@ -505,7 +506,12 @@ const v = data.value; const auth0 = v.auth0 || {}; const connections = v.connections || {}; - const allowedEmails = Array.isArray(v.allowedEmails) ? v.allowedEmails : []; + // legacy: allowedEmails -> adminEmails + const adminEmails = Array.isArray(v.adminEmails) + ? v.adminEmails + : Array.isArray(v.allowedEmails) + ? v.allowedEmails + : []; const domain = String(auth0.domain || "").trim(); const clientId = String(auth0.clientId || "").trim(); const google = String(connections.google || "").trim(); @@ -513,7 +519,7 @@ saveAuthOverride({ auth0: { domain, clientId }, connections: { google }, - allowedEmails, + adminEmails, }); return true; } catch { @@ -597,7 +603,7 @@ async function manualAuthorize(connection) { const cfg = getAuthConfig(); if (!cfg.auth0.domain || !cfg.auth0.clientId) { - openAuthModal(); + toast("로그인 설정이 서버(.env)에 없습니다. 관리자에게 문의하세요."); return; } if (!globalThis.crypto || !crypto.subtle) { @@ -745,11 +751,13 @@ } } - function isAllowedEmail(email) { - const { allowedEmails } = getAuthConfig(); - if (!allowedEmails.length) return true; // 설정이 비어있으면 로그인만으로 허용 + function isManageAdminEmail(email) { + const cfg = getAuthConfig(); + const admins = Array.isArray(cfg.adminEmails) ? cfg.adminEmails : []; const e = String(email || "").trim().toLowerCase(); - return allowedEmails.includes(e); + if (admins.length) return admins.includes(e); + // 안전한 기본값: 설정이 비어있으면 기본 관리자만 관리 가능 + return DEFAULT_ADMIN_EMAILS.has(e); } function updateAuthUi() { @@ -787,7 +795,7 @@ // 로그인 기능이 "enabled"일 때만 관리 잠금을 적용합니다. state.canManage = auth.mode === "enabled" ? Boolean(auth.user && auth.authorized) : true; - const lockMsg = "관리 기능은 로그인(허용 이메일) 후 사용 가능합니다."; + const lockMsg = "관리 기능은 로그인(관리자 이메일) 후 사용 가능합니다."; el.btnAdd.disabled = !state.canManage; el.btnImport.disabled = !state.canManage; @@ -814,11 +822,7 @@ auth.mode = "misconfigured"; updateAuthUi(); applyManageLock(); - // 브라우저당 1회: 설정 모달 자동 오픈 - if (!localStorage.getItem(AUTH_SETUP_SHOWN_KEY)) { - localStorage.setItem(AUTH_SETUP_SHOWN_KEY, "1"); - setTimeout(() => openAuthModal(), 80); - } + toastOnce("misconf", "로그인 설정이 서버(.env)에 없습니다. 관리자에게 문의하세요."); return; } @@ -829,7 +833,7 @@ await manualHandleCallbackIfNeeded().catch(() => {}); auth.user = await manualLoadUser(); const email = auth.user && auth.user.email ? String(auth.user.email) : ""; - auth.authorized = Boolean(auth.user) && isAllowedEmail(email); + auth.authorized = Boolean(auth.user) && isManageAdminEmail(email); auth.serverCanManage = null; const t = loadTokens(); if (auth.user && t && t.id_token) { @@ -883,7 +887,7 @@ const isAuthed = await auth.client.isAuthenticated(); 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.authorized = Boolean(auth.user) && isManageAdminEmail(email); auth.serverCanManage = null; // 로그인되었으면 서버에 사용자 upsert 및 can_manage 동기화(서버가 있을 때만) @@ -893,7 +897,7 @@ const raw = claims && claims.__raw ? String(claims.__raw) : ""; if (raw) { const cfg = getAuthConfig(); - const r = await fetch("/api/auth/sync", { + const r = await fetch(apiUrl("/api/auth/sync"), { method: "POST", headers: { Authorization: `Bearer ${raw}`, @@ -915,9 +919,7 @@ } if (auth.user && !auth.authorized) { - toastOnce("deny", "로그인은 되었지만 허용 이메일이 아니라서 관리 기능이 잠금 상태입니다."); - } else if (auth.user && getAuthConfig().allowedEmails.length === 0) { - toastOnce("allowall", "주의: 허용 이메일 목록이 비어있어서 로그인한 모든 계정이 관리 가능 상태입니다."); + toastOnce("deny", "로그인은 되었지만 관리자 이메일이 아니라서 관리 기능이 잠금 상태입니다."); } updateAuthUi(); @@ -926,7 +928,7 @@ async function login() { if (auth.mode !== "enabled" || !auth.client) { - openAuthModal(); + toast("로그인 설정이 서버(.env)에 필요합니다."); return; } await auth.client.loginWithRedirect(); @@ -934,7 +936,7 @@ async function loginWithConnection(connection) { if (auth.mode !== "enabled") { - openAuthModal(); + toast("로그인 설정이 서버(.env)에 필요합니다."); return; } if (auth.client) { @@ -983,29 +985,6 @@ location.assign(u.toString()); } - 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(", "); - if (el.authConnGoogle) el.authConnGoogle.value = cfg.connections.google || ""; - - 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); @@ -1173,7 +1152,7 @@ return; } if (auth.mode === "enabled" && !state.canManage && (act === "fav" || act === "edit" || act === "del")) { - toast("관리 기능은 로그인(허용 이메일) 후 사용 가능합니다."); + toast("관리 기능은 로그인(관리자 이메일) 후 사용 가능합니다."); return; } if (act === "fav") { @@ -1281,7 +1260,7 @@ el.btnAdd.addEventListener("click", () => { if (auth.mode === "enabled" && !state.canManage) - return toast("관리 기능은 로그인(허용 이메일) 후 사용 가능합니다."); + return toast("관리 기능은 로그인(관리자 이메일) 후 사용 가능합니다."); openModal("add", null); }); el.btnClose.addEventListener("click", closeModal); @@ -1299,7 +1278,7 @@ el.btnExport.addEventListener("click", exportJson); el.btnImport.addEventListener("click", () => { if (auth.mode === "enabled" && !state.canManage) - return toast("관리 기능은 로그인(허용 이메일) 후 사용 가능합니다."); + return toast("관리 기능은 로그인(관리자 이메일) 후 사용 가능합니다."); el.file.click(); }); el.file.addEventListener("change", async () => { @@ -1324,57 +1303,9 @@ if (el.btnGoogle) el.btnGoogle.addEventListener("click", () => { const c = getAuthConfig().connections.google; - if (!c) return openAuthModal(); + if (!c) return toast("서버(.env)에 AUTH0_GOOGLE_CONNECTION 설정이 필요합니다."); return loginWithConnection(c).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); - const connGoogle = el.authConnGoogle ? String(el.authConnGoogle.value || "").trim() : ""; - - if (!domain || !clientId) { - toast("Domain과 Client ID를 입력하세요."); - return; - } - - saveAuthOverride({ - auth0: { domain, clientId }, - connections: { google: connGoogle }, - allowedEmails: emails, - }); - toast("저장했습니다. 페이지를 새로고침합니다."); - closeAuthModal(); - setTimeout(() => location.reload(), 200); - }); - } } async function main() { diff --git a/server.js b/server.js index 0ac200b..85f8efd 100644 --- a/server.js +++ b/server.js @@ -25,6 +25,17 @@ function safeIdent(s) { return v; } +function parseCsv(s) { + return String(s || "") + .split(",") + .map((x) => x.trim()) + .filter(Boolean); +} + +function parseEmailCsv(s) { + return parseCsv(s).map((x) => x.toLowerCase()); +} + const PORT = Number(env("PORT", "8000")) || 8000; const DB_HOST = must("DB_HOST"); const DB_PORT = Number(env("DB_PORT", "5432")) || 5432; @@ -34,7 +45,12 @@ const DB_PASSWORD = must("DB_PASSWORD"); const TABLE = safeIdent(env("TABLE", "ncue_user") || "ncue_user"); const CONFIG_TABLE = "ncue_app_config"; const CONFIG_TOKEN = env("CONFIG_TOKEN", "").trim(); -const ADMIN_EMAILS = new Set(["dosangyoon@gmail.com", "dsyoon@ncue.net"]); +const ADMIN_EMAILS = new Set(parseEmailCsv(env("ADMIN_EMAILS", "dosangyoon@gmail.com,dsyoon@ncue.net"))); + +// Auth0 config via .env (preferred) +const AUTH0_DOMAIN = env("AUTH0_DOMAIN", "").trim(); +const AUTH0_CLIENT_ID = env("AUTH0_CLIENT_ID", "").trim(); +const AUTH0_GOOGLE_CONNECTION = env("AUTH0_GOOGLE_CONNECTION", "").trim(); const pool = new pg.Pool({ host: DB_HOST, @@ -133,14 +149,15 @@ app.post("/api/auth/sync", async (req, res) => { const name = payload.name ? String(payload.name).trim() : null; const picture = payload.picture ? String(payload.picture).trim() : null; const provider = sub.includes("|") ? sub.split("|", 1)[0] : null; + const isAdmin = email ? isAdminEmail(email) : false; if (!sub) return res.status(400).json({ ok: false, error: "missing_sub" }); const q = ` insert into public.${TABLE} - (sub, email, name, picture, provider, first_login_at, last_login_at, updated_at) + (sub, email, name, picture, provider, first_login_at, last_login_at, can_manage, updated_at) values - ($1, $2, $3, $4, $5, now(), now(), now()) + ($1, $2, $3, $4, $5, now(), now(), $6, now()) on conflict (sub) do update set email = excluded.email, name = excluded.name, @@ -148,10 +165,11 @@ app.post("/api/auth/sync", async (req, res) => { provider = excluded.provider, first_login_at = coalesce(public.${TABLE}.first_login_at, excluded.first_login_at), last_login_at = now(), + can_manage = (public.${TABLE}.can_manage or $6), updated_at = now() returning can_manage, first_login_at, last_login_at, last_logout_at `; - const r = await pool.query(q, [sub, email, name, picture, provider]); + const r = await pool.query(q, [sub, email, name, picture, provider, isAdmin]); const canManage = Boolean(r.rows?.[0]?.can_manage); res.json({ ok: true, canManage, user: r.rows?.[0] || null }); @@ -191,10 +209,29 @@ app.post("/api/auth/logout", async (req, res) => { // Shared auth config for all browsers (read-only public) app.get("/api/config/auth", async (_req, res) => { try { + // Prefer .env config (no UI needed) + if (AUTH0_DOMAIN && AUTH0_CLIENT_ID && AUTH0_GOOGLE_CONNECTION) { + return res.json({ + ok: true, + value: { + auth0: { domain: AUTH0_DOMAIN, clientId: AUTH0_CLIENT_ID }, + connections: { google: AUTH0_GOOGLE_CONNECTION }, + adminEmails: [...ADMIN_EMAILS], + }, + updated_at: null, + source: "env", + }); + } + await ensureConfigTable(); const r = await pool.query(`select value, updated_at from public.${CONFIG_TABLE} where key = $1`, ["auth"]); if (!r.rows?.length) return res.status(404).json({ ok: false, error: "not_set" }); - res.json({ ok: true, value: r.rows[0].value, updated_at: r.rows[0].updated_at }); + const v = r.rows[0].value || {}; + // legacy: allowedEmails -> adminEmails + if (v && typeof v === "object" && !v.adminEmails && Array.isArray(v.allowedEmails)) { + v.adminEmails = v.allowedEmails; + } + res.json({ ok: true, value: v, updated_at: r.rows[0].updated_at, source: "db" }); } catch (e) { res.status(500).json({ ok: false, error: "server_error" }); } @@ -211,26 +248,26 @@ app.post("/api/config/auth", async (req, res) => { const body = req.body && typeof req.body === "object" ? req.body : {}; const auth0 = body.auth0 && typeof body.auth0 === "object" ? body.auth0 : {}; const connections = body.connections && typeof body.connections === "object" ? body.connections : {}; - const allowedEmails = Array.isArray(body.allowedEmails) ? body.allowedEmails : []; + // legacy: allowedEmails -> adminEmails + const adminEmails = Array.isArray(body.adminEmails) + ? body.adminEmails + : Array.isArray(body.allowedEmails) + ? body.allowedEmails + : []; const domain = String(auth0.domain || "").trim(); const clientId = String(auth0.clientId || "").trim(); const googleConn = String(connections.google || "").trim(); - const emails = allowedEmails.map((x) => String(x).trim().toLowerCase()).filter(Boolean); + const emails = adminEmails.map((x) => String(x).trim().toLowerCase()).filter(Boolean); if (!domain || !clientId || !googleConn) { return res.status(400).json({ ok: false, error: "missing_fields" }); } - // Optional safety: ensure at least one admin is present in allowedEmails - if (emails.length && !emails.some(isAdminEmail)) { - return res.status(400).json({ ok: false, error: "admin_email_missing" }); - } - const value = { auth0: { domain, clientId }, connections: { google: googleConn }, - allowedEmails: emails, + adminEmails: emails, }; await pool.query(