hook.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. package hook
  2. import (
  3. "bytes"
  4. "crypto/hmac"
  5. "crypto/sha256"
  6. "encoding/hex"
  7. "encoding/json"
  8. "fmt"
  9. "net/http"
  10. "time"
  11. )
  12. type HookClient struct {
  13. BaseURL string
  14. Domain string
  15. SecretKey string
  16. HTTPClient *http.Client
  17. }
  18. type HookPayload struct {
  19. Email string `json:"email"`
  20. Domain string `json:"domain"`
  21. Action string `json:"action"`
  22. Timestamp time.Time `json:"timestamp"`
  23. }
  24. func NewHookClient(baseURL, domain, secretKey string) *HookClient {
  25. return &HookClient{
  26. BaseURL: baseURL,
  27. Domain: domain,
  28. SecretKey: secretKey,
  29. HTTPClient: &http.Client{
  30. Timeout: 10 * time.Second,
  31. },
  32. }
  33. }
  34. func (c *HookClient) SendWebhook(email, action string) error {
  35. payload := HookPayload{
  36. Email: email,
  37. Domain: c.Domain,
  38. Action: action,
  39. Timestamp: time.Now(),
  40. }
  41. payloadBytes, err := json.Marshal(payload)
  42. if err != nil {
  43. return fmt.Errorf("erreur de marshalling: %w", err)
  44. }
  45. // Calcul de la signature
  46. signature := c.calculateHMAC(payloadBytes)
  47. // Création de la requête
  48. req, err := http.NewRequest(
  49. "POST",
  50. fmt.Sprintf("%s/webhook/user-sync", c.BaseURL),
  51. bytes.NewBuffer(payloadBytes),
  52. )
  53. if err != nil {
  54. return fmt.Errorf("erreur de création de requête: %w", err)
  55. }
  56. // Ajout des headers
  57. req.Header.Set("Content-Type", "application/json")
  58. req.Header.Set("X-Webhook-Signature", signature)
  59. req.Header.Set("X-Webhook-Domain", c.Domain)
  60. // Envoi de la requête avec retry
  61. var lastErr error
  62. for retry := 0; retry < 3; retry++ {
  63. resp, err := c.HTTPClient.Do(req)
  64. if err != nil {
  65. lastErr = err
  66. time.Sleep(time.Duration(retry+1) * time.Second)
  67. continue
  68. }
  69. defer resp.Body.Close()
  70. if resp.StatusCode == http.StatusOK {
  71. return nil
  72. }
  73. if resp.StatusCode == http.StatusTooManyRequests {
  74. // Attendre avant de réessayer
  75. time.Sleep(time.Duration(retry+1) * time.Second)
  76. continue
  77. }
  78. // Erreur définitive
  79. if resp.StatusCode >= 400 && resp.StatusCode != http.StatusTooManyRequests {
  80. return fmt.Errorf("erreur HTTP %d", resp.StatusCode)
  81. }
  82. }
  83. return fmt.Errorf("échec après 3 tentatives: %w", lastErr)
  84. }
  85. func (c *HookClient) calculateHMAC(message []byte) string {
  86. mac := hmac.New(sha256.New, []byte(c.SecretKey))
  87. mac.Write(message)
  88. return hex.EncodeToString(mac.Sum(nil))
  89. }
  90. func (c *HookClient) Close() error {
  91. if c.HTTPClient != nil {
  92. // Close idle connections
  93. if transport, ok := c.HTTPClient.Transport.(*http.Transport); ok {
  94. transport.CloseIdleConnections()
  95. }
  96. // Clear the client
  97. c.HTTPClient = nil
  98. }
  99. return nil
  100. }