Apply access gating in fallback mode

Mirror anonymous/user/admin link access tiers in the inline fallback app, disabling open/copy for restricted services and re-rendering after login/logout.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dsyoon
2026-02-07 21:03:32 +09:00
parent ee985a707a
commit 2440ead5bf

View File

@@ -465,6 +465,30 @@
})(); })();
const baseOrder = new Map(baseLinks.map((l, i) => [l.id, i])); const baseOrder = new Map(baseLinks.map((l, i) => [l.id, i]));
// Access levels (same as script.js)
const ACCESS_ANON_IDS = new Set(["dsyoon-ncue-net", "family-ncue-net", "link-ncue-net"]);
const ACCESS_USER_IDS = new Set([
"dsyoon-ncue-net",
"family-ncue-net",
"tts-ncue-net",
"meeting-ncue-net",
"link-ncue-net",
"dreamgirl-ncue-net",
]);
const ACCESS_ADMIN_EMAILS = new Set(["dosangyoon@gmail.com", "dsyoon@ncue.net"]);
let sessionEmail = "";
function isAdminEmail(email) {
return ACCESS_ADMIN_EMAILS.has(String(email || "").trim().toLowerCase());
}
function canAccessLink(link) {
const id = String(link && link.id ? link.id : "");
const email = String(sessionEmail || "").trim().toLowerCase();
if (email && isAdminEmail(email)) return true;
if (email) return ACCESS_USER_IDS.has(id);
return ACCESS_ANON_IDS.has(id);
}
const state = { const state = {
store: loadStore(), store: loadStore(),
query: "", query: "",
@@ -525,11 +549,17 @@
const u = esc(link.url); const u = esc(link.url);
const desc = esc(link.description || ""); const desc = esc(link.description || "");
const star = link.favorite ? "star on" : "star"; const star = link.favorite ? "star on" : "star";
const accessible = canAccessLink(link);
const tags = (link.tags || []).slice(0, 8).map((x) => `<span class="tag">#${esc(x)}</span>`).join(""); const tags = (link.tags || []).slice(0, 8).map((x) => `<span class="tag">#${esc(x)}</span>`).join("");
const favTag = link.favorite ? `<span class="tag fav">★ 즐겨찾기</span>` : ""; const favTag = link.favorite ? `<span class="tag fav">★ 즐겨찾기</span>` : "";
const lockTag = accessible ? "" : `<span class="tag lock">접근 제한</span>`;
const letter = esc((link.title || d || "L").trim().slice(0, 1).toUpperCase()); const letter = esc((link.title || d || "L").trim().slice(0, 1).toUpperCase());
const openHtml = accessible
? `<a class="btn mini" href="${u}" target="_blank" rel="noopener noreferrer">열기</a>`
: `<button class="btn mini" type="button" disabled aria-disabled="true" title="이 링크는 현재 권한으로 접근할 수 없습니다.">열기</button>`;
const copyAttrs = accessible ? "" : ` disabled aria-disabled="true" title="이 링크는 현재 권한으로 접근할 수 없습니다."`;
return ` return `
<article class="card" data-id="${esc(link.id)}"> <article class="card${accessible ? "" : " disabled"}" data-id="${esc(link.id)}" data-access="${accessible ? "1" : "0"}">
<div class="card-head"> <div class="card-head">
<div class="card-title"> <div class="card-title">
<div class="favicon" aria-hidden="true"><div class="letter">${letter}</div></div> <div class="favicon" aria-hidden="true"><div class="letter">${letter}</div></div>
@@ -543,10 +573,10 @@
</button> </button>
</div> </div>
<div class="card-desc">${desc || "&nbsp;"}</div> <div class="card-desc">${desc || "&nbsp;"}</div>
<div class="tags">${favTag}${tags}</div> <div class="tags">${favTag}${lockTag}${tags}</div>
<div class="card-actions"> <div class="card-actions">
<a class="btn mini" href="${u}" target="_blank" rel="noopener noreferrer">열기</a> ${openHtml}
<button class="btn mini" type="button" data-act="copy">URL 복사</button> <button class="btn mini" type="button" data-act="copy"${copyAttrs}>URL 복사</button>
<button class="btn mini" type="button" data-act="edit">편집</button> <button class="btn mini" type="button" data-act="edit">편집</button>
<button class="btn mini mini-danger" type="button" data-act="del">삭제</button> <button class="btn mini mini-danger" type="button" data-act="del">삭제</button>
</div> </div>
@@ -771,10 +801,12 @@
} }
const isAuthed = await client.isAuthenticated(); const isAuthed = await client.isAuthenticated();
auth.user = isAuthed ? await client.getUser() : null; auth.user = isAuthed ? await client.getUser() : null;
sessionEmail = auth.user && auth.user.email ? String(auth.user.email).trim().toLowerCase() : "";
if (el.btnLogout) el.btnLogout.hidden = !auth.user; if (el.btnLogout) el.btnLogout.hidden = !auth.user;
if (el.snsLogin) el.snsLogin.hidden = Boolean(auth.user); if (el.snsLogin) el.snsLogin.hidden = Boolean(auth.user);
if (el.user) el.user.hidden = !auth.user; if (el.user) el.user.hidden = !auth.user;
if (el.userText && auth.user) el.userText.textContent = auth.user.email || auth.user.name || "로그인됨"; if (el.userText && auth.user) el.userText.textContent = auth.user.email || auth.user.name || "로그인됨";
render();
} }
async function loginWithConnection(provider) { async function loginWithConnection(provider) {
@@ -795,6 +827,7 @@
async function logout() { async function logout() {
const client = await ensureAuthClient(); const client = await ensureAuthClient();
if (!client) return; if (!client) return;
sessionEmail = "";
await client.logout({ await client.logout({
logoutParams: { returnTo: location.origin === "null" ? location.href : location.origin + location.pathname }, logoutParams: { returnTo: location.origin === "null" ? location.href : location.origin + location.pathname },
}); });
@@ -870,6 +903,11 @@
const card = e.target.closest(".card"); const card = e.target.closest(".card");
if (!card) return; if (!card) return;
const id = card.getAttribute("data-id"); const id = card.getAttribute("data-id");
if ((btn.getAttribute("data-act") === "copy" || btn.getAttribute("data-act") === "open") && card.getAttribute("data-access") === "0") {
toast("이 링크는 현재 권한으로 접근할 수 없습니다.");
e.preventDefault();
return;
}
const act = btn.getAttribute("data-act"); const act = btn.getAttribute("data-act");
const link = id ? getById(id) : null; const link = id ? getById(id) : null;
if (!link) return; if (!link) return;