package middleware import ( "fmt" "net/http" "strings" "github.com/explorer/backend/auth" "github.com/explorer/backend/featureflags" ) // AuthMiddleware handles authentication and authorization type AuthMiddleware struct { walletAuth *auth.WalletAuth } // NewAuthMiddleware creates a new auth middleware func NewAuthMiddleware(walletAuth *auth.WalletAuth) *AuthMiddleware { return &AuthMiddleware{ walletAuth: walletAuth, } } // RequireAuth is middleware that requires authentication func (m *AuthMiddleware) RequireAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { address, track, err := m.extractAuth(r) if err != nil { writeUnauthorized(w) return } ctx := ContextWithAuth(r.Context(), address, track, true) next.ServeHTTP(w, r.WithContext(ctx)) }) } // RequireTrack is middleware that requires a specific track level func (m *AuthMiddleware) RequireTrack(requiredTrack int) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { track := UserTrack(r.Context()) if !featureflags.HasAccess(track, requiredTrack) { writeForbidden(w, requiredTrack) return } next.ServeHTTP(w, r) }) } } // OptionalAuth is middleware that optionally authenticates (for Track 1 endpoints) func (m *AuthMiddleware) OptionalAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { address, track, err := m.extractAuth(r) if err != nil { // No auth provided (or auth failed) — fall back to Track 1. ctx := ContextWithAuth(r.Context(), "", defaultTrackLevel, false) next.ServeHTTP(w, r.WithContext(ctx)) return } ctx := ContextWithAuth(r.Context(), address, track, true) next.ServeHTTP(w, r.WithContext(ctx)) }) } // extractAuth extracts authentication information from the request. // Returns ErrMissingAuthorization when no usable Bearer token is present; // otherwise returns the error from JWT validation. func (m *AuthMiddleware) extractAuth(r *http.Request) (string, int, error) { authHeader := r.Header.Get("Authorization") if authHeader == "" { return "", 0, ErrMissingAuthorization } parts := strings.Split(authHeader, " ") if len(parts) != 2 || parts[0] != "Bearer" { return "", 0, ErrMissingAuthorization } token := parts[1] address, track, err := m.walletAuth.ValidateJWT(token) if err != nil { return "", 0, err } return address, track, nil } // writeUnauthorized writes a 401 Unauthorized response func writeUnauthorized(w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(`{"error":{"code":"unauthorized","message":"Authentication required"}}`)) } // writeForbidden writes a 403 Forbidden response func writeForbidden(w http.ResponseWriter, requiredTrack int) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusForbidden) w.Write([]byte(`{"error":{"code":"forbidden","message":"Insufficient permissions","required_track":` + fmt.Sprintf("%d", requiredTrack) + `}}`)) }