-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathexecution.go
124 lines (101 loc) · 3.3 KB
/
execution.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
package webhook
import (
"context"
"crypto/ed25519"
"net/http"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/pkg/errors"
"google.golang.org/protobuf/types/known/timestamppb"
messagingpb "github.com/code-payments/code-protobuf-api/generated/go/messaging/v1"
"github.com/code-payments/code-server/pkg/metrics"
"github.com/code-payments/code-server/pkg/code/common"
code_data "github.com/code-payments/code-server/pkg/code/data"
"github.com/code-payments/code-server/pkg/code/data/webhook"
"github.com/code-payments/code-server/pkg/code/server/grpc/messaging"
)
const (
metricsPackageName = "webhook"
contentTypeHeaderName = "Content-Type"
contentTypeHeaderValue = "application/jwt"
)
// Execute executes the provided webhook. It does not manage the DB record's state.
func Execute(
ctx context.Context,
data code_data.Provider,
messagingClient messaging.InternalMessageClient,
record *webhook.Record,
webhookTimeout time.Duration,
) error {
tracer := metrics.TraceMethodCall(ctx, metricsPackageName, "Execute")
defer tracer.End()
err := func() error {
//
// Part 1: Basic validation checks
//
if record.State != webhook.StatePending {
return errors.New("webhook is not in a pending state")
}
if record.NextAttemptAt == nil || record.NextAttemptAt.After(time.Now()) {
return errors.New("webhook is not scheduled yet")
}
// Assumes all webhooks are tied to an intent
rendezvousAccount, err := common.NewAccountFromPublicKeyString(record.WebhookId)
if err != nil {
return errors.Wrap(err, "webhook id is not a public key")
}
//
// Part 2: Generate the JWT HTTP request body
//
jsonPayloadProvider, ok := jsonPayloadProviders[record.Type]
if !ok {
return errors.Errorf("%d webhook type not supported", record.Type)
}
kvs, err := jsonPayloadProvider(ctx, data, record)
if err != nil {
return errors.Wrap(err, "error getting webhook content")
}
signer := common.GetSubsidizer()
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, jwt.MapClaims(kvs))
requestBody, err := token.SignedString(ed25519.PrivateKey(signer.PrivateKey().ToBytes()))
if err != nil {
return errors.Wrap(err, "error signing jwt")
}
//
// Part 3: Execute the HTTP POST
//
webhookReq, err := http.NewRequest(http.MethodPost, record.Url, strings.NewReader(requestBody))
if err != nil {
return errors.Wrap(err, "error creating http request")
}
webhookReq.Header.Set(contentTypeHeaderName, contentTypeHeaderValue)
webhookCtx, cancel := context.WithTimeout(context.Background(), webhookTimeout)
webhookReq = webhookReq.WithContext(webhookCtx)
defer cancel()
resp, err := http.DefaultClient.Do(webhookReq)
if err != nil {
return errors.Wrap(err, "error executing http post request")
} else if resp.StatusCode != http.StatusOK {
return errors.Errorf("%d status code returned", resp.StatusCode)
}
//
// Part 4: Notify the messaging stream on success
//
_, err = messagingClient.InternallyCreateMessage(ctx, rendezvousAccount, &messagingpb.Message{
Kind: &messagingpb.Message_WebhookCalled{
WebhookCalled: &messagingpb.WebhookCalled{
Timestamp: timestamppb.Now(),
},
},
})
if err != nil {
return errors.Wrap(err, "error creating notification message")
}
return nil
}()
if err != nil {
tracer.OnError(err)
}
return err
}