t189:/portal/daemon/extract 拿掉 tenant 等值比對——geek6688 萃取永遠 401
leo 實測(拔掉所有 Gemini 金鑰後跑 daemon): 深海熱泉.md → failed 雲端萃取失敗(HTTP 401):X-Arcrun-API-Key 不正確 ## 真兇=我在 t181 自己加的錯誤假設 t181 那輪改成 apiKey !== portalTenant(env) 就 401, 假設「daemon 的 api_key = 實例的 CONSOLE_TENANT」。實測不成立: geek6688:tenant=ckxt8yr9、daemon api_key=yuga3bse ⇒ 真用戶**永遠 401** youlin :兩者碰巧相同 ⇒ 我這邊怎麼測都對 又一次「在 A 能動不代表 B 能動」(與 t188 同源)。 ## 更糟的是:我把錯誤假設寫成了測試 t181 附的「API Key 錯 → 401(租戶隔離)」只證明「符合我的假設」, 不證明假設是對的 ⇒ **綠燈把錯誤焊死**。 本次翻轉成守衛:「key 與實例 tenant 不同**也要能萃**」, 若有人再加回等值比對,該則會紅。 ## 正解 照本 repo 既有慣例——這把 key 是租戶識別,不是共用密語 (webhooks-named.ts 用 owner_id: apiKey)。 本端點只用實例自己的 env.AI 生成、不寫任何資料、不回傳庫內內容 ⇒ 有帶 key 即可。 測試:portal-admin 31 passed(含四則 extract 全過); 唯一 failed 的 v-admin 為基準線既有(stash 對照確認),與本次無關。
This commit is contained in:
@@ -740,9 +740,22 @@ function toPublicLibrary(rec: PortalRecord) {
|
|||||||
// 而 daemon 送卡片上雲時本來就帶這個 header(collector/direct.go:355),沿用同一把最自然。
|
// 而 daemon 送卡片上雲時本來就帶這個 header(collector/direct.go:355),沿用同一把最自然。
|
||||||
portalRouter.post('/portal/daemon/extract', (c) =>
|
portalRouter.post('/portal/daemon/extract', (c) =>
|
||||||
run(c, async () => {
|
run(c, async () => {
|
||||||
|
// 🔴 t189(leo 08-04 實撞:geek6688 萃取回 401「X-Arcrun-API-Key 不正確」):
|
||||||
|
//
|
||||||
|
// t181 那一輪我改成 `apiKey !== portalTenant(c.env)` 就 401,
|
||||||
|
// **但那假設了「daemon 的 api_key = 實例的 CONSOLE_TENANT」——這個假設是錯的**:
|
||||||
|
// geek6688:實例 tenant = ckxt8yr9,daemon config 的 api_key = yuga3bse ⇒ 永遠 401
|
||||||
|
// youlin :兩者碰巧都是 yuga3bse ⇒ 「看起來是好的」
|
||||||
|
// 又一次「在 A 能動不代表 B 能動」(與 t188 同源)。
|
||||||
|
// ⚠️ 當時我還寫了「key 錯就 401」的測試,**把錯誤假設固化成綠燈**——
|
||||||
|
// 測試只證明「符合我的假設」,不證明「假設是對的」。
|
||||||
|
//
|
||||||
|
// 正解=照本 repo 既有慣例:這把 key 是**租戶識別**,不是要比對的共用密語
|
||||||
|
// (見 `webhooks-named.ts` 的 `owner_id: apiKey` 用法)。
|
||||||
|
// 本端點只用實例自己的 `env.AI` 生成、**不寫任何資料**、不回傳庫內內容
|
||||||
|
// ⇒ 有帶 key 即可,不做等值比對。
|
||||||
const apiKey = (c.req.header('X-Arcrun-API-Key') ?? '').trim();
|
const apiKey = (c.req.header('X-Arcrun-API-Key') ?? '').trim();
|
||||||
if (!apiKey) return c.json({ error: '缺少 X-Arcrun-API-Key header' }, 401);
|
if (!apiKey) return c.json({ error: '缺少 X-Arcrun-API-Key header' }, 401);
|
||||||
if (apiKey !== portalTenant(c.env)) return c.json({ error: 'X-Arcrun-API-Key 不正確' }, 401);
|
|
||||||
|
|
||||||
const body = (await c.req.json().catch(() => null)) as
|
const body = (await c.req.json().catch(() => null)) as
|
||||||
| { page_name?: string; text?: string }
|
| { page_name?: string; text?: string }
|
||||||
|
|||||||
@@ -642,10 +642,20 @@ describe('POST /portal/daemon/extract(t181:Workers AI 萃卡,免金鑰)'
|
|||||||
expect(res.status).toBe(401);
|
expect(res.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('API Key 錯 → 401(租戶隔離)', async () => {
|
// 🔴 t189:這則原本是「API Key 錯 → 401(租戶隔離)」,**是錯的,而且害我看到假綠**。
|
||||||
|
//
|
||||||
|
// 它假設「daemon 的 api_key = 實例的 CONSOLE_TENANT」,但實測不成立:
|
||||||
|
// geek6688:tenant=ckxt8yr9、daemon api_key=yuga3bse ⇒ 真用戶**永遠 401**、萃不了
|
||||||
|
// youlin :兩者碰巧相同 ⇒ 我這邊測起來都對
|
||||||
|
// 舊測試只證明「符合我的假設」,不證明「假設是對的」——
|
||||||
|
// **把錯誤假設寫成測試,就是把假綠焊死。**
|
||||||
|
//
|
||||||
|
// 翻轉成守衛:**key 與 tenant 不同也要能萃**(這正是 leo 撞到的情境)。
|
||||||
|
// 若哪天有人又加回等值比對,這則會紅。
|
||||||
|
it('key 與實例 tenant 不同也要能用(t189:多帳號 daemon 的常態)', async () => {
|
||||||
const res = await json('POST', '/portal/daemon/extract',
|
const res = await json('POST', '/portal/daemon/extract',
|
||||||
{ page_name: 'x', text: 'y' }, { 'X-Arcrun-API-Key': 'someone-else' });
|
{ page_name: 'x', text: 'y' }, { 'X-Arcrun-API-Key': 'another-tenant-key' });
|
||||||
expect(res.status).toBe(401);
|
expect(res.status).not.toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('缺 page_name 或 text → 400(不打 AI、不假裝成功)', async () => {
|
it('缺 page_name 或 text → 400(不打 AI、不假裝成功)', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user