github.com/bluestoneag/bluephish@v0.1.0/webhook/webhook.go (about) 1 package webhook 2 3 import ( 4 "bytes" 5 "crypto/hmac" 6 "crypto/sha256" 7 "encoding/hex" 8 "encoding/json" 9 "errors" 10 "fmt" 11 "net/http" 12 "time" 13 14 log "github.com/bluestoneag/bluephish/logger" 15 ) 16 17 const ( 18 19 // DefaultTimeoutSeconds is the number of seconds before a timeout occurs 20 // when sending a webhook 21 DefaultTimeoutSeconds = 10 22 23 // MinHTTPStatusErrorCode is the lower bound of HTTP status codes which 24 // indicate an error occurred 25 MinHTTPStatusErrorCode = 400 26 27 // SignatureHeader is the name of the HTTP header which contains the 28 // webhook signature 29 SignatureHeader = "X-Gophish-Signature" 30 31 // Sha256Prefix is the prefix that specifies the hashing algorithm used 32 // for the signature 33 Sha256Prefix = "sha256" 34 ) 35 36 // Sender represents a type which can send webhooks to an EndPoint 37 type Sender interface { 38 Send(endPoint EndPoint, data interface{}) error 39 } 40 41 type defaultSender struct { 42 client *http.Client 43 } 44 45 var senderInstance = &defaultSender{ 46 client: &http.Client{ 47 Timeout: time.Second * DefaultTimeoutSeconds, 48 CheckRedirect: func(req *http.Request, via []*http.Request) error { 49 return http.ErrUseLastResponse 50 }, 51 }, 52 } 53 54 // SetTransport sets the underlying transport for the default webhook client. 55 func SetTransport(tr *http.Transport) { 56 senderInstance.client.Transport = tr 57 } 58 59 // EndPoint represents a URL to send the webhook to, as well as a secret used 60 // to sign the event 61 type EndPoint struct { 62 URL string 63 Secret string 64 } 65 66 // Send sends data to a single EndPoint 67 func Send(endPoint EndPoint, data interface{}) error { 68 return senderInstance.Send(endPoint, data) 69 } 70 71 // SendAll sends data to multiple EndPoints 72 func SendAll(endPoints []EndPoint, data interface{}) { 73 for _, e := range endPoints { 74 go func(e EndPoint) { 75 senderInstance.Send(e, data) 76 }(e) 77 } 78 } 79 80 // Send contains the implementation of sending webhook to an EndPoint 81 func (ds defaultSender) Send(endPoint EndPoint, data interface{}) error { 82 jsonData, err := json.Marshal(data) 83 if err != nil { 84 log.Error(err) 85 return err 86 } 87 88 req, err := http.NewRequest("POST", endPoint.URL, bytes.NewBuffer(jsonData)) 89 if err != nil { 90 log.Error(err) 91 return err 92 } 93 signat, err := sign(endPoint.Secret, jsonData) 94 if err != nil { 95 log.Error(err) 96 return err 97 } 98 req.Header.Set(SignatureHeader, fmt.Sprintf("%s=%s", Sha256Prefix, signat)) 99 req.Header.Set("Content-Type", "application/json") 100 resp, err := ds.client.Do(req) 101 if err != nil { 102 log.Error(err) 103 return err 104 } 105 defer resp.Body.Close() 106 107 if resp.StatusCode >= MinHTTPStatusErrorCode { 108 errMsg := fmt.Sprintf("http status of response: %s", resp.Status) 109 log.Error(errMsg) 110 return errors.New(errMsg) 111 } 112 return nil 113 } 114 115 func sign(secret string, data []byte) (string, error) { 116 hash1 := hmac.New(sha256.New, []byte(secret)) 117 _, err := hash1.Write(data) 118 if err != nil { 119 return "", err 120 } 121 hexStr := hex.EncodeToString(hash1.Sum(nil)) 122 return hexStr, nil 123 }