111 lines
3 KiB
Go
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})
|
|
}
|