You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

54 lines
1.5 KiB

package middleware
import (
"context"
"net/http"
"strings"
"quake2dropzone/internal/auth"
)
type contextKey string
const ClaimsKey contextKey = "claims"
// AuthRequired validates the JWT from the Authorization: Bearer <token> header.
// On success it injects the claims into the request context.
func AuthRequired(jwtSecret string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
header := r.Header.Get("Authorization")
if !strings.HasPrefix(header, "Bearer ") {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
tokenStr := strings.TrimPrefix(header, "Bearer ")
claims, err := auth.ParseJWT(jwtSecret, tokenStr)
if err != nil {
http.Error(w, "invalid token", http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), ClaimsKey, claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// AdminRequired enforces the "admin" role in the JWT claims.
// Must be used after AuthRequired.
func AdminRequired(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims, ok := r.Context().Value(ClaimsKey).(*auth.Claims)
if !ok || claims.Role != "admin" {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
// GetClaims retrieves the claims from the request context.
func GetClaims(r *http.Request) *auth.Claims {
claims, _ := r.Context().Value(ClaimsKey).(*auth.Claims)
return claims
}