From 05c44be6c02ad97d249ddf772e89e3f232e8a3bb Mon Sep 17 00:00:00 2001 From: mlctrez Date: Thu, 24 Mar 2022 20:21:29 +0000 Subject: [PATCH] refactor identity create/read for functional arguments to support PWA (#53) This PR is for refactoring interaction with CreateIdentity(), GetIdentity(), and NewClient() to allow operations on the identity contents from within a PWA where a filesystem is not present. It was suggested to use functional arguments so this pull request reflects this. Co-authored-by: mlctrez Reviewed-on: https://git.mills.io/saltyim/saltyim/pulls/53 Co-authored-by: mlctrez Co-committed-by: mlctrez --- client.go | 16 ++--- cmd/salty-chat/chat.go | 2 +- cmd/salty-chat/makeuser.go | 6 +- cmd/salty-chat/read.go | 2 +- cmd/salty-chat/register.go | 2 +- cmd/salty-chat/send.go | 2 +- identity.go | 137 ++++++++++++++++++++++++++++++------- 7 files changed, 129 insertions(+), 38 deletions(-) diff --git a/client.go b/client.go index dfc8059..3c99d3c 100644 --- a/client.go +++ b/client.go @@ -45,33 +45,33 @@ type Client struct { cache configCache } -// NewClient reeturns a new Salty IM client for sending and receiving +// NewClient returns a new Salty IM client for sending and receiving // encrypted messages to other Salty IM users as well as decrypting // and displaying messages of the user's own inbox. -func NewClient(me *Addr, identity string) (*Client, error) { - key, m, err := GetIdentity(identity) +func NewClient(me *Addr, options ...IdentityOption) (*Client, error) { + ident, err := GetIdentity(options...) if err != nil { - return nil, fmt.Errorf("error opening identity %s: %w", identity, err) + return nil, fmt.Errorf("error opening identity %s: %w", ident.Source(), err) } if me == nil || me.IsZero() { - me = m + me = ident.addr } if me == nil || me.IsZero() { - return nil, fmt.Errorf("unable to find your user addressn in %s", identity) + return nil, fmt.Errorf("unable to find your user addressn in %s", ident.Source()) } if err := me.Refresh(); err != nil { return nil, fmt.Errorf("error looking up user endpoint: %w", err) } - log.Debugf("Using identity %s with public key %s", identity, key) + log.Debugf("Using identity %s with public key %s", ident.Source(), ident.key) log.Debugf("Salty Addr is %s", me) log.Debugf("Endpoint is %s", me.Endpoint()) return &Client{ me: me, - key: key, + key: ident.key, cache: make(configCache), }, nil } diff --git a/cmd/salty-chat/chat.go b/cmd/salty-chat/chat.go index cd36d20..3a25e2e 100644 --- a/cmd/salty-chat/chat.go +++ b/cmd/salty-chat/chat.go @@ -47,7 +47,7 @@ func init() { } func chat(me *saltyim.Addr, identity, user string) { - cli, err := saltyim.NewClient(me, identity) + cli, err := saltyim.NewClient(me, saltyim.IdentityPath(identity)) if err != nil { fmt.Fprintf(os.Stderr, "error initializing client: %s\n", err) os.Exit(2) diff --git a/cmd/salty-chat/makeuser.go b/cmd/salty-chat/makeuser.go index 898c54e..3d40f62 100644 --- a/cmd/salty-chat/makeuser.go +++ b/cmd/salty-chat/makeuser.go @@ -107,12 +107,12 @@ func makeuser(me *saltyim.Addr, identity, endpoint string) { os.Exit(2) } - if err := saltyim.CreateIdentity(identity, me.String()); err != nil { + if _, err := saltyim.CreateIdentity(saltyim.IdentityPath(identity), saltyim.IdentityAddr(me)); err != nil { fmt.Fprintf(os.Stderr, "error creating identity %q for %s: %s\n", identity, me, err) os.Exit(2) } - key, _, err := saltyim.GetIdentity(identity) + ident, err := saltyim.GetIdentity(saltyim.IdentityPath(identity)) if err != nil { fmt.Fprintf(os.Stderr, "error reading identity %s for %s: %s\n", identity, me, err) os.Exit(2) @@ -121,7 +121,7 @@ func makeuser(me *saltyim.Addr, identity, endpoint string) { ctx := setupCtx{ Config: saltyim.Config{ Endpoint: u.String(), - Key: key.PublicKey().ID().String(), + Key: ident.Key().PublicKey().ID().String(), }, Addr: me, } diff --git a/cmd/salty-chat/read.go b/cmd/salty-chat/read.go index d4a1a60..fb288d0 100644 --- a/cmd/salty-chat/read.go +++ b/cmd/salty-chat/read.go @@ -74,7 +74,7 @@ func init() { } func read(me *saltyim.Addr, identity string, prehook, posthook string, args ...string) { - cli, err := saltyim.NewClient(me, identity) + cli, err := saltyim.NewClient(me, saltyim.IdentityPath(identity)) if err != nil { fmt.Fprintf(os.Stderr, "error initializing client: %s\n", err) os.Exit(2) diff --git a/cmd/salty-chat/register.go b/cmd/salty-chat/register.go index a69bcbc..173aaea 100644 --- a/cmd/salty-chat/register.go +++ b/cmd/salty-chat/register.go @@ -50,7 +50,7 @@ func init() { } func register(me *saltyim.Addr, identity string) { - cli, err := saltyim.NewClient(me, identity) + cli, err := saltyim.NewClient(me, saltyim.IdentityPath(identity)) if err != nil { fmt.Fprintf(os.Stderr, "error initializing client: %s\n", err) os.Exit(2) diff --git a/cmd/salty-chat/send.go b/cmd/salty-chat/send.go index 45915c5..9cfdf2f 100644 --- a/cmd/salty-chat/send.go +++ b/cmd/salty-chat/send.go @@ -67,7 +67,7 @@ func send(me *saltyim.Addr, identity, user string, args ...string) { os.Exit(2) } - cli, err := saltyim.NewClient(me, identity) + cli, err := saltyim.NewClient(me, saltyim.IdentityPath(identity)) if err != nil { fmt.Fprintf(os.Stderr, "error initializing client: %s\n", err) os.Exit(2) diff --git a/identity.go b/identity.go index c723d55..275df93 100644 --- a/identity.go +++ b/identity.go @@ -2,8 +2,10 @@ package saltyim import ( "bufio" + "bytes" "fmt" "io" + "io/ioutil" "os" "path/filepath" "strings" @@ -42,48 +44,137 @@ func DefaultEndpoint() string { } // CreateIdentity ... -func CreateIdentity(fn, user string) error { +func CreateIdentity(options ...IdentityOption) (*Identity, error) { + + ident := &Identity{} + for _, option := range options { + option(ident) + } + + if ident.addr == nil || ident.addr.IsZero() { + return nil, fmt.Errorf("unable to find user address in options") + } + + buf := &bytes.Buffer{} + + salty.GenerateKeys(buf) + + buf.Write([]byte(fmt.Sprintf("# user: %s\n", ident.addr.String()))) + + ident.contents = buf.Bytes() + + if ident.path == "" { + return ident, nil + } + + fn := ident.path + f, err := os.OpenFile(fn, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) if err != nil { - return fmt.Errorf("error opening identity %q for writing: %w", fn, err) + return ident, fmt.Errorf("error opening identity %q for writing: %w", fn, err) } defer f.Close() - salty.GenerateKeys(f) - - f.Write([]byte(fmt.Sprintf("# user: %s\n", user))) + if _, err := f.Write(ident.contents); err != nil { + return ident, fmt.Errorf("error writing identity %q: %w", fn, err) + } if err := f.Sync(); err != nil { - return fmt.Errorf("error syncing identity %q for writing: %w", fn, err) + return ident, fmt.Errorf("error syncing identity %q for writing: %w", fn, err) } if err := f.Close(); err != nil { - return fmt.Errorf("error closing identity %q for writing: %w", fn, err) + return ident, fmt.Errorf("error closing identity %q for writing: %w", fn, err) } - return nil + return ident, nil } // GetIdentity ... -func GetIdentity(fn string) (*keys.EdX25519Key, *Addr, error) { - // Handle unix home with `~` - if strings.HasPrefix(fn, "~/") { - dirname, _ := os.UserHomeDir() - fn = filepath.Join(dirname, fn[2:]) +func GetIdentity(options ...IdentityOption) (*Identity, error) { + + ident := &Identity{} + for _, option := range options { + option(ident) } - id, err := os.Open(fn) + if ident.path != "" { + fn := ident.path + // Handle unix home with `~` + if strings.HasPrefix(fn, "~/") { + dirname, _ := os.UserHomeDir() + fn = filepath.Join(dirname, fn[2:]) + } + + id, err := os.Open(fn) + if err != nil { + return ident, fmt.Errorf("error opening identity %q: %s", ident.Source(), err) + } + defer id.Close() + identityBytes, err := ioutil.ReadAll(id) + if err != nil { + return ident, fmt.Errorf("error opening identity %q: %s", ident.Source(), err) + } + ident.contents = identityBytes + } + + key, err := salty.ParseIdentity(bytes.NewBuffer(ident.contents)) if err != nil { - return nil, nil, fmt.Errorf("error opening identity %q: %s", fn, err) + return ident, fmt.Errorf("error reading identity %q: %s", ident.Source(), err) } - defer id.Close() + ident.key = key - key, err := salty.ParseIdentity(id) - if err != nil { - return nil, nil, fmt.Errorf("error reading identity %q: %s", fn, err) - } + me, err := readUser(bytes.NewBuffer(ident.contents)) + ident.addr = me - id.Seek(0, 0) - me, err := readUser(id) - return key, me, err + return ident, err +} + +// Identity allows interaction with CreateIdentity, GetIdentity, and NewClient +type Identity struct { + // path where identity is read or written to + path string + // contents where identity is read from or stored + contents []byte + // key is the identity key + key *keys.EdX25519Key + // addr is the user / endpoint + addr *Addr +} + +func (i *Identity) Source() string { + if i.path != "" { + return i.path + } + return "[]byte" +} + +func (i *Identity) Contents() []byte { + return i.contents +} + +func (i *Identity) Key() *keys.EdX25519Key { + return i.key +} + +func (i *Identity) Addr() *Addr { + return i.addr +} + +// IdentityOption represents functional options for various identity operations +type IdentityOption func(*Identity) + +// IdentityAddr sets the identity Addr option +func IdentityAddr(addr *Addr) IdentityOption { + return func(i *Identity) { i.addr = addr } +} + +// IdentityPath indicates that an identity should be read / written from a file path +func IdentityPath(path string) IdentityOption { + return func(i *Identity) { i.path = path } +} + +// IdentityContents indicates that the identity should be read from a byte array +func IdentityContents(contents []byte) IdentityOption { + return func(i *Identity) { i.contents = contents } }