ScuttleBot

scuttlebot / internal / api / middleware.go
Source Blame History 66 lines
2d8a379… lmata 1 package api
2d8a379… lmata 2
2d8a379… lmata 3 import (
68677f9… noreply 4 "context"
2d8a379… lmata 5 "net/http"
2d8a379… lmata 6 "strings"
68677f9… noreply 7
68677f9… noreply 8 "github.com/conflicthq/scuttlebot/internal/auth"
2d8a379… lmata 9 )
2d8a379… lmata 10
68677f9… noreply 11 type ctxKey string
68677f9… noreply 12
68677f9… noreply 13 const ctxAPIKey ctxKey = "apikey"
68677f9… noreply 14
68677f9… noreply 15 // apiKeyFromContext returns the authenticated APIKey from the request context,
68677f9… noreply 16 // or nil if not authenticated.
68677f9… noreply 17 func apiKeyFromContext(ctx context.Context) *auth.APIKey {
68677f9… noreply 18 k, _ := ctx.Value(ctxAPIKey).(*auth.APIKey)
68677f9… noreply 19 return k
68677f9… noreply 20 }
68677f9… noreply 21
68677f9… noreply 22 // authMiddleware validates the Bearer token and injects the APIKey into context.
2d8a379… lmata 23 func (s *Server) authMiddleware(next http.Handler) http.Handler {
2d8a379… lmata 24 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2d8a379… lmata 25 token := bearerToken(r)
2d8a379… lmata 26 if token == "" {
2d8a379… lmata 27 writeError(w, http.StatusUnauthorized, "missing authorization header")
2d8a379… lmata 28 return
2d8a379… lmata 29 }
68677f9… noreply 30 key := s.apiKeys.Lookup(token)
68677f9… noreply 31 if key == nil {
2d8a379… lmata 32 writeError(w, http.StatusUnauthorized, "invalid token")
2d8a379… lmata 33 return
2d8a379… lmata 34 }
68677f9… noreply 35 // Update last-used timestamp in the background.
68677f9… noreply 36 go s.apiKeys.TouchLastUsed(key.ID)
68677f9… noreply 37
68677f9… noreply 38 ctx := context.WithValue(r.Context(), ctxAPIKey, key)
68677f9… noreply 39 next.ServeHTTP(w, r.WithContext(ctx))
2d8a379… lmata 40 })
68677f9… noreply 41 }
68677f9… noreply 42
68677f9… noreply 43 // requireScope returns middleware that rejects requests without the given scope.
68677f9… noreply 44 func (s *Server) requireScope(scope auth.Scope, next http.HandlerFunc) http.HandlerFunc {
68677f9… noreply 45 return func(w http.ResponseWriter, r *http.Request) {
68677f9… noreply 46 key := apiKeyFromContext(r.Context())
68677f9… noreply 47 if key == nil {
68677f9… noreply 48 writeError(w, http.StatusUnauthorized, "missing authentication")
68677f9… noreply 49 return
68677f9… noreply 50 }
68677f9… noreply 51 if !key.HasScope(scope) {
68677f9… noreply 52 writeError(w, http.StatusForbidden, "insufficient scope: requires "+string(scope))
68677f9… noreply 53 return
68677f9… noreply 54 }
68677f9… noreply 55 next(w, r)
68677f9… noreply 56 }
2d8a379… lmata 57 }
2d8a379… lmata 58
2d8a379… lmata 59 func bearerToken(r *http.Request) string {
2d8a379… lmata 60 auth := r.Header.Get("Authorization")
2d8a379… lmata 61 token, found := strings.CutPrefix(auth, "Bearer ")
2d8a379… lmata 62 if !found {
2d8a379… lmata 63 return ""
2d8a379… lmata 64 }
2d8a379… lmata 65 return strings.TrimSpace(token)
2d8a379… lmata 66 }

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button