6
1
mirror of https://git.mills.io/saltyim/saltyim.git synced 2024-06-20 22:08:21 +00:00

Add support for client and server (broker) registration (#43)

Co-authored-by: James Mills <prologic@shortcircuit.net.au>
Reviewed-on: https://git.mills.io/prologic/saltyim/pulls/43
This commit is contained in:
James Mills 2022-03-22 22:59:09 +00:00
parent 958f4ea4f6
commit 801d6b93bb
12 changed files with 271 additions and 7 deletions

@ -3,8 +3,10 @@ package saltyim
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"time"
@ -168,3 +170,34 @@ func (cli *Client) Send(user, msg string) error {
return nil
}
// Register sends a registration requestn to a broker
func (cli *Client) Register() error {
req := RegisterRequest{
Addr: cli.me,
Key: cli.key.ID().String(),
}
data, err := json.Marshal(req)
if err != nil {
return fmt.Errorf("error serializing register request: %w", err)
}
signed, err := salty.Sign(cli.key, data)
if err != nil {
return fmt.Errorf("error signing registration request: %w", err)
}
body := bytes.NewBuffer(signed)
endpointURL, err := url.Parse(cli.endpoint)
if err != nil {
return fmt.Errorf("error parsing endpoint %s: %w", cli.endpoint, err)
}
endpointURL.Path = "/api/v1/register"
res, err := Request(http.MethodPost, endpointURL.String(), nil, body)
if err != nil {
return fmt.Errorf("error registering to broker %s: %w", endpointURL, err)
}
defer res.Body.Close()
return nil
}

@ -0,0 +1,66 @@
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.
A request is sent to the broker to the registration endpoint with the contents
of the user's public key and salty address, signed with the user's private key.
If the broker can verify the request was signed correctly by the user a new
account is created and a Well-Known COnfing and Inbox created.`,
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
user := viper.GetString("user")
endpoint := viper.GetString("endpoint")
identity := viper.GetString("identity")
var profiles []profile
viper.UnmarshalKey("profiles", &profiles)
for _, p := range profiles {
if user == p.User {
endpoint = p.Endpoint
identity = p.Identity
}
}
var me saltyim.Addr
if sp := strings.Split(user, "@"); len(sp) > 1 {
me.User = sp[0]
me.Domain = sp[1]
}
register(me, identity, endpoint)
},
}
func init() {
rootCmd.AddCommand(registerCmd)
}
func register(me saltyim.Addr, identity, endpoint string) {
cli, err := saltyim.NewClient(me, identity, endpoint)
if err != nil {
fmt.Fprintf(os.Stderr, "error initializing client: %s\n", err)
os.Exit(2)
}
if err := cli.Register(); err != nil {
fmt.Fprintf(os.Stderr, "error registering to %s: %s\n", endpoint, err)
os.Exit(2)
}
fmt.Println("Success!")
}

@ -23,7 +23,9 @@ var (
version bool
// Basic options
store string
data string
store string
baseURL string
)
const (
@ -47,7 +49,9 @@ func init() {
flag.BoolVarP(&version, "version", "v", false, "display version information")
// Basic options
flag.StringVarP(&data, "data", "d", internal.DefaultData, "data directory")
flag.StringVarP(&store, "store", "s", internal.DefaultStore, "store to use")
flag.StringVarP(&baseURL, "base-url", "u", internal.DefaultBaseURL, "base url to use")
}
func flagNameFromEnvironmentName(s string) string {
@ -91,7 +95,9 @@ func main() {
internal.WithDebug(debug),
// Basic options
internal.WithData(data),
internal.WithStore(store),
internal.WithBaseURL(baseURL),
)
if err != nil {
log.WithError(err).Fatal("error creating server")

0
data/.gitkeep Normal file

@ -0,0 +1 @@
{"endpoint":"http://0.0.0.0:8000/inbox/01FYSBPFYJD0RGWFF41RMVYBY3","key":"kex1ekt5cru4vs42wnaxppkjn5pexmt2w6uxx9z2mz0fqeuc80e0g9gsggs8ah"}

4
go.mod

@ -8,7 +8,7 @@ require (
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.4.0
github.com/spf13/viper v1.10.1
go.mills.io/salty v0.0.0-20220318125419-fb3d6fc9e870
go.mills.io/salty v0.0.0-20220322161301-ce2b9f6573fa
)
require (
@ -58,7 +58,7 @@ require (
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.27.1 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

4
go.sum

@ -567,6 +567,8 @@ go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsX
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
go.mills.io/salty v0.0.0-20220318125419-fb3d6fc9e870 h1:fH4ftkY8i0Y2ycstDXmVmqxKyY+l4Gx4OvgxBm/wk8Q=
go.mills.io/salty v0.0.0-20220318125419-fb3d6fc9e870/go.mod h1:bQ9yvK7wwThD4tzoioJq/YAuwYOB2XA9tAUHIYtjre8=
go.mills.io/salty v0.0.0-20220322161301-ce2b9f6573fa h1:KBxzYJMWP7MXd72RgqsMCGOSEqV6aaDDSdSb8usJCzQ=
go.mills.io/salty v0.0.0-20220322161301-ce2b9f6573fa/go.mod h1:bQ9yvK7wwThD4tzoioJq/YAuwYOB2XA9tAUHIYtjre8=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -1043,6 +1045,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

@ -4,7 +4,10 @@ import (
"net/http"
"github.com/julienschmidt/httprouter"
log "github.com/sirupsen/logrus"
"github.com/unrolled/render"
"go.mills.io/saltyim"
)
// API ...
@ -29,6 +32,7 @@ func (a *API) initRoutes() {
router := a.router.Group("/api/v1")
router.GET("/ping", a.PingEndpoint())
router.POST("/register", a.RegisterEndpoint())
}
// PingEndpoint ...
@ -38,3 +42,27 @@ func (a *API) PingEndpoint() httprouter.Handle {
_, _ = w.Write([]byte(`{}`))
}
}
// RegisterEndpoint ...
func (a *API) RegisterEndpoint() httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
req, signer, err := saltyim.NewRegisterRequest(r.Body)
if err != nil {
log.WithError(err).Error("error parsing register request")
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
if signer != req.Key {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
if err := CreateConfig(a.config, req.Addr, req.Key); err != nil {
log.WithError(err).Errorf("error creating config for %s", req.Addr)
http.Error(w, "Error", http.StatusInternalServerError)
return
}
http.Error(w, "Account Created", http.StatusCreated)
}
}

@ -1,14 +1,31 @@
package internal
import (
"net/url"
log "github.com/sirupsen/logrus"
)
// Config contains the server configuration parameters
type Config struct {
Debug bool
Store string
Debug bool
Data string
Store string
BaseURL string
baseURL *url.URL
}
// Validate validates the configuration is valid which for the most part
// just ensures that default secrets are actually configured correctly
func (c *Config) Validate() error {
// Automatically correct missing Scheme in Pod Base URL
if c.baseURL.Scheme == "" {
log.Warnf("base url (-u/--base-url) %s is missing the scheme", c.BaseURL)
c.baseURL.Scheme = "http"
c.BaseURL = c.baseURL.String()
}
if c.Debug {
return nil
}

@ -1,5 +1,7 @@
package internal
import "net/url"
const (
// InvalidConfigValue is the constant value for invalid config values
// which must be changed for production configurations before successful
@ -9,14 +11,21 @@ const (
// DefaultDebug is the default debug mode
DefaultDebug = false
// DefaultData is the default data directory for storage
DefaultData = "./data"
// DefaultStore is the default data store used for accounts, sessions, etc
DefaultStore = "bitcask://saltyim.db"
// DefaultBaseURL is the default Base URL for the server
DefaultBaseURL = "http://0.0.0.0:8000"
)
func NewConfig() *Config {
return &Config{
Debug: DefaultDebug,
Store: DefaultStore,
Debug: DefaultDebug,
Store: DefaultStore,
BaseURL: DefaultBaseURL,
}
}
@ -31,6 +40,14 @@ func WithDebug(debug bool) Option {
}
}
// WithData sets the data directory to use for storage
func WithData(data string) Option {
return func(cfg *Config) error {
cfg.Data = data
return nil
}
}
// WithStore sets the store to use for accounts, sessions, etc.
func WithStore(store string) Option {
return func(cfg *Config) error {
@ -38,3 +55,16 @@ func WithStore(store string) Option {
return nil
}
}
// WithBaseURL sets the Base URL used for constructing feed URLs
func WithBaseURL(baseURL string) Option {
return func(cfg *Config) error {
u, err := url.Parse(baseURL)
if err != nil {
return err
}
cfg.BaseURL = baseURL
cfg.baseURL = u
return nil
}
}

47
internal/tasks.go Normal file

@ -0,0 +1,47 @@
package internal
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"go.mills.io/saltyim"
)
const (
wellknownPath = ".well-known/salty"
)
func CreateConfig(conf *Config, addr saltyim.Addr, key string) error {
p := filepath.Join(conf.Data, wellknownPath)
fn := filepath.Join(p, fmt.Sprintf("%s.json", addr.Hash()))
if err := os.MkdirAll(p, 0755); err != nil {
return fmt.Errorf("error creating config paths %s: %w", p, err)
}
ulid, err := saltyim.GenerateULID()
if err != nil {
return fmt.Errorf("error generating ulid")
}
endpointURL := *conf.baseURL
endpointURL.Path = fmt.Sprintf("/inbox/%s", ulid)
config := saltyim.Config{
Endpoint: endpointURL.String(),
Key: key,
}
data, err := json.Marshal(config)
if err != nil {
return fmt.Errorf("error serializing config")
}
if err := os.WriteFile(fn, data, 0644); err != nil {
return fmt.Errorf("error writing config to %s: %w", fn, err)
}
return nil
}

32
types.go Normal file

@ -0,0 +1,32 @@
package saltyim
import (
"encoding/json"
"io"
"io/ioutil"
"go.mills.io/salty"
)
// RegisterRequest is the request used by clients to register to a broker
type RegisterRequest struct {
Addr Addr
Key string
}
// NewRegisterRequest reads the signed request body from a client, verifies its signature
// and returns the resulting `RegisterRequest` and key used to sign the request on success
// otherwise an empty object and en error on failure.
func NewRegisterRequest(r io.Reader) (req RegisterRequest, signer string, err error) {
body, err := ioutil.ReadAll(r)
if err != nil {
return
}
out, key, err := salty.Verify(body)
if err != nil {
return
}
signer = key.ID().String()
err = json.Unmarshal(out, &req)
return
}