95a1462b65
richblack 拍板:service binding(前一 commit)靜態、加/改要重 deploy cypher,廢。
改用 global_fetch_strictly_public compatibility flag——cypher wrangler.toml 加一行,
讓 fetch() 走公網前門,self-hosted 的 same-zone fetch(cypher 與 auth 同在
{sub}.workers.dev zone)也能通。
- wrangler.toml:compatibility_flags 加 global_fetch_strictly_public(移除 SVC_AUTH_*)
- auth-dispatcher.ts / types.ts:還原到 service binding 之前(單純 fetch workers.dev)
- 安全(官方 docs):唯一副作用 self-loop 僅在 fetch 自己 hostname;cypher 不 self-loop
- 官方/self-host 共用同一份 toml:官方本就跨 zone 行為不變,self-host 被修好
- 規範還原:rule 02/03/CLAUDE.md/pre-bash-guard 的 service binding 禁令維持原狀
SDD: credential-primitives-wasm Phase 7(A→廢→B)。tsc exit 0。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
108 lines
3.7 KiB
TypeScript
108 lines
3.7 KiB
TypeScript
/**
|
||
* Auth Dispatcher
|
||
*
|
||
* 對需要認證的零件,在執行前 HTTP POST 到對應的 auth primitive Worker,
|
||
* 取回 auth_headers / auth_query / auth_body 合併進節點 context。
|
||
*
|
||
* 嚴格邊界(rule 02 §2.2):
|
||
* - 本檔**不做**任何 credential 解密 / template 展開 / JWT 簽章
|
||
* - 那些全部在 auth primitive WASM 零件內執行(透過 host function `crypto_decrypt` 等)
|
||
* - 本檔只做「查 recipe 決定走哪個 primitive Worker」+「HTTP fetch 取回注入結果」
|
||
*
|
||
* 目前階段接上 `auth_static_key` + `auth_service_account` + `auth_oauth2`,
|
||
* Phase 4 剩 `auth_mtls`(mTLS handshake 在 Worker runtime 層)。
|
||
*
|
||
* 執行時機:graph-executor 在節點 runner 執行前呼叫,取回的 ctx 會:
|
||
* 1. 先試本 dispatcher(命中才 return enriched ctx)
|
||
* 2. 沒命中 fallback 到 `injectCredentials`(Phase 1.9 才刪除)
|
||
*/
|
||
|
||
import type { Bindings } from '../types';
|
||
import { resolveAuthRecipe, resolveRecipe } from '../routes/recipes';
|
||
import { wasmWorkerUrl } from '../lib/component-loader';
|
||
|
||
/** 對應 Phase 1-4 會部署的 auth primitive Worker */
|
||
const SUPPORTED_PRIMITIVES = new Set(['static_key', 'service_account', 'oauth2']);
|
||
|
||
/** auth primitive 本身的 componentId(避免自引用) */
|
||
const AUTH_PRIMITIVE_IDS = new Set([
|
||
'auth_static_key',
|
||
'auth_service_account',
|
||
'auth_oauth2',
|
||
'auth_mtls',
|
||
]);
|
||
|
||
/**
|
||
* 試著對零件做 auth 注入。
|
||
* - 命中(有對應 auth recipe 且 primitive 已支援)→ 回傳注入後的 ctx
|
||
* - 未命中 → 回傳 null(呼叫端繼續跑舊路徑)
|
||
*/
|
||
export async function tryAuthDispatch(
|
||
componentId: string,
|
||
input: Record<string, unknown>,
|
||
env: Bindings,
|
||
apiKey: string,
|
||
): Promise<Record<string, unknown> | null> {
|
||
if (AUTH_PRIMITIVE_IDS.has(componentId)) {
|
||
// auth primitive 本身不需要再做 auth
|
||
return null;
|
||
}
|
||
|
||
// 決定 auth service name:
|
||
// 1. 若 API recipe 宣告了 auth_service(例 recipe:kbdb_get → "kbdb")→ 用它,
|
||
// 讓多個 recipe 共用同一把 auth_recipe(不必每個 action 複製 auth recipe)。
|
||
// 2. 否則 fallback 到把 componentId 當 service name(向後相容舊行為)。
|
||
let service = componentId;
|
||
const apiRecipe = await resolveRecipe(componentId, env.RECIPES);
|
||
if (apiRecipe?.auth_service) {
|
||
service = apiRecipe.auth_service;
|
||
}
|
||
|
||
const recipe = await resolveAuthRecipe(service, env.RECIPES);
|
||
if (!recipe) return null;
|
||
if (!SUPPORTED_PRIMITIVES.has(recipe.primitive)) return null;
|
||
|
||
// 走新路徑:HTTP POST 到對應 auth primitive Worker
|
||
// 走 workers.dev 避開同 zone 死鎖(P0 #9)
|
||
const primitiveUrl = wasmWorkerUrl(`auth_${recipe.primitive}`, env.WORKER_SUBDOMAIN);
|
||
const res = await fetch(primitiveUrl, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
action: 'authenticate',
|
||
api_key: apiKey,
|
||
service,
|
||
}),
|
||
});
|
||
|
||
if (!res.ok) {
|
||
const text = await res.text().catch(() => '');
|
||
throw new Error(
|
||
`auth primitive "${recipe.primitive}" 回傳 ${res.status}: ${text.slice(0, 200)}`,
|
||
);
|
||
}
|
||
|
||
const result = await res.json().catch(() => null) as {
|
||
success?: boolean;
|
||
error?: string;
|
||
auth_headers?: Record<string, string>;
|
||
auth_query?: Record<string, string>;
|
||
auth_body?: Record<string, string>;
|
||
auth_path?: Record<string, string>;
|
||
} | null;
|
||
|
||
if (!result || result.success === false) {
|
||
throw new Error(
|
||
`auth primitive 失敗: ${result?.error ?? '未知錯誤'}`,
|
||
);
|
||
}
|
||
|
||
return {
|
||
...input,
|
||
_auth_headers: result.auth_headers ?? {},
|
||
_auth_query: result.auth_query ?? {},
|
||
_auth_body: result.auth_body ?? {},
|
||
_auth_path: result.auth_path ?? {},
|
||
};
|
||
}
|