Make the service bot actually work finally (#73)

Co-authored-by: James Mills <prologic@shortcircuit.net.au>
Reviewed-on: #73
Reviewed-by: xuu <xuu@noreply@mills.io>
pull/77/head
James Mills 6 months ago
parent bcbf7eeedc
commit 5e22d087ee
  1. 24
      bin/salty-chat.sh
  2. 12
      client.go
  3. 44
      cmd/salty-chat/register.go
  4. 2
      go.mod
  5. 2
      go.sum
  6. 56
      internal/server.go
  7. 41
      service.go

@ -115,11 +115,11 @@ lookup () {
domain="$(echo "$user" | awk -F@ '{ print $2 }')"
hash="$(printf "%s" "$user" | sha256sum | cut -f 1 -d ' ')"
discovery_host="$(dig +short SRV _salty._tcp."$domain" | cut -f 4 -d' ' | sed 's/.$//')"
discovery_host="$(dig +short SRV _salty._tcp."$domain" | cut -f 4 -d' ' | sed 's/\.$//')"
if [ -z "$discovery_host" ]; then
discovery_host="$domain"
else
discovery_host="$(echo "$discovery_host" | sed 's/.$//')"
discovery_host="$(echo "$discovery_host" | sed 's/\.$//')"
fi
info=$(mktemp /tmp/salty.XXXXXX)
@ -154,7 +154,8 @@ readmsgs () {
key="$(jq -r '.key' < "$salty_json")"
rm "$salty_json"
export MSGBUS_URI=$(dirname "$endpoint")
MSGBUS_URI=$(dirname "$endpoint")
export MSGBUS_URI
topic=$(basename "$endpoint")
msgbus sub "$topic" "$0"
@ -302,21 +303,16 @@ register () {
nick="$(echo "$user" | awk -F@ '{ print $1 }')"
domain="$(echo "$user" | awk -F@ '{ print $2 }')"
discovery_host="$(dig +short SRV _salty._tcp."$domain" | cut -f 4 -d' ' | sed 's/.$//')"
discovery_host="$(dig +short SRV _salty._tcp."$domain" | cut -f 4 -d' ' | sed 's/\.$//')"
if [ -z "$discovery_host" ]; then
discovery_host="$(echo "$discovery_host" | sed 's/.$//')"
echo "register is only supported with a saltyd service"
exit 1
fi
identity_file="$data_path/$nick.key"
if [ -f "$identity_file" ]; then
printf "user key already exists!"
return 1
fi
# Check for msgbus env.. probably can make it fallback to looking for a config file?
if [ -z "$MSGBUS_URI" ]; then
printf "missing MSGBUS_URI in environment"
echo "user key already exists!"
return 1
fi
@ -326,10 +322,10 @@ register () {
pubkey=$(grep key: "$identity_file" | awk '{print $4}')
export SALTY_IDENTITY="$identity_file"
msgbus sub "$pubkey" "$0" &
msgbus -u "https://$discovery_host/inbox" sub "$pubkey" "$0" &
pid="$!"
sendmsg "salty@$discovery_host" REGISTER
sendmsg "salty@$domain" REGISTER
sleep 1
kill "$pid"
}

@ -165,17 +165,17 @@ func (cli *Client) Send(user, msg string) error {
return fmt.Errorf("error looking up user %s: %w", user, err)
}
return cli.SendWithConfig(user, &Config{Endpoint: addr.Endpoint().String(), Key: addr.key.String()}, msg)
return cli.SendToAddr(addr, msg)
}
func (cli *Client) SendWithConfig(user string, config *Config, msg string) error {
b, err := salty.Encrypt(cli.key, PackMessage(cli.me, msg), []string{config.Key})
func (cli *Client) SendToAddr(addr *Addr, msg string) error {
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)
return fmt.Errorf("error encrypting message to %s: %w", addr, err)
}
if err := Send(config.Endpoint, string(b)); err != nil {
return fmt.Errorf("error sending message to %s: %w", user, err)
if err := Send(addr.Endpoint().String(), string(b)); err != nil {
return fmt.Errorf("error sending message to %s: %w", addr, err)
}
return nil

@ -3,45 +3,25 @@ package main
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.mills.io/saltyim"
)
var registerCmd = &cobra.Command{
Use: "register",
Aliases: []string{"auth", "reg"},
Short: "Registers a new account with a broker",
Long: `This command registers a new account with a broker.
TBD`,
Args: cobra.ExactArgs(0),
Aliases: []string{"reg"},
Short: "Creates a new user account and registers it with a Salty Broker",
Long: `This command registers creates a new identity for a new user, creating a new
key pair and registering with a Salty Broker (in instance of saltyd). Effectively this is
an automated account creation and registration process that combinaes make-user + manual
steps to setup and serve a valid Salty Discovery Endpoint (.well-known/salty/...).
NOTE: This is only spported on a Salty Broker.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintln(os.Stderr, "✋ This is being re-designed. Stay tuned! 🤗")
os.Exit(1)
user := viper.GetString("user")
identity := viper.GetString("identity")
var profiles []profile
viper.UnmarshalKey("profiles", &profiles)
for _, p := range profiles {
if user == p.User {
identity = p.Identity
}
}
me := &saltyim.Addr{}
if sp := strings.Split(user, "@"); len(sp) > 1 {
me.User = sp[0]
me.Domain = sp[1]
}
// XXX: What if me.IsZero()
register(me, identity)
register(args...)
},
}
@ -49,7 +29,9 @@ func init() {
rootCmd.AddCommand(registerCmd)
}
func register(me *saltyim.Addr, identity string) {
func register(args ...string) {
user := args[0]
cli, err := saltyim.NewClient(me, saltyim.WithIdentityPath(identity))
if err != nil {
fmt.Fprintf(os.Stderr, "error initializing client: %s\n", err)

@ -79,7 +79,7 @@ require (
require (
git.mills.io/prologic/bitcask v1.0.2
git.mills.io/prologic/msgbus v0.1.9
git.mills.io/prologic/msgbus v0.1.10
git.mills.io/prologic/observe v0.0.0-20210712230028-fc31c7aa2bd1
git.mills.io/prologic/useragent v0.0.0-20210714100044-d249fe7921a0
github.com/NYTimes/gziphandler v1.1.1

@ -57,6 +57,8 @@ git.mills.io/prologic/msgbus v0.1.9-0.20220326234253-3502f7b24292 h1:4WDEWtE5gCJ
git.mills.io/prologic/msgbus v0.1.9-0.20220326234253-3502f7b24292/go.mod h1:2YmGBm9WJjfMTBki/PuD5eG0CUULXesaV6kpVF/jJ2g=
git.mills.io/prologic/msgbus v0.1.9 h1:OIPW1B47wtoGwzYHPo9LQS8EwrDOVWDcn56VjtFwdGU=
git.mills.io/prologic/msgbus v0.1.9/go.mod h1:3HKT07iPSoi77CC3TpukUU5rkUErHcXThVHeYOej5kI=
git.mills.io/prologic/msgbus v0.1.10 h1:g9H7ea1lt1uHg6z43d4TaMjLaC2Ww4QzylZF0r128XI=
git.mills.io/prologic/msgbus v0.1.10/go.mod h1:2YmGBm9WJjfMTBki/PuD5eG0CUULXesaV6kpVF/jJ2g=
git.mills.io/prologic/observe v0.0.0-20210712230028-fc31c7aa2bd1 h1:e6ZyAOFGLZJZYL2galNvfuNMqeQDdilmQ5WRBXCNL5s=
git.mills.io/prologic/observe v0.0.0-20210712230028-fc31c7aa2bd1/go.mod h1:/rNXqsTHGrilgNJYH/8wsIRDScyxXUhpbSdNbBatAKY=
git.mills.io/prologic/useragent v0.0.0-20210714100044-d249fe7921a0 h1:MojWEgZyiugUbgyjydrdSAkHlADnbt90dXyURRYFzQ4=

@ -6,7 +6,6 @@ import (
"net"
"net/http"
"os/signal"
"path"
"path/filepath"
"syscall"
"time"
@ -109,9 +108,7 @@ func (s *Server) Run() (err error) {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()
if s.svc != nil {
s.svc.Run(ctx)
}
go s.svc.Run(ctx)
<-ctx.Done()
log.Infof("Received signal %s", ctx.Err())
@ -225,53 +222,36 @@ func (s *Server) setupCronJobs() error {
return nil
}
func (s *Server) setupSvcUser() {
func (s *Server) setupServiceUser() error {
log.Infof("starting service user %s", s.config.SvcUser)
// create our addr
me, err := saltyim.ParseAddr(s.config.SvcUser)
if err != nil {
log.WithError(err).Error("error parsing svc user addr: %w", err)
return
return err
}
// create or load client for services user
fn := filepath.Join(s.config.Data, servicesIdentity)
ident, err := saltyim.GetOrCreateIdentity(
id, err := saltyim.GetOrCreateIdentity(
saltyim.WithIdentityPath(fn),
saltyim.WithIdentityAddr(me),
)
if err != nil {
log.WithError(err).Error("error getting or creating svc user's identity")
return
}
if err := CreateConfig(s.config, me.Hash(), ident.Key().ID().String()); err != nil {
log.WithError(err).Error("error creating service config")
return
return err
}
var cli *saltyim.Client
for {
cli, err = saltyim.NewClient(me, saltyim.WithIdentity(ident))
if err != nil {
log.WithError(err).Warn("error creating svc user's client")
time.Sleep(time.Second * 3)
continue
} else {
break
}
if err := CreateConfig(s.config, me.Hash(), id.Key().ID().String()); err != nil {
return err
}
svc, err := saltyim.NewService(cli)
svc, err := saltyim.NewService(me, id)
if err != nil {
log.WithError(err).Errorf("error creating service")
return
return err
}
s.svc = svc
svc.TextFunc("register", func(ctx context.Context, bot *saltyim.Service, key *keys.EdX25519PublicKey, msg *lextwt.SaltyText) error {
svc.TextFunc("register", func(ctx context.Context, svc *saltyim.Service, key *keys.EdX25519PublicKey, msg *lextwt.SaltyText) error {
addr, err := saltyim.ParseAddr(msg.User.String())
if err != nil {
return err
@ -282,18 +262,13 @@ func (s *Server) setupSvcUser() {
return err
}
return bot.SendWithConfig(msg.User.String(), &saltyim.Config{
Endpoint: s.config.BaseURL + "/" + path.Join("inbox", key.String()),
Key: key.String(),
}, "OK")
return svc.Respond(msg.User.String(), "OK")
})
log.Println(s.svc)
return nil
}
func (s *Server) runStartupJobs() {
// time.Sleep(time.Second * 5)
log.Info("running startup jobs")
for name, jobSpec := range StartupJobs {
job := jobSpec.Factory(s.config, s.db)
@ -433,6 +408,12 @@ func NewServer(bind string, options ...Option) (*Server, error) {
server.cron.Start()
log.Info("started background jobs")
if err := server.setupServiceUser(); err != nil {
log.WithError(err).Error("error setting up service user")
return nil, err
}
log.Info("succeessfully setup service user")
server.setupMetrics()
log.Infof("serving metrics endpoint at http://%s/metrics", server.bind)
@ -447,7 +428,6 @@ func NewServer(bind string, options ...Option) (*Server, error) {
server.initRoutes()
go server.runStartupJobs()
go server.setupSvcUser()
return server, nil
}

@ -6,6 +6,7 @@ import (
"fmt"
"strings"
"sync"
"time"
"github.com/keys-pub/keys"
log "github.com/sirupsen/logrus"
@ -14,10 +15,13 @@ import (
)
type Service struct {
*Client
mu sync.RWMutex
me *Addr
id *Identity
cli *Client
textFns map[string]MessageTextHandlerFunc
eventFns map[string]MessageEventHandlerFunc
}
@ -25,14 +29,15 @@ type Service struct {
type MessageTextHandlerFunc func(context.Context, *Service, *keys.EdX25519PublicKey, *lextwt.SaltyText) error
type MessageEventHandlerFunc func(context.Context, *Service, *keys.EdX25519PublicKey, *lextwt.SaltyEvent) error
func NewService(client *Client) (*Service, error) {
func NewService(me *Addr, id *Identity) (*Service, error) {
svc := &Service{
Client: client,
me: me,
id: id,
textFns: make(map[string]MessageTextHandlerFunc),
eventFns: make(map[string]MessageEventHandlerFunc),
}
svc.TextFunc("ping", func(ctx context.Context, svc *Service, key *keys.EdX25519PublicKey, msg *lextwt.SaltyText) error {
return svc.Send(msg.User.String(), "Pong!")
return svc.Respond(msg.User.String(), "Pong!")
})
return svc, nil
@ -43,7 +48,7 @@ func (svc *Service) String() string {
defer svc.mu.RUnlock()
buf := &bytes.Buffer{}
fmt.Fprintln(buf, "Bot: ", svc.Client.me)
fmt.Fprintln(buf, "Bot: ", svc.me)
for k := range svc.textFns {
fmt.Fprintln(buf, " - TextCmd: ", k)
}
@ -53,9 +58,29 @@ func (svc *Service) String() string {
return buf.String()
}
func (svc *Service) Respond(user, msg string) error {
if svc.cli == nil {
return fmt.Errorf("service not connected")
}
return svc.cli.Send(user, msg)
}
func (svc *Service) Run(ctx context.Context) {
log.Println("listining for bot: ", svc.Me())
msgch := svc.Read(ctx, "", "")
// create the service user's client in a loop until successful
// TODO: Should this timeout? Use a context?
for {
cli, err := NewClient(svc.me, WithIdentity(svc.id))
if err != nil {
log.WithError(err).Warn("error creating service user client")
time.Sleep(time.Second * 3)
continue
}
svc.cli = cli
break
}
log.Println("listining for bot: ", svc.me)
msgch := svc.cli.Read(ctx, "", "")
for {
select {
case <-ctx.Done():

Loading…
Cancel
Save