Files
Arcrun/cypher-executor/src/lib/auth-recipe-seeds.ts
T
uncle6me-web 90777e3877 fix(auth-recipe): 回灌 telegram/line_notify/kbdb 種子,補 AuthInjectSpec.path(修 source/live 漂移)
issue #13 升級:根因不只 acr parts 殘留,是 auth-recipe-seeds.ts source 漏了 3 個 static_key
auth recipe(telegram/line_notify/kbdb),但 prod KV 手動 seed 過 → 任何全新 self-hosted
`POST /init/seed` 只 seed 23 個、漏 telegram → self-host(leo21c/mira)telegram_send 的
auth_service:'telegram' resolve 不到 → {{auth.bot_token}} 注入空 → telegram 發訊壞掉。

實測 smoking gun(2026-06-29):
- prod cypher.arcrun.dev/auth-recipes = 27 個(含 telegram)
- self-host arcrun-cypher-executor.leo21c.workers.dev = 23 個(無 telegram/line_notify/kbdb)

修法(資料/型別,非業務邏輯,守 mindset §1 不建 component):
- auth-recipe-seeds.ts 補 telegram(inject.path bot_token)/line_notify(header Bearer)/kbdb(header Bearer)
- recipes.ts AuthInjectSpec 補 path?(WASM auth_static_key + SDD §六早有此欄、source 介面漏 → tsc 擋)
- google_user(oauth2)暫不回灌:內嵌 client_secret 不可進 git + 介面無 oauth2 欄 → 留 Phase D

形態取自 prod GET /auth-recipes/{service};設計權威 auth-recipe.md §六(telegram path)/§七(kbdb 共用)。
telegram 自此與 notion(header)/gsheets(service_account) 同一條 recipe+auth-recipe 鏈,一致。
tsc 全綠;local 模擬 token-in-path 注入 PASS。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 12:37:19 +08:00

749 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Auth Recipe Seeds
*
* 平台預建的 auth recipe 定義,部署時寫入 RECIPES KV。
* 新增服務 = 在此加一筆,不需改其他程式碼。
*
* KV key: auth_recipe:{service}
*/
import type { AuthRecipeDefinition } from '../routes/recipes';
const now = Date.now();
// ⚠️ 已知 source/live drift2026-06-29 盤點,未在此檔修):
// prod RECIPES KV 另有 `auth_recipe:google_user`primitive: oauth2)。**故意不回灌 source**,原因:
// (1) 它內嵌 client_secretGOCSPX-...= 機密,不可進 gitwiki-secret-scan / 一般安全);
// (2) 本檔的 AuthRecipeDefinition 介面尚無 oauth2 欄位(client_id/secret/token_endpoint/scopes),
// 回灌前需先擴 schema + 把 secret 改成部署期注入(wrangler secret / env),屬獨立工作。
// → google_user 留待 oauth2 seed 機制(含 secret 注入)獨立處理;本次只修無機密的 static_key 漂移。
export const AUTH_RECIPE_SEEDS: AuthRecipeDefinition[] = [
// ── Static Key 類 ──────────────────────────────────────────────────────────
{
kind: 'auth_recipe',
service: 'notion',
version: 1,
primitive: 'static_key',
base_url: 'https://api.notion.com/v1',
display_name: 'Notion',
description: 'Notion API — 頁面、資料庫讀寫',
required_secrets: [
{
key: 'notion_token',
label: 'Internal Integration Token',
help: '至 https://www.notion.so/my-integrations 建立 Integration',
help_url: 'https://www.notion.so/my-integrations',
},
],
inject: {
header: {
Authorization: 'Bearer {{secret.notion_token}}',
'Notion-Version': '2022-06-28',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'slack',
version: 1,
primitive: 'static_key',
base_url: 'https://slack.com/api',
display_name: 'Slack',
description: 'Slack Bot API — 發訊息、查頻道',
required_secrets: [
{
key: 'slack_bot_token',
label: 'Bot User OAuth Token (xoxb-...)',
help: '至 https://api.slack.com/apps 建立 App,取得 Bot Token',
help_url: 'https://api.slack.com/apps',
},
],
inject: {
header: {
Authorization: 'Bearer {{secret.slack_bot_token}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'github',
version: 1,
primitive: 'static_key',
base_url: 'https://api.github.com',
display_name: 'GitHub',
description: 'GitHub REST API — repo、issue、PR 操作',
required_secrets: [
{
key: 'github_token',
label: 'Personal Access Token (classic 或 fine-grained)',
help: '至 https://github.com/settings/tokens 建立',
help_url: 'https://github.com/settings/tokens',
},
],
inject: {
header: {
Authorization: 'Bearer {{secret.github_token}}',
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'openai',
version: 1,
primitive: 'static_key',
base_url: 'https://api.openai.com/v1',
display_name: 'OpenAI',
description: 'OpenAI API — Chat Completions、Embeddings 等',
required_secrets: [
{
key: 'openai_api_key',
label: 'API Key (sk-...)',
help: '至 https://platform.openai.com/api-keys 建立',
help_url: 'https://platform.openai.com/api-keys',
},
],
inject: {
header: {
Authorization: 'Bearer {{secret.openai_api_key}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'anthropic',
version: 1,
primitive: 'static_key',
base_url: 'https://api.anthropic.com/v1',
display_name: 'Anthropic (Claude)',
description: 'Anthropic API — Claude 模型呼叫',
required_secrets: [
{
key: 'anthropic_api_key',
label: 'API Key',
help: '至 https://console.anthropic.com/settings/keys 建立',
help_url: 'https://console.anthropic.com/settings/keys',
},
],
inject: {
header: {
'x-api-key': '{{secret.anthropic_api_key}}',
'anthropic-version': '2023-06-01',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'airtable',
version: 1,
primitive: 'static_key',
base_url: 'https://api.airtable.com/v0',
display_name: 'Airtable',
description: 'Airtable API — 讀寫 Base 資料',
required_secrets: [
{
key: 'airtable_token',
label: 'Personal Access Token',
help: '至 https://airtable.com/create/tokens 建立',
help_url: 'https://airtable.com/create/tokens',
},
],
inject: {
header: {
Authorization: 'Bearer {{secret.airtable_token}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'discord',
version: 1,
primitive: 'static_key',
base_url: 'https://discord.com/api/v10',
display_name: 'Discord',
description: 'Discord Bot API — 發訊息、管理伺服器',
required_secrets: [
{
key: 'discord_bot_token',
label: 'Bot Token',
help: '至 https://discord.com/developers/applications 建立 Bot,取得 Token',
help_url: 'https://discord.com/developers/applications',
},
],
inject: {
header: {
Authorization: 'Bot {{secret.discord_bot_token}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'stripe',
version: 1,
primitive: 'static_key',
base_url: 'https://api.stripe.com/v1',
display_name: 'Stripe',
description: 'Stripe API — 支付、客戶、訂閱管理',
required_secrets: [
{
key: 'stripe_secret_key',
label: 'Secret Key (sk_live_... 或 sk_test_...)',
help: '至 https://dashboard.stripe.com/apikeys 取得',
help_url: 'https://dashboard.stripe.com/apikeys',
},
],
inject: {
header: {
Authorization: 'Bearer {{secret.stripe_secret_key}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'twilio',
version: 1,
primitive: 'static_key',
base_url: 'https://api.twilio.com/2010-04-01',
display_name: 'Twilio',
description: 'Twilio API — SMS、電話、WhatsApp',
required_secrets: [
{
key: 'twilio_account_sid',
label: 'Account SID',
help: '至 https://console.twilio.com/ 取得',
help_url: 'https://console.twilio.com/',
},
{
key: 'twilio_auth_token',
label: 'Auth Token',
help: '至 https://console.twilio.com/ 取得',
help_url: 'https://console.twilio.com/',
},
],
inject: {
header: {
Authorization: 'Basic {{secret.twilio_account_sid}}:{{secret.twilio_auth_token}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'sendgrid',
version: 1,
primitive: 'static_key',
base_url: 'https://api.sendgrid.com/v3',
display_name: 'SendGrid',
description: 'SendGrid Email API — 發送交易郵件',
required_secrets: [
{
key: 'sendgrid_api_key',
label: 'API Key (SG....)',
help: '至 https://app.sendgrid.com/settings/api_keys 建立',
help_url: 'https://app.sendgrid.com/settings/api_keys',
},
],
inject: {
header: {
Authorization: 'Bearer {{secret.sendgrid_api_key}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'hubspot',
version: 1,
primitive: 'static_key',
base_url: 'https://api.hubapi.com',
display_name: 'HubSpot',
description: 'HubSpot CRM API — 聯絡人、公司、交易管理',
required_secrets: [
{
key: 'hubspot_token',
label: 'Private App Access Token',
help: '至 HubSpot Settings → Integrations → Private Apps 建立',
help_url: 'https://developers.hubspot.com/docs/api/private-apps',
},
],
inject: {
header: {
Authorization: 'Bearer {{secret.hubspot_token}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'linear',
version: 1,
primitive: 'static_key',
base_url: 'https://api.linear.app',
display_name: 'Linear',
description: 'Linear API — Issue、Project 管理',
required_secrets: [
{
key: 'linear_api_key',
label: 'Personal API Key',
help: '至 https://linear.app/settings/api 建立',
help_url: 'https://linear.app/settings/api',
},
],
inject: {
header: {
Authorization: '{{secret.linear_api_key}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'shopify',
version: 1,
primitive: 'static_key',
base_url: 'https://{{secret.shopify_store}}.myshopify.com/admin/api/2024-01',
display_name: 'Shopify',
description: 'Shopify Admin API — 訂單、商品、客戶管理',
required_secrets: [
{
key: 'shopify_access_token',
label: 'Admin API Access Token',
help: '至 Shopify Admin → Apps → App and sales channel settings → Private apps',
help_url: 'https://shopify.dev/docs/apps/auth/admin-app-access-tokens',
},
{
key: 'shopify_store',
label: 'Store subdomain(不含 .myshopify.com',
help: '例如 my-store(對應 my-store.myshopify.com',
},
],
inject: {
header: {
'X-Shopify-Access-Token': '{{secret.shopify_access_token}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'resend',
version: 1,
primitive: 'static_key',
base_url: 'https://api.resend.com',
display_name: 'Resend',
description: 'Resend Email API — 發送交易郵件',
required_secrets: [
{
key: 'resend_api_key',
label: 'API Key (re_...)',
help: '至 https://resend.com/api-keys 建立',
help_url: 'https://resend.com/api-keys',
},
],
inject: {
header: {
Authorization: 'Bearer {{secret.resend_api_key}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'supabase',
version: 1,
primitive: 'static_key',
base_url: 'https://{{secret.supabase_project_ref}}.supabase.co/rest/v1',
display_name: 'Supabase',
description: 'Supabase REST API — 資料庫讀寫',
required_secrets: [
{
key: 'supabase_service_key',
label: 'Service Role Key (eyJ...)',
help: '至 Supabase Project Settings → API → service_role key',
help_url: 'https://supabase.com/dashboard',
},
{
key: 'supabase_project_ref',
label: 'Project Reference IDURL 中的 xxx.supabase.co 的 xxx',
},
],
inject: {
header: {
Authorization: 'Bearer {{secret.supabase_service_key}}',
apikey: '{{secret.supabase_service_key}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'typeform',
version: 1,
primitive: 'static_key',
base_url: 'https://api.typeform.com',
display_name: 'Typeform',
description: 'Typeform API — 表單、問卷回應讀取',
required_secrets: [
{
key: 'typeform_token',
label: 'Personal Access Token',
help: '至 https://admin.typeform.com/account#/section/tokens 建立',
help_url: 'https://developer.typeform.com/get-started/',
},
],
inject: {
header: {
Authorization: 'Bearer {{secret.typeform_token}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'jira',
version: 1,
primitive: 'static_key',
base_url: 'https://{{secret.jira_domain}}.atlassian.net/rest/api/3',
display_name: 'Jira',
description: 'Jira API — Issue、Sprint、Project 管理',
required_secrets: [
{
key: 'jira_api_token',
label: 'API Token',
help: '至 https://id.atlassian.com/manage-profile/security/api-tokens 建立',
help_url: 'https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/',
},
{
key: 'jira_email',
label: '你的 Atlassian 帳號 Email',
},
{
key: 'jira_domain',
label: 'Jira 子網域(xxx.atlassian.net 的 xxx',
},
],
inject: {
header: {
Authorization: 'Basic {{secret.jira_email}}:{{secret.jira_api_token}}',
Accept: 'application/json',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'gemini',
version: 1,
primitive: 'static_key',
base_url: 'https://generativelanguage.googleapis.com/v1beta',
display_name: 'Google Gemini',
description: 'Google Gemini API — generateContent / embedContent(使用 API Key',
required_secrets: [
{
key: 'gemini_api_key',
label: 'API Key',
help: '至 https://aistudio.google.com/apikey 建立',
help_url: 'https://aistudio.google.com/apikey',
},
],
inject: {
header: {
'x-goog-api-key': '{{secret.gemini_api_key}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'trello',
version: 1,
primitive: 'static_key',
base_url: 'https://api.trello.com/1',
display_name: 'Trello',
description: 'Trello API — boards / cards / listsAPI key + token 走 query string',
required_secrets: [
{
key: 'trello_api_key',
label: 'API Key',
help: '至 https://trello.com/power-ups/admin 建立 Power-Up 後取得',
help_url: 'https://trello.com/power-ups/admin',
},
{
key: 'trello_token',
label: 'Token',
help: '於 Power-Up 頁面點「Generate Token」授權後取得',
help_url: 'https://trello.com/power-ups/admin',
},
],
inject: {
query: {
key: '{{secret.trello_api_key}}',
token: '{{secret.trello_token}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'mailgun',
version: 1,
primitive: 'static_key',
base_url: 'https://api.mailgun.net/v3',
display_name: 'Mailgun',
description: 'Mailgun API — 寄信(username 固定 "api"password 為 Private API Key,走 Basic Auth',
required_secrets: [
{
key: 'mailgun_api_key',
label: 'Private API Key',
help: '至 Mailgun Dashboard → API Security → Sending Keys 建立',
help_url: 'https://app.mailgun.com/mg/sending/domains',
},
{
key: 'mailgun_domain',
label: 'Sending Domain',
help: '你在 Mailgun 設定好的 sending domain(例:mg.yourdomain.com',
help_url: 'https://app.mailgun.com/mg/sending/domains',
},
],
inject: {
header: {
Authorization: 'Basic api:{{secret.mailgun_api_key}}',
},
},
created_at: now,
updated_at: now,
},
// ── 訊息 / URL-path 注入類(static_key)────────────────────────────────────
//
// 2026-06-29 補:以下三個 static_key auth recipe 一直存在於 prod RECIPES KV(手動 seed 過),
// 但**從未進 source seed**auth-recipe-seeds.ts)→ 任何全新 self-hosted `POST /init/seed`
// 只會 seed 23 個、漏掉 telegram/line_notify/kbdb → self-hostmira/leo21c)的 telegram 發訊
// 走不通(telegram_send 的 auth_service:'telegram' 找不到 auth recipe → {{auth.bot_token}} 注入空)。
// 這正是「source vs live drift = 假綠」(總管反覆踩的同一類)。把 prod 現役定義回灌 source
// 讓 official 與 self-host 共用同一份種子。形態取自 prod GET /auth-recipes/{service}2026-06-29)。
// 設計權威:auth-recipe.md §六(line 70-71, telegram path 注入) + §七(line 150-151, kbdb 共用)。
{
kind: 'auth_recipe',
service: 'telegram',
version: 1,
primitive: 'static_key',
base_url: 'https://api.telegram.org',
display_name: 'Telegram Bot',
description: 'Telegram Bot API — sendMessage 等(bot token 注入 URL path /bot{token}/',
required_secrets: [
{
key: 'telegram_bot_token',
label: 'Bot Token(從 @BotFather 取得)',
help: '在 Telegram 對 @BotFather 送 /newbot 建立 bot,取得格式為 123456:ABC... 的 token',
help_url: 'https://core.telegram.org/bots/features#botfather',
},
],
// path 注入:recipe:telegram_send 的 endpoint 用 {{auth.bot_token}} 從 _auth_path 取值
// auth_static_key WASM 解密後輸出 auth_path → auth-dispatcher 帶進 _auth_path
// → makeRecipeRunner interpolate)。token 不落 header/query/body,符合 Telegram 的 URL-path 慣例。
inject: {
path: {
bot_token: '{{secret.telegram_bot_token}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'line_notify',
version: 1,
primitive: 'static_key',
base_url: 'https://notify-api.line.me',
display_name: 'LINE Notify',
description: 'LINE Notify — 推播訊息(static_key Bearer',
required_secrets: [
{
key: 'line_token',
label: 'LINE Notify Token',
help: '至 https://notify-bot.line.me/my/ 發行個人存取權杖',
help_url: 'https://notify-bot.line.me/my/',
},
],
inject: {
header: {
Authorization: 'Bearer {{secret.line_token}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'kbdb',
version: 1,
primitive: 'static_key',
base_url: 'https://kbdb.finally.click',
display_name: 'KBDB',
description: 'KBDB partner API — block 讀寫(static_key Bearer)。kbdb_* recipe 共用此把 auth。',
required_secrets: [
{
key: 'kbdb_api_key',
label: 'KBDB API Key(至 arcrun 取統一 API Key 當 credential',
help: 'KBDB 採 Supabase 模式:要用 → 去 arcrun 取統一 API Key 當此 credential',
help_url: 'https://arcrun.dev',
},
],
inject: {
header: {
Authorization: 'Bearer {{secret.kbdb_api_key}}',
},
},
created_at: now,
updated_at: now,
},
// ── Service Account 類(Google 家族,共用同一份 service_account_json)────────
{
kind: 'auth_recipe',
service: 'google_sheets_sa',
version: 1,
primitive: 'service_account',
service_account_kind: 'google_jwt',
base_url: 'https://sheets.googleapis.com/v4',
display_name: 'Google Sheets (Service Account)',
description: 'Google Sheets API — 試算表讀寫(使用 Service Account',
token_exchange: {
endpoint: 'https://oauth2.googleapis.com/token',
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
},
required_secrets: [
{
key: 'google_service_account',
label: 'Service Account JSON(整份貼上)',
type: 'json_blob',
help: '至 GCP Console → IAM → Service Accounts → Keys → Add Key → JSON,下載後整份貼入',
help_url: 'https://console.cloud.google.com/iam-admin/serviceaccounts',
},
],
inject: {
header: {
Authorization: 'Bearer {{runtime.access_token}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'google_gmail_sa',
version: 1,
primitive: 'service_account',
service_account_kind: 'google_jwt',
base_url: 'https://gmail.googleapis.com/gmail/v1',
display_name: 'Gmail (Service Account)',
description: 'Gmail API — 發送郵件(使用 Service Account + Domain-Wide Delegation',
token_exchange: {
endpoint: 'https://oauth2.googleapis.com/token',
scopes: ['https://www.googleapis.com/auth/gmail.send'],
},
required_secrets: [
{
key: 'google_service_account',
label: 'Service Account JSON(整份貼上)',
type: 'json_blob',
help: '需要 Domain-Wide Delegation,至 GCP Console → IAM → Service Accounts 設定',
help_url: 'https://developers.google.com/workspace/guides/create-credentials#service-account',
},
],
inject: {
header: {
Authorization: 'Bearer {{runtime.access_token}}',
},
},
created_at: now,
updated_at: now,
},
{
kind: 'auth_recipe',
service: 'google_drive_sa',
version: 1,
primitive: 'service_account',
service_account_kind: 'google_jwt',
base_url: 'https://www.googleapis.com/drive/v3',
display_name: 'Google Drive (Service Account)',
description: 'Google Drive API — 檔案上傳、下載、管理(使用 Service Account',
token_exchange: {
endpoint: 'https://oauth2.googleapis.com/token',
scopes: ['https://www.googleapis.com/auth/drive'],
},
required_secrets: [
{
key: 'google_service_account',
label: 'Service Account JSON(整份貼上)',
type: 'json_blob',
help: '至 GCP Console → IAM → Service Accounts → Keys → Add Key → JSON',
help_url: 'https://console.cloud.google.com/iam-admin/serviceaccounts',
},
],
inject: {
header: {
Authorization: 'Bearer {{runtime.access_token}}',
},
},
created_at: now,
updated_at: now,
},
];