From 8f0914ef22475297fd76119b1e7bcffab2057a2e Mon Sep 17 00:00:00 2001 From: James Mills Date: Sat, 19 Mar 2022 11:42:36 +0000 Subject: [PATCH] Add make-user command (#9) Co-authored-by: James Mills Reviewed-on: https://git.mills.io/prologic/saltyim/pulls/9 --- .salty-chat.sh.swp | Bin 0 -> 16384 bytes cmd/salty-chat/makeuser.go | 98 +++++++++++++++++++++++++ config.go | 23 ------ go.mod | 1 + go.sum | 20 +++++ identity.go | 70 ++++++++++++++++-- lookup.go | 4 +- salty-chat.sh => scripts/salty-chat.sh | 0 utils.go | 39 ++++++++++ 9 files changed, 225 insertions(+), 30 deletions(-) create mode 100644 .salty-chat.sh.swp create mode 100644 cmd/salty-chat/makeuser.go rename salty-chat.sh => scripts/salty-chat.sh (100%) diff --git a/.salty-chat.sh.swp b/.salty-chat.sh.swp new file mode 100644 index 0000000000000000000000000000000000000000..753f5d1ae5f0a3e13434f03d04c2921f4670e21e GIT binary patch literal 16384 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 literal 0 HcmV?d00001 diff --git a/cmd/salty-chat/makeuser.go b/cmd/salty-chat/makeuser.go new file mode 100644 index 0000000..51b9edb --- /dev/null +++ b/cmd/salty-chat/makeuser.go @@ -0,0 +1,98 @@ +package main + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "go.mills.io/saltyim" +) + +const ( + postSetupInstructions = `Create this file and place it on your web server +so that it is accessible at a top-level domain or sub-domain at the URL: + +{{ .Addr.HashURI }} + +{ + "endpoint": "{{ .Config.Endpoint }}", + "key": "{{ .Config.Key }}" +} + +To verify you have done this correctly: + +$ salty-chat lookup {{ .Addr }}` +) + +type setupCtx struct { + Config saltyim.Config + Addr saltyim.Addr +} + +var makeuserCmd = &cobra.Command{ + Use: "make-user nick@domain", + Aliases: []string{"mkuser", "setup"}, + Short: "Creates a new Salty User", + Long: `This command creates a new Salty User and Key Pair storing the +Private Key in either $XDG_CONFIG_HOME/salty/ or $HOME/.salty/ as well as +information on how to setup the Well-Known Config and URI for Discovery by +others Salty IM Users. + +A valid top-level domain or sub-domain is required and the is in the form of: + +username@domain`, + Args: cobra.MaximumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + uri := viper.GetString("uri") + user := args[0] + makeuser(uri, user) + }, +} + +func init() { + rootCmd.AddCommand(makeuserCmd) +} + +func makeuser(uri, user string) { + if _, err := saltyim.ParseAddr(user); err != nil { + fmt.Fprintf(os.Stderr, "error parsing user %q: %s\n", user, err) + os.Exit(2) + } + + config := saltyim.GetConfigDir() + if config == "" { + log.Fatal("unable to find a suitable configurtion directory") + } + + if err := os.MkdirAll(config, 0700); err != nil { + fmt.Fprintf(os.Stderr, "error creating configuration directory %s: %s", config, err) + os.Exit(2) + } + + identity := filepath.Join(config, fmt.Sprintf("%s.key", user)) + + if err := saltyim.CreateIdentity(identity, user); err != nil { + fmt.Fprintf(os.Stderr, "error creating identity %s for %s: %s", identity, user, err) + os.Exit(2) + } + + key, me, err := saltyim.GetIdentity(identity) + if err != nil { + fmt.Fprintf(os.Stderr, "error reading identity %s for %s: %s", identity, user, err) + os.Exit(2) + } + + ctx := setupCtx{ + Config: saltyim.Config{ + Endpoint: fmt.Sprintf("%s/%s", uri, me.User), + Key: key.PublicKey().ID().String(), + }, + Addr: me, + } + + fmt.Println(saltyim.MustRenderString(postSetupInstructions, ctx)) +} diff --git a/config.go b/config.go index 1e9dc2c..b688cb3 100644 --- a/config.go +++ b/config.go @@ -1,31 +1,8 @@ package saltyim -import ( - "bufio" - "fmt" - "io" - "strings" -) - // Config represents a Salty Config for a User which at a minimum is required // to have an Endpoint and Key (Public Key) type Config struct { Endpoint string `json:"endpoint"` Key string `json:"key"` } - -func ReadUser(fd io.Reader) (Addr, error) { - scan := bufio.NewScanner(fd) - - var a 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 Addr{}, fmt.Errorf("user not found") - } - a.User, a.Domain = user[0], user[1] - } - } - return a, scan.Err() -} diff --git a/go.mod b/go.mod index a3b1bc1..38e18c1 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( require ( git.mills.io/prologic/msgbus v0.1.3-0.20220319044708-6df82dc484d7 + github.com/Masterminds/sprig/v3 v3.2.2 github.com/buger/goterm v1.0.4 github.com/gen2brain/beeep v0.0.0-20210529141713-5586760f0cc1 github.com/gorilla/websocket v1.5.0 // indirect diff --git a/go.sum b/go.sum index 0bcc9f8..bacd0e0 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,12 @@ git.mills.io/prologic/msgbus v0.1.3-0.20220319044708-6df82dc484d7/go.mod h1:mWGX github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= +github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5 h1:VauE2GcJNZFun2Och6tIT2zJZK1v6jxALQDA9BIji/E= github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5/go.mod h1:gxOHeajFfvGQh/fxlC8oOKBe23xnnJTif00IFFbiT+o= @@ -223,6 +229,8 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -267,9 +275,13 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= @@ -332,6 +344,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -340,6 +354,8 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mmcloughlin/professor v0.0.0-20170922221822-6b97112ab8b3/go.mod h1:LQkXsHRSPIEklPCq8OMQAzYNS2NGtYStdNE/ej1oJU8= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -402,6 +418,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -413,6 +431,7 @@ github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY52 github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= @@ -487,6 +506,7 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= diff --git a/identity.go b/identity.go index f87e386..04e8a00 100644 --- a/identity.go +++ b/identity.go @@ -1,21 +1,58 @@ package saltyim import ( + "bufio" "fmt" - "log" + "io" "os" + "path/filepath" + "strings" "github.com/keys-pub/keys" "go.mills.io/salty" ) var ( + defaultConfigPaths = []string{ + "$HOME/.config/salty/", + "$HOME/.salty/", + } + defaultIdentityPaths = []string{ - "$XDG_CONFIG_HOME/.salty/$USER.key", + "$HOME/.config/salty/$USER.key", "$HOME/.salty/$USER.key", } ) +func readUser(fd io.Reader) (Addr, error) { + scan := bufio.NewScanner(fd) + + var a 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 Addr{}, fmt.Errorf("user not found") + } + a.User, a.Domain = user[0], user[1] + } + } + return a, scan.Err() +} + +// GetConfigDir returns a suitable configuration directory +func GetConfigDir() string { + if configDir := os.Getenv("XDG_CONFIG_HOME"); configDir != "" { + return filepath.Join(configDir, "salty") + } + for _, p := range defaultConfigPaths { + if p := os.ExpandEnv(p); DirExists(p) || DirExists(filepath.Join(p, "..")) { + return p + } + } + return "" +} + // DefaultIdentity returns a default identity file (if one exists) otherwise // returns an empty string func DefaultIdentity() string { @@ -27,21 +64,44 @@ func DefaultIdentity() string { return "" } +// CreateIdentity ... +func CreateIdentity(fn, user string) error { + f, err := os.OpenFile(fn, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) + if err != nil { + return fmt.Errorf("error opening identity %s for writing: %w", fn, err) + } + defer f.Close() + + salty.GenerateKeys(f) + + f.Write([]byte(fmt.Sprintf("# user: %s\n", user))) + + if err := f.Sync(); err != nil { + return fmt.Errorf("error syncing identity %s for writing: %w", fn, err) + } + + if err := f.Close(); err != nil { + return fmt.Errorf("error closing identity %s for writing: %w", fn, err) + } + + return nil +} + // GetIdentity ... func GetIdentity(fn string) (*keys.EdX25519Key, Addr, error) { id, err := os.Open(fn) if err != nil { - log.Fatalf("error opening identity file: %q", fn) + return nil, Addr{}, fmt.Errorf("error opening identity file: %q", fn) } defer id.Close() key, err := salty.ParseIdentity(id) if err != nil { - log.Fatalf("error reading private key: %q", fn) + return nil, Addr{}, fmt.Errorf("error reading private key: %q", fn) } id.Seek(0, 0) - me, err := ReadUser(id) + me, err := readUser(id) if err != nil { return key, Addr{}, fmt.Errorf("error reading user from keyfile: %q", fn) } diff --git a/lookup.go b/lookup.go index 06a153f..b3179a6 100644 --- a/lookup.go +++ b/lookup.go @@ -23,7 +23,7 @@ func (a Addr) IsZero() bool { } func (a Addr) String() string { - return fmt.Sprint(a.User, "@", a.Domain) + return fmt.Sprintf("%s@%s", a.User, a.Domain) } // Formatted returns a formatted user used in the Salty Message Format @@ -48,7 +48,7 @@ func (a Addr) HashURI() string { func ParseAddr(addr string) (Addr, error) { parts := strings.Split(addr, "@") if len(parts) != 2 { - return Addr{}, fmt.Errorf("error parsing addr %q, expected nick@domain", addr) + return Addr{}, fmt.Errorf("expected nick@domain found %q", addr) } return Addr{parts[0], parts[1]}, nil diff --git a/salty-chat.sh b/scripts/salty-chat.sh similarity index 100% rename from salty-chat.sh rename to scripts/salty-chat.sh diff --git a/utils.go b/utils.go index 0134966..aef01cf 100644 --- a/utils.go +++ b/utils.go @@ -1,12 +1,15 @@ package saltyim import ( + "bytes" "fmt" + "html/template" "io" "net/http" "os" "time" + "github.com/Masterminds/sprig/v3" log "github.com/sirupsen/logrus" ) @@ -14,6 +17,26 @@ const ( defaultRequestTimeout = time.Second * 5 ) +func MustRenderString(s string, ctx interface{}) string { + out, err := RenderString(s, ctx) + if err != nil { + log.WithError(err).Fatal("error rendering string template") + } + return out +} + +// DirExists returns true if the given directory exists +func DirExists(name string) bool { + stat, err := os.Stat(name) + if err != nil { + if os.IsNotExist(err) { + return false + } + return false + } + return stat.IsDir() +} + // FileExists returns true if the given file exists func FileExists(name string) bool { if _, err := os.Stat(name); err != nil { @@ -24,6 +47,22 @@ func FileExists(name string) bool { return true } +// RenderString renders the string `s` as a `text/template` using the provided +// context `ctx` as input into the template. +// Typically used to render the results into a Markdown document. +func RenderString(s string, ctx interface{}) (string, error) { + t := template.Must( + template.New("s").Funcs(sprig.FuncMap()).Parse(s), + ) + buf := bytes.NewBuffer([]byte{}) + err := t.Execute(buf, ctx) + if err != nil { + return "", err + } + + return buf.String(), nil +} + // Request is a generic request handling function for making artbitrary HTPT // requests to Salty endpoints for looking up Salty Addresses, Configs and // publishing encrypted messages.