forked from getlago/lago-go-client
-
Notifications
You must be signed in to change notification settings - Fork 0
/
webhook.go
133 lines (115 loc) · 3.28 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
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
128
129
130
131
132
133
package lago
import (
"context"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"net/http"
jwt "github.com/golang-jwt/jwt/v5"
)
type WebhookRequest struct {
client *Client
}
func (c *Client) Webhook() *WebhookRequest {
return &WebhookRequest{
client: c,
}
}
func (wr *WebhookRequest) GetPublicKey(ctx context.Context) (*rsa.PublicKey, *Error) {
clientRequest := &ClientRequest{
Path: "webhooks/public_key",
}
result, err := wr.client.Get(ctx, clientRequest)
if err != nil {
return nil, err
}
validatedResult, ok := result.(string)
if !ok {
return nil, &Error{
Err: errors.New("response is not a string"),
HTTPStatusCode: http.StatusInternalServerError,
Message: "response is not a string",
}
}
// Decode the base64-encoded key
bytesResult, decodeErr := base64.StdEncoding.DecodeString(validatedResult)
if err != nil {
return nil, &Error{
Err: decodeErr,
HTTPStatusCode: http.StatusInternalServerError,
Message: "cannot decode the key",
}
}
// Parse the PEM block
block, _ := pem.Decode(bytesResult)
if block == nil || block.Type != "PUBLIC KEY" {
return nil, &Error{
Err: errors.New("Failed to decode PEM block containing public key"),
HTTPStatusCode: http.StatusInternalServerError,
Message: "Failed to decode PEM block containing public key",
}
}
// Parse the DER-encoded public key
publicKey, parseErr := x509.ParsePKIXPublicKey(block.Bytes)
if parseErr != nil {
return nil, &Error{
Err: parseErr,
HTTPStatusCode: http.StatusInternalServerError,
Message: "Failed to to parse the public key",
}
}
rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
if !ok {
return nil, &Error{
Err: errors.New("Unexpected type of public key"),
HTTPStatusCode: http.StatusInternalServerError,
Message: "Unexpected type of public key",
}
}
return rsaPublicKey, nil
}
func (wr *WebhookRequest) parseSignature(ctx context.Context, signature string) (*jwt.Token, *Error) {
publicKey, err := wr.GetPublicKey(ctx)
if err != nil {
return nil, err
}
token, parseErr := jwt.Parse(signature, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return publicKey, nil
})
if parseErr != nil {
return nil, &Error{
Err: parseErr,
HTTPStatusCode: http.StatusInternalServerError,
Message: "cannot parse token",
}
}
return token, nil
}
func (wr *WebhookRequest) ValidateSignature(ctx context.Context, signature string) (bool, *Error) {
if token, err := wr.parseSignature(ctx, signature); err == nil && token.Valid {
return true, nil
} else {
return false, err
}
}
func (wr *WebhookRequest) ValidateBody(ctx context.Context, signature string, body string) (bool, *Error) {
if token, err := wr.parseSignature(ctx, signature); err == nil && token.Valid {
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return false, &Error{
Err: errors.New("error casting claims"),
HTTPStatusCode: http.StatusInternalServerError,
Message: "cannot parse token",
}
}
return claims["data"] == body, nil
} else {
return false, err
}
}