-
Notifications
You must be signed in to change notification settings - Fork 1
/
reciever.go
90 lines (81 loc) · 2.41 KB
/
reciever.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
package qstash
import (
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"github.com/golang-jwt/jwt/v5"
"os"
"strings"
"time"
)
var (
ErrInvalidSignature = fmt.Errorf("failed to validate signature")
)
// Receiver offers a simple way to verify the signature of a request.
type Receiver struct {
CurrentSigningKey string
NextSigningKey string
}
func NewReceiverWithEnv() *Receiver {
return &Receiver{
CurrentSigningKey: os.Getenv(currentSigningKeyEnvProperty),
NextSigningKey: os.Getenv(nextSigningKeyEnvProperty),
}
}
func NewReceiver(currentSigningKey, nextSigningKey string) *Receiver {
return &Receiver{
CurrentSigningKey: currentSigningKey,
NextSigningKey: nextSigningKey,
}
}
type claims struct {
Body string `json:"body"`
jwt.RegisteredClaims
}
func verify(key string, opts VerifyOptions) (err error) {
token, err := jwt.ParseWithClaims(opts.Signature, &claims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, ErrInvalidSignature
}
return []byte(key), nil
}, jwt.WithLeeway(opts.Tolerance), jwt.WithIssuer("Upstash"))
if err != nil {
return ErrInvalidSignature
}
c, ok := token.Claims.(*claims)
if !ok {
return ErrInvalidSignature
}
if opts.Url != "" && c.Subject != opts.Url {
return ErrInvalidSignature
}
h := sha256.New()
h.Write([]byte(opts.Body))
bHash := h.Sum(nil)
b64hash := strings.Trim(base64.URLEncoding.EncodeToString(bHash), "=")
if strings.Trim(c.Body, "=") != b64hash {
return ErrInvalidSignature
}
return nil
}
type VerifyOptions struct {
// Signature is the signature from the `Upstash-Signature` header.
Signature string
// Url is the address of the endpoint where the request was sent to. When set to `None`, url is not check.
Url string
// Body is the raw request body.
Body string
// Tolerance is the duration to tolerate when checking `nbf` and `exp` claims, to deal with small clock differences among different servers.
Tolerance time.Duration
}
// Verify verifies the signature of a request.
// It tries to verify the signature with the current signing key.
// If that fails, maybe because you have rotated the keys recently, it will try to verify the signature with the next signing key.
func (r *Receiver) Verify(opts VerifyOptions) (err error) {
err = verify(r.CurrentSigningKey, opts)
if errors.Is(err, ErrInvalidSignature) {
err = verify(r.NextSigningKey, opts)
}
return
}