Files
Pulse/internal/api/notifications.go
rcourtman 6a48c759e8 Fix critical notification system bugs and security issues
This commit addresses multiple critical issues identified in the notification
system audit conducted with Codex:

**Critical Fixes:**

1. **Queue Retry Logic (Critical #1)**
   - Fixed broken retry/DLQ system where send functions never returned errors
   - Made sendGroupedEmail(), sendGroupedWebhook(), sendGroupedApprise() return errors
   - Made sendWebhookRequest() return errors
   - ProcessQueuedNotification() now properly propagates errors to queue
   - Retry logic and DLQ now function correctly

2. **Attempt Counter Bug (Critical #2)**
   - Fixed double-increment bug in queue processing
   - Separated UpdateStatus() from attempt tracking
   - Added IncrementAttempt() method
   - Notifications now get correct number of retry attempts

3. **Secret Exposure (Critical #3 & #4)**
   - Masked webhook headers and customFields in GET /api/notifications/webhooks
   - Added redactSecretsFromURL() to sanitize webhook URLs in history
   - Truncated/redacted response bodies in webhook history
   - Protected against credential harvesting via API

4. **Email Rate Limiting (Critical #5)**
   - Added emailManager field to NotificationManager
   - Shared EnhancedEmailManager instance across sends
   - Rate limiter now accumulates across multiple emails
   - SMTP rate limits are now enforced correctly

5. **SSRF Protection (High #6)**
   - Added DNS resolution of webhook URLs
   - Added isPrivateIP() check using CIDR ranges
   - Blocks all private IP ranges (10/8, 172.16/12, 192.168/16, 127/8, 169.254/16)
   - Blocks IPv6 private ranges (::1, fe80::/10, fc00::/7)
   - Prevents DNS rebinding attacks
   - Returns error instead of warning for private IPs

**New Features:**

6. **Health Endpoint (High #8)**
   - Added GET /api/notifications/health
   - Returns queue stats (pending, sending, sent, failed, dlq)
   - Shows email/webhook configuration status
   - Provides overall health indicator

**Related to notification system audit**

Files changed:
- internal/notifications/notifications.go: Error returns, rate limiting, SSRF hardening
- internal/notifications/queue.go: Attempt tracking fix
- internal/api/notifications.go: Secret masking, health endpoint
2025-11-06 23:26:03 +00:00

753 lines
24 KiB
Go

package api
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
"github.com/rcourtman/pulse-go-rewrite/internal/notifications"
"github.com/rcourtman/pulse-go-rewrite/internal/utils"
"github.com/rs/zerolog/log"
)
// NotificationHandlers handles notification-related HTTP endpoints
type NotificationHandlers struct {
monitor *monitoring.Monitor
}
// NewNotificationHandlers creates new notification handlers
func NewNotificationHandlers(monitor *monitoring.Monitor) *NotificationHandlers {
return &NotificationHandlers{
monitor: monitor,
}
}
// SetMonitor updates the monitor reference for notification handlers.
func (h *NotificationHandlers) SetMonitor(m *monitoring.Monitor) {
h.monitor = m
}
// GetEmailConfig returns the current email configuration
func (h *NotificationHandlers) GetEmailConfig(w http.ResponseWriter, r *http.Request) {
config := h.monitor.GetNotificationManager().GetEmailConfig()
// For security, don't return the password
config.Password = ""
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(config)
}
// UpdateEmailConfig updates the email configuration
func (h *NotificationHandlers) UpdateEmailConfig(w http.ResponseWriter, r *http.Request) {
// Read raw body for debugging
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// NEVER log the body as it contains passwords
log.Info().
Msg("Received email config update")
var config notifications.EmailConfig
if err := json.Unmarshal(body, &config); err != nil {
log.Error().Err(err).Msg("Failed to parse email config") // Don't log body with passwords
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// If password is empty, preserve the existing password
if config.Password == "" {
existingConfig := h.monitor.GetNotificationManager().GetEmailConfig()
config.Password = existingConfig.Password
}
log.Info().
Bool("enabled", config.Enabled).
Str("smtp", config.SMTPHost).
Str("from", config.From).
Int("toCount", len(config.To)).
Bool("hasPassword", config.Password != "").
Msg("Parsed email config")
h.monitor.GetNotificationManager().SetEmailConfig(config)
// Save to persistent storage
if err := h.monitor.GetConfigPersistence().SaveEmailConfig(config); err != nil {
// Log error but don't fail the request
log.Error().Err(err).Msg("Failed to save email configuration")
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
}
// GetAppriseConfig returns the current Apprise configuration.
func (h *NotificationHandlers) GetAppriseConfig(w http.ResponseWriter, r *http.Request) {
config := h.monitor.GetNotificationManager().GetAppriseConfig()
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(config); err != nil {
log.Error().Err(err).Msg("Failed to encode Apprise configuration response")
}
}
// UpdateAppriseConfig updates the Apprise configuration.
func (h *NotificationHandlers) UpdateAppriseConfig(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
var config notifications.AppriseConfig
if err := json.Unmarshal(body, &config); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
log.Info().
Bool("enabled", config.Enabled).
Str("mode", string(config.Mode)).
Int("targetCount", len(config.Targets)).
Str("cliPath", config.CLIPath).
Str("serverUrl", config.ServerURL).
Str("configKey", config.ConfigKey).
Bool("hasApiKey", config.APIKey != "").
Str("apiKeyHeader", config.APIKeyHeader).
Bool("skipTlsVerify", config.SkipTLSVerify).
Int("timeoutSeconds", config.TimeoutSeconds).
Msg("Parsed Apprise configuration update")
h.monitor.GetNotificationManager().SetAppriseConfig(config)
if err := h.monitor.GetConfigPersistence().SaveAppriseConfig(config); err != nil {
log.Error().Err(err).Msg("Failed to save Apprise configuration")
}
normalized := h.monitor.GetNotificationManager().GetAppriseConfig()
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(normalized); err != nil {
log.Error().Err(err).Msg("Failed to encode Apprise configuration response")
}
}
// GetWebhooks returns all webhook configurations with secrets masked
func (h *NotificationHandlers) GetWebhooks(w http.ResponseWriter, r *http.Request) {
webhooks := h.monitor.GetNotificationManager().GetWebhooks()
// Mask sensitive fields in headers and customFields
maskedWebhooks := make([]map[string]interface{}, len(webhooks))
for i, webhook := range webhooks {
whMap := map[string]interface{}{
"id": webhook.ID,
"name": webhook.Name,
"url": webhook.URL,
"method": webhook.Method,
"enabled": webhook.Enabled,
"service": webhook.Service,
}
// Mask headers - only show keys, not values
if webhook.Headers != nil && len(webhook.Headers) > 0 {
maskedHeaders := make(map[string]string)
for key := range webhook.Headers {
maskedHeaders[key] = "***REDACTED***"
}
whMap["headers"] = maskedHeaders
}
// Mask custom fields - only show keys, not values
if webhook.CustomFields != nil && len(webhook.CustomFields) > 0 {
maskedFields := make(map[string]string)
for key := range webhook.CustomFields {
maskedFields[key] = "***REDACTED***"
}
whMap["customFields"] = maskedFields
}
// Include template if present
if webhook.Template != "" {
whMap["template"] = webhook.Template
}
maskedWebhooks[i] = whMap
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(maskedWebhooks)
}
// CreateWebhook creates a new webhook
func (h *NotificationHandlers) CreateWebhook(w http.ResponseWriter, r *http.Request) {
// Read the raw body to preserve all fields
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
var webhook notifications.WebhookConfig
if err := json.Unmarshal(bodyBytes, &webhook); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Validate webhook URL
if err := notifications.ValidateWebhookURL(webhook.URL); err != nil {
http.Error(w, fmt.Sprintf("Invalid webhook URL: %v", err), http.StatusBadRequest)
return
}
// Generate ID if not provided
if webhook.ID == "" {
webhook.ID = utils.GenerateID("webhook")
}
h.monitor.GetNotificationManager().AddWebhook(webhook)
// Save webhooks to persistent storage with all fields
webhooks := h.monitor.GetNotificationManager().GetWebhooks()
if err := h.monitor.GetConfigPersistence().SaveWebhooks(webhooks); err != nil {
log.Error().Err(err).Msg("Failed to save webhooks")
}
// Return the full webhook data including any extra fields like 'service'
var responseData map[string]interface{}
if err := json.Unmarshal(bodyBytes, &responseData); err != nil {
log.Warn().Err(err).Msg("Failed to unmarshal webhook response data")
responseData = make(map[string]interface{})
}
responseData["id"] = webhook.ID
if err := utils.WriteJSONResponse(w, responseData); err != nil {
log.Error().Err(err).Msg("Failed to write webhook creation response")
}
}
// UpdateWebhook updates an existing webhook
func (h *NotificationHandlers) UpdateWebhook(w http.ResponseWriter, r *http.Request) {
// Extract webhook ID from URL path
// Path is like /api/notifications/webhooks/{id} after routing
path := strings.TrimPrefix(r.URL.Path, "/api/notifications/webhooks/")
webhookID := path
if webhookID == "" {
http.Error(w, "Invalid URL - missing webhook ID", http.StatusBadRequest)
return
}
// Read the raw body to preserve all fields
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
var webhook notifications.WebhookConfig
if err := json.Unmarshal(bodyBytes, &webhook); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Validate webhook URL
if err := notifications.ValidateWebhookURL(webhook.URL); err != nil {
http.Error(w, fmt.Sprintf("Invalid webhook URL: %v", err), http.StatusBadRequest)
return
}
webhook.ID = webhookID
if err := h.monitor.GetNotificationManager().UpdateWebhook(webhookID, webhook); err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
// Save webhooks to persistent storage
webhooks := h.monitor.GetNotificationManager().GetWebhooks()
if err := h.monitor.GetConfigPersistence().SaveWebhooks(webhooks); err != nil {
log.Error().Err(err).Msg("Failed to save webhooks")
}
// Return the full webhook data including any extra fields like 'service'
var responseData map[string]interface{}
if err := json.Unmarshal(bodyBytes, &responseData); err != nil {
log.Warn().Err(err).Msg("Failed to unmarshal webhook response data")
responseData = make(map[string]interface{})
}
responseData["id"] = webhookID
if err := utils.WriteJSONResponse(w, responseData); err != nil {
log.Error().Err(err).Str("webhookID", webhookID).Msg("Failed to write webhook update response")
}
}
// DeleteWebhook deletes a webhook
func (h *NotificationHandlers) DeleteWebhook(w http.ResponseWriter, r *http.Request) {
// Extract webhook ID from URL path
// Path is like /api/notifications/webhooks/{id} after routing
path := strings.TrimPrefix(r.URL.Path, "/api/notifications/webhooks/")
webhookID := path
if webhookID == "" {
http.Error(w, "Invalid URL - missing webhook ID", http.StatusBadRequest)
return
}
if err := h.monitor.GetNotificationManager().DeleteWebhook(webhookID); err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
// Save webhooks to persistent storage
webhooks := h.monitor.GetNotificationManager().GetWebhooks()
if err := h.monitor.GetConfigPersistence().SaveWebhooks(webhooks); err != nil {
log.Error().Err(err).Msg("Failed to save webhooks")
}
if err := utils.WriteJSONResponse(w, map[string]string{"status": "success"}); err != nil {
log.Error().Err(err).Str("webhookID", webhookID).Msg("Failed to write webhook deletion response")
}
}
// TestNotification sends a test notification
func (h *NotificationHandlers) TestNotification(w http.ResponseWriter, r *http.Request) {
// Read body for debugging
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// NEVER log the body as it contains passwords
log.Info().
Msg("Test notification request received")
var req struct {
Method string `json:"method"` // "email" or "webhook"
Type string `json:"type"` // Alternative field name used by frontend
Config *notifications.EmailConfig `json:"config,omitempty"` // Optional config for testing
WebhookID string `json:"webhookId,omitempty"` // For webhook testing
}
if err := json.Unmarshal(body, &req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Support both "method" and "type" field names
if req.Method == "" && req.Type != "" {
req.Method = req.Type
}
// Get actual node info from monitor state
state := h.monitor.GetState()
var nodeInfo *notifications.TestNodeInfo
// Use first available node and instance
if len(state.Nodes) > 0 {
for _, node := range state.Nodes {
nodeInfo = &notifications.TestNodeInfo{
NodeName: node.Name,
InstanceURL: node.Instance,
}
break
}
}
// Handle webhook testing
if req.Method == "webhook" && req.WebhookID != "" {
log.Info().
Str("webhookId", req.WebhookID).
Msg("Testing specific webhook")
// Get the webhook by ID and test it
webhooks := h.monitor.GetNotificationManager().GetWebhooks()
var foundWebhook *notifications.WebhookConfig
for _, wh := range webhooks {
if wh.ID == req.WebhookID {
foundWebhook = &wh
break
}
}
if foundWebhook == nil {
http.Error(w, "Webhook not found", http.StatusNotFound)
return
}
// Send test webhook
if err := h.monitor.GetNotificationManager().SendTestWebhook(*foundWebhook); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
} else if req.Method == "email" && req.Config != nil {
// If config is provided, use it for testing (without saving)
// If password is empty, use the saved password
if req.Config.Password == "" {
savedConfig := h.monitor.GetNotificationManager().GetEmailConfig()
req.Config.Password = savedConfig.Password
}
log.Info().
Bool("enabled", req.Config.Enabled).
Str("smtp", req.Config.SMTPHost).
Str("from", req.Config.From).
Int("toCount", len(req.Config.To)).
Strs("to", req.Config.To).
Bool("hasPassword", req.Config.Password != "").
Msg("Testing email with provided config")
if err := h.monitor.GetNotificationManager().SendTestNotificationWithConfig(req.Method, req.Config, nodeInfo); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
} else {
// Use saved config
if err := h.monitor.GetNotificationManager().SendTestNotification(req.Method); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "success", "message": "Test notification sent"})
}
// GetWebhookTemplates returns available webhook templates
func (h *NotificationHandlers) GetWebhookTemplates(w http.ResponseWriter, r *http.Request) {
templates := notifications.GetWebhookTemplates()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(templates)
}
// GetWebhookHistory returns recent webhook delivery history with URLs redacted
func (h *NotificationHandlers) GetWebhookHistory(w http.ResponseWriter, r *http.Request) {
history := h.monitor.GetNotificationManager().GetWebhookHistory()
// Redact secrets from URLs in history
for i := range history {
history[i].WebhookURL = redactSecretsFromURL(history[i].WebhookURL)
// Note: ResponseBody is not stored in WebhookDelivery struct
// Error messages are already limited in length
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(history)
}
// redactSecretsFromURL masks tokens and credentials in URLs
func redactSecretsFromURL(urlStr string) string {
// Redact common patterns like:
// - /bot123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11/sendMessage → /botXXX:REDACTED/sendMessage
// - ?token=abc123 → ?token=REDACTED
// - ?apikey=abc123 → ?apikey=REDACTED
// Redact Telegram bot tokens
if idx := strings.Index(urlStr, "/bot"); idx != -1 {
if endIdx := strings.Index(urlStr[idx:], "/"); endIdx != -1 {
urlStr = urlStr[:idx+4] + "REDACTED" + urlStr[idx+endIdx:]
}
}
// Redact query parameters with sensitive names
if idx := strings.Index(urlStr, "?"); idx != -1 {
sensitiveParams := []string{"token", "apikey", "api_key", "key", "secret", "password"}
for _, param := range sensitiveParams {
pattern := param + "="
if paramIdx := strings.Index(urlStr, pattern); paramIdx != -1 {
start := paramIdx + len(pattern)
end := start
for end < len(urlStr) && urlStr[end] != '&' && urlStr[end] != '#' {
end++
}
urlStr = urlStr[:start] + "REDACTED" + urlStr[end:]
}
}
}
return urlStr
}
// GetEmailProviders returns available email providers
func (h *NotificationHandlers) GetEmailProviders(w http.ResponseWriter, r *http.Request) {
providers := notifications.GetEmailProviders()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(providers)
}
// TestWebhook tests a webhook configuration
func (h *NotificationHandlers) TestWebhook(w http.ResponseWriter, r *http.Request) {
// First try to decode as basic webhook config
var basicWebhook notifications.WebhookConfig
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := json.Unmarshal(bodyBytes, &basicWebhook); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Convert to enhanced webhook for testing
webhook := notifications.EnhancedWebhookConfig{
WebhookConfig: basicWebhook,
Service: "generic", // Default to generic if not specified
RetryEnabled: false, // Don't retry during testing
}
if len(basicWebhook.CustomFields) > 0 {
customFields := make(map[string]interface{}, len(basicWebhook.CustomFields))
for key, value := range basicWebhook.CustomFields {
customFields[key] = value
}
webhook.CustomFields = customFields
}
// If the webhook has a custom template, use it
if basicWebhook.Template != "" {
webhook.PayloadTemplate = basicWebhook.Template
}
// Try to extract service from body if present
var serviceCheck struct {
Service string `json:"service"`
}
if err := json.Unmarshal(bodyBytes, &serviceCheck); err == nil && serviceCheck.Service != "" {
webhook.Service = serviceCheck.Service
// Also set it in the basic webhook for consistency
basicWebhook.Service = serviceCheck.Service
webhook.WebhookConfig.Service = serviceCheck.Service
}
log.Info().
Str("service", webhook.Service).
Str("url", webhook.URL).
Str("name", webhook.Name).
Msg("Testing webhook")
// Get template for the service (if not using custom template)
if webhook.PayloadTemplate == "" {
templates := notifications.GetWebhookTemplates()
for _, tmpl := range templates {
if tmpl.Service == webhook.Service {
webhook.PayloadTemplate = tmpl.PayloadTemplate
if webhook.Headers == nil {
webhook.Headers = make(map[string]string)
}
// Only copy headers that don't contain template syntax
// This prevents issues with headers that have Go template expressions
for k, v := range tmpl.Headers {
if !strings.Contains(v, "{{") {
webhook.Headers[k] = v
}
}
log.Info().Str("service", webhook.Service).Msg("Found template for service")
break
}
}
}
// If still no template found, use a simple generic template
if webhook.PayloadTemplate == "" {
webhook.PayloadTemplate = `{
"alert": {
"id": "{{.ID}}",
"type": "{{.Type}}",
"level": "{{.Level}}",
"resourceName": "{{.ResourceName}}",
"node": "{{.Node}}",
"message": "{{.Message}}",
"value": {{.Value}},
"threshold": {{.Threshold}}
},
"source": "pulse-monitoring",
"timestamp": {{.Timestamp}}
}`
}
// Test the webhook
status, response, err := h.monitor.GetNotificationManager().TestEnhancedWebhook(webhook)
result := map[string]interface{}{
"status": status,
"response": response,
}
if err != nil {
result["error"] = err.Error()
w.WriteHeader(http.StatusBadRequest)
} else if status < 200 || status >= 300 {
// HTTP error from webhook endpoint
result["error"] = fmt.Sprintf("Webhook returned HTTP %d: %s", status, response)
result["success"] = false
w.WriteHeader(http.StatusBadRequest)
} else {
result["success"] = true
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(result)
}
// GetNotificationHealth returns health status of notification system
func (h *NotificationHandlers) GetNotificationHealth(w http.ResponseWriter, r *http.Request) {
// Get queue stats
queueStats := make(map[string]interface{})
if queue := h.monitor.GetNotificationManager().GetQueue(); queue != nil {
stats, err := queue.GetQueueStats()
if err != nil {
log.Warn().Err(err).Msg("Failed to get queue stats for health check")
queueStats["error"] = err.Error()
queueStats["healthy"] = false
} else {
queueStats = map[string]interface{}{
"pending": stats["pending"],
"sending": stats["sending"],
"sent": stats["sent"],
"failed": stats["failed"],
"dlq": stats["dlq"],
"healthy": true,
}
}
} else {
queueStats["error"] = "queue not initialized"
queueStats["healthy"] = false
}
// Get config status
nm := h.monitor.GetNotificationManager()
emailCfg := nm.GetEmailConfig()
webhooks := nm.GetWebhooks()
health := map[string]interface{}{
"queue": queueStats,
"email": map[string]interface{}{
"enabled": emailCfg.Enabled,
"configured": emailCfg.SMTPHost != "",
},
"webhooks": map[string]interface{}{
"total": len(webhooks),
"enabled": countEnabledWebhooks(webhooks),
},
"overall_healthy": queueStats["healthy"] == true,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(health)
}
func countEnabledWebhooks(webhooks []notifications.WebhookConfig) int {
count := 0
for _, wh := range webhooks {
if wh.Enabled {
count++
}
}
return count
}
// HandleNotifications routes notification requests to appropriate handlers
func (h *NotificationHandlers) HandleNotifications(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/api/notifications")
requireAnyScope := func(required string, scopes ...string) bool {
record := getAPITokenRecordFromRequest(r)
if record == nil {
return true
}
for _, scope := range scopes {
if scope != "" && record.HasScope(scope) {
return true
}
}
respondMissingScope(w, required)
return false
}
switch {
case path == "/email" && r.Method == http.MethodGet:
if !requireAnyScope(config.ScopeSettingsRead, config.ScopeSettingsRead, config.ScopeSettingsWrite) {
return
}
h.GetEmailConfig(w, r)
case path == "/email" && r.Method == http.MethodPut:
if !requireAnyScope(config.ScopeSettingsWrite, config.ScopeSettingsWrite) {
return
}
h.UpdateEmailConfig(w, r)
case path == "/apprise" && r.Method == http.MethodGet:
if !requireAnyScope(config.ScopeSettingsRead, config.ScopeSettingsRead, config.ScopeSettingsWrite) {
return
}
h.GetAppriseConfig(w, r)
case path == "/apprise" && r.Method == http.MethodPut:
if !requireAnyScope(config.ScopeSettingsWrite, config.ScopeSettingsWrite) {
return
}
h.UpdateAppriseConfig(w, r)
case path == "/webhooks" && r.Method == http.MethodGet:
if !requireAnyScope(config.ScopeSettingsRead, config.ScopeSettingsRead, config.ScopeSettingsWrite) {
return
}
h.GetWebhooks(w, r)
case path == "/webhooks" && r.Method == http.MethodPost:
if !requireAnyScope(config.ScopeSettingsWrite, config.ScopeSettingsWrite) {
return
}
h.CreateWebhook(w, r)
case path == "/webhooks/test" && r.Method == http.MethodPost:
if !requireAnyScope(config.ScopeSettingsWrite, config.ScopeSettingsWrite) {
return
}
h.TestWebhook(w, r)
case strings.HasPrefix(path, "/webhooks/") && r.Method == http.MethodPut:
if !requireAnyScope(config.ScopeSettingsWrite, config.ScopeSettingsWrite) {
return
}
h.UpdateWebhook(w, r)
case strings.HasPrefix(path, "/webhooks/") && r.Method == http.MethodDelete:
if !requireAnyScope(config.ScopeSettingsWrite, config.ScopeSettingsWrite) {
return
}
h.DeleteWebhook(w, r)
case path == "/webhook-templates" && r.Method == http.MethodGet:
if !requireAnyScope(config.ScopeSettingsRead, config.ScopeSettingsRead, config.ScopeSettingsWrite) {
return
}
h.GetWebhookTemplates(w, r)
case path == "/webhook-history" && r.Method == http.MethodGet:
if !requireAnyScope(config.ScopeSettingsRead, config.ScopeSettingsRead, config.ScopeSettingsWrite) {
return
}
h.GetWebhookHistory(w, r)
case path == "/email-providers" && r.Method == http.MethodGet:
if !requireAnyScope(config.ScopeSettingsRead, config.ScopeSettingsRead, config.ScopeSettingsWrite) {
return
}
h.GetEmailProviders(w, r)
case path == "/test" && r.Method == http.MethodPost:
if !requireAnyScope(config.ScopeSettingsWrite, config.ScopeSettingsWrite) {
return
}
h.TestNotification(w, r)
case path == "/health" && r.Method == http.MethodGet:
if !requireAnyScope(config.ScopeSettingsRead, config.ScopeSettingsRead, config.ScopeSettingsWrite) {
return
}
h.GetNotificationHealth(w, r)
default:
http.Error(w, "Not found", http.StatusNotFound)
}
}