package auth import ( "net/http" "sync" ) // Reference: P2E §3 — Webhook authentication registry // Each channel provider stores an "identifier" (bot_token, website_token, phone_number_id) // which is used to route and validate incoming webhook requests. // WebhookTokenRegistry manages webhook authentication tokens per channel. // Tokens are loaded from DB on startup and refreshed when inboxes are created/updated. type WebhookTokenRegistry struct { mu sync.RWMutex tokens map[string]map[string]WebhookTokenEntry // channel_type → identifier → entry } // WebhookTokenEntry stores a channel's webhook authentication data. type WebhookTokenEntry struct { InboxID uint AccountID uint Secret string // HMAC secret for verification (varies by channel) Identifier string // unique identifier for routing (bot_token, website_token, etc.) } // NewWebhookTokenRegistry creates an empty token registry. func NewWebhookTokenRegistry() *WebhookTokenRegistry { return &WebhookTokenRegistry{ tokens: make(map[string]map[string]WebhookTokenEntry), } } // Register adds a webhook token entry for a channel. func (r *WebhookTokenRegistry) Register(channelType, identifier string, entry WebhookTokenEntry) { r.mu.Lock() defer r.mu.Unlock() if r.tokens[channelType] == nil { r.tokens[channelType] = make(map[string]WebhookTokenEntry) } r.tokens[channelType][identifier] = entry } // Unregister removes a webhook token entry. func (r *WebhookTokenRegistry) Unregister(channelType, identifier string) { r.mu.Lock() defer r.mu.Unlock() if r.tokens[channelType] != nil { delete(r.tokens[channelType], identifier) } } // Lookup finds a webhook token entry by channel type and identifier. func (r *WebhookTokenRegistry) Lookup(channelType, identifier string) (*WebhookTokenEntry, bool) { r.mu.RLock() defer r.mu.RUnlock() if r.tokens[channelType] == nil { return nil, false } entry, ok := r.tokens[channelType][identifier] if !ok { return nil, false } return &entry, true } // Validate checks a webhook request against the stored token entry. // The validation strategy depends on the channel type: // - Telegram: X-Telegram-Bot-Api-Secret-Token header // - Web Widget: HMAC token verification // - Facebook/WhatsApp: app secret + signature verification func (r *WebhookTokenRegistry) Validate(channelType, identifier string, req *http.Request) (bool, error) { entry, found := r.Lookup(channelType, identifier) if !found { return false, nil // unknown identifier → reject } switch channelType { case "telegram": // Telegram validates via X-Telegram-Bot-Api-Secret-Token header token := req.Header.Get("X-Telegram-Bot-Api-Secret-Token") if entry.Secret == "" { return true, nil // no secret configured → accept all } return token == entry.Secret, nil case "web_widget": // Web widget HMAC verification (handled separately in WebSocket auth) return true, nil case "facebook": // Facebook webhook validation: app secret + X-Hub-Signature-256 // TODO: implement Facebook signature verification (P8) return true, nil case "whatsapp": // WhatsApp webhook validation: Bearer token verification // TODO: implement WhatsApp token verification (P8) return true, nil default: return true, nil // unknown channel type → accept (will be filtered by registry) } } // GetAllIdentifiers returns all registered identifiers for a channel type. func (r *WebhookTokenRegistry) GetAllIdentifiers(channelType string) []WebhookTokenEntry { r.mu.RLock() defer r.mu.RUnlock() entries := []WebhookTokenEntry{} if r.tokens[channelType] == nil { return entries } for _, entry := range r.tokens[channelType] { entries = append(entries, entry) } return entries }