Files
authkit/middleware.go
2026-06-18 16:28:33 +02:00

52 lines
1.5 KiB
Go

package authkit
import (
"context"
"fmt"
"net/http"
)
// ErrorWriter writes an auth failure response.
type ErrorWriter func(w http.ResponseWriter, status int, msg string)
// PlainError is the default ErrorWriter; writes plain text.
func PlainError(w http.ResponseWriter, status int, msg string) {
http.Error(w, msg, status)
}
// KeyResolver maps a plaintext key to a Caller.
// Return (nil, nil) for an unknown/invalid key (→ 401).
// Return a non-nil error only for an internal failure (→ 500).
type KeyResolver func(ctx context.Context, plaintextKey string) (*Caller, error)
// RequireScope enforces that the request carries a valid key with the given
// scope. On success it stores the Caller in the request context and calls next.
func RequireScope(scope string, resolve KeyResolver, errw ErrorWriter, next http.Handler) http.Handler {
if errw == nil {
errw = PlainError
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
key := ExtractAPIKey(r)
if key == "" {
errw(w, http.StatusUnauthorized, "missing api key")
return
}
caller, err := resolve(r.Context(), key)
if err != nil {
errw(w, http.StatusInternalServerError, "internal error")
return
}
if caller == nil {
errw(w, http.StatusUnauthorized, "invalid api key")
return
}
if !caller.Has(scope) {
errw(w, http.StatusForbidden, fmt.Sprintf("forbidden: missing scope %s", scope))
return
}
next.ServeHTTP(w, r.WithContext(WithCaller(r.Context(), caller)))
})
}