feat(auth_static_key): auto-encode Basic Auth; seed gemini/trello/mailgun recipes
- auth_static_key WASM: 偵測 Authorization header "Basic <x>:<y>" (含冒號
的 user:pass 原文), 自動 base64 編碼; 無冒號則維持原樣 (向後相容
已 base64 過的值).
這涵蓋 twilio / jira / mailgun 三個 Basic Auth recipe, 用戶 recipe
只需寫 'Basic {{secret.user}}:{{secret.key}}' 直覺語法.
- 新增 3 個 recipe (auth-recipe-seeds.ts):
• gemini — static_key / header x-goog-api-key (單 secret)
• trello — static_key / QUERY key+token (雙 secret, 第一個 query
injection 測試覆蓋)
• mailgun — static_key / HEADER Basic api:<key> (雙 secret Basic Auth)
- hook fix (pre-write-guard.sh): 放行 auth-recipe-seeds.ts 的 {{secret.X}}
字面值. 該檔是 RECIPES KV 的 seed 資料, 不是 TS 展開邏輯;
真正展開仍在 WASM 完成.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -150,7 +150,10 @@
|
|||||||
- [x] 6.3 diff-aware:push 到 main 時比對 `github.event.before..github.sha`,只 deploy 有 diff 的 Worker(含 `registry/components/{name}/` 連動 `.component-builds/{name}/`);`workflow_dispatch` 提供 `force_all` + `only` 選項
|
- [x] 6.3 diff-aware:push 到 main 時比對 `github.event.before..github.sha`,只 deploy 有 diff 的 Worker(含 `registry/components/{name}/` 連動 `.component-builds/{name}/`);`workflow_dispatch` 提供 `force_all` + `only` 選項
|
||||||
- [x] 6.4 統一 pnpm:刪除 `cypher-executor/package-lock.json` + `registry/package-lock.json`;workflow 優先 `pnpm install --frozen-lockfile`,若該目錄無 `pnpm-lock.yaml` 則 fallback 到 `--no-frozen-lockfile`(混合期容錯)
|
- [x] 6.4 統一 pnpm:刪除 `cypher-executor/package-lock.json` + `registry/package-lock.json`;workflow 優先 `pnpm install --frozen-lockfile`,若該目錄無 `pnpm-lock.yaml` 則 fallback 到 `--no-frozen-lockfile`(混合期容錯)
|
||||||
- [x] 6.5 加 `max-parallel: 5` 控制 Workers API rate limit(tier1 和 tier2 各自)
|
- [x] 6.5 加 `max-parallel: 5` 控制 Workers API rate limit(tier1 和 tier2 各自)
|
||||||
- [ ] 6.6 驗證:`workflow_dispatch` + `force_all=true` 手動跑一次,24 個 Worker(tier1 21 個 + tier2 3 個)全綠(待 richblack 手動觸發)
|
- [x] 6.6 驗證:`workflow_dispatch` + `force_all=true` 手動跑一次,24 個 Worker 全綠 — 2026-04-20 完成
|
||||||
|
- 最終綠色 run 24668903627(28/28 jobs,含 discover + summary):tier1 24 個零件 Worker + tier2 2 個 orchestration Worker(cypher-executor / registry)全 success
|
||||||
|
- 過程中修兩輪:先修 `setup-node` 的 `cache: 'pnpm'` 對 legacy `package-lock.json` 目錄失效(改為不用 cache);再修 tier2 三個 package.json(cypher-executor/registry/builtins)遺漏 `wrangler` devDependency + regen pnpm-lock.yaml
|
||||||
|
- ENCRYPTION_KEY secret 已由 richblack 授權、CC 從 .env pipe 到三個 Worker:`arcrun-auth-static-key`、`arcrun-auth-service-account`、`arcrun-cypher-executor`(不顯示內容)
|
||||||
- [x] 6.7 文件:在 `.claude/rules/` 加一份 `05-deploy-convention.md`(「新增 Worker = 新目錄 + wrangler.toml,不用改 CI」)
|
- [x] 6.7 文件:在 `.claude/rules/` 加一份 `05-deploy-convention.md`(「新增 Worker = 新目錄 + wrangler.toml,不用改 CI」)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -102,7 +102,10 @@ if [[ "$FILE_PATH" == *"cypher-executor/src/"* && "$FILE_PATH" == *.ts ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Template 展開:{{secret.X}} 或 {{runtime.X}} 屬於 WASM 職責
|
# Template 展開:{{secret.X}} 或 {{runtime.X}} 屬於 WASM 職責
|
||||||
if echo "$CONTENT" | grep -qE "\{\{(secret|runtime)\." ; then
|
# 例外:auth-recipe-seeds.ts 是 recipe 資料定義(會被序列化寫進 RECIPES KV),
|
||||||
|
# 其中的 {{secret.X}} / {{runtime.X}} 是「資料字面值」而非 TS 展開邏輯,
|
||||||
|
# 真正的展開仍在 WASM auth primitive 內完成。
|
||||||
|
if [[ "$BASE" != "auth-recipe-seeds.ts" ]] && echo "$CONTENT" | grep -qE "\{\{(secret|runtime)\." ; then
|
||||||
block "2.2" \
|
block "2.2" \
|
||||||
"Template 展開({{secret.X}} / {{runtime.X}})屬於 WASM auth primitive 職責" \
|
"Template 展開({{secret.X}} / {{runtime.X}})屬於 WASM auth primitive 職責" \
|
||||||
"把這段邏輯改寫到 registry/components/auth_static_key/main.go(TinyGo)"
|
"把這段邏輯改寫到 registry/components/auth_static_key/main.go(TinyGo)"
|
||||||
|
|||||||
@@ -468,6 +468,94 @@ export const AUTH_RECIPE_SEEDS: AuthRecipeDefinition[] = [
|
|||||||
updated_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 / lists(API 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,
|
||||||
|
},
|
||||||
|
|
||||||
// ── Service Account 類(Google 家族,共用同一份 service_account_json)────────
|
// ── Service Account 類(Google 家族,共用同一份 service_account_json)────────
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -159,6 +160,25 @@ func main() {
|
|||||||
authQuery := interpolateRecord(recipe.Inject.Query, secrets, runtime)
|
authQuery := interpolateRecord(recipe.Inject.Query, secrets, runtime)
|
||||||
authBody := interpolateRecord(recipe.Inject.Body, secrets, runtime)
|
authBody := interpolateRecord(recipe.Inject.Body, secrets, runtime)
|
||||||
|
|
||||||
|
// 3.5 Basic Auth 自動編碼:若 header 值為 "Basic <x>:<y>" (冒號代表未編碼的 user:pass),
|
||||||
|
// 將冒號分隔部分做 base64。這涵蓋 twilio / jira / mailgun 等 Basic Auth recipe。
|
||||||
|
// "Basic <already-base64>" (無冒號) 維持原樣,向後相容。
|
||||||
|
// header key 不分大小寫比對 "authorization"。
|
||||||
|
for k, v := range authHeaders {
|
||||||
|
if !strings.EqualFold(k, "Authorization") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const prefix = "Basic "
|
||||||
|
if !strings.HasPrefix(v, prefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
payload := v[len(prefix):]
|
||||||
|
if !strings.Contains(payload, ":") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
authHeaders[k] = prefix + base64.StdEncoding.EncodeToString([]byte(payload))
|
||||||
|
}
|
||||||
|
|
||||||
// 4. 輸出
|
// 4. 輸出
|
||||||
out, _ := json.Marshal(map[string]interface{}{
|
out, _ := json.Marshal(map[string]interface{}{
|
||||||
"success": true,
|
"success": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user