From e0e8d8d6620c3dfb888e1a375860bac1ea5c5c6e Mon Sep 17 00:00:00 2001 From: dsyoon Date: Fri, 3 Apr 2026 22:54:03 +0900 Subject: [PATCH] =?UTF-8?q?fix(ops-auth):=20=EB=A7=A4=EC=A7=81=20=EB=A7=81?= =?UTF-8?q?=ED=81=AC=20URL=EC=9D=84=20URL=20API=EB=A1=9C=20=EC=A1=B0?= =?UTF-8?q?=EB=A6=BD=ED=95=B4=20=EB=B3=B8=EB=AC=B8=20=EB=AC=B8=EC=9E=A5?= =?UTF-8?q?=EA=B3=BC=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - buildMagicVerifyUrl(BASE_URL, token) 추가 Made-with: Cursor --- ops-auth.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ops-auth.js b/ops-auth.js index a290bf7..c44acde 100644 --- a/ops-auth.js +++ b/ops-auth.js @@ -107,6 +107,18 @@ function getBaseUrl() { return `http://localhost:${port}`; } +/** + * 매직 링크 절대 URL — BASE_URL + `/auth/verify/:token` 만 조합 (본문 문장·조사와 문자열 결합 금지) + */ +function buildMagicVerifyUrl(baseUrl, token) { + const base = String(baseUrl || "").trim().replace(/\/$/, ""); + const t = String(token || "").trim(); + if (!base) throw new Error("BASE_URL이 비어 있습니다."); + if (!t) throw new Error("인증 토큰이 비어 있습니다."); + const path = `/auth/verify/${encodeURIComponent(t)}`; + return new URL(path, `${base}/`).href; +} + function sanitizeReturnTo(v) { const s = (v || "").toString().trim(); if (!s.startsWith("/") || s.startsWith("//")) return "/learning"; @@ -402,7 +414,7 @@ module.exports = function createOpsAuth(DATA_DIR, hooks = {}) { }); saveMagicLinks(MAGIC_LINK_PATH, list); /** 경로에 토큰만 두기 (쿼리 `?token=`는 일부 웹메일/Outlook에서 href가 잘림) */ - const linkUrl = `${BASE_URL}/auth/verify/${encodeURIComponent(token)}`; + const linkUrl = buildMagicVerifyUrl(BASE_URL, token); await sendMagicLinkEmail(email, linkUrl); if (typeof hooks.onMagicLinkRequested === "function") { try {