middleware.go raw

   1  package bearer
   2  
   3  import (
   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.
  12  type 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.
  17  type 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.
  23  type 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.
  30  func 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.
  38  func NewAuthenticationMiddleware(signer Signer, tokenProvider TokenProvider) *AuthenticationMiddleware {
  39  	return &AuthenticationMiddleware{
  40  		signer:        signer,
  41  		tokenProvider: tokenProvider,
  42  	}
  43  }
  44  
  45  const authenticationMiddlewareID = "BearerTokenAuthentication"
  46  
  47  // ID returns the resolver identifier
  48  func (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.
  54  func (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.
  78  type SignHTTPSMessage struct{}
  79  
  80  // NewSignHTTPSMessage returns an initialized signer for HTTP messages.
  81  func 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.
  90  func (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  }
 105