Files
status/security/protect_wire_test.go
zeekayandHanzo Dev 78ab4f2f83 Declare operations on the group that serves them, and build on Go 1.27
zip's registration is a verb method on the group, so a route that is not a typed
operation says which method it answers with. The security package's own
two-method router goes with it: it existed so both the app and a sub-router could
satisfy one name, and restating a routing surface is what let the two disagree —
the methods it named no longer exist. The app spells itself as a group, which
registers at exactly the paths it did.

The toolchain and the pin move together, because below zip v1.36.50 the JSON
encoder was selected on a build tag: a repo whose go directive rises first writes
a wire that is neither the old release's nor the new one's until the pin catches
up.

metrics_test.go still does not compile, before and after: it compares a metric
registry against a gatherer from a different metrics library, so the two Gather
signatures disagree. That is its own change.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-17 09:58:39 -07:00

139 lines
4.3 KiB
Go

package security
import (
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/zap-proto/zip"
)
// ApplySecurityMiddleware is what stands between the internet and the routes
// registered after it. Three things about it are load-bearing, and only one of
// them was pinned before: the refusal (401 and its body), the ADMISSION of a
// live session — a middleware that refused everything would have satisfied the
// old test — and the barrier property, that routes registered BEFORE it stay
// public. All three are asserted here on exact bytes.
func protectedApp(t *testing.T, c *Config) *zip.App {
t.Helper()
app := zip.New(zip.Config{})
// Registered before the middleware: the unprotected half of the surface.
app.Raw(http.MethodGet, "/public", func(c *zip.Ctx) error { return c.String(http.StatusOK, "public") })
if err := c.ApplySecurityMiddleware(app.Group("").Group("")); err != nil {
t.Fatalf("ApplySecurityMiddleware: %v", err)
}
app.Raw(http.MethodGet, "/private", func(c *zip.Ctx) error { return c.String(http.StatusOK, "private") })
return app
}
func oidcSessionConfig() *Config {
return &Config{OIDC: &OIDCConfig{
IssuerURL: "https://idp.test",
RedirectURL: "http://localhost:80/authorization-code/callback",
ClientID: "client-id",
ClientSecret: "client-secret",
Scopes: []string{"openid"},
SessionTTL: DefaultOIDCSessionTTL,
}}
}
func TestProtectWire(t *testing.T) {
c := oidcSessionConfig()
app := protectedApp(t, c)
// A session the server actually knows, established the way the callback
// establishes one.
sessions.SetWithTTL("live-session", "[email protected]", DefaultOIDCSessionTTL)
t.Cleanup(func() { sessions.Delete("live-session") })
for _, tc := range []struct {
name string
path string
cookies []*http.Cookie
status int
body string
}{
{
name: "no cookie at all",
path: "/private",
status: http.StatusUnauthorized,
body: "token is missing or invalid",
},
{
name: "a session id the server does not know",
path: "/private",
cookies: []*http.Cookie{{Name: cookieNameSession, Value: "invented"}},
status: http.StatusUnauthorized,
body: "token is missing or invalid",
},
{
name: "the wrong cookie name carries no session",
path: "/private",
cookies: []*http.Cookie{{Name: "session", Value: "live-session"}},
status: http.StatusUnauthorized,
body: "token is missing or invalid",
},
{
// A bearer is not how this surface is entered; only the cookie is.
name: "a live session is admitted",
path: "/private",
cookies: []*http.Cookie{{Name: cookieNameSession, Value: "live-session"}},
status: http.StatusOK,
body: "private",
},
{
name: "a route registered before the middleware stays public",
path: "/public",
status: http.StatusOK,
body: "public",
},
} {
t.Run(tc.name, func(t *testing.T) {
req := httptest.NewRequest("GET", tc.path, http.NoBody)
for _, ck := range tc.cookies {
req.AddCookie(ck)
}
resp, err := app.Fiber().Test(req)
if err != nil {
t.Fatalf("Test: %v", err)
}
b, _ := io.ReadAll(resp.Body)
_ = resp.Body.Close()
if resp.StatusCode != tc.status {
t.Errorf("status = %d, want %d", resp.StatusCode, tc.status)
}
if string(b) != tc.body {
t.Errorf("body = %q, want %q", string(b), tc.body)
}
})
}
}
// The Authorization header is the default way g8 reads a token. This surface
// replaces that with the session cookie, so a bearer must buy nothing — pinned
// because a conversion that reverted to the default extractor would otherwise
// look like it worked.
func TestProtectWire_BearerBuysNothing(t *testing.T) {
c := oidcSessionConfig()
app := protectedApp(t, c)
sessions.SetWithTTL("bearer-session", "[email protected]", DefaultOIDCSessionTTL)
t.Cleanup(func() { sessions.Delete("bearer-session") })
req := httptest.NewRequest("GET", "/private", http.NoBody)
req.Header.Set("Authorization", "Bearer bearer-session")
resp, err := app.Fiber().Test(req)
if err != nil {
t.Fatalf("Test: %v", err)
}
b, _ := io.ReadAll(resp.Body)
_ = resp.Body.Close()
if resp.StatusCode != http.StatusUnauthorized {
t.Errorf("status = %d, want 401 — a bearer is not a session here", resp.StatusCode)
}
if string(b) != "token is missing or invalid" {
t.Errorf("body = %q, want %q", string(b), "token is missing or invalid")
}
}