package auth import ( "context" "errors" "net/http" ) type ctxKey struct{} // WithUser stores the verified identity on the request context. func WithUser(ctx context.Context, u User) context.Context { return context.WithValue(ctx, ctxKey{}, u) } // FromContext returns the identity attached by the middleware. func FromContext(ctx context.Context) (User, bool) { u, ok := ctx.Value(ctxKey{}).(User) return u, ok } // Middleware verifies the Authorization header and rejects requests that carry // no usable credential. type Middleware struct { verifier Verifier } // NewMiddleware returns middleware backed by the verifier. func NewMiddleware(v Verifier) *Middleware { return &Middleware{verifier: v} } // Wrap protects h, making the verified identity available to it. func (m *Middleware) Wrap(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token, err := BearerToken(r.Header.Get("Authorization")) if err != nil { unauthorized(w, err) return } user, err := m.verifier.Verify(r.Context(), token) if err != nil { unauthorized(w, err) return } next.ServeHTTP(w, r.WithContext(WithUser(r.Context(), user))) }) } // WrapFunc protects a handler function. func (m *Middleware) WrapFunc(next http.HandlerFunc) http.HandlerFunc { return m.Wrap(next).ServeHTTP } func unauthorized(w http.ResponseWriter, err error) { w.Header().Set("WWW-Authenticate", `Bearer realm="go-places"`) // The response deliberately does not echo the reason. if errors.Is(err, ErrMissingToken) { http.Error(w, "unauthorized: a bearer token is required", http.StatusUnauthorized) return } http.Error(w, "unauthorized: the token was rejected", http.StatusUnauthorized) }