update_server/internal/http/api_key_middleware.go
2026-06-10 20:51:17 +03:00

111 lines
3 KiB
Go

package httpserver
import (
"errors"
"net/http"
"strings"
"update_server/internal/apikeys"
)
func (h *handler) requireAPIKey(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if h.apiKeys == nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]any{"error": "api key auth is unavailable"})
return
}
token, ok := bearerToken(r.Header.Get("Authorization"))
if !ok {
writeAPIKeyAuthError(w, http.StatusUnauthorized, "missing bearer api key")
return
}
state, err := h.apiKeys.Authenticate(r.Context(), token)
if err != nil {
if errors.Is(err, apikeys.ErrUnauthenticated) {
writeAPIKeyAuthError(w, http.StatusUnauthorized, "invalid api key")
return
}
writeJSON(w, http.StatusInternalServerError, map[string]any{"error": "api key lookup failed"})
return
}
next.ServeHTTP(w, r.WithContext(apikeys.NewContext(r.Context(), state)))
})
}
func (h *handler) requireAPIKeyPermission(permission apikeys.Permission) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
state, ok := apikeys.FromContext(r.Context())
if !ok {
writeAPIKeyAuthError(w, http.StatusUnauthorized, "api key authentication required")
return
}
if !apikeys.HasPermission(state.APIKey, permission) {
writeJSON(w, http.StatusForbidden, map[string]any{"error": "api key permission denied"})
return
}
next.ServeHTTP(w, r)
})
}
}
func (h *handler) requireAPIKeyProjectAccess(routeParam string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
state, ok := apikeys.FromContext(r.Context())
if !ok {
writeAPIKeyAuthError(w, http.StatusUnauthorized, "api key authentication required")
return
}
projectID, err := routeID(r, routeParam)
if err != nil {
writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid project id"})
return
}
allowed, err := h.apiKeys.CanAccessProject(r.Context(), state.APIKey, projectID)
if err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]any{"error": "project access lookup failed"})
return
}
if !allowed {
writeJSON(w, http.StatusForbidden, map[string]any{"error": "api key cannot access this project"})
return
}
next.ServeHTTP(w, r)
})
}
}
func bearerToken(header string) (string, bool) {
header = strings.TrimSpace(header)
if header == "" {
return "", false
}
parts := strings.Fields(header)
if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") {
return "", false
}
token := strings.TrimSpace(parts[1])
if token == "" {
return "", false
}
return token, true
}
func writeAPIKeyAuthError(w http.ResponseWriter, status int, message string) {
w.Header().Set("WWW-Authenticate", `Bearer realm="update-server"`)
writeJSON(w, status, map[string]any{"error": message})
}