Webhook-to-SSE gateway with hierarchical topic routing and signature verification
at main 103 lines 2.0 kB view raw
1package main 2 3import ( 4 "log" 5 "os" 6 "strings" 7 8 "github.com/fsnotify/fsnotify" 9 "gopkg.in/yaml.v3" 10) 11 12type Configuration struct { 13 Paths map[string]PathConfiguration `yaml:"paths"` 14} 15 16type PathConfiguration struct { 17 Verify string `yaml:"verify"` 18 Secret string `yaml:"secret"` 19 SignatureHeader string `yaml:"signature_header"` 20 SubscribeSecret string `yaml:"subscribe_secret"` 21} 22 23func LoadConfiguration(path string) (*Configuration, error) { 24 data, err := os.ReadFile(path) 25 if err != nil { 26 return nil, err 27 } 28 expanded := os.ExpandEnv(string(data)) 29 var cfg Configuration 30 if err := yaml.Unmarshal([]byte(expanded), &cfg); err != nil { 31 return nil, err 32 } 33 return &cfg, nil 34} 35 36var newWatcher = fsnotify.NewWatcher 37 38func WatchConfiguration(path string, callback func(*Configuration)) (func(), error) { 39 watcher, err := newWatcher() 40 if err != nil { 41 return nil, err 42 } 43 if err := watcher.Add(path); err != nil { 44 watcher.Close() 45 return nil, err 46 } 47 48 done := make(chan struct{}) 49 go func() { 50 defer watcher.Close() 51 for { 52 select { 53 case <-done: 54 return 55 case event := <-watcher.Events: 56 if event.Has(fsnotify.Write) || event.Has(fsnotify.Create) { 57 cfg, err := LoadConfiguration(path) 58 if err != nil { 59 log.Printf("reloading configuration: %v", err) 60 continue 61 } 62 callback(cfg) 63 } 64 } 65 } 66 }() 67 68 return func() { close(done) }, nil 69} 70 71func (c *Configuration) LookupSubscribeSecret(path string) string { 72 if c == nil { 73 return "" 74 } 75 for { 76 if pc, ok := c.Paths[path]; ok && pc.SubscribeSecret != "" { 77 return pc.SubscribeSecret 78 } 79 i := strings.LastIndex(path, "/") 80 if i < 0 { 81 break 82 } 83 path = path[:i] 84 } 85 return "" 86} 87 88func (c *Configuration) LookupVerification(path string) *PathConfiguration { 89 if c == nil { 90 return nil 91 } 92 for { 93 if pc, ok := c.Paths[path]; ok && pc.Verify != "" { 94 return &pc 95 } 96 i := strings.LastIndex(path, "/") 97 if i < 0 { 98 break 99 } 100 path = path[:i] 101 } 102 return nil 103}