fix(mcp): address PR #15 review — aud validation, OAUTH_KV auto-inject, TTL/sunset docs, drift test
leo review 5 條逐條處理: 1. RFC 8707 aud 驗證(真缺口):partner-auth OAuth 路徑補「at.aud === resourceUri(originOf(url))」, 不符回 401 invalid_token(防別的 arcrun-mcp 部署簽的 token passthrough)。加 aud 不符→401 測試。 2. deploy.ts injectWranglerConfig 涵蓋 OAUTH_KV(在 PR 內補):OAUTH_KV 納入 REQUIRED_KV_NAMESPACES → acr init/update 自動建 namespace + 注入用戶帳號真 id(比照 SUBMISSIONS_KV 家族)。wrangler.toml 註解 更新(CLI 路徑自動、手動直推才需手建)。注入 regex 已驗證命中。 3. MCP_TOKEN_TTL 預設維持 30 天(leo 拍板不改):OAUTH.md 明寫為有意取捨(無 refresh token → 到期重走 OAuth=再輸 owner secret),MCP_TOKEN_TTL 可調、7 天為更保守選項。per-owner 可調另開 issue #19(非阻塞)。 4. ALLOW_PLAINTEXT_NAMESPACE 逃生門標 SUNSET(code + wrangler.toml + OAUTH.md),開 issue #18 追蹤 「遷移完成後移除整段 code path + Env 欄位」。 5. 防 drift 測試:spy KV 攔所有 put,斷言對 OAUTH_KV 的每一次 put 都帶 expirationTtl>0(完整流程 + store 層兩道),防未來往這顆短效 KV 塞長效資料。 驗證:mcp tsc exit 0、vitest 45/45(+aud +drift×2);cli tsc exit 0。 Refs #15 #18 #19 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015d5jDbuqT5Htwv3Q88XXKk
This commit is contained in:
@@ -70,10 +70,17 @@ describe("partner-auth: 無 / 壞 Authorization → 401 + WWW-Authenticate(RFC
|
||||
describe("partner-auth: OAuth access token 路徑(遠端 claude.ai)", () => {
|
||||
it("有效 access_token → 解出綁定 namespace", async () => {
|
||||
const kv = makeKV();
|
||||
// aud 必須 == 本次請求 origin 的 canonical resource URI(app.request 打 https://mcp.arcrun.dev/mcp)
|
||||
await putAccessToken(
|
||||
kv,
|
||||
"good-token",
|
||||
{ namespace: "leo", client_id: "c1", scope: "mcp", aud: "x", exp: Math.floor(Date.now() / 1000) + 100 },
|
||||
{
|
||||
namespace: "leo",
|
||||
client_id: "c1",
|
||||
scope: "mcp",
|
||||
aud: "https://mcp.arcrun.dev/mcp",
|
||||
exp: Math.floor(Date.now() / 1000) + 100,
|
||||
},
|
||||
100,
|
||||
);
|
||||
const r = await buildApp(baseEnv({ MULTI_TENANT: "false", OAUTH_KV: kv }))("Bearer good-token");
|
||||
@@ -81,6 +88,25 @@ describe("partner-auth: OAuth access token 路徑(遠端 claude.ai)", () =>
|
||||
expect((await r.json()).org_namespace).toBe("leo");
|
||||
});
|
||||
|
||||
it("RFC 8707 aud 不符 → 401 invalid_token(防 token passthrough)", async () => {
|
||||
const kv = makeKV();
|
||||
await putAccessToken(
|
||||
kv,
|
||||
"wrong-aud-token",
|
||||
{
|
||||
namespace: "leo",
|
||||
client_id: "c1",
|
||||
scope: "mcp",
|
||||
aud: "https://other-mcp.example.com/mcp", // 別的部署簽的 token
|
||||
exp: Math.floor(Date.now() / 1000) + 100,
|
||||
},
|
||||
100,
|
||||
);
|
||||
const r = await buildApp(baseEnv({ MULTI_TENANT: "false", OAUTH_KV: kv }))("Bearer wrong-aud-token");
|
||||
expect(r.status).toBe(401);
|
||||
expect(r.headers.get("WWW-Authenticate")).toContain("resource_metadata=");
|
||||
});
|
||||
|
||||
it("未知 token(非 OAuth、非 static)在 self-hosted → 401(明碼 namespace 不再放行)", async () => {
|
||||
const kv = makeKV();
|
||||
const r = await buildApp(baseEnv({ MULTI_TENANT: "false", OAUTH_KV: kv }))("Bearer leo");
|
||||
|
||||
Reference in New Issue
Block a user