authkit: initial shared auth module
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
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)))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user