mirror of
https://git.mills.io/saltyim/saltyim.git
synced 2024-06-28 09:41:02 +00:00
05c44be6c0
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>
181 lines
4.1 KiB
Go
181 lines
4.1 KiB
Go
package saltyim
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/keys-pub/keys"
|
|
"go.mills.io/salty"
|
|
)
|
|
|
|
func readUser(fd io.Reader) (*Addr, error) {
|
|
scan := bufio.NewScanner(fd)
|
|
|
|
addr := &Addr{}
|
|
|
|
for scan.Scan() {
|
|
if strings.HasPrefix(scan.Text(), "# user:") {
|
|
user := strings.Split(strings.TrimSpace(strings.TrimPrefix(scan.Text(), "# user:")), "@")
|
|
if len(user) != 2 {
|
|
return nil, nil
|
|
}
|
|
addr.User, addr.Domain = user[0], user[1]
|
|
}
|
|
}
|
|
return addr, scan.Err()
|
|
}
|
|
|
|
// DefaultIdentity returns a default identity file (if one exists) otherwise
|
|
// returns an empty string
|
|
func DefaultIdentity() string {
|
|
return os.ExpandEnv("$HOME/.config/salty/$USER.key")
|
|
}
|
|
|
|
// DefaultEndpoint returns a default inbox file (if one exists) otherwise
|
|
// returns an empty string
|
|
func DefaultEndpoint() string {
|
|
return os.ExpandEnv("https://msgbus.mills.io/$USER")
|
|
}
|
|
|
|
// CreateIdentity ...
|
|
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 ident, fmt.Errorf("error opening identity %q for writing: %w", fn, err)
|
|
}
|
|
defer f.Close()
|
|
|
|
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 ident, fmt.Errorf("error syncing identity %q for writing: %w", fn, err)
|
|
}
|
|
|
|
if err := f.Close(); err != nil {
|
|
return ident, fmt.Errorf("error closing identity %q for writing: %w", fn, err)
|
|
}
|
|
|
|
return ident, nil
|
|
}
|
|
|
|
// GetIdentity ...
|
|
func GetIdentity(options ...IdentityOption) (*Identity, error) {
|
|
|
|
ident := &Identity{}
|
|
for _, option := range options {
|
|
option(ident)
|
|
}
|
|
|
|
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 ident, fmt.Errorf("error reading identity %q: %s", ident.Source(), err)
|
|
}
|
|
ident.key = key
|
|
|
|
me, err := readUser(bytes.NewBuffer(ident.contents))
|
|
ident.addr = me
|
|
|
|
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 }
|
|
}
|