Files
gochat/backend/internal/auth/webhook_registry.go
T
rogee aeddedf2a3 Reorganize repo: backend/, deploy/, docs/ layout + AGENTS.md
Restructure the monorepo into clear top-level directories:
- backend/: Go module root (cmd, internal, pkg, configs, migrations,
  docs/swagger, scripts, tests, go.mod, Makefile, .air.toml)
- deploy/: Docker (Dockerfile, docker-compose*), quickstart, fluentd
- docs/: project documentation + reports/ (moved from repo root)
- AGENTS.md: new AI coding-agent guide at repo root

Update all references to the new layout:
- Dockerfile: COPY backend/go.mod, COPY backend/ (context = repo root)
- docker-compose files: context ../.., dockerfile deploy/docker/Dockerfile,
  env_file ../../.env, volume mounts ../../backend:/app
- deploy/quickstart/compose.yaml: dockerfile deploy/docker/Dockerfile
- CI: working-directory: backend for go commands, file deploy/docker/Dockerfile,
  coverage path backend/coverage.out, health_check backend/scripts/
- backend/Makefile: docker target uses -f ../deploy/docker/Dockerfile ../
- README: architecture tree, quickstart, config paths updated

Move root stray scripts (rename_models.*, run_m11_tests.sh, verify_build.sh,
gorm_bool_main.go) to backend/scripts/legacy/. All moves via git mv to
preserve history. Build, vet, SQLite tests, and docker compose config verified.
2026-07-07 14:44:12 +08:00

122 lines
3.7 KiB
Go

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
}