6b1bd7c812
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
52 lines
1.5 KiB
Go
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)))
|
|
})
|
|
}
|