-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwebhook.go
More file actions
134 lines (120 loc) · 3.8 KB
/
webhook.go
File metadata and controls
134 lines (120 loc) · 3.8 KB
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
134
package onfido
import (
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"os"
"time"
)
// Constants
const (
WebhookSignatureHeader = "X-Signature"
WebhookTokenEnv = "ONFIDO_WEBHOOK_TOKEN"
)
// Webhook errors
var (
ErrInvalidWebhookSignature = errors.New("invalid request, payload hash doesn't match signature")
ErrMissingWebhookToken = errors.New("webhook token not found in environmental variable")
)
// Webhook represents a webhook handler
type Webhook struct {
Token string
SkipSignatureValidation bool
}
// WebhookRequest represents an incoming webhook request from Onfido
type WebhookRequest struct {
Payload struct {
ResourceType string `json:"resource_type"`
Action string `json:"action"`
Object struct {
ID string `json:"id"`
Status string `json:"status"`
CompletedAtIso8601 time.Time `json:"completed_at_iso8601"`
Href string `json:"href"`
} `json:"object"`
Resource struct {
CreatedAt time.Time `json:"created_at"`
Error interface{} `json:"error"`
Link struct {
CompletedRedirectURL interface{} `json:"completed_redirect_url"`
URL string `json:"url"`
Language interface{} `json:"language"`
ExpiresAt interface{} `json:"expires_at"`
ExpiredRedirectURL interface{} `json:"expired_redirect_url"`
} `json:"link"`
UpdatedAt time.Time `json:"updated_at"`
WorkflowID string `json:"workflow_id"`
Status string `json:"status"`
Output struct {
DateOfBirth string `json:"date_of_birth"`
DocumentIssuingCountry string `json:"document_issuing_country"`
DocumentMediaIds []struct {
ID string `json:"id"`
} `json:"document_media_ids"`
DocumentNumber string `json:"document_number"`
DocumentType string `json:"document_type"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
SelfieMediaIds []struct {
ID string `json:"id"`
} `json:"selfie_media_ids"`
} `json:"output"`
DashboardURL string `json:"dashboard_url"`
WorkflowVersionID int `json:"workflow_version_id"`
ID string `json:"id"`
ApplicantID string `json:"applicant_id"`
Reasons []interface{} `json:"reasons"`
} `json:"resource"`
} `json:"payload"`
}
// NewWebhookFromEnv creates a new webhook handler using
// configuration from environment variables.
func NewWebhookFromEnv() (*Webhook, error) {
token := os.Getenv(WebhookTokenEnv)
if token == "" {
return nil, ErrMissingWebhookToken
}
return NewWebhook(token), nil
}
// NewWebhook creates a new webhook handler
func NewWebhook(token string) *Webhook {
return &Webhook{
Token: token,
}
}
// ValidateSignature validates the request body against the signature header.
func (wh *Webhook) ValidateSignature(body []byte, signature string) error {
mac := hmac.New(sha1.New, []byte(wh.Token))
if _, err := mac.Write(body); err != nil {
return err
}
sig, err := hex.DecodeString(signature)
if err != nil || !hmac.Equal(sig, mac.Sum(nil)) {
return ErrInvalidWebhookSignature
}
return nil
}
// ParseFromRequest parses the webhook request body and returns
// it as WebhookRequest if the request signature is valid.
func (wh *Webhook) ParseFromRequest(req *http.Request) (*WebhookRequest, error) {
signature := req.Header.Get(WebhookSignatureHeader)
body, err := ioutil.ReadAll(req.Body)
if err != nil {
return nil, err
}
defer req.Body.Close()
if !wh.SkipSignatureValidation {
if err := wh.ValidateSignature(body, signature); err != nil {
return nil, err
}
}
var wr WebhookRequest
if err := json.Unmarshal(body, &wr); err != nil {
return nil, err
}
return &wr, nil
}