-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathiam-signed.go
127 lines (106 loc) · 4.02 KB
/
iam-signed.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package iamsigned
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"github.com/aws/aws-sdk-go/aws/credentials"
v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
"golang.org/x/net/context/ctxhttp"
)
type (
graphqlError struct {
Locations []graphqlErrorLocation `json:"locations"`
Message string `json:"message"`
}
graphqlErrorLocation struct {
Column int `json:"column"`
Line int `json:"line"`
}
graphqlResponse struct {
Data json.RawMessage `json:"data"`
Errors []graphqlError `json:"errors"`
}
)
type AWSService string
const (
AppSyncService AWSService = "appsync"
APIGatewayService AWSService = "execute-api"
)
// AppSync signs and send a request to appsync. It also parse the response and looks for graphql errors
func AppSync(payload []byte, endpoint, region string, creds *credentials.Credentials) ([]byte, error) {
return AppSyncWithContext(context.Background(), payload, endpoint, region, creds)
}
// AppSyncWithContext does the same as AppSyncDeliver, with a context.Context object
func AppSyncWithContext(ctx context.Context, payload []byte, endpoint, region string, creds *credentials.Credentials) ([]byte, error) {
body, err := deliverWithContext(ctx, payload, AppSyncService, endpoint, region, http.MethodPost, creds)
if err != nil {
return nil, err
}
return ParseGraphQLResponse(body)
}
// APIGatewayWithContext does the same as APIGatewayDeliver, with a context.Context object
func APIGatewayWithContext(ctx context.Context, payload []byte, endpoint, region, method string, creds *credentials.Credentials) ([]byte, error) {
body, err := deliverWithContext(ctx, payload, APIGatewayService, endpoint, region, method, creds)
if err != nil {
return nil, err
}
buf := new(bytes.Buffer)
if _, err := buf.ReadFrom(body); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// APIGateway signs and sends a request to API Gateway
func APIGateway(payload []byte, endpoint, region, method string, creds *credentials.Credentials) ([]byte, error) {
return APIGatewayWithContext(context.Background(), payload, endpoint, region, method, creds)
}
// ParseGraphQLResponse attempts to read the response, and extract grpahql-formatted errors
func ParseGraphQLResponse(body io.ReadCloser) (json.RawMessage, error) {
var parsed graphqlResponse
buf := new(bytes.Buffer)
if _, err := buf.ReadFrom(body); err != nil {
return []byte{}, fmt.Errorf("could not read buffer: %w", err)
}
if err := json.Unmarshal(buf.Bytes(), &parsed); err != nil {
newStr := buf.String()
return []byte{}, fmt.Errorf("could not parse response '%s': %w", newStr, err)
}
if len(parsed.Errors) > 0 {
errStr := fmt.Sprintf("GraphQL returned %v error(s)", len(parsed.Errors))
for _, err := range parsed.Errors {
errStr += fmt.Sprintf("\n %+v: %s", err.Locations, err.Message)
}
return parsed.Data, fmt.Errorf(errStr)
}
return parsed.Data, nil
}
func deliver(ctx context.Context, payload []byte, service AWSService, endpoint, region, method string, creds *credentials.Credentials) (io.ReadCloser, error) {
return deliverWithContext(context.Background(), payload, service, endpoint, region, method, creds)
}
func deliverWithContext(ctx context.Context, payload []byte, service AWSService, endpoint, region, method string, creds *credentials.Credentials) (io.ReadCloser, error) {
// Create http request
req, err := http.NewRequest(method, endpoint, bytes.NewBuffer(payload))
if err != nil {
return nil, fmt.Errorf("could not create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
// Sign the request
signer := v4.NewSigner(creds)
_, err = signer.Sign(req, bytes.NewReader(payload), string(service), region, time.Now())
if err != nil {
return nil, fmt.Errorf("failed to sign the request: %w", err)
}
// Fire !
response, err := ctxhttp.Do(ctx, nil, req)
if err != nil {
return nil, fmt.Errorf("could not send request: %w", err)
}
if response.StatusCode != 200 {
return nil, fmt.Errorf("received status code %v", response.StatusCode)
}
return response.Body, nil
}