forgejo-mcp/operation/extracttoken_test.go
Christoph Görn 63e16242a3
fix: 🔒️ reject bare tokens in stateless HTTP auth
extractToken accepted Authorization values with no scheme prefix,
silently treating them as per-request tokens. The stateless-http-auth
spec mandates bare tokens be rejected and treated as no header. Drop
the fallback branch so unrecognized/scheme-less values fall through to
the global singleton instead of forging an identity.

- operation: extractToken returns "" for scheme-less headers (task 2.5)
- test: extractToken case-insensitivity + bare-token rejection (4.4)
- test: ephemeral-client construction failure surfaces error, no
  singleton downgrade (4.3)
- docs: cross-link stateless-http-auth OpenSpec change from README (5.2)
- tasks: mark completed blocker fixes (1.3, 2.4, 2.5, 4.2-4.4, 5.x, 6.3)
2026-06-01 23:41:10 +02:00

44 lines
1.4 KiB
Go

package operation
import "testing"
func TestExtractToken(t *testing.T) {
tests := []struct {
name string
auth string
want string
}{
{"empty header", "", ""},
{"token scheme", "token abc123", "abc123"},
{"bearer scheme", "Bearer abc123", "abc123"},
{"lowercase bearer", "bearer abc123", "abc123"},
{"uppercase bearer", "BEARER abc123", "abc123"},
{"uppercase token", "TOKEN abc123", "abc123"},
{"mixed-case token", "ToKeN abc123", "abc123"},
{"unrecognized scheme", "Basic abc123", ""},
// Spec: bare tokens (no scheme prefix) MUST be rejected and treated
// as if no Authorization header were present.
{"bare token rejected", "abc123", ""},
{"bare token with leading space rejected", " abc123", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := extractToken(tt.auth); got != tt.want {
t.Errorf("extractToken(%q) = %q, want %q", tt.auth, got, tt.want)
}
})
}
}
// TestExtractToken_CaseInsensitiveEquivalence asserts every case variant of a
// recognized scheme resolves to the identical token value (spec scenario
// "Scheme matching is case-insensitive").
func TestExtractToken_CaseInsensitiveEquivalence(t *testing.T) {
variants := []string{"bearer abc123", "Bearer abc123", "BEARER abc123", "token abc123", "TOKEN abc123"}
for _, v := range variants {
if got := extractToken(v); got != "abc123" {
t.Errorf("extractToken(%q) = %q, want %q", v, got, "abc123")
}
}
}