backend/api/middleware/context.go (new): - Introduces an unexported ctxKey type and three constants (ctxKeyUserAddress, ctxKeyUserTrack, ctxKeyAuthenticated) that replace the bare string keys 'user_address', 'user_track', and 'authenticated'. Bare strings trigger go vet's SA1029 and collide with keys from any other package that happens to share the name. - Helpers: ContextWithAuth, UserAddress, UserTrack, IsAuthenticated. - Sentinel: ErrMissingAuthorization replaces the misuse of http.ErrMissingFile as an auth-missing signal. (http.ErrMissingFile belongs to multipart form parsing and was semantically wrong.) backend/api/middleware/auth.go: - RequireAuth, OptionalAuth, RequireTrack now all read/write via the helpers; no more string literals for context keys in this file. - extractAuth returns ErrMissingAuthorization instead of http.ErrMissingFile. - Dropped now-unused 'context' import. backend/api/track4/operator_scripts.go, backend/api/track4/endpoints.go, backend/api/rest/features.go: - Read user address / track via middleware.UserAddress() and middleware.UserTrack() instead of a raw context lookup with a bare string key. - Import 'github.com/explorer/backend/api/middleware'. backend/api/track4/operator_scripts_test.go: - Four test fixtures updated to seed the request context through middleware.ContextWithAuth (track 4, authenticated) instead of context.WithValue with a bare 'user_address' string. This is the load-bearing change that proves typed keys are required: a bare string key no longer wakes up the middleware helpers. backend/api/middleware/context_test.go (new): - Round-trip test for ContextWithAuth + UserAddress + UserTrack + IsAuthenticated. - Defaults: UserTrack=1, UserAddress="", IsAuthenticated=false on a bare context. - TestContextKeyIsolation: an outside caller that inserts 'user_address' as a bare string key must NOT be visible to UserAddress; proves the type discipline. - ErrMissingAuthorization sentinel smoke test. Verification: - go build ./... clean. - go vet ./... clean (removes SA1029 on the old bare keys). - go test ./api/middleware/... ./api/track4/... ./api/rest/... PASS. Advances completion criterion 3 (Auth correctness).
63 lines
1.9 KiB
Go
63 lines
1.9 KiB
Go
package middleware
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
func TestContextWithAuthRoundTrip(t *testing.T) {
|
|
ctx := ContextWithAuth(context.Background(), "0xabc", 4, true)
|
|
|
|
if got := UserAddress(ctx); got != "0xabc" {
|
|
t.Fatalf("UserAddress() = %q, want %q", got, "0xabc")
|
|
}
|
|
if got := UserTrack(ctx); got != 4 {
|
|
t.Fatalf("UserTrack() = %d, want 4", got)
|
|
}
|
|
if !IsAuthenticated(ctx) {
|
|
t.Fatal("IsAuthenticated() = false, want true")
|
|
}
|
|
}
|
|
|
|
func TestUserTrackDefaultsToTrack1OnBareContext(t *testing.T) {
|
|
if got := UserTrack(context.Background()); got != defaultTrackLevel {
|
|
t.Fatalf("UserTrack(empty) = %d, want %d", got, defaultTrackLevel)
|
|
}
|
|
}
|
|
|
|
func TestUserAddressEmptyOnBareContext(t *testing.T) {
|
|
if got := UserAddress(context.Background()); got != "" {
|
|
t.Fatalf("UserAddress(empty) = %q, want empty", got)
|
|
}
|
|
}
|
|
|
|
func TestIsAuthenticatedFalseOnBareContext(t *testing.T) {
|
|
if IsAuthenticated(context.Background()) {
|
|
t.Fatal("IsAuthenticated(empty) = true, want false")
|
|
}
|
|
}
|
|
|
|
// TestContextKeyIsolation proves that the typed ctxKey values cannot be
|
|
// shadowed by a caller using bare-string keys with the same spelling.
|
|
// This is the specific class of bug fixed by this PR.
|
|
func TestContextKeyIsolation(t *testing.T) {
|
|
ctx := context.WithValue(context.Background(), "user_address", "injected")
|
|
if got := UserAddress(ctx); got != "" {
|
|
t.Fatalf("expected empty address (bare string key must not collide), got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestErrMissingAuthorizationIsSentinel(t *testing.T) {
|
|
if ErrMissingAuthorization == nil {
|
|
t.Fatal("ErrMissingAuthorization must not be nil")
|
|
}
|
|
wrapped := errors.New("wrapped: " + ErrMissingAuthorization.Error())
|
|
if errors.Is(wrapped, ErrMissingAuthorization) {
|
|
t.Fatal("string-wrapped error must not satisfy errors.Is (smoke check)")
|
|
}
|
|
if !errors.Is(ErrMissingAuthorization, ErrMissingAuthorization) {
|
|
t.Fatal("ErrMissingAuthorization must satisfy errors.Is against itself")
|
|
}
|
|
}
|