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:
parent
958f4ea4f6
commit
801d6b93bb
33
client.go
33
client.go
@ -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
|
||||
}
|
||||
|
66
cmd/salty-chat/register.go
Normal file
66
cmd/salty-chat/register.go
Normal file
@ -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
data/.gitkeep
Normal file
1
data/.well-known/salty/d3d52221e8da5a8ae012f4e2db0631c181f4156f0edcde5cffa25b347c7ceda8.json
Normal file
1
data/.well-known/salty/d3d52221e8da5a8ae012f4e2db0631c181f4156f0edcde5cffa25b347c7ceda8.json
Normal file
@ -0,0 +1 @@
|
||||
{"endpoint":"http://0.0.0.0:8000/inbox/01FYSBPFYJD0RGWFF41RMVYBY3","key":"kex1ekt5cru4vs42wnaxppkjn5pexmt2w6uxx9z2mz0fqeuc80e0g9gsggs8ah"}
|
4
go.mod
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
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
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
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
|
||||
}
|
Loading…
Reference in New Issue
Block a user