package httpserver import ( "context" "net/http" "strings" "github.com/go-chi/chi/v5/middleware" "github.com/google/uuid" "github.com/nexavpn/nexavpn/backend/internal/apiutil" "github.com/nexavpn/nexavpn/backend/internal/auth" ) type contextKey string const claimsContextKey contextKey = "claims" func BaseMiddleware(next http.Handler) http.Handler { return middleware.RealIP(middleware.RequestID(middleware.Logger(next))) } func AuthMiddleware(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 ") { apiutil.Error(w, http.StatusUnauthorized, "unauthorized", "missing bearer token") return } claims, err := auth.ParseAccessToken(jwtSecret, strings.TrimPrefix(header, "Bearer ")) if err != nil { apiutil.Error(w, http.StatusUnauthorized, "unauthorized", "invalid access token") return } ctx := context.WithValue(r.Context(), claimsContextKey, claims) next.ServeHTTP(w, r.WithContext(ctx)) }) } } func AdminOnly(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { claims, ok := ClaimsFromContext(r.Context()) if !ok || claims.Role != "admin" { apiutil.Error(w, http.StatusForbidden, "forbidden", "admin role required") return } next.ServeHTTP(w, r) }) } func ClaimsFromContext(ctx context.Context) (auth.Claims, bool) { claims, ok := ctx.Value(claimsContextKey).(auth.Claims) return claims, ok } func MustUserID(ctx context.Context) (uuid.UUID, bool) { claims, ok := ClaimsFromContext(ctx) if !ok { return uuid.Nil, false } return claims.UserID, true }