t181 修正:/portal/daemon/extract 認證改用 X-Arcrun-API-Key(帳密行不通)
【我自己的設計錯誤】前一版用帳密認證,但查證 daemon 實際行為後發現行不通: **密碼只在連線精靈當下用過就丟、不落地**(config.json 沒有密碼欄,刻意的安全設計, wiki 記為「密碼零落地」),而背景萃取是每輪自動跑的 ⇒ 根本拿不到密碼。 【改法】沿用 daemon 送卡片上雲時本來就帶的 `X-Arcrun-API-Key`(=namespace, collector/direct.go:355)⇒ 同一把憑證、同一個身份模型,不必為此新增任何儲存。 不符即 401(租戶隔離)。 測試四則全過(沒帶 key 401/key 錯 401/缺參數 400/ **回歸守衛:錯誤訊息不得出現 gemini_api_key 或 credential**)。 全檔 38 passed,唯一 failed 與 tsc 的 1190 行 'auto' 皆為基準線既有、與本次無關。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -820,46 +820,32 @@ describe('/portal/admin/ai + /portal/daemon/report-capabilities(t131)', () =
|
||||
// 「52 檔全滅還要把金鑰傳給別人才查得出原因」三種災難。
|
||||
|
||||
describe('POST /portal/daemon/extract(t181:Workers AI 萃卡,免金鑰)', () => {
|
||||
const USER_EMAIL = 'daemon@example.com';
|
||||
const USER_PW = 'unit-test-pw-1';
|
||||
const USER_RECORD = 'rec_daemon_extract';
|
||||
// 認證=X-Arcrun-API-Key(=namespace,wrangler.test.toml CONSOLE_TENANT=leo),
|
||||
// **不是帳密**:daemon 密碼不落地(連線精靈用完即丟),背景萃取拿不到密碼。
|
||||
const KEY = { 'X-Arcrun-API-Key': 'leo' };
|
||||
|
||||
function mockEmailLookup(email: string, recordId: string | null) {
|
||||
const needle = new URLSearchParams({ page_name: email }).toString();
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({
|
||||
path: (p: string) => p.startsWith('/entries?') && p.includes(needle) && p.includes(encodeURIComponent(NS)),
|
||||
method: 'GET',
|
||||
})
|
||||
.reply(200, { success: true, entries: recordId ? [{ content: recordId }] : [], count: recordId ? 1 : 0 });
|
||||
}
|
||||
it('沒帶 API Key → 401', async () => {
|
||||
const res = await json('POST', '/portal/daemon/extract', { page_name: 'x', text: 'y' });
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
|
||||
it('API Key 錯 → 401(租戶隔離)', async () => {
|
||||
const res = await json('POST', '/portal/daemon/extract',
|
||||
{ page_name: 'x', text: 'y' }, { 'X-Arcrun-API-Key': 'someone-else' });
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
|
||||
it('缺 page_name 或 text → 400(不打 AI、不假裝成功)', async () => {
|
||||
const res = await json('POST', '/portal/daemon/extract', { email: USER_EMAIL, password: USER_PW });
|
||||
const res = await json('POST', '/portal/daemon/extract', {}, KEY);
|
||||
expect(res.status).toBe(400);
|
||||
const d = (await res.json()) as { error?: string };
|
||||
expect(String(d.error)).toContain('page_name');
|
||||
});
|
||||
|
||||
it('缺帳密 → 400', async () => {
|
||||
const res = await json('POST', '/portal/daemon/extract', { page_name: 'x', text: 'y' });
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it('帳密錯 → 401(認證與 daemon/config 同一把)', async () => {
|
||||
mockEmailLookup(USER_EMAIL, USER_RECORD);
|
||||
mockGetRecord(USER_RECORD, adminValues({ email: USER_EMAIL, password_hash: storedHash }));
|
||||
const res = await json('POST', '/portal/daemon/extract', {
|
||||
email: USER_EMAIL, password: 'wrong-password', page_name: 'x', text: 'y',
|
||||
});
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
|
||||
// 🔴 回歸守衛:這條路**不得**要求任何 Gemini/API 金鑰。
|
||||
// 若哪天有人把它改回打 Google,這則會因為錯誤訊息提到 credential/gemini 而紅。
|
||||
// 🔴 回歸守衛:這條路**不得**要求任何 Gemini/API 金鑰——免金鑰正是它存在的理由。
|
||||
// 若哪天有人把它改回打 Google,錯誤訊息會出現 credential/gemini_api_key ⇒ 這則會紅。
|
||||
it('錯誤訊息不得要求任何金鑰(免金鑰是本端點存在的理由)', async () => {
|
||||
const res = await json('POST', '/portal/daemon/extract', { email: USER_EMAIL, password: USER_PW });
|
||||
const res = await json('POST', '/portal/daemon/extract', {}, KEY);
|
||||
const raw = await res.text();
|
||||
expect(raw).not.toContain('gemini_api_key');
|
||||
expect(raw).not.toContain('credential');
|
||||
|
||||
Reference in New Issue
Block a user