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))) }) }