diff --git a/mcp/src/oauth/consent.ts b/mcp/src/oauth/consent.ts index ef1bd2a..e7629a1 100644 --- a/mcp/src/oauth/consent.ts +++ b/mcp/src/oauth/consent.ts @@ -59,7 +59,7 @@ export function consentPage(p: ConsentParams, error?: string): string { code { background: rgba(127,127,127,.15); padding: .1rem .35rem; border-radius: .25rem; font-size: .85rem; word-break: break-all; } label { display: block; margin: 1.25rem 0 .35rem; font-weight: 600; } - input[type=password] { width: 100%; padding: .6rem .7rem; font-size: 1rem; + input[type=password], input[type=email] { width: 100%; padding: .6rem .7rem; font-size: 1rem; border: 1px solid #8888; border-radius: .5rem; box-sizing: border-box; } button { margin-top: 1.25rem; width: 100%; padding: .7rem; font-size: 1rem; font-weight: 600; border: 0; border-radius: .5rem; background: #8a5f1e; color: #fff; cursor: pointer; } @@ -75,12 +75,16 @@ export function consentPage(p: ConsentParams, error?: string): string { ${errBlock}
${fields} - - + + + +
-

祕密不正確不會發出授權碼。此頁不儲存你的輸入。

+

用你登入知識庫(Portal)的同一組帳密,**不需要另外的祕密**。 + 帳密不正確不會發出授權碼。此頁不儲存你的輸入。

`; } diff --git a/mcp/src/oauth/routes.ts b/mcp/src/oauth/routes.ts index 2dea19c..cb65153 100644 --- a/mcp/src/oauth/routes.ts +++ b/mcp/src/oauth/routes.ts @@ -184,9 +184,8 @@ export function registerOAuthRoutes< 302, ); } - if (!c.env.MCP_OWNER_SECRET) { - return c.text("server_error: MCP_OWNER_SECRET not configured", 503); - } + // 2026-07-30:不再檢查 MCP_OWNER_SECRET(改用 Portal 帳密驗證,見 POST 分支)。 + // 舊行為:未設此 env → 直接 503 ⇒ **每個封測者接自己的 AI 都死在這頁**。 const params: ConsentParams = { client_id: q.client_id ?? "", redirect_uri: q.redirect_uri, @@ -222,9 +221,6 @@ export function registerOAuthRoutes< 302, ); } - if (!c.env.MCP_OWNER_SECRET) { - return c.text("server_error: MCP_OWNER_SECRET not configured", 503); - } const consent: ConsentParams = { client_id: p.client_id ?? "", redirect_uri: redirectUri, @@ -234,10 +230,39 @@ export function registerOAuthRoutes< scope: p.scope ?? "mcp", resource: canonicalResource, // 一律存 canonical,不存 client 原樣值 }; - // ★ owner 祕密把關:錯誤不發碼、重顯同意頁。這是「只知 URL 的人進不來」的唯一閘。 - const supplied = p.owner_secret ?? ""; - if (!supplied || !constantTimeEqual(supplied, c.env.MCP_OWNER_SECRET)) { - return c.html(consentPage(consent, "Owner 祕密不正確,請重試。"), 401); + // ★ 把關:用**用戶自己的 Portal 帳密**,不再另設一把 MCP_OWNER_SECRET + // (leo 2026-07-30:「claude 裡有一個直接輸入帳密連線的,為什麼不用那個? + // 跟他輸入 portal 的帳密一樣不就好了?」) + // + // 為什麼換掉 owner secret(三個實際問題,都是封測撞出來的): + // ① **沒人給得了封測者**——安裝器產生後從不顯示(完成頁 grep「Owner 祕密」=0), + // CF secret 又唯寫讀不回 ⇒ 用戶卡在這頁,只能找 leo 手動 wrangler 覆寫 + // ② **多一把要記的金鑰**——違反「拿一把金鑰就很難了」(D36 精神) + // ③ **全實例共用一把**,無法分辨是誰連上來的(企業多人版必要) + // 風險評估(leo 判斷,總管原本誇大成「繞過帳密的旁路」已更正): + // secret 要貼進 claude.ai(本身有帳密保護)⇒ 洩漏 secret 與洩漏 portal 帳密風險相同。 + const email = (p.email ?? "").trim(); + const password = p.password ?? ""; + if (!email || !password) { + return c.html(consentPage(consent, "請輸入你的 Portal 帳號與密碼。"), 401); + } + // 認證下沉到 cypher 的 /portal/login(唯一真相源;同樣吃它的節流與停用檢查)。 + // 走 service binding(MCP 與 cypher 同帳號,屬 D28 允許的零件級組合)。 + let loginOk = false; + try { + const res = await c.env.CYPHER_EXECUTOR.fetch( + new Request("https://cypher/portal/login", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ email, password }), + }), + ); + loginOk = res.ok; + } catch { + return c.html(consentPage(consent, "暫時無法驗證帳密,請稍後再試。"), 503); + } + if (!loginOk) { + return c.html(consentPage(consent, "帳號或密碼不正確,請重試。"), 401); } if (!c.env.OAUTH_KV) { return c.text("server_error: OAUTH_KV not configured", 503);