feat: updates

This commit is contained in:
Jon Lundy
2022-09-04 08:34:22 -06:00
parent 8c54eefcdd
commit f65e9ca666
10 changed files with 523 additions and 75 deletions

142
app/salty/salty-addr.go Normal file
View File

@@ -0,0 +1,142 @@
package salty
import (
"context"
"crypto/sha256"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/keys-pub/keys"
"github.com/sour-is/ev/internal/logz"
)
// 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"`
}
type Capabilities struct {
AcceptEncoding string
}
func (c Capabilities) String() string {
return fmt.Sprint("accept-encoding: ", c.AcceptEncoding)
}
type Addr struct {
User string
Domain string
capabilities Capabilities
discoveredDomain string
dns DNSResolver
endpoint *url.URL
key *keys.EdX25519PublicKey
}
// ParseAddr parsers a Salty Address for a user into it's user and domain
// parts and returns an Addr object with the User and Domain and a method
// for returning the expected User's Well-Known URI
func (s *service) ParseAddr(addr string) (*Addr, error) {
parts := strings.Split(strings.ToLower(addr), "@")
if len(parts) != 2 {
return nil, fmt.Errorf("expected nick@domain found %q", addr)
}
return &Addr{User: parts[0], Domain: parts[1], dns: s.dns}, nil
}
func (a *Addr) String() string {
return fmt.Sprintf("%s@%s", a.User, a.Domain)
}
// Hash returns the Hex(SHA256Sum()) of the Address
func (a *Addr) Hash() string {
return fmt.Sprintf("%x", sha256.Sum256([]byte(strings.ToLower(a.String()))))
}
// URI returns the Well-Known URI for this Addr
func (a *Addr) URI() string {
return fmt.Sprintf("https://%s/.well-known/salty/%s.json", a.DiscoveredDomain(), a.User)
}
// HashURI returns the Well-Known HashURI for this Addr
func (a *Addr) HashURI() string {
return fmt.Sprintf("https://%s/.well-known/salty/%s.json", a.DiscoveredDomain(), a.Hash())
}
// DiscoveredDomain returns the discovered domain (if any) of fallbacks to the Domain
func (a *Addr) DiscoveredDomain() string {
if a.discoveredDomain != "" {
return a.discoveredDomain
}
return a.Domain
}
func (a *Addr) Refresh(ctx context.Context) error {
ctx, span := logz.Span(ctx)
defer span.End()
span.AddEvent(fmt.Sprintf("Looking up SRV record for _salty._tcp.%s", a.Domain))
if target, _, err := a.dns.LookupSRV(ctx, "salty", "tcp", a.Domain); err == nil {
a.discoveredDomain = target
span.AddEvent(fmt.Sprintf("Discovered salty services %s", a.discoveredDomain))
} else if err != nil {
span.AddEvent(fmt.Sprintf("error looking up SRV record for _salty._tcp.%s : %s", a.Domain, err))
}
config, cap, err := fetchConfig(ctx, a.HashURI())
if err != nil {
// Fallback to plain user nick
config, cap, err = fetchConfig(ctx, 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 {
return fmt.Errorf("error parsing endpoint %s: %w", config.Endpoint, err)
}
a.endpoint = u
a.capabilities = cap
span.AddEvent(fmt.Sprintf("Discovered endpoint: %v", a.endpoint))
span.AddEvent(fmt.Sprintf("Discovered capability: %v", a.capabilities))
return nil
}
func fetchConfig(ctx context.Context, addr string) (config Config, cap Capabilities, err error) {
ctx, span := logz.Span(ctx)
defer span.End()
var req *http.Request
req, err = http.NewRequestWithContext(ctx, http.MethodGet, addr, nil)
if err != nil {
return
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return
}
if err = json.NewDecoder(res.Body).Decode(&config); err != nil {
return
}
cap.AcceptEncoding = res.Header.Get("Accept-Encoding")
return
}

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"path"
"strings"
@@ -19,12 +20,21 @@ import (
"go.uber.org/multierr"
)
type DNSResolver interface {
LookupSRV(ctx context.Context, service, proto, name string) (string, []*net.SRV, error)
}
type service struct {
baseURL string
es *es.EventStore
dns DNSResolver
Mresolver_create_salty_user syncint64.Counter
Mresolver_salty_user syncint64.Counter
m_create_user syncint64.Counter
m_get_user syncint64.Counter
m_api_ping syncint64.Counter
m_api_register syncint64.Counter
m_api_lookup syncint64.Counter
m_api_send syncint64.Counter
}
type contextKey struct {
name string
@@ -51,13 +61,25 @@ func New(ctx context.Context, es *es.EventStore, baseURL string) (*service, erro
m := logz.Meter(ctx)
svc := &service{baseURL: baseURL, es: es}
svc := &service{baseURL: baseURL, es: es, dns: net.DefaultResolver}
var err, errs error
svc.Mresolver_create_salty_user, err = m.SyncInt64().Counter("resolver_create_salty_user")
svc.m_create_user, err = m.SyncInt64().Counter("salty_create_user")
errs = multierr.Append(errs, err)
svc.Mresolver_salty_user, err = m.SyncInt64().Counter("resolver_salty_user")
svc.m_get_user, err = m.SyncInt64().Counter("salty_get_user")
errs = multierr.Append(errs, err)
svc.m_api_ping, err = m.SyncInt64().Counter("salty_api_ping")
errs = multierr.Append(errs, err)
svc.m_api_register, err = m.SyncInt64().Counter("salty_api_register")
errs = multierr.Append(errs, err)
svc.m_api_lookup, err = m.SyncInt64().Counter("salty_api_lookup")
errs = multierr.Append(errs, err)
svc.m_api_send, err = m.SyncInt64().Counter("salty_api_send")
errs = multierr.Append(errs, err)
span.RecordError(err)
@@ -73,6 +95,12 @@ func (s *service) BaseURL() string {
func (s *service) RegisterHTTP(mux *http.ServeMux) {
mux.Handle("/.well-known/salty/", logz.Htrace(s, "lookup"))
}
func (s *service) RegisterAPIv1(mux *http.ServeMux) {
mux.HandleFunc("/ping", s.apiv1)
mux.HandleFunc("/register", s.apiv1)
mux.HandleFunc("/lookup/", s.apiv1)
mux.HandleFunc("/send", s.apiv1)
}
func (s *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := logz.Span(ctx)
@@ -112,7 +140,7 @@ func (s *service) CreateSaltyUser(ctx context.Context, nick string, pub string)
ctx, span := logz.Span(ctx)
defer span.End()
s.Mresolver_create_salty_user.Add(ctx, 1)
s.m_create_user.Add(ctx, 1)
streamID := fmt.Sprintf("saltyuser-%x", sha256.Sum256([]byte(strings.ToLower(nick))))
span.AddEvent(streamID)
@@ -142,7 +170,7 @@ func (s *service) SaltyUser(ctx context.Context, nick string) (*SaltyUser, error
ctx, span := logz.Span(ctx)
defer span.End()
s.Mresolver_salty_user.Add(ctx, 1)
s.m_get_user.Add(ctx, 1)
streamID := fmt.Sprintf("saltyuser-%x", sha256.Sum256([]byte(strings.ToLower(nick))))
span.AddEvent(streamID)
@@ -168,3 +196,54 @@ func (s *service) GetMiddleware() func(http.Handler) http.Handler {
})
}
}
func (s *service) apiv1(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := logz.Span(ctx)
defer span.End()
switch r.Method {
case http.MethodGet:
switch {
case r.URL.Path == "/ping":
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{}`))
case strings.HasPrefix(r.URL.Path, "/lookup/"):
addr, err := s.ParseAddr(strings.TrimPrefix(r.URL.Path, "/lookup/"))
if err != nil {
span.RecordError(err)
w.WriteHeader(http.StatusBadRequest)
return
}
err = addr.Refresh(ctx)
if err != nil {
span.RecordError(err)
w.WriteHeader(http.StatusBadRequest)
return
}
json.NewEncoder(w).Encode(addr)
return
default:
w.WriteHeader(http.StatusNotFound)
return
}
case http.MethodPost:
switch r.URL.Path {
case "/register":
case "/send":
default:
w.WriteHeader(http.StatusNotFound)
return
}
default:
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
}