From 57a6ff5ec1b8950d86930bed2b82b2cc86ef7e73 Mon Sep 17 00:00:00 2001 From: James Mills Date: Thu, 31 Mar 2022 00:51:38 +0000 Subject: [PATCH] Add support for performing lookups via the broker we were served from (#99) Depends on #95 Co-authored-by: James Mills Reviewed-on: https://git.mills.io/saltyim/saltyim/pulls/99 --- Makefile | 2 +- client.go | 21 +++++-- internal/pwa/components/newchat.go | 4 +- internal/pwa/components/saltychat.go | 1 + internal/pwa/utils/lookup.go | 41 ++++++++++++++ internal/server.go | 6 ++ internal/web/app.wasm | 4 +- lookup.go | 85 +++++++++++++++++++++++++++- 8 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 internal/pwa/utils/lookup.go diff --git a/Makefile b/Makefile index ff2df51..0164c03 100644 --- a/Makefile +++ b/Makefile @@ -70,7 +70,7 @@ generate: ## Genereate any code required by the build echo 'Running in debug mode...'; \ fi -PWA_SRCS = $(shell ls *.go) $(shell find ./internal/pwa -type f) +PWA_SRCS = $(shell ls *.go) ./internal/server.go $(shell find ./internal/pwa -type f) internal/web/app.wasm: $(PWA_SRCS) @GOARCH=wasm GOOS=js $(GOCMD) build -o ./internal/web/app.wasm ./internal/pwa/ diff --git a/client.go b/client.go index 6b63e81..8adb9f2 100644 --- a/client.go +++ b/client.go @@ -98,6 +98,8 @@ type Client struct { id *Identity key *keys.EdX25519Key cache addrCache + + lookup Lookuper } // NewClient reeturns a new Salty IM client for sending and receiving @@ -125,10 +127,11 @@ func NewClient(me *Addr, options ...IdentityOption) (*Client, error) { log.Debugf("Endpoint is %s", me.Endpoint()) return &Client{ - me: me, - id: id, - key: id.key, - cache: make(addrCache), + me: me, + id: id, + key: id.key, + cache: make(addrCache), + lookup: &DirectLookup{}, }, nil } @@ -138,7 +141,7 @@ func (cli *Client) getAddr(user string) (*Addr, error) { return addr, nil } - addr, err := LookupAddr(user) + addr, err := cli.lookup.LookupAddr(user) if err != nil { return nil, fmt.Errorf("error: failed to lookup user %s: %w", user, err) } @@ -252,6 +255,12 @@ func (cli *Client) String() string { return b.String() } +// SetLookup sets the internal lookup interface to use (Lookuper) for looking +// up Salty Addresses. By default the DirectLookup implementation is used. +func (cli *Client) SetLookup(lookup Lookuper) { + cli.lookup = lookup +} + // Drain drains this user's inbox by simulteneiously reading until empty anda // subscribing to the inbox for new messages. func (cli *Client) Drain(ctx context.Context, extraenvs, prehook, posthook string) chan Message { @@ -376,7 +385,7 @@ func (cli *Client) SendToAddr(addr *Addr, msg string) error { // Register sends a registration request to the service user of a Salty Broker func (cli *Client) Register() error { - svc, err := LookupAddr(fmt.Sprintf("%s@%s", ServiceUser, cli.Me().Domain)) + svc, err := cli.lookup.LookupAddr(fmt.Sprintf("%s@%s", ServiceUser, cli.Me().Domain)) if err != nil { return fmt.Errorf("error looking up service user on %s: %w", cli.Me().Domain, err) } diff --git a/internal/pwa/components/newchat.go b/internal/pwa/components/newchat.go index f1f6697..5ca214f 100644 --- a/internal/pwa/components/newchat.go +++ b/internal/pwa/components/newchat.go @@ -7,8 +7,8 @@ import ( "github.com/mlctrez/goapp-mdc/pkg/button" "github.com/mlctrez/goapp-mdc/pkg/icon" "github.com/mlctrez/goapp-mdc/pkg/textfield" - "go.mills.io/saltyim" "go.mills.io/saltyim/internal/pwa/storage" + "go.mills.io/saltyim/internal/pwa/utils" ) type NewChat struct { @@ -61,7 +61,7 @@ func (n *NewChat) Render() app.UI { func (n *NewChat) newChat() func(button app.HTMLButton) { return func(button app.HTMLButton) { button.OnClick(func(ctx app.Context, e app.Event) { - addr, err := saltyim.LookupAddr(n.user.Value) + addr, err := utils.LookupAddr(n.user.Value) if err != nil { n.dialog.ShowDialog("error", err.Error()) return diff --git a/internal/pwa/components/saltychat.go b/internal/pwa/components/saltychat.go index d41891f..fdf3e13 100644 --- a/internal/pwa/components/saltychat.go +++ b/internal/pwa/components/saltychat.go @@ -107,6 +107,7 @@ func (h *SaltyChat) connect(ctx app.Context) { h.dialog.ShowDialog("error setting up client", err.Error()) return } + newClient.SetLookup(&saltyim.ProxyLookup{LookupEndpoint: app.Getenv("LookupEndpoint")}) client = newClient diff --git a/internal/pwa/utils/lookup.go b/internal/pwa/utils/lookup.go new file mode 100644 index 0000000..516ebb9 --- /dev/null +++ b/internal/pwa/utils/lookup.go @@ -0,0 +1,41 @@ +package utils + +import ( + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/maxence-charriere/go-app/v9/pkg/app" + "go.mills.io/saltyim" +) + +// LookupAddr performs a lookup of a Salty Addr directly and if the request +// fails for whatever reason (usuaully due to Cross-Orogin-Resource-Sharing +// policies / CORS) it uses the Salty Broker the PWA was served from +// initially to perform the lookup by proxying the lookup through the broker. +// Why? CORS sucks. +func LookupAddr(user string) (*saltyim.Addr, error) { + addr, err := saltyim.LookupAddr(user) + if err == nil { + return addr, nil + } + + // Fallback to proxying the lookup through the broker... + + res, err := saltyim.Request(http.MethodGet, app.Getenv("LookupEndpoint")+user, nil, nil) + if err != nil { + return nil, err + } + defer res.Body.Close() + + data, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + + if err := json.Unmarshal(data, &addr); err != nil { + return nil, err + } + + return addr, nil +} diff --git a/internal/server.go b/internal/server.go index 1a6c8f2..828fa07 100644 --- a/internal/server.go +++ b/internal/server.go @@ -7,6 +7,7 @@ import ( "net/http" "os/signal" "path/filepath" + "strings" "syscall" "time" @@ -291,6 +292,11 @@ func (s *Server) initRoutes() { Name: "Salty Chat", ShortName: "Salty Chat", Description: "Secure, easy, self-hosted messaging", + + Env: map[string]string{ + "LookupEndpoint": strings.TrimSuffix(s.config.BaseURL, "/") + "/api/v1/lookup/", + }, + Icon: app.Icon{ Large: "/web/favicon-lg.png", Default: "/web/favicon-ap.png", diff --git a/internal/web/app.wasm b/internal/web/app.wasm index ace5999..477aaaa 100755 --- a/internal/web/app.wasm +++ b/internal/web/app.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:95a9c74c800295301b1cb5a08cba6679ee5c16bd9f91ac0cdf6c44e808a33df7 -size 27991136 +oid sha256:6756046e89856a68e31c8dde6a2c1229674064e47dbf815fd87465a193e45e2c +size 28023655 diff --git a/lookup.go b/lookup.go index effc5cc..35cf2e1 100644 --- a/lookup.go +++ b/lookup.go @@ -4,6 +4,7 @@ import ( "crypto/sha256" "encoding/json" "fmt" + "io/ioutil" "net/http" "net/url" "strings" @@ -13,9 +14,16 @@ import ( ) var ( - _ json.Marshaler = (*Addr)(nil) + _ json.Marshaler = (*Addr)(nil) + _ json.Unmarshaler = (*Addr)(nil) + _ Lookuper = (*DirectLookup)(nil) + _ Lookuper = (*ProxyLookup)(nil) ) +type Lookuper interface { + LookupAddr(user string) (*Addr, error) +} + func fetchConfig(addr string) (Config, Capabilities, error) { // Attempt using hash res, err := Request(http.MethodGet, addr, nil, nil) @@ -79,6 +87,37 @@ func (a *Addr) MarshalJSON() ([]byte, error) { }) } +func (a *Addr) UnmarshalJSON(data []byte) error { + res := struct { + Addr string + User string + Domain string + Key string + Endpoint string + }{} + + if err := json.Unmarshal(data, &res); err != nil { + return err + } + + a.User = res.User + a.Domain = res.Domain + + u, err := url.Parse(res.Endpoint) + if err != nil { + return fmt.Errorf("error parsing endpoint %q: %w", res.Endpoint, err) + } + a.endpoint = u + + key, err := keys.NewEdX25519PublicKeyFromID(keys.ID(res.Key)) + if err != nil { + return fmt.Errorf("error parsing public key %q: %w", res.Key, err) + } + a.key = key + + return nil +} + func (a *Addr) String() string { return fmt.Sprintf("%s@%s", a.User, a.Domain) } @@ -198,3 +237,47 @@ func LookupAddr(addr string) (*Addr, error) { } return a, nil } + +// DirectLookup performs a direct lookup request +type DirectLookup struct{} + +// LookupAddr performs a lookup of a Salty Addr directly +func (l *DirectLookup) LookupAddr(user string) (*Addr, error) { + return LookupAddr(user) +} + +// ProxyLookup proxies lookup requests through a Salty Broker's /api/v1/lookup endpoint +type ProxyLookup struct { + // LookupEndpoint is the uri of the lookup endpoint of a broker + LookupEndpoint string +} + +// LookupAddr performs a lookup of a Salty Addr directly and if the request fails for +// whatever reason (usuaully due to Cross-Orogin-Resource-Sharing policies / CORS) it +// uses the Salty Broker the PWA was served from initially to perform the lookup by +// proxying the lookup through the broker. Why? CORS sucks. +func (l *ProxyLookup) LookupAddr(user string) (*Addr, error) { + addr, err := LookupAddr(user) + if err == nil { + return addr, nil + } + + // Fallback to proxying the lookup through the broker... + + res, err := Request(http.MethodGet, l.LookupEndpoint+user, nil, nil) + if err != nil { + return nil, err + } + defer res.Body.Close() + + data, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + + if err := json.Unmarshal(data, &addr); err != nil { + return nil, err + } + + return addr, nil +}