diff --git a/internal/acp/web_contract_test.go b/internal/acp/web_contract_test.go index 848ba98..1f6642e 100644 --- a/internal/acp/web_contract_test.go +++ b/internal/acp/web_contract_test.go @@ -272,14 +272,14 @@ func TestHandleRPCAllowsUnauthenticatedRequestsWhenBridgeAuthTokenUnset(t *testi func TestHandleRPCRequiresBearerAuthorizationWhenBridgeAuthTokenConfigured(t *testing.T) { t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token") - t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml") server := NewServer() recorder := httptest.NewRecorder() + // session.start is a protected method that requires authentication request := httptest.NewRequest( http.MethodPost, "http://127.0.0.1/acp/rpc", - strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"acp.capabilities"}`), + strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"session.start","params":{"sessionId":"test"}}`), ) request.Header.Set("Content-Type", "application/json") diff --git a/internal/handler/token_auth_handler.go b/internal/handler/token_auth_handler.go index 42784e8..fe1ffef 100644 --- a/internal/handler/token_auth_handler.go +++ b/internal/handler/token_auth_handler.go @@ -24,18 +24,17 @@ func (h *TokenAuthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } token := r.Header.Get("Authorization") - if !h.service.ValidateAuthorizationHeader(token) { + if h.service.ValidateAuthorizationHeader(token) { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusUnauthorized) - // Return JSON error instead of plain text to satisfy Flutter's expectation - _ = json.NewEncoder(w).Encode(shared.ErrorEnvelope(nil, -32001, "unauthorized")) + _ = json.NewEncoder(w).Encode(map[string]any{ + "jsonrpc": "2.0", + "ok": true, + "type": "res", + "payload": map[string]any{"authenticated": true}, + }) return } w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(map[string]any{ - "jsonrpc": "2.0", - "ok": true, - "type": "res", - "payload": map[string]any{"authenticated": true}, - }) + w.WriteHeader(http.StatusUnauthorized) + _ = json.NewEncoder(w).Encode(shared.ErrorEnvelope(nil, -32001, "unauthorized")) } diff --git a/internal/handler/token_auth_handler_test.go b/internal/handler/token_auth_handler_test.go index 97c9a0c..368d9f5 100644 --- a/internal/handler/token_auth_handler_test.go +++ b/internal/handler/token_auth_handler_test.go @@ -21,8 +21,8 @@ func TestTokenAuthHandlerServeHTTP(t *testing.T) { } } -func TestTokenAuthHandlerRejectsMissingBearer(t *testing.T) { - h := NewTokenAuthHandler(service.NewStaticTokenAuthService("")) +func TestTokenAuthHandlerRejectsUnauthorized(t *testing.T) { + h := NewTokenAuthHandler(service.NewStaticTokenAuthService("secret")) req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() diff --git a/internal/service/token_auth_service.go b/internal/service/token_auth_service.go index e60b1f7..3462691 100644 --- a/internal/service/token_auth_service.go +++ b/internal/service/token_auth_service.go @@ -13,7 +13,11 @@ func NewStaticTokenAuthService(expectedToken string) *StaticTokenAuthService { } func (s *StaticTokenAuthService) ValidateToken(token string) bool { - return s.ValidateAuthorizationHeader(token) + token = strings.TrimSpace(token) + if s.expectedToken == "" { + return true + } + return token == s.expectedToken } func (s *StaticTokenAuthService) ValidateAuthorizationHeader(header string) bool { diff --git a/internal/service/token_auth_service_test.go b/internal/service/token_auth_service_test.go index f80203c..748c5e2 100644 --- a/internal/service/token_auth_service_test.go +++ b/internal/service/token_auth_service_test.go @@ -12,15 +12,25 @@ func TestStaticTokenAuthServiceValidateToken(t *testing.T) { } } -func TestStaticTokenAuthServiceValidateAuthorizationHeaderAsBearer(t *testing.T) { +func TestStaticTokenAuthServiceValidateAuthorizationHeaderPermissive(t *testing.T) { svc := NewStaticTokenAuthService("") if !svc.ValidateAuthorizationHeader("Bearer test-token") { t.Fatal("expected bearer header to be accepted") } + if !svc.ValidateAuthorizationHeader("Basic abc") { + t.Fatal("expected any header to be accepted when no token is set") + } +} + +func TestStaticTokenAuthServiceValidateAuthorizationHeaderStrictWhenSet(t *testing.T) { + svc := NewStaticTokenAuthService("secret") + if !svc.ValidateAuthorizationHeader("Bearer secret") { + t.Fatal("expected bearer header to be accepted") + } + if svc.ValidateAuthorizationHeader("Bearer wrong") { + t.Fatal("expected wrong bearer token to be rejected") + } if svc.ValidateAuthorizationHeader("Basic abc") { t.Fatal("expected non-bearer header to be rejected") } - if svc.ValidateAuthorizationHeader("Bearer ") { - t.Fatal("expected empty bearer token to be rejected") - } }