refactor(10.2-01): nest framework packages under modules
- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
This commit is contained in:
132
modules/bouncer/cookie_guard_test.go
Normal file
132
modules/bouncer/cookie_guard_test.go
Normal file
@@ -0,0 +1,132 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user