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).
61 lines
2.2 KiB
Go
61 lines
2.2 KiB
Go
package middleware
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
)
|
|
|
|
// ctxKey is an unexported type for request-scoped authentication values.
|
|
// Using a distinct type (rather than a bare string) keeps our keys out of
|
|
// collision range for any other package that also calls context.WithValue,
|
|
// and silences go vet's SA1029.
|
|
type ctxKey string
|
|
|
|
const (
|
|
ctxKeyUserAddress ctxKey = "user_address"
|
|
ctxKeyUserTrack ctxKey = "user_track"
|
|
ctxKeyAuthenticated ctxKey = "authenticated"
|
|
)
|
|
|
|
// Default track level applied to unauthenticated requests (Track 1 = public).
|
|
const defaultTrackLevel = 1
|
|
|
|
// ErrMissingAuthorization is returned by extractAuth when no usable
|
|
// Authorization header is present on the request. Callers should treat this
|
|
// as "no auth supplied" rather than a hard failure for optional-auth routes.
|
|
var ErrMissingAuthorization = errors.New("middleware: authorization header missing or malformed")
|
|
|
|
// ContextWithAuth returns a child context carrying the supplied
|
|
// authentication state. It is the single place in the package that writes
|
|
// the auth context keys.
|
|
func ContextWithAuth(parent context.Context, address string, track int, authenticated bool) context.Context {
|
|
ctx := context.WithValue(parent, ctxKeyUserAddress, address)
|
|
ctx = context.WithValue(ctx, ctxKeyUserTrack, track)
|
|
ctx = context.WithValue(ctx, ctxKeyAuthenticated, authenticated)
|
|
return ctx
|
|
}
|
|
|
|
// UserAddress returns the authenticated wallet address stored on ctx, or
|
|
// "" if the context is not authenticated.
|
|
func UserAddress(ctx context.Context) string {
|
|
addr, _ := ctx.Value(ctxKeyUserAddress).(string)
|
|
return addr
|
|
}
|
|
|
|
// UserTrack returns the access tier recorded on ctx. If no track was set
|
|
// (e.g. the request bypassed all auth middleware) the caller receives
|
|
// Track 1 (public) so route-level checks can still make a decision.
|
|
func UserTrack(ctx context.Context) int {
|
|
if track, ok := ctx.Value(ctxKeyUserTrack).(int); ok {
|
|
return track
|
|
}
|
|
return defaultTrackLevel
|
|
}
|
|
|
|
// IsAuthenticated reports whether the current request carried a valid auth
|
|
// token that was successfully parsed by the middleware.
|
|
func IsAuthenticated(ctx context.Context) bool {
|
|
ok, _ := ctx.Value(ctxKeyAuthenticated).(bool)
|
|
return ok
|
|
}
|