-
Notifications
You must be signed in to change notification settings - Fork 1
/
webhook.go
99 lines (86 loc) · 2.63 KB
/
webhook.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
// File generated from our OpenAPI spec by Stainless.
package lithic
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/lithic-com/lithic-go/option"
)
// WebhookService contains methods and other services that help with interacting
// with the lithic API. Note, unlike clients, this service does not read variables
// from the environment automatically. You should not instantiate this service
// directly, and instead use the [NewWebhookService] method instead.
type WebhookService struct {
Options []option.RequestOption
}
// NewWebhookService generates a new service that applies the given options to each
// request. These options are applied after the parent client's options (if there
// is one), and before any request-specific options.
func NewWebhookService(opts ...option.RequestOption) (r *WebhookService) {
r = &WebhookService{}
r.Options = opts
return
}
// Validates whether or not the webhook payload was sent by Lithic.
//
// An error will be raised if the webhook payload was not sent by Lithic.
func (r *WebhookService) VerifySignature(payload []byte, headers http.Header, secret string, now time.Time) (err error) {
whsecret, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(secret, "whsec_"))
if err != nil {
return fmt.Errorf("invalid webhook secret: %s", err)
}
id := headers.Get("webhook-id")
if len(id) == 0 {
return errors.New("could not find webhook-id header")
}
sign := headers.Values("webhook-signature")
if len(sign) == 0 {
return errors.New("could not find webhook-signature header")
}
unixtime := headers.Get("webhook-timestamp")
if len(unixtime) == 0 {
return errors.New("could not find webhook-timestamp header")
}
timestamp, err := strconv.ParseInt(unixtime, 10, 64)
if err != nil {
return fmt.Errorf("invalid signature headers: %s", err)
}
if timestamp < now.Unix()-300 {
return errors.New("webhook timestamp too old")
}
if timestamp > now.Unix()+300 {
return errors.New("webhook timestamp too new")
}
mac := hmac.New(sha256.New, whsecret)
mac.Write([]byte(id))
mac.Write([]byte("."))
mac.Write([]byte(unixtime))
mac.Write([]byte("."))
mac.Write(payload)
expected := mac.Sum(nil)
for _, part := range sign {
parts := strings.Split(part, ",")
if len(parts) != 2 {
continue
}
if parts[0] != "v1" {
continue
}
signature, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil {
continue
}
if hmac.Equal(signature, expected) {
return nil
}
}
return errors.New("None of the given webhook signatures match the expected signature")
}
type WebhookVerifySignatureParams struct {
}