6
1
mirror of https://git.mills.io/saltyim/saltyim.git synced 2024-06-16 11:58:24 +00:00

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 <mlctrez@gmail.com>
Reviewed-on: https://git.mills.io/saltyim/saltyim/pulls/53
Co-authored-by: mlctrez <mlctrez@noreply@mills.io>
Co-committed-by: mlctrez <mlctrez@noreply@mills.io>
This commit is contained in:
mlctrez 2022-03-24 20:21:29 +00:00 committed by James Mills
parent 332202551d
commit 05c44be6c0
7 changed files with 129 additions and 38 deletions

@ -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
}

@ -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)

@ -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,
}

@ -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)

@ -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)

@ -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)

@ -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 }
}