canonical_id: "auth_service_account" display_name: "Auth Primitive — Service Account (Google JWT)" category: "auth" version: "v1" wasi_target: "preview1" stability: "floating" runtime_compat: - "cf-workers" - "workerd" - "wazero" constraints: max_size_kb: 2048 max_cold_start_ms: 100 no_network_syscall: false no_filesystem_syscall: true io_model: "stdin_stdout_json" input_schema: type: object required: [action, api_key, service] properties: action: type: string enum: [authenticate] description: 目前僅支援 authenticate api_key: type: string description: 租戶識別(ak_ 前綴),用來組 {api_key}:cred:{name} KV key service: type: string description: auth recipe 名稱,對應 auth_recipe:{service} 的 KV 記錄 request: type: object description: (保留)下游零件的 HTTP request 上下文 output_schema: type: object properties: success: type: boolean auth_headers: type: object additionalProperties: type: string auth_query: type: object additionalProperties: type: string auth_body: type: object additionalProperties: type: string runtime: type: object description: 包含 access_token(token exchange 後取得) properties: access_token: type: string gherkin_tests: - scenario: "缺少 api_key" given: '{"action":"authenticate","service":"google_sheets_sa"}' then_contains: '{"success":false' - scenario: "找不到 auth recipe" given: '{"action":"authenticate","api_key":"ak_nonexistent","service":"nonexistent"}' then_contains: '{"success":false' tags: [auth, credential, primitive, service_account, google] description: "Service Account auth primitive (Google JWT 方案)。讀取 auth_recipe + 解密 service_account_json → 解析 PEM private key → 組 JWT → crypto_sign_rs256 (host function) → token exchange endpoint → 取 access_token → 展開 {{runtime.access_token}} 模板。透過 host function crypto_sign_rs256,private key 僅以 PKCS8 bytes 傳給 host,解密後 plaintext 不離開 WASM。" config_example: | auth_step: component: "auth_service_account" action: "authenticate" service: "google_sheets_sa"