From 63b79409f4793fbe9e896b9e30c6405772c7603b Mon Sep 17 00:00:00 2001 From: Iris Lightshard Date: Sun, 26 May 2024 17:24:49 -0600 Subject: [PATCH] implement adapter configuration API --- server/api.go | 122 +++++++++++++++++++++++++++++++++++++++++++---- server/server.go | 25 +++++----- 2 files changed, 125 insertions(+), 22 deletions(-) diff --git a/server/api.go b/server/api.go index 57284f5..fb51793 100644 --- a/server/api.go +++ b/server/api.go @@ -1,14 +1,101 @@ package server import ( + "encoding/json" + "errors" + "forge.lightcrystal.systems/lightcrystal/underbbs/adapter" + "forge.lightcrystal.systems/lightcrystal/underbbs/models" "hacklab.nilfm.cc/quartzgun/renderer" "hacklab.nilfm.cc/quartzgun/router" + "hacklab.nilfm.cc/quartzgun/util" "html/template" "net/http" + "strings" ) -func apiConfigureAdapters(next http.Handler) http.Handler { +func getSubscriberKey(req *http.Request) (string, error) { + authHeader := req.Header.Get("Authorization") + if strings.HasPrefix(authHeader, "Bearer ") { + return strings.Split(authHeader, "Bearer ")[1], nil + } + return "", errors.New("No subscriber key") +} + +func getSubscriberByKey(key string, subscribers map[*Subscriber][]adapter.Adapter) *Subscriber { + for s, _ := range subscribers { + if s.key == key { + return s + } + } + return nil +} + +func setAdaptersForSubscriber(key string, adapters []adapter.Adapter, subscribers map[*Subscriber][]adapter.Adapter) error { + var ptr *Subscriber = nil + for s, _ := range subscribers { + if s.key == key { + ptr = s + } + } + if ptr != nil { + subscribers[ptr] = adapters + return nil + } + return errors.New("subscriber not present in map") +} + +func apiConfigureAdapters(next http.Handler, subscribers map[*Subscriber][]adapter.Adapter) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + // get subscriber key + skey, err := getSubscriberKey(req) + if err != nil { + w.WriteHeader(500) + return + } + subscriber := getSubscriberByKey(skey, subscribers) + if subscriber == nil { + w.WriteHeader(404) + return + } + // decode adapter config from request body + settings := make([]models.Settings, 0) + err = json.NewDecoder(req.Body).Decode(&settings) + if err != nil { + w.WriteHeader(400) + next.ServeHTTP(w, req) + return + } + // iterate through settings and create adapters + adapters := make([]adapter.Adapter, 0) + for _, s := range settings { + var a adapter.Adapter + switch s.Protocol { + case "nostr": + a = &adapter.NostrAdapter{} + break + default: + break + + } + err := a.Init(s, subscriber.data) + if err != nil { + util.AddContextValue(req, "data", err.Error()) + w.WriteHeader(500) + next.ServeHTTP(w, req) + } + + adapters = append(adapters, a) + } + + // TODO: cancel subscriptions on any existing adapters + // store the adapters in the subscriber map + err = setAdaptersForSubscriber(skey, adapters, subscribers) + if err != nil { + util.AddContextValue(req, "data", err.Error()) + w.WriteHeader(500) + next.ServeHTTP(w, req) + } + w.WriteHeader(201) next.ServeHTTP(w, req) }) @@ -28,14 +115,23 @@ func apiAdapterSubscribe(next http.Handler) http.Handler { }) } -func ProtectWithSubscriberKey(next http.Handler) http.Handler { +func ProtectWithSubscriberKey(next http.Handler, subscribers map[*Subscriber][]adapter.Adapter) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.WriteHeader(201) - next.ServeHTTP(w, req) + authHeader := req.Header.Get("Authorization") + if strings.HasPrefix(authHeader, "Bearer ") { + subscriberKey := strings.Split(authHeader, "Bearer ")[1] + for s, _ := range subscribers { + if s.key == subscriberKey { + next.ServeHTTP(w, req) + return + } + } + } + w.WriteHeader(http.StatusUnauthorized) }) } -func apiMux() http.Handler { +func (self *BBSServer) apiMux() http.Handler { errTemplate, err := template.New("err").Parse("{{ $params := (.Context).Value \"params\" }}

ERROR {{ $params.ErrorCode }}

{{ $params.ErrorMessage }}

") if err != nil { panic("error template was malformed") @@ -45,10 +141,20 @@ func apiMux() http.Handler { } // adapters (POST & GET) - rtr.Post("/adapters", ProtectWithSubscriberKey(apiConfigureAdapters(renderer.JSON("data")))) - rtr.Get("/adapters", ProtectWithSubscriberKey(apiGetAdapters(renderer.JSON("data")))) + rtr.Post("/adapters", ProtectWithSubscriberKey( + apiConfigureAdapters(renderer.JSON("data"), self.subscribers), + self.subscribers, + )) + rtr.Get("/adapters", ProtectWithSubscriberKey( + apiGetAdapters(renderer.JSON("data")), + self.subscribers, + )) + // adapters/:name/subscribe - rtr.Post(`/adapters/(?P\S+)/subscribe`, ProtectWithSubscriberKey(apiAdapterSubscribe(renderer.JSON("data")))) + rtr.Post(`/adapters/(?P\S+)/subscribe`, ProtectWithSubscriberKey( + apiAdapterSubscribe(renderer.JSON("data")), + self.subscribers, + )) return http.HandlerFunc(rtr.ServeHTTP) } diff --git a/server/server.go b/server/server.go index b0e2521..ebc3621 100644 --- a/server/server.go +++ b/server/server.go @@ -3,7 +3,6 @@ package server import ( "context" "errors" - "fmt" "forge.lightcrystal.systems/lightcrystal/underbbs/adapter" "forge.lightcrystal.systems/lightcrystal/underbbs/models" "golang.org/x/time/rate" @@ -20,6 +19,7 @@ import ( type Subscriber struct { key string msgs chan []byte + data chan models.SocketData closeSlow func() } @@ -43,7 +43,7 @@ func New() *BBSServer { srvr.serveMux.Handle("/app/", http.StripPrefix("/app/", renderer.Subtree("./dist"))) // api - srvr.serveMux.Handle("/api/", http.StripPrefix("/api", apiMux())) + srvr.serveMux.Handle("/api/", http.StripPrefix("/api", srvr.apiMux())) // websocket srvr.serveMux.HandleFunc("/subscribe", srvr.subscribeHandler) @@ -66,27 +66,24 @@ func (self *BBSServer) subscribeHandler(w http.ResponseWriter, r *http.Request) return } - msgc := make(chan models.SocketData) - - // attempt to initialize adapters - adapters := make([]adapter.Adapter, 0, 4) - - // keep reference to the adapters so we can execute commands on them later ctx := r.Context() ctx = c.CloseRead(ctx) s := &Subscriber{ key: cookie.GenToken(64), msgs: make(chan []byte, self.subscribeMessageBuffer), + data: make(chan models.SocketData), closeSlow: func() { c.Close(websocket.StatusPolicyViolation, "connection too slow to keep up with messages") }, } + // start with an empty set of adapters + // we'll configure them separately + adapters := make([]adapter.Adapter, 0, 4) self.addSubscriber(s, adapters) - fmt.Println("subscriber added to map") - + // defer cleanup and write messages in the background defer self.deleteSubscriber(s) defer c.Close(websocket.StatusInternalError, "") @@ -102,11 +99,11 @@ func (self *BBSServer) subscribeHandler(w http.ResponseWriter, r *http.Request) } }() - fmt.Println("giving subscriber their key: " + s.key) - s.msgs <- []byte("{ 'key':'" + s.key + " }") - fmt.Println("subscriber key sent, listening on the data channel") + // give user their key + s.msgs <- []byte("{ 'key':'" + s.key + "' }") - listen([]chan models.SocketData{msgc}, s.msgs) + // block on the data channel, serializing and passing the data to the subscriber + listen([]chan models.SocketData{s.data}, s.msgs) if errors.Is(err, context.Canceled) { return