6
1
mirror of https://git.mills.io/saltyim/saltyim.git synced 2024-06-16 11:58:24 +00:00

Fix Lookup to actually use SRV records (where available) and refactor (#62)

Co-authored-by: James Mills <prologic@shortcircuit.net.au>
Reviewed-on: https://git.mills.io/saltyim/saltyim/pulls/62
Reviewed-by: xuu <xuu@noreply@mills.io>
This commit is contained in:
James Mills 2022-03-25 23:49:44 +00:00
parent fda4628414
commit 211f725986
8 changed files with 101 additions and 80 deletions

@ -18,7 +18,7 @@ import (
"go.mills.io/saltyim/internal/exec"
)
type configCache map[string]Config
type addrCache map[string]*Addr
// PackMessage formts an outoing message in the Message Format
// <timestamp>\t(<sender>) <message>
@ -42,7 +42,7 @@ func Send(endpoint, msg string) error {
type Client struct {
me *Addr
key *keys.EdX25519Key
cache configCache
cache addrCache
}
// NewClient returns a new Salty IM client for sending and receiving
@ -72,24 +72,24 @@ func NewClient(me *Addr, options ...IdentityOption) (*Client, error) {
return &Client{
me: me,
key: ident.key,
cache: make(configCache),
cache: make(addrCache),
}, nil
}
func (cli *Client) getConfig(user string) (Config, error) {
config, ok := cli.cache[user]
func (cli *Client) getAddr(user string) (*Addr, error) {
addr, ok := cli.cache[user]
if ok {
return config, nil
return addr, nil
}
config, err := Lookup(user)
addr, err := LookupAddr(user)
if err != nil {
return Config{}, fmt.Errorf("error: failed to lookup user %s: %w", user, err)
return nil, fmt.Errorf("error: failed to lookup user %s: %w", user, err)
}
cli.cache[user] = config
cli.cache[user] = addr
return config, nil
return addr, nil
}
func (cli *Client) handleMessage(prehook, posthook string, msgs chan string) msgbus.HandlerFunc {
@ -147,17 +147,17 @@ func (cli *Client) Read(ctx context.Context, prehook, posthook string) chan stri
// Send sends an encrypted message to the specified user
func (cli *Client) Send(user, msg string) error {
cfg, err := cli.getConfig(user)
addr, err := cli.getAddr(user)
if err != nil {
return fmt.Errorf("error looking up user %s: %w", user, err)
}
b, err := salty.Encrypt(cli.key, PackMessage(cli.me, msg), []string{cfg.Key})
b, err := salty.Encrypt(cli.key, PackMessage(cli.me, msg), []string{addr.Key().ID().String()})
if err != nil {
return fmt.Errorf("error encrypting message to %s: %w", user, err)
}
if err := Send(cfg.Endpoint, string(b)); err != nil {
if err := Send(addr.Endpoint().String(), string(b)); err != nil {
return fmt.Errorf("error sending message to %s: %w", user, err)
}

@ -44,7 +44,7 @@ func lookup(user string) {
os.Exit(2)
}
config, err := saltyim.Lookup(user)
config, err := saltyim.LookupAddr(user)
if err != nil {
fmt.Fprintf(os.Stderr, "error looking up user %s: %s\n", user, err)
os.Exit(2)

@ -10,8 +10,8 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/net/context"
"golang.org/x/term"
"go.mills.io/saltyim"
)
@ -92,7 +92,7 @@ func read(me *saltyim.Addr, identity string, prehook, posthook string, args ...s
}()
for msg := range cli.Read(ctx, prehook, posthook) {
if terminal.IsTerminal(syscall.Stdin) {
if term.IsTerminal(syscall.Stdin) {
fmt.Println(saltyim.FormatMessage(msg))
} else {
fmt.Println(msg)

@ -1,8 +0,0 @@
package saltyim
// Config represents a Salty Config for a User which at a minimum is required
// to have an Endpoint and Key (Public Key)
type Config struct {
Endpoint string `json:"endpoint"`
Key string `json:"key"`
}

4
go.mod

@ -16,7 +16,7 @@ require (
github.com/spf13/viper v1.10.1
github.com/stretchr/testify v1.7.0
go.mills.io/salty v0.0.0-20220322161301-ce2b9f6573fa
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
)
require (
@ -63,9 +63,9 @@ require (
github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect
github.com/vmihailenco/tagparser v0.1.2 // indirect
github.com/writeas/go-strip-markdown/v2 v2.1.1 // indirect
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 // indirect
golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 // indirect
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.0 // indirect

@ -156,7 +156,7 @@ func (h *SaltyChat) handleSendMessage(ctx app.Context, e app.Event) {
if h.client != nil {
friendAddress := strings.TrimSpace(h.friend.Value)
h.friend.Value = friendAddress
if _, err := saltyim.Lookup(friendAddress); err != nil {
if _, err := saltyim.LookupAddr(friendAddress); err != nil {
h.showDialog("problem with send-to address", err.Error())
} else {
if err := h.client.Send(friendAddress, msg); err == nil {

@ -30,9 +30,9 @@ func getUserColor(s string) tcell.Color {
type ChatTUI struct {
mu sync.RWMutex
cli *saltyim.Client
user string
config saltyim.Config
cli *saltyim.Client
user string
addr *saltyim.Addr
// Configurations.
palette map[string]string
@ -41,15 +41,15 @@ type ChatTUI struct {
// NewChatTUI initializes a new chatApp.
// Sets up connection with broker, and initializes UI.
func NewChatTUI(cli *saltyim.Client, user string) (*ChatTUI, error) {
config, err := saltyim.Lookup(user)
addr, err := saltyim.LookupAddr(user)
if err != nil {
return nil, fmt.Errorf("error looking up user %s: %s", user, err)
}
return &ChatTUI{
cli: cli,
user: user,
config: config,
cli: cli,
user: user,
addr: addr,
palette: map[string]string{
"background": "000000",
@ -128,7 +128,7 @@ func (c *ChatTUI) SetScreen(inCh <-chan string, outCh chan<- string) {
app := tview.NewApplication()
chatTitle := fmt.Sprintf("Chatting to %s via %s", c.user, c.config.Endpoint)
chatTitle := fmt.Sprintf("Chatting to %s via %s", c.user, c.addr.Endpoint().String())
inputTitle := fmt.Sprintf("Connected to %s as %s", c.cli.Me().Endpoint(), c.cli.Me())
// Generate UI components.

119
lookup.go

@ -10,28 +10,72 @@ import (
"net/url"
"strings"
sync "github.com/sasha-s/go-deadlock"
"github.com/keys-pub/keys"
log "github.com/sirupsen/logrus"
)
var (
_ json.Marshaler = (*Addr)(nil)
)
func fetchConfig(addr string) (Config, error) {
// Attempt using hash
res, err := Request(http.MethodGet, addr, nil, nil)
if err != nil {
return Config{}, err
}
data, err := ioutil.ReadAll(res.Body)
if err != nil {
return Config{}, err
}
var config Config
if err := json.Unmarshal(data, &config); err != nil {
return Config{}, err
}
return config, err
}
// Config represents a Salty Config for a User which at a minimum is required
// to have an Endpoint and Key (Public Key)
type Config struct {
Endpoint string `json:"endpoint"`
Key string `json:"key"`
}
// Addr represents a Salty IM User's Address
type Addr struct {
mu sync.RWMutex
User string
Domain string
key *keys.EdX25519PublicKey
endpoint *url.URL
discoveredDomain string
}
// IsZero returns true if the address is empty
func (a *Addr) IsZero() bool {
a.mu.RLock()
defer a.mu.RUnlock()
return a.User == "" && a.Domain == ""
}
func (a *Addr) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Addr string
User string
Domain string
Key string
Endpoint string
}{
User: a.User,
Domain: a.Domain,
Addr: a.String(),
Key: a.key.ID().String(),
Endpoint: a.Endpoint().String(),
})
}
func (a *Addr) String() string {
return fmt.Sprintf("%s@%s", a.User, a.Domain)
}
@ -47,20 +91,19 @@ func (a *Addr) Formatted() string {
return fmt.Sprintf("(%s)", a)
}
// Key returns the Publib Kcy of this User (Salty Addr) as discovered
func (a *Addr) Key() *keys.EdX25519PublicKey {
return a.key
}
// Endpoint returns the discovered Endpoint
func (a *Addr) Endpoint() *url.URL {
a.mu.RLock()
defer a.mu.RUnlock()
return a.endpoint
}
// DiscoveredDomain returns the discovered domain (if any) of fallbacks to the Domain
func (a *Addr) DiscoveredDomain() string {
a.mu.RLock()
defer a.mu.RUnlock()
if a.discoveredDomain != "" {
return a.discoveredDomain
}
@ -78,18 +121,26 @@ func (a *Addr) HashURI() string {
}
func (a *Addr) Refresh() error {
a.mu.Lock()
defer a.mu.Unlock()
log.Debugf("Looking up SRV record for _salty._tcp.%s", a.Domain)
_, records, err := net.LookupSRV("salty", "tcp", a.Domain)
if err == nil && len(records) > 0 {
a.discoveredDomain = records[0].Target
log.Debugf("Discovered salty services %s", a.discoveredDomain)
}
config, err := Lookup(a.String())
config, err := fetchConfig(a.HashURI())
if err != nil {
return fmt.Errorf("error looking up endpoint for %s: %w", a, err)
// Fallback to plain user nick
config, err = fetchConfig(a.URI())
}
if err != nil {
return fmt.Errorf("error looking up user %s: %w", a, err)
}
key, err := keys.NewEdX25519PublicKeyFromID(keys.ID(config.Key))
if err != nil {
return fmt.Errorf("error parsing public key %s: %w", config.Key, err)
}
a.key = key
u, err := url.Parse(config.Endpoint)
if err != nil {
@ -112,38 +163,16 @@ func ParseAddr(addr string) (*Addr, error) {
return &Addr{User: parts[0], Domain: parts[1]}, nil
}
// Lookup looks up a Salty Address for a User by parsing the user's domain and
// LookupAddr looks up a Salty Address for a User by parsing the user's domain and
// making a request to the user's Well-Known URI expected to be located at
// https://domain/.well-known/salty/<user>.json
func Lookup(addr string) (Config, error) {
func LookupAddr(addr string) (*Addr, error) {
a, err := ParseAddr(addr)
if err != nil {
return Config{}, err
return nil, err
}
config, err := fetchConfig(a.HashURI())
if err != nil {
// Fallback to plain user nick
config, err = fetchConfig(a.URI())
if err := a.Refresh(); err != nil {
return nil, err
}
return config, err
}
func fetchConfig(addr string) (Config, error) {
// Attempt using hash
res, err := Request(http.MethodGet, addr, nil, nil)
if err != nil {
return Config{}, err
}
data, err := ioutil.ReadAll(res.Body)
if err != nil {
return Config{}, err
}
var config Config
if err := json.Unmarshal(data, &config); err != nil {
return Config{}, err
}
return config, err
return a, nil
}