From 4435527e74a2370b37f8dc1a1a5c4d3d1eaf0770 Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Tue, 25 Feb 2025 19:06:52 -0600 Subject: [PATCH] adding api key --- pkg/cloud/api/server.go | 10 +++++-- pkg/http/middleware.go | 63 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/pkg/cloud/api/server.go b/pkg/cloud/api/server.go index 27c0347..63de1ca 100644 --- a/pkg/cloud/api/server.go +++ b/pkg/cloud/api/server.go @@ -116,8 +116,14 @@ func (s *APIServer) setupStaticFileServing() { } func (s *APIServer) setupRoutes() { - // Add CORS middleware - s.router.Use(srHttp.CommonMiddleware) + // Create a middleware chain + middlewareChain := func(next http.Handler) http.Handler { + // Order matters: first API key check, then CORS headers + return srHttp.CommonMiddleware(srHttp.APIKeyMiddleware(next)) + } + + // Add middleware to router + s.router.Use(middlewareChain) // Basic endpoints s.router.HandleFunc("/api/nodes", s.getNodes).Methods("GET") diff --git a/pkg/http/middleware.go b/pkg/http/middleware.go index 073680c..eaaa36d 100644 --- a/pkg/http/middleware.go +++ b/pkg/http/middleware.go @@ -1,7 +1,11 @@ +// Package httpx provides HTTP utilities for the application package httpx import ( + "log" "net/http" + "os" + "strings" ) // CommonMiddleware returns an http.Handler that sets up typical @@ -10,10 +14,10 @@ func CommonMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS") - w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Authorization") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Authorization,X-API-Key") if r.Method == http.MethodOptions { - // Possibly respond 200 here + // Preflight request response w.WriteHeader(http.StatusOK) return } @@ -25,3 +29,58 @@ func CommonMiddleware(next http.Handler) http.Handler { next.ServeHTTP(w, r) }) } + +// APIKeyMiddleware creates middleware that validates API keys. +func APIKeyMiddleware(next http.Handler) http.Handler { + apiKey := os.Getenv("API_KEY") + if apiKey == "" { + log.Printf("WARNING: API_KEY environment variable not set, API endpoints are unprotected!") + } + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Skip API key check for static file requests + if isStaticFileRequest(r.URL.Path) { + next.ServeHTTP(w, r) + + return + } + + // Skip API key check if it's not configured (development mode) + if apiKey == "" { + next.ServeHTTP(w, r) + + return + } + + // Check API key in header or query parameter + requestKey := r.Header.Get("X-API-Key") + if requestKey == "" { + requestKey = r.URL.Query().Get("api_key") + } + + // Validate API key + if requestKey == "" || requestKey != apiKey { + log.Printf("Unauthorized API access attempt: %s %s", r.Method, r.URL.Path) + http.Error(w, "Unauthorized", http.StatusUnauthorized) + + return + } + + next.ServeHTTP(w, r) + }) +} + +// isStaticFileRequest returns true if the request is for static content. +func isStaticFileRequest(path string) bool { + // Skip API key check for static files (adjust as needed) + staticExtensions := []string{".js", ".css", ".html", ".png", ".jpg", ".svg", ".ico", ".woff", ".woff2"} + + for _, ext := range staticExtensions { + if strings.HasSuffix(path, ext) { + return true + } + } + + // Also skip for the root path (which serves index.html) + return path == "/" +}