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}