| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- package webhooks
- import (
- "crypto/hmac"
- "crypto/sha1"
- "crypto/sha256"
- "crypto/subtle"
- "encoding/hex"
- "net/http"
- "strings"
- "github.com/OliveTin/OliveTin/internal/config"
- log "github.com/sirupsen/logrus"
- )
- type AuthVerifier struct {
- config config.WebhookConfig
- }
- func NewAuthVerifier(cfg config.WebhookConfig) *AuthVerifier {
- return &AuthVerifier{config: cfg}
- }
- func (v *AuthVerifier) Verify(r *http.Request, payload []byte) bool {
- verifier := v.getVerifier()
- if verifier == nil {
- return v.handleUnknownAuthType()
- }
- return verifier(r, payload)
- }
- type authVerifierFunc func(*http.Request, []byte) bool
- func (v *AuthVerifier) getVerifier() authVerifierFunc {
- if v.config.AuthType == "" || v.config.AuthType == "none" {
- return func(_ *http.Request, _ []byte) bool {
- return true
- }
- }
- verifierMap := v.buildVerifierMap()
- if verifier, ok := verifierMap[v.config.AuthType]; ok {
- return verifier
- }
- return nil
- }
- func (v *AuthVerifier) buildVerifierMap() map[string]authVerifierFunc {
- return map[string]authVerifierFunc{
- "hmac-sha256": v.verifyHMAC256,
- "hmac-sha1": v.verifyHMAC1,
- "bearer": func(r *http.Request, _ []byte) bool {
- return v.verifyBearer(r)
- },
- "basic": func(r *http.Request, _ []byte) bool {
- return v.verifyBasic(r)
- },
- }
- }
- func (v *AuthVerifier) handleUnknownAuthType() bool {
- log.WithFields(log.Fields{
- "authType": v.config.AuthType,
- }).Warnf("Unknown auth type, rejecting")
- return false
- }
- func (v *AuthVerifier) verifyHMAC256(r *http.Request, payload []byte) bool {
- if v.config.Secret == "" {
- log.Warnf("HMAC-SHA256 auth requires secret")
- return false
- }
- headerName := v.config.AuthHeader
- if headerName == "" {
- headerName = "X-Webhook-Signature"
- }
- signature := r.Header.Get(headerName)
- if signature == "" {
- log.Debugf("Missing signature header: %s", headerName)
- return false
- }
- expectedSig := strings.TrimPrefix(signature, "sha256=")
- mac := hmac.New(sha256.New, []byte(v.config.Secret))
- mac.Write(payload)
- computedSig := hex.EncodeToString(mac.Sum(nil))
- return hmac.Equal([]byte(expectedSig), []byte(computedSig))
- }
- func (v *AuthVerifier) verifyHMAC1(r *http.Request, payload []byte) bool {
- if v.config.Secret == "" {
- log.Warnf("HMAC-SHA1 auth requires secret")
- return false
- }
- headerName := v.config.AuthHeader
- if headerName == "" {
- headerName = "X-Webhook-Signature"
- }
- signature := r.Header.Get(headerName)
- if signature == "" {
- log.Debugf("Missing signature header: %s", headerName)
- return false
- }
- expectedSig := strings.TrimPrefix(signature, "sha1=")
- mac := hmac.New(sha1.New, []byte(v.config.Secret))
- mac.Write(payload)
- computedSig := hex.EncodeToString(mac.Sum(nil))
- return hmac.Equal([]byte(expectedSig), []byte(computedSig))
- }
- func (v *AuthVerifier) verifyBearer(r *http.Request) bool {
- if v.config.Secret == "" {
- log.Warnf("Bearer auth requires secret")
- return false
- }
- authHeader := r.Header.Get("Authorization")
- if !strings.HasPrefix(authHeader, "Bearer ") {
- log.Debugf("Missing or invalid Bearer token")
- return false
- }
- token := strings.TrimPrefix(authHeader, "Bearer ")
- tokenBytes := []byte(token)
- secretBytes := []byte(v.config.Secret)
- return len(tokenBytes) == len(secretBytes) && subtle.ConstantTimeCompare(tokenBytes, secretBytes) == 1
- }
- func (v *AuthVerifier) verifyBasic(r *http.Request) bool {
- if v.config.Secret == "" {
- log.Warnf("Basic auth requires secret")
- return false
- }
- username, password, ok := r.BasicAuth()
- if !ok {
- log.Debugf("Missing Basic auth header")
- return false
- }
- return v.verifyBasicCredentials(username, password)
- }
- func (v *AuthVerifier) verifyBasicCredentials(username, password string) bool {
- parts := strings.SplitN(v.config.Secret, ":", 2)
- if len(parts) == 2 {
- return v.verifyBasicWithUsername(username, password, parts[0], parts[1])
- }
- return v.verifyBasicPasswordOnly(password)
- }
- func (v *AuthVerifier) verifyBasicWithUsername(username, password, expectedUsername, expectedPassword string) bool {
- usernameMatch := subtle.ConstantTimeCompare([]byte(username), []byte(expectedUsername))
- passwordMatch := subtle.ConstantTimeCompare([]byte(password), []byte(expectedPassword))
- return usernameMatch == 1 && passwordMatch == 1
- }
- func (v *AuthVerifier) verifyBasicPasswordOnly(password string) bool {
- return subtle.ConstantTimeCompare([]byte(password), []byte(v.config.Secret)) == 1
- }
|