diff --git a/docs/api-reference.md b/docs/api-reference.md index 10fbbd1..e243cb8 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -52,7 +52,7 @@ bridge 的认证与跨域规则来自: -- `ACP_AUTH_TOKEN` +- `BRIDGE_AUTH_TOKEN` - `ACP_ALLOWED_ORIGINS` 默认 allowed origins: @@ -67,8 +67,8 @@ bridge 的认证与跨域规则来自: - 空 `Origin` 默认允许。 - `:*` 表示前缀匹配,例如 `http://localhost:*`。 - auth 使用 bearer header。 -- 如果 `ACP_AUTH_TOKEN` 为空,则接受任意非空 bearer header。 -- 如果 `ACP_AUTH_TOKEN` 非空,则必须匹配裸 token 或 `Bearer `。 +- 如果 `BRIDGE_AUTH_TOKEN` 为空,则 bridge auth 默认放行。 +- 如果 `BRIDGE_AUTH_TOKEN` 非空,则必须匹配裸 token 或 `Bearer `。 错误行为: @@ -165,7 +165,7 @@ HTTP 与 WebSocket 统一使用 JSON-RPC 2.0 结构: 说明: -- `bridgeOrigin` 读取 `BRIDGE_PUBLIC_BASE_URL` +- `bridgeOrigin` 读取 `BRIDGE_SERVER_URL` - 默认值:`https://xworkmate-bridge.svc.plus` ## 4. Bridge JSON-RPC Methods @@ -816,13 +816,13 @@ bridge 在 session 执行期间会通过 `session.update` 推送通知,统一 | 变量 | 默认值 | 作用 | | --- | --- | --- | | `ACP_LISTEN_ADDR` | `127.0.0.1:8787` | bridge listen 地址 | -| `ACP_AUTH_TOKEN` | 空 | bridge bearer 校验 token | +| `BRIDGE_AUTH_TOKEN` | 空 | bridge bearer 校验 token | | `ACP_ALLOWED_ORIGINS` | `https://xworkmate.svc.plus,http://localhost:*,http://127.0.0.1:*` | bridge allowed origins | | `ACP_MULTI_AGENT_ENABLED` | `true` | `acp.capabilities` 中的 `multiAgent` 开关 | | `ACP_MULTI_AGENT_MODEL` | `gpt-4o` | multi-agent 默认模型 | -| `BRIDGE_PUBLIC_BASE_URL` | `https://xworkmate-bridge.svc.plus` | bootstrap health 中的 public base URL | +| `BRIDGE_SERVER_URL` | `https://xworkmate-bridge.svc.plus` | bootstrap health 与外部调用统一使用的 bridge base URL | | `INTERNAL_SERVICE_TOKEN` | 空 | upstream provider / gateway 优先使用的内部服务 token | -| `BRIDGE_AUTH_TOKEN` | 空 | `INTERNAL_SERVICE_TOKEN` 的 fallback,用于 upstream forwarding | +| `BRIDGE_AUTH_TOKEN` | 空 | bridge 入站 bearer token;当 `INTERNAL_SERVICE_TOKEN` 为空时也作为 upstream forwarding token | | `IMAGE` | 空 | `/api/ping` 版本信息来源 | ### 9.2 Gemini adapter diff --git a/docs/architecture/bridge-runtime-design.md b/docs/architecture/bridge-runtime-design.md index 81634e1..a807487 100644 --- a/docs/architecture/bridge-runtime-design.md +++ b/docs/architecture/bridge-runtime-design.md @@ -159,10 +159,10 @@ single-agent 主链路优先走 bridge 内建 provider catalog。`runSingleAgent ### 4.1 Auth -bridge 主入口使用 `ACP_AUTH_TOKEN` 驱动的 bearer auth: +bridge 主入口使用 `BRIDGE_AUTH_TOKEN` 驱动的 bearer auth: - 如果配置了 token,则必须完全匹配该 token 或 `Bearer ` -- 如果未配置 token,则只要求存在非空 bearer header +- 如果未配置 token,则默认放行 Gemini adapter 的 auth 更宽松: diff --git a/docs/internal-reference.md b/docs/internal-reference.md index 3361a9e..b101184 100644 --- a/docs/internal-reference.md +++ b/docs/internal-reference.md @@ -60,7 +60,7 @@ APP-facing bridge 主控面。负责 HTTP / WebSocket 路由、JSON-RPC method d - `func (s *Server) HandleBridgeBootstrapHealth(w http.ResponseWriter, r *http.Request)` - 参数:标准 HTTP writer/request。 - 返回:无显式返回;输出 bridge health JSON。 - - 副作用:读取 `BRIDGE_PUBLIC_BASE_URL`。 + - 副作用:读取 `BRIDGE_SERVER_URL`。 - 场景:bootstrap 自检或部署健康检查。 - `func RunStdio(input io.Reader, output io.Writer)` diff --git a/example/config.yaml b/example/config.yaml index 9934424..3b2db5e 100644 --- a/example/config.yaml +++ b/example/config.yaml @@ -1,11 +1,11 @@ # Example configuration for xworkmate-bridge public ingress and provider sync. # The bridge itself validates inbound requests with: -# Authorization: Bearer ${ACP_AUTH_TOKEN} +# Authorization: Bearer ${BRIDGE_AUTH_TOKEN} # Each external provider can also carry its own outbound Authorization header. bridge: listenAddr: 127.0.0.1:8787 - authToken: ${ACP_AUTH_TOKEN} + authToken: ${BRIDGE_AUTH_TOKEN} allowedOrigins: - https://xworkmate.svc.plus - http://localhost:* @@ -37,6 +37,6 @@ providers: enabled: true notes: - - The bridge reads its own auth token from ACP_AUTH_TOKEN. + - The bridge reads its own auth token from BRIDGE_AUTH_TOKEN. - The provider catalog is normally synced through xworkmate.providers.sync. - session.message should resend routing on follow-up turns for codex and opencode. diff --git a/internal/acp/bootstrap.go b/internal/acp/bootstrap.go index d15f93e..14be9da 100644 --- a/internal/acp/bootstrap.go +++ b/internal/acp/bootstrap.go @@ -22,7 +22,7 @@ func (s *Server) HandleBridgeBootstrapHealth(w http.ResponseWriter, r *http.Requ } func bridgePublicBaseURL() string { - value := strings.TrimSpace(shared.EnvOrDefault("BRIDGE_PUBLIC_BASE_URL", "https://xworkmate-bridge.svc.plus")) + value := strings.TrimSpace(shared.EnvOrDefault("BRIDGE_SERVER_URL", "https://xworkmate-bridge.svc.plus")) if value == "" { return "https://xworkmate-bridge.svc.plus" } diff --git a/internal/acp/server.go b/internal/acp/server.go index 1c50a7c..d7e2274 100644 --- a/internal/acp/server.go +++ b/internal/acp/server.go @@ -96,7 +96,7 @@ func NewServer() *Server { gateway: gatewayruntime.NewManager(), providerCatalog: providerCatalog, providerOrder: providerOrder, - authService: service.NewStaticTokenAuthService(strings.TrimSpace(shared.EnvOrDefault("ACP_AUTH_TOKEN", ""))), + authService: service.NewStaticTokenAuthService(strings.TrimSpace(shared.EnvOrDefault("BRIDGE_AUTH_TOKEN", ""))), } } @@ -300,7 +300,14 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { } func (s *Server) authorized(r *http.Request) bool { - if s == nil || s.authService == nil { + if s == nil { + return false + } + expected := strings.TrimSpace(shared.EnvOrDefault("BRIDGE_AUTH_TOKEN", "")) + if expected == "" { + return true + } + if s.authService == nil { return false } return s.authService.ValidateAuthorizationHeader(r.Header.Get("Authorization")) diff --git a/internal/acp/web_contract_test.go b/internal/acp/web_contract_test.go index 19f13f2..74e966c 100644 --- a/internal/acp/web_contract_test.go +++ b/internal/acp/web_contract_test.go @@ -110,7 +110,26 @@ func TestHandleRPCAllowsPreflightForConfiguredOrigin(t *testing.T) { } } -func TestHandleRPCRequiresBearerAuthorization(t *testing.T) { +func TestHandleRPCAllowsRequestsWhenBridgeAuthTokenUnset(t *testing.T) { + server := NewServer() + recorder := httptest.NewRecorder() + request := httptest.NewRequest( + http.MethodPost, + "http://127.0.0.1/acp/rpc", + strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"acp.capabilities"}`), + ) + request.Header.Set("Content-Type", "application/json") + + server.HandleRPC(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("expected 200 when BRIDGE_AUTH_TOKEN is unset, got %d", recorder.Code) + } +} + +func TestHandleRPCRequiresBearerAuthorizationWhenBridgeAuthTokenConfigured(t *testing.T) { + t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token") + server := NewServer() recorder := httptest.NewRecorder() request := httptest.NewRequest( @@ -195,6 +214,14 @@ func TestHandleRPCCapabilitiesStillReturnsJSONResult(t *testing.T) { } } +func TestAuthorizedAllowsRequestsWhenBridgeAuthTokenUnset(t *testing.T) { + server := NewServer() + request := httptest.NewRequest(http.MethodGet, "http://127.0.0.1/acp", nil) + if !server.authorized(request) { + t.Fatal("expected requests to be authorized when BRIDGE_AUTH_TOKEN is unset") + } +} + func TestHandleRPCCapabilitiesReturnsCanonicalProviderContract(t *testing.T) { server := NewServer() recorder := httptest.NewRecorder() @@ -338,6 +365,7 @@ func mustStringList(t *testing.T, value any) []string { func TestHandleWebSocketRequiresBearerAuthorization(t *testing.T) { t.Setenv("ACP_ALLOWED_ORIGINS", "https://xworkmate.svc.plus") + t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token") server := NewServer() recorder := httptest.NewRecorder()