middleware.go

  1package bearer
  2
  3import (
  4	"context"
  5	"fmt"
  6
  7	"github.com/aws/smithy-go/middleware"
  8	smithyhttp "github.com/aws/smithy-go/transport/http"
  9)
 10
 11// Message is the middleware stack's request transport message value.
 12type Message interface{}
 13
 14// Signer provides an interface for implementations to decorate a request
 15// message with a bearer token. The signer is responsible for validating the
 16// message type is compatible with the signer.
 17type Signer interface {
 18	SignWithBearerToken(context.Context, Token, Message) (Message, error)
 19}
 20
 21// AuthenticationMiddleware provides the Finalize middleware step for signing
 22// an request message with a bearer token.
 23type AuthenticationMiddleware struct {
 24	signer        Signer
 25	tokenProvider TokenProvider
 26}
 27
 28// AddAuthenticationMiddleware helper adds the AuthenticationMiddleware to the
 29// middleware Stack in the Finalize step with the options provided.
 30func AddAuthenticationMiddleware(s *middleware.Stack, signer Signer, tokenProvider TokenProvider) error {
 31	return s.Finalize.Add(
 32		NewAuthenticationMiddleware(signer, tokenProvider),
 33		middleware.After,
 34	)
 35}
 36
 37// NewAuthenticationMiddleware returns an initialized AuthenticationMiddleware.
 38func NewAuthenticationMiddleware(signer Signer, tokenProvider TokenProvider) *AuthenticationMiddleware {
 39	return &AuthenticationMiddleware{
 40		signer:        signer,
 41		tokenProvider: tokenProvider,
 42	}
 43}
 44
 45const authenticationMiddlewareID = "BearerTokenAuthentication"
 46
 47// ID returns the resolver identifier
 48func (m *AuthenticationMiddleware) ID() string {
 49	return authenticationMiddlewareID
 50}
 51
 52// HandleFinalize implements the FinalizeMiddleware interface in order to
 53// update the request with bearer token authentication.
 54func (m *AuthenticationMiddleware) HandleFinalize(
 55	ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler,
 56) (
 57	out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
 58) {
 59	token, err := m.tokenProvider.RetrieveBearerToken(ctx)
 60	if err != nil {
 61		return out, metadata, fmt.Errorf("failed AuthenticationMiddleware wrap message, %w", err)
 62	}
 63
 64	signedMessage, err := m.signer.SignWithBearerToken(ctx, token, in.Request)
 65	if err != nil {
 66		return out, metadata, fmt.Errorf("failed AuthenticationMiddleware sign message, %w", err)
 67	}
 68
 69	in.Request = signedMessage
 70	return next.HandleFinalize(ctx, in)
 71}
 72
 73// SignHTTPSMessage provides a bearer token authentication implementation that
 74// will sign the message with the provided bearer token.
 75//
 76// Will fail if the message is not a smithy-go HTTP request or the request is
 77// not HTTPS.
 78type SignHTTPSMessage struct{}
 79
 80// NewSignHTTPSMessage returns an initialized signer for HTTP messages.
 81func NewSignHTTPSMessage() *SignHTTPSMessage {
 82	return &SignHTTPSMessage{}
 83}
 84
 85// SignWithBearerToken returns a copy of the HTTP request with the bearer token
 86// added via the "Authorization" header, per RFC 6750, https://datatracker.ietf.org/doc/html/rfc6750.
 87//
 88// Returns an error if the request's URL scheme is not HTTPS, or the request
 89// message is not an smithy-go HTTP Request pointer type.
 90func (SignHTTPSMessage) SignWithBearerToken(ctx context.Context, token Token, message Message) (Message, error) {
 91	req, ok := message.(*smithyhttp.Request)
 92	if !ok {
 93		return nil, fmt.Errorf("expect smithy-go HTTP Request, got %T", message)
 94	}
 95
 96	if !req.IsHTTPS() {
 97		return nil, fmt.Errorf("bearer token with HTTP request requires HTTPS")
 98	}
 99
100	reqClone := req.Clone()
101	reqClone.Header.Set("Authorization", "Bearer "+token.Value)
102
103	return reqClone, nil
104}