- bouncer TestPhase10CookieGuard: cookie read without Bearer, Bearer wins, empty cookie, frontend audience and blacklisted jti rejected - boardwalk TestPhase10BoardwalkServing: HEAD, query strings, encoded traversal, index by name, nested prefix, MIME fallback, constructor errors - cabana TestPhase10Coverage: mounted unsafe routes vs the CSRF walk, option and filter edges, read-only labels, relation message defaults, bundle fallback locale, cookie refresh of an expired token in the refresh window - phrasebook override precedence, new locale, Bundle merge order, Forms shapes - surf prefix collision for deeper paths and the default /backend prefix - swagger2openapi TestUnionRewrite and converter branch tests - framework tests no longer name the application (acme fixtures instead)
133 lines
4.7 KiB
Go
133 lines
4.7 KiB
Go
package bouncer
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestPhase10CookieGuard covers the backend guard's summer_admin cookie
|
|
// transport (D-19): the cookie is read only when no Bearer header is sent,
|
|
// Bearer wins when both are present, an empty cookie is unauthenticated, and
|
|
// the cookie carries no weaker token than the header (audience, blacklist).
|
|
func TestPhase10CookieGuard(t *testing.T) {
|
|
const (
|
|
cookie = "summer_admin"
|
|
issuer = "https://app.test/backend"
|
|
)
|
|
users := memUsers{byID: map[uint]*Principal{
|
|
2: {ID: 2, Backend: true},
|
|
3: {ID: 3, Backend: true},
|
|
}}
|
|
withCookie := func(value string) *http.Request {
|
|
r := httptest.NewRequest(http.MethodGet, "/backend/api/v1/auth/me", nil)
|
|
r.AddCookie(&http.Cookie{Name: cookie, Value: value})
|
|
return r
|
|
}
|
|
mint := func(sub, audience string) (string, string) {
|
|
t.Helper()
|
|
tok, jti, err := MintAudience(secret, sub, issuer, time.Hour, audience)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return tok, jti
|
|
}
|
|
guard := NewBackendJWTGuard(secret, users, nil, nil, cookie)
|
|
|
|
t.Run("cookie without bearer", func(t *testing.T) {
|
|
tok, _ := mint("2", AudienceBackend)
|
|
principal, err := guard.Authenticate(withCookie(tok))
|
|
if err != nil || principal == nil || principal.ID != 2 {
|
|
t.Fatalf("cookie token rejected: %v %+v", err, principal)
|
|
}
|
|
})
|
|
|
|
t.Run("cookie value is trimmed", func(t *testing.T) {
|
|
tok, _ := mint("2", AudienceBackend)
|
|
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
r.Header.Set("Cookie", cookie+"= "+tok+" ")
|
|
if principal, err := guard.Authenticate(r); err != nil || principal == nil {
|
|
t.Fatalf("padded cookie rejected: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("bearer wins over cookie", func(t *testing.T) {
|
|
bearerTok, _ := mint("3", AudienceBackend)
|
|
cookieTok, _ := mint("2", AudienceBackend)
|
|
r := withCookie(cookieTok)
|
|
r.Header.Set("Authorization", "Bearer "+bearerTok)
|
|
principal, err := guard.Authenticate(r)
|
|
if err != nil || principal == nil || principal.ID != 3 {
|
|
t.Fatalf("bearer did not win: %v %+v", err, principal)
|
|
}
|
|
// A bad Bearer is not rescued by a good cookie.
|
|
bad := withCookie(cookieTok)
|
|
bad.Header.Set("Authorization", "Bearer not-a-token")
|
|
if principal, err := guard.Authenticate(bad); err == nil || principal != nil {
|
|
t.Fatal("an invalid bearer fell back to the cookie")
|
|
}
|
|
})
|
|
|
|
t.Run("empty or missing cookie is unauthenticated", func(t *testing.T) {
|
|
for name, r := range map[string]*http.Request{
|
|
"empty": withCookie(""),
|
|
"blank": withCookie(" "),
|
|
"missing": httptest.NewRequest(http.MethodGet, "/", nil),
|
|
} {
|
|
principal, err := guard.Authenticate(r)
|
|
if err == nil || principal != nil || err.Error() != msgTokenNotProvided {
|
|
t.Fatalf("%s cookie: principal=%+v err=%v, want %q", name, principal, err, msgTokenNotProvided)
|
|
}
|
|
}
|
|
other := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
tok, _ := mint("2", AudienceBackend)
|
|
other.AddCookie(&http.Cookie{Name: "summer_other", Value: tok})
|
|
if _, err := guard.Authenticate(other); err == nil {
|
|
t.Fatal("a token under another cookie name was accepted")
|
|
}
|
|
})
|
|
|
|
t.Run("frontend audience in the cookie is rejected", func(t *testing.T) {
|
|
tok, _ := mint("2", AudienceUser)
|
|
if principal, err := guard.Authenticate(withCookie(tok)); err == nil || principal != nil {
|
|
t.Fatal("backend guard accepted a frontend-audience cookie")
|
|
}
|
|
})
|
|
|
|
t.Run("blacklisted jti in the cookie is rejected", func(t *testing.T) {
|
|
bl := NewMemoryBlacklist()
|
|
blocking := NewBackendJWTGuard(secret, users, bl, nil, cookie)
|
|
tok, jti := mint("2", AudienceBackend)
|
|
if _, err := blocking.Authenticate(withCookie(tok)); err != nil {
|
|
t.Fatalf("fresh cookie rejected: %v", err)
|
|
}
|
|
if err := bl.Add(context.Background(), jti, time.Now().Add(time.Hour), time.Now().Add(-time.Second)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
principal, err := blocking.Authenticate(withCookie(tok))
|
|
if err == nil || principal != nil || err.Error() != msgBadSignature {
|
|
t.Fatalf("blacklisted cookie: principal=%+v err=%v", principal, err)
|
|
}
|
|
})
|
|
|
|
t.Run("bearer-only guard ignores the cookie", func(t *testing.T) {
|
|
tok, _ := mint("2", AudienceBackend)
|
|
bearerOnly := NewBackendJWTGuard(secret, users, nil, nil)
|
|
if principal, err := bearerOnly.Authenticate(withCookie(tok)); err == nil || principal != nil {
|
|
t.Fatal("a guard without cookie names read the cookie")
|
|
}
|
|
})
|
|
|
|
t.Run("second cookie name is tried after an empty first", func(t *testing.T) {
|
|
tok, _ := mint("2", AudienceBackend)
|
|
two := NewBackendJWTGuard(secret, users, nil, nil, "legacy_admin", cookie)
|
|
r := withCookie(tok)
|
|
r.AddCookie(&http.Cookie{Name: "legacy_admin", Value: ""})
|
|
if principal, err := two.Authenticate(r); err != nil || principal == nil {
|
|
t.Fatalf("second cookie name not tried: %v", err)
|
|
}
|
|
})
|
|
}
|