func (a *Authenticator) Authorizer(scope []string, force, loadClient, loadResourceOwner bool) func(http.Handler) http.Handler {
	scopeStr := oauth2.Scope(scope).String()
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			tracer, rcx := xo.CreateTracer(r.Context(), "flame/Authenticator.Authorizer")
			tracer.Tag("scope", scopeStr)
			tracer.Tag("force", force)
			tracer.Tag("loadClient", loadClient)
			tracer.Tag("loadResourceOwner", loadResourceOwner)
			defer tracer.End()
			r = r.WithContext(rcx)
			if !force && r.Header.Get("Authorization") == "" {
				next.ServeHTTP(w, r)
				return
			}
			defer xo.Resume(func(err error) {
				var bearerError *oauth2.Error
				if errors.As(err, &bearerError) {
					_ = oauth2.WriteBearerError(w, bearerError)
					return
				}
				tracer.Record(err)
				if a.reporter != nil {
					a.reporter(err)
				}
				_ = oauth2.WriteBearerError(w, oauth2.ServerError(""))
			})
			tk, err := oauth2.ParseBearerToken(r)
			xo.AbortIf(err)
			key, err := a.policy.Verify(tk)
			if heat.ErrExpiredToken.Is(err) {
				xo.Abort(oauth2.InvalidToken("expired bearer token"))
			} else if err != nil {
				xo.Abort(oauth2.InvalidToken("malformed bearer token"))
			}
			ctx := &Context{
				Context: rcx,
				Request: r,
				writer:  w,
				Tracer:  tracer,
			}
			accessToken := a.getToken(ctx, key.Base.ID)
			if accessToken == nil {
				xo.Abort(oauth2.InvalidToken("unknown bearer token"))
			}
			data := accessToken.GetTokenData()
			if data.Type != AccessToken {
				xo.Abort(oauth2.InvalidToken("invalid bearer token type"))
			}
			if data.ExpiresAt.Before(time.Now()) {
				xo.Abort(oauth2.InvalidToken("expired access token"))
			}
			if !data.Scope.Includes(scope) {
				xo.Abort(oauth2.InsufficientScope(scope))
			}
			rcx = context.WithValue(rcx, AccessTokenContextKey, accessToken)
			if !loadClient {
				next.ServeHTTP(w, r.WithContext(rcx))
				return
			}
			client := a.getFirstClient(ctx, data.ClientID)
			if client == nil {
				xo.Abort(xo.F("missing client"))
			}
			rcx = context.WithValue(rcx, ClientContextKey, client)
			if data.ResourceOwnerID == nil || !loadResourceOwner {
				next.ServeHTTP(w, r.WithContext(rcx))
				return
			}
			resourceOwner := a.getFirstResourceOwner(ctx, client, *data.ResourceOwnerID)
			if resourceOwner == nil {
				xo.Abort(oauth2.InvalidToken("missing resource owner"))
			}
			rcx = context.WithValue(rcx, ResourceOwnerContextKey, resourceOwner)
			next.ServeHTTP(w, r.WithContext(rcx))
		})
	}
}
Authorizer returns a middleware that can be used to authorize a request by
requiring an access token with the provided scope to be granted.
package flame
import (
	"context"
	"errors"
	"net/http"
	"strings"
	"time"
	"github.com/256dpi/oauth2/v2"
	"github.com/256dpi/xo"
	"go.mongodb.org/mongo-driver/bson"
	"github.com/256dpi/fire/coal"
	"github.com/256dpi/fire/heat"
)
type ctxKey string
const (
	AccessTokenContextKey = ctxKey("access-token")
	ClientContextKey = ctxKey("client")
	ResourceOwnerContextKey = ctxKey("resource-owner")
)
type Authenticator struct {
	store    *coal.Store
	policy   *Policy
	reporter func(error)
}
func NewAuthenticator(store *coal.Store, policy *Policy, reporter func(error)) *Authenticator {
	return &Authenticator{
		store:    store,
		policy:   policy,
		reporter: reporter,
	}
}
func (a *Authenticator) Endpoint(prefix string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		tracer, rcx := xo.CreateTracer(r.Context(), "flame/Authenticator.Endpoint")
		tracer.Tag("prefix", prefix)
		defer tracer.End()
		r = r.WithContext(rcx)
		defer xo.Resume(func(err error) {
			var oauth2Error *oauth2.Error
			if errors.As(err, &oauth2Error) {
				_ = oauth2.WriteError(w, oauth2Error)
				return
			}
			tracer.Record(err)
			if a.reporter != nil {
				a.reporter(err)
			}
			_ = oauth2.WriteError(w, oauth2.ServerError(""))
		})
		s := strings.Split(strings.Trim(strings.TrimPrefix(r.URL.Path, prefix), "/"), "/")
		if len(s) != 1 {
			w.WriteHeader(http.StatusNotFound)
			return
		}
		ctx := &Context{
			Context: rcx,
			Request: r,
			writer:  w,
			Tracer:  tracer,
		}
		switch s[0] {
		case "authorize":
			a.authorizationEndpoint(ctx)
		case "token":
			a.tokenEndpoint(ctx)
		case "revoke":
			a.revocationEndpoint(ctx)
		case "introspect":
			a.introspectionEndpoint(ctx)
		default:
			w.WriteHeader(http.StatusNotFound)
		}
	})
}