diff --git a/cmd/mailchain/commands/account.go b/cmd/mailchain/commands/account.go index ebfaddd2f..d510f9e34 100644 --- a/cmd/mailchain/commands/account.go +++ b/cmd/mailchain/commands/account.go @@ -18,19 +18,19 @@ import ( "encoding/hex" "fmt" - "github.com/mailchain/mailchain/cmd/mailchain/internal/config" + "github.com/mailchain/mailchain/cmd/mailchain/internal/prompts" "github.com/mailchain/mailchain/cmd/mailchain/internal/settings" "github.com/mailchain/mailchain/crypto/multikey" + "github.com/mailchain/mailchain/internal/keystore" "github.com/mailchain/mailchain/internal/keystore/kdf/multi" "github.com/mailchain/mailchain/internal/keystore/kdf/scrypt" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/viper" "github.com/ttacon/chalk" ) // account represents the say command -func accountCmd(v *viper.Viper) (*cobra.Command, error) { +func accountCmd(config *settings.Base) (*cobra.Command, error) { cmd := &cobra.Command{ Use: "account", Short: "Manage Accounts", @@ -44,39 +44,51 @@ It is safe to transfer the entire directory or the individual keys therein between ethereum nodes by simply copying. Make sure you backup your keys regularly.`, - // PersistentPreRunE: preRun, } - cmd.PersistentFlags().StringP("key-type", "", "", "Select the chain [secp256k1]") + ks, err := config.Keystore.Produce() + if err != nil { + return nil, errors.WithMessage(err, "could not create `keystore`") + } - cmd.AddCommand(accountAddCmd(v)) - cmd.AddCommand(accountListCmd(v)) + cmd.PersistentFlags().StringP("key-type", "", "", "Select the chain [secp256k1]") + cmd.AddCommand(accountAddCmd(ks, prompts.Secret, prompts.Secret)) + cmd.AddCommand(accountListCmd(ks)) return cmd, nil } -func accountAddCmd(v *viper.Viper) *cobra.Command { +func accountAddCmd(ks keystore.Store, + passphrasePrompt, privateKeyPrompt func(suppliedSecret string, prePromptNote string, promptLabel string, allowEmpty bool, confirmPrompt bool) (string, error), +) *cobra.Command { cmd := &cobra.Command{ Use: "add", Short: "Add private key", RunE: func(cmd *cobra.Command, args []string) error { - s := settings.New(v) keytype, err := getKeyType(cmd) if err != nil { return errors.WithMessage(err, "could not determine `key-type`") } - privateKey, err := cmd.Flags().GetString("private-key") + cmdPK, _ := cmd.Flags().GetString("private-key") + privateKey, err := privateKeyPrompt(cmdPK, + "", + "Private Key", + false, + false, + ) if err != nil { - return errors.WithMessage(err, "could not get `private-key`") - } - if privateKey == "" { - return errors.Errorf("private key must be specified") + return errors.WithMessage(err, "could not get private key") } pk, err := multikey.PrivateKeyFromHex(privateKey, keytype) if err != nil { return errors.WithMessage(err, "`private-key` could not be decoded") } - - passphrase, err := config.Passphrase(cmd) + cmdPassphrase, _ := cmd.Flags().GetString("passphrase") + passphrase, err := passphrasePrompt(cmdPassphrase, + fmt.Sprint(chalk.Yellow, "Note: To derive a storage key passphrase is required. The passphrase must be secure and not guessable."), + "Passphrase", + false, + true, + ) if err != nil { return errors.WithMessage(err, "could not get `passphrase`") } @@ -84,11 +96,6 @@ func accountAddCmd(v *viper.Viper) *cobra.Command { if err != nil { return errors.WithMessage(err, "could not create `random salt`") } - // TODO: currently this only does scrypt need flag + config etc - ks, err := s.Keystore.Produce() - if err != nil { - return errors.WithMessage(err, "could not create `keystore`") - } address, err := ks.Store(pk, keytype, multi.OptionsBuilders{ Scrypt: []scrypt.DeriveOptionsBuilder{ @@ -106,30 +113,25 @@ func accountAddCmd(v *viper.Viper) *cobra.Command { }, } + cmd.Flags().StringP("key-type", "", "", "Select the key type [secp256k1]") cmd.Flags().StringP("chain", "C", "", "Select the chain [ethereum]") cmd.Flags().StringP("private-key", "K", "", "Specify the private key to store") - _ = cmd.MarkFlagRequired("private-key") cmd.Flags().String("passphrase", "", "Passphrase to encrypt/decrypt key with") return cmd } -func accountListCmd(v *viper.Viper) *cobra.Command { +func accountListCmd(ks keystore.Store) *cobra.Command { return &cobra.Command{ Use: "list", Short: "List accounts", RunE: func(cmd *cobra.Command, args []string) error { - s := settings.New(v) - ks, err := s.Keystore.Produce() - if err != nil { - return errors.WithMessage(err, "could not create `keystore`") - } addresses, err := ks.GetAddresses() if err != nil { return errors.WithMessage(err, "could not get addresses") } for _, x := range addresses { - fmt.Println(hex.EncodeToString(x)) + cmd.Println(hex.EncodeToString(x)) } return nil }, @@ -137,17 +139,11 @@ func accountListCmd(v *viper.Viper) *cobra.Command { } func getKeyType(cmd *cobra.Command) (string, error) { - keyType, err := cmd.Flags().GetString("key-type") - if err != nil { - return "", errors.WithMessage(err, "failed to get `key-type`") - } + keyType, _ := cmd.Flags().GetString("key-type") if keyType != "" { return keyType, nil } - chain, err := cmd.Flags().GetString("chain") - if err != nil { - return "", errors.WithMessage(err, "failed to get `chain`") - } + chain, _ := cmd.Flags().GetString("chain") if chain == "" { return "", errors.Errorf("either `key-type` or `chain` must be specified") } diff --git a/cmd/mailchain/commands/account_test.go b/cmd/mailchain/commands/account_test.go new file mode 100644 index 000000000..b1e0bf9e9 --- /dev/null +++ b/cmd/mailchain/commands/account_test.go @@ -0,0 +1,374 @@ +// Copyright 2019 Finobo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commands + +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/mailchain/mailchain/cmd/mailchain/commands/commandstest" + "github.com/mailchain/mailchain/cmd/mailchain/internal/prompts/promptstest" + "github.com/mailchain/mailchain/cmd/mailchain/internal/settings" + "github.com/mailchain/mailchain/crypto/multikey" + "github.com/mailchain/mailchain/internal/keystore" + "github.com/mailchain/mailchain/internal/keystore/keystoretest" + "github.com/mailchain/mailchain/internal/testutil" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" +) + +func Test_getKeyType(t *testing.T) { + type args struct { + cmd *cobra.Command + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + "key-type", + args{ + func() *cobra.Command { + c := &cobra.Command{} + c.Flags().StringP("key-type", "", "", "Select the key type [secp256k1]") + c.Flags().Set("key-type", "secp256k1") + return c + }(), + }, + "secp256k1", + false, + }, + { + "chain", + args{ + func() *cobra.Command { + c := &cobra.Command{} + c.Flags().StringP("key-type", "", "", "") + c.Flags().StringP("chain", "", "", "") + c.Flags().Set("chain", "ethereum") + return c + }(), + }, + "secp256k1", + false, + }, + { + "err-not-specified", + args{ + func() *cobra.Command { + c := &cobra.Command{} + c.Flags().StringP("key-type", "", "", "") + c.Flags().StringP("chain", "", "", "") + return c + }(), + }, + "", + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getKeyType(tt.args.cmd) + if (err != nil) != tt.wantErr { + t.Errorf("getKeyType() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("getKeyType() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_accountListCmd(t *testing.T) { + assert := assert.New(t) + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + type args struct { + ks keystore.Store + } + tests := []struct { + name string + args args + cmdArgs []string + cmdFlags map[string]string + wantOutput string + wantExecErr bool + }{ + { + "success", + args{ + func() keystore.Store { + m := keystoretest.NewMockStore(mockCtrl) + m.EXPECT().GetAddresses().Return([][]byte{ + testutil.MustHexDecodeString("5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761"), + testutil.MustHexDecodeString("4cb0a77b76667dac586c40cc9523ace73b5d772bd503c63ed0ca596eae1658b2"), + }, nil) + return m + }(), + }, + nil, + map[string]string{}, + "5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761\n4cb0a77b76667dac586c40cc9523ace73b5d772bd503c63ed0ca596eae1658b2\n", + false, + }, + { + "err-store", + args{ + func() keystore.Store { + m := keystoretest.NewMockStore(mockCtrl) + m.EXPECT().GetAddresses().Return([][]byte{}, errors.Errorf("failed")) + return m + }(), + }, + nil, + map[string]string{}, + "Error: could not get addresses: failed", + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := accountListCmd(tt.args.ks) + if !assert.NotNil(got) { + t.Error("accountListCmd() is nil") + } + _, out, err := commandstest.ExecuteCommandC(got, tt.cmdArgs, tt.cmdFlags) + if (err != nil) != tt.wantExecErr { + t.Errorf("configChainEthereumNetwork().execute() error = %v, wantExecErr %v", err, tt.wantExecErr) + return + } + if !commandstest.AssertCommandOutput(t, got, err, out, tt.wantOutput) { + t.Errorf("configChainEthereumNetwork().Execute().out != %v", tt.wantOutput) + } + }) + } +} + +func Test_accountAddCmd(t *testing.T) { + assert := assert.New(t) + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + type args struct { + ks keystore.Store + passphrasePrompt func(suppliedSecret string, prePromptNote string, promptLabel string, allowEmpty bool, confirmPrompt bool) (string, error) + privateKeyPrompt func(suppliedSecret string, prePromptNote string, promptLabel string, allowEmpty bool, confirmPrompt bool) (string, error) + } + tests := []struct { + name string + args args + cmdArgs []string + cmdFlags map[string]string + wantOutput string + wantExecErr bool + }{ + { + "success", + args{ + func() keystore.Store { + m := keystoretest.NewMockStore(mockCtrl) + pk, _ := multikey.PrivateKeyFromHex("01901E63389EF02EAA7C5782E08B40D98FAEF835F28BD144EECF5614A415943F", "secp256k1") + m.EXPECT().Store(pk, "secp256k1", gomock.Any()).Return(testutil.MustHexDecodeString("5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761"), nil) + return m + }(), + promptstest.MockRequiredSecret(t, "passphrase-secret", nil), + promptstest.MockRequiredSecret(t, "01901E63389EF02EAA7C5782E08B40D98FAEF835F28BD144EECF5614A415943F", nil), + }, + nil, + map[string]string{ + "key-type": "secp256k1", + }, + "\x1b[32mPrivate key added\x1b[39m for address=5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761\n", + false, + }, + { + "err-keystore", + args{ + func() keystore.Store { + m := keystoretest.NewMockStore(mockCtrl) + pk, _ := multikey.PrivateKeyFromHex("01901E63389EF02EAA7C5782E08B40D98FAEF835F28BD144EECF5614A415943F", "secp256k1") + m.EXPECT().Store(pk, "secp256k1", gomock.Any()).Return(nil, errors.Errorf("failed")) + return m + }(), + promptstest.MockRequiredSecret(t, "passphrase-secret", nil), + promptstest.MockRequiredSecret(t, "01901E63389EF02EAA7C5782E08B40D98FAEF835F28BD144EECF5614A415943F", nil), + }, + nil, + map[string]string{ + "key-type": "secp256k1", + }, + "Error: key could not be stored: failed", + true, + }, + { + "err-passphrase", + args{ + func() keystore.Store { + m := keystoretest.NewMockStore(mockCtrl) + return m + }(), + promptstest.MockRequiredSecret(t, "", errors.Errorf("failed")), + promptstest.MockRequiredSecret(t, "01901E63389EF02EAA7C5782E08B40D98FAEF835F28BD144EECF5614A415943F", nil), + }, + nil, + map[string]string{ + "key-type": "secp256k1", + }, + "Error: could not get `passphrase`: failed", + true, + }, + { + "err-private-key-invalid", + args{ + func() keystore.Store { + m := keystoretest.NewMockStore(mockCtrl) + return m + }(), + nil, + promptstest.MockRequiredSecret(t, "01901E63389EF02EAA7C5782E08B40D98FAEF835F28BD144EECF5614A41594F", nil), + }, + nil, + map[string]string{ + "key-type": "secp256k1", + }, + "Error: `private-key` could not be decoded: invalid hex string", + true, + }, + { + "err-private-key", + args{ + func() keystore.Store { + m := keystoretest.NewMockStore(mockCtrl) + return m + }(), + nil, + promptstest.MockRequiredSecret(t, "", errors.Errorf("failed")), + }, + nil, + map[string]string{ + "key-type": "secp256k1", + }, + "Error: could not get private key: failed", + true, + }, + { + "err-key-type", + args{ + func() keystore.Store { + m := keystoretest.NewMockStore(mockCtrl) + return m + }(), + nil, + nil, + }, + nil, + map[string]string{ + "chain": "invalid", + }, + "Error: could not determine `key-type`: no key type for specified chain", + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := accountAddCmd(tt.args.ks, tt.args.passphrasePrompt, tt.args.privateKeyPrompt) + if !assert.NotNil(got) { + t.Error("accountListCmd() is nil") + } + _, out, err := commandstest.ExecuteCommandC(got, tt.cmdArgs, tt.cmdFlags) + if (err != nil) != tt.wantExecErr { + t.Errorf("configChainEthereumNetwork().execute() error = %v, wantExecErr %v", err, tt.wantExecErr) + return + } + if !commandstest.AssertCommandOutput(t, got, err, out, tt.wantOutput) { + t.Errorf("configChainEthereumNetwork().Execute().out != %v", tt.wantOutput) + } + }) + } +} + +func Test_accountCmd(t *testing.T) { + type args struct { + config *settings.Base + } + tests := []struct { + name string + args args + wantErr bool + wantNil bool + cmdArgs []string + cmdFlags map[string]string + wantExecErr bool + }{ + { + "success", + args{ + func() *settings.Base { + v := viper.New() + config := settings.New(v) + return config + }(), + }, + false, + false, + nil, + map[string]string{}, + false, + }, + { + "err-keystore", + args{ + func() *settings.Base { + v := viper.New() + v.Set("keystore.kind", "invalid") + config := settings.New(v) + return config + }(), + }, + true, + true, + nil, + map[string]string{}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := accountCmd(tt.args.config) + if (err != nil) != tt.wantErr { + t.Errorf("getKeyType() error = %v, wantErr %v", err, tt.wantErr) + return + } + if (got == nil) != tt.wantNil { + t.Error("accountListCmd() is nil") + } + if !tt.wantNil { + _, _, err = commandstest.ExecuteCommandC(got, tt.cmdArgs, tt.cmdFlags) + if (err != nil) != tt.wantExecErr { + t.Errorf("configChainEthereumNetwork().execute() error = %v, wantExecErr %v", err, tt.wantExecErr) + return + } + if (err != nil) != tt.wantErr { + t.Errorf("accountCmd() error = %v, wantErr %v", err, tt.wantErr) + return + } + } + }) + } +} diff --git a/cmd/mailchain/commands/commandstest/test_util.go b/cmd/mailchain/commands/commandstest/test_util.go index 118338bc5..d3a17e363 100644 --- a/cmd/mailchain/commands/commandstest/test_util.go +++ b/cmd/mailchain/commands/commandstest/test_util.go @@ -23,7 +23,7 @@ func ExecuteCommandC(root *cobra.Command, args []string, flags map[string]string return c, buf.String(), err } -func AssertCommandOutput(t *testing.T, cmd *cobra.Command, err error, out string, wantOutput string) bool { +func AssertCommandOutput(t *testing.T, cmd *cobra.Command, err error, out, wantOutput string) bool { assert := assert.New(t) if err == nil { if !assert.Equal(wantOutput, out) { diff --git a/cmd/mailchain/commands/execute.go b/cmd/mailchain/commands/execute.go index f9cf16687..ae531dda1 100644 --- a/cmd/mailchain/commands/execute.go +++ b/cmd/mailchain/commands/execute.go @@ -14,7 +14,7 @@ package commands -import "github.com/spf13/viper" +import "github.com/spf13/viper" // nolint: depguard // Execute run the command func Execute(viper *viper.Viper) error { diff --git a/cmd/mailchain/commands/postrun.go b/cmd/mailchain/commands/postrun.go index a7149dd15..61f983045 100644 --- a/cmd/mailchain/commands/postrun.go +++ b/cmd/mailchain/commands/postrun.go @@ -15,20 +15,18 @@ package commands import ( - "github.com/ttacon/chalk" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" // nolint: depguard + "github.com/ttacon/chalk" ) - - func prerunWriteConfig(v *viper.Viper) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { if err := v.WriteConfig(); err != nil { return errors.WithStack(err) - } + } cmd.Printf(chalk.Green.Color("Config saved\n")) return nil - } -} \ No newline at end of file + } +} diff --git a/cmd/mailchain/commands/prerun.go b/cmd/mailchain/commands/prerun.go index fbcb3976f..ddfefb466 100644 --- a/cmd/mailchain/commands/prerun.go +++ b/cmd/mailchain/commands/prerun.go @@ -18,7 +18,7 @@ import ( "github.com/mailchain/mailchain/cmd/mailchain/internal/settings" "github.com/spf13/cobra" "github.com/spf13/viper" // nolint: depguard -) +) func prerunInitConfig(v *viper.Viper) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { diff --git a/cmd/mailchain/commands/root.go b/cmd/mailchain/commands/root.go index 0181c30dd..8215be137 100644 --- a/cmd/mailchain/commands/root.go +++ b/cmd/mailchain/commands/root.go @@ -15,6 +15,7 @@ package commands import ( + "github.com/mailchain/mailchain/cmd/mailchain/internal/settings" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -29,12 +30,10 @@ Complete documentation is available at https://github.com/mailchain/mailchain`, } cmd.PersistentFlags().String("config", "", "config file (default is $HOME/.mailchain/.mailchain.yaml)") cmd.PersistentFlags().String("log-level", "warn", "log level [Panic,Fatal,Error,Warn,Info,Debug]") - - // TODO: this should not be persistent flags - cmd.PersistentFlags().Bool("empty-passphrase", false, "no passphrase and no prompt") cmd.PersistentFlags().Bool("prevent-init-config", false, "stop automatically creating config if non is found") - account, err := accountCmd(v) + config := settings.New(v) + account, err := accountCmd(config) if err != nil { return nil, err } diff --git a/cmd/mailchain/internal/config/passphrase.go b/cmd/mailchain/internal/config/passphrase.go deleted file mode 100644 index b4645bac2..000000000 --- a/cmd/mailchain/internal/config/passphrase.go +++ /dev/null @@ -1,56 +0,0 @@ -package config - -import ( - "fmt" - - "github.com/manifoldco/promptui" - "github.com/pkg/errors" - "github.com/spf13/cobra" // nolint: depguard - "github.com/ttacon/chalk" -) - -// Passphrase is extracted from the command -func Passphrase(cmd *cobra.Command) (string, error) { - passphrase, err := cmd.Flags().GetString("passphrase") - if err != nil { - return "", errors.WithMessage(err, "could not get `passphrase`") - } - if passphrase != "" { - return passphrase, nil - } - emptyPassphrase, err := cmd.Flags().GetBool("empty-passphrase") - if err != nil { - return "", errors.WithMessage(err, "could not get `empty-passphrase`") - } - if emptyPassphrase { - return "", nil - } - fmt.Println(chalk.Yellow, "Note: To derive a storage key passphrase is required. The passphrase must be secure and not guessable.") - return passphraseFromPrompt() -} - -func passphraseFromPrompt() (string, error) { - prompt := promptui.Prompt{ - Label: "Passphrase", - Mask: '*', - } - password, err := prompt.Run() - if err != nil { - return "", errors.Errorf("failed read passphrase") - } - - confirmPrompt := promptui.Prompt{ - Label: "Repeat passphrase: ", - Mask: '*', - } - confirm, err := confirmPrompt.Run() - if err != nil { - fmt.Printf("Prompt failed %v\n", err) - return "", errors.Errorf("failed read passphrase confirmation") - } - if password != confirm { - return "", errors.Errorf("Passphrases do not match") - } - - return password, nil -} diff --git a/cmd/mailchain/internal/http/server.go b/cmd/mailchain/internal/http/server.go index 5c1a10347..a0be4f68a 100644 --- a/cmd/mailchain/internal/http/server.go +++ b/cmd/mailchain/internal/http/server.go @@ -19,8 +19,8 @@ import ( "net/http" "github.com/gorilla/mux" - "github.com/mailchain/mailchain/cmd/mailchain/internal/config" "github.com/mailchain/mailchain/cmd/mailchain/internal/http/handlers" + "github.com/mailchain/mailchain/cmd/mailchain/internal/prompts" "github.com/mailchain/mailchain/cmd/mailchain/internal/settings" "github.com/mailchain/mailchain/cmd/mailchain/internal/settings/defaults" "github.com/mailchain/mailchain/internal/keystore/kdf/multi" @@ -30,6 +30,7 @@ import ( log "github.com/sirupsen/logrus" // nolint:depguard "github.com/spf13/cobra" "github.com/spf13/viper" // nolint:depguard + "github.com/ttacon/chalk" "github.com/urfave/negroni" ) @@ -51,7 +52,13 @@ func CreateRouter(s *settings.Base, cmd *cobra.Command) (http.Handler, error) { if err != nil { return nil, errors.WithMessage(err, "could not create `keystore`") } - passphrase, err := config.Passphrase(cmd) + cmdPassphrase, _ := cmd.Flags().GetString("passphrase") + passphrase, err := prompts.Secret(cmdPassphrase, + fmt.Sprint(chalk.Yellow, "Note: To derive a storage key passphrase is required. The passphrase must be secure and not guessable."), + "Passphrase", + false, + true, + ) if err != nil { return nil, errors.WithMessage(err, "could not get `passphrase`") } diff --git a/cmd/mailchain/internal/prompts/promptstest/input.go b/cmd/mailchain/internal/prompts/promptstest/input.go index 71ab6a2d4..c337e91e8 100644 --- a/cmd/mailchain/internal/prompts/promptstest/input.go +++ b/cmd/mailchain/internal/prompts/promptstest/input.go @@ -6,7 +6,6 @@ import ( func MockRequiredInputWithDefault(t *testing.T, returnValue string, returnErr error) func(label string, defaultValue string) (string, error) { return func(label string, defaultValue string) (string, error) { - return returnValue, returnErr } } diff --git a/cmd/mailchain/internal/prompts/promptstest/secret.go b/cmd/mailchain/internal/prompts/promptstest/secret.go new file mode 100644 index 000000000..7e3da97b7 --- /dev/null +++ b/cmd/mailchain/internal/prompts/promptstest/secret.go @@ -0,0 +1,9 @@ +package promptstest + +import "testing" + +func MockRequiredSecret(t *testing.T, returnValue string, returnErr error) func(suppliedSecret, prePromptNote, promptLabel string, allowEmpty, confirmPrompt bool) (string, error) { + return func(suppliedSecret, prePromptNote, promptLabel string, allowEmpty, confirmPrompt bool) (string, error) { + return returnValue, returnErr + } +} diff --git a/cmd/mailchain/internal/prompts/secret.go b/cmd/mailchain/internal/prompts/secret.go new file mode 100644 index 000000000..862eea707 --- /dev/null +++ b/cmd/mailchain/internal/prompts/secret.go @@ -0,0 +1,47 @@ +package prompts + +import ( + "fmt" + + "github.com/manifoldco/promptui" + "github.com/pkg/errors" +) + +// Secret is extracted from the command, otherwise from prompt +func Secret(suppliedSecret, prePromptNote, promptLabel string, allowEmpty, confirmPrompt bool) (string, error) { + if suppliedSecret != "" { + return suppliedSecret, nil + } + if allowEmpty { + return "", nil + } + fmt.Println(prePromptNote) + return secretFromPrompt(promptLabel, confirmPrompt) +} + +func secretFromPrompt(promptLabel string, confirmPrompt bool) (string, error) { + prompt := promptui.Prompt{ + Label: fmt.Sprintf("%s", promptLabel), + Mask: '*', + } + secret, err := prompt.Run() + if err != nil { + return "", errors.Errorf("failed read %q", promptLabel) + } + if confirmPrompt { + confirmPromptValue := promptui.Prompt{ + Label: fmt.Sprintf("Repeat %s", promptLabel), + Mask: '*', + } + confirm, err := confirmPromptValue.Run() + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + return "", errors.Errorf("failed read passphrase confirmation") + } + if secret != confirm { + return "", errors.Errorf("%s do not match", promptLabel) + } + } + + return secret, nil +} diff --git a/crypto/multikey/chain-type.go b/crypto/multikey/chain_type.go similarity index 100% rename from crypto/multikey/chain-type.go rename to crypto/multikey/chain_type.go diff --git a/crypto/multikey/chain-type_test.go b/crypto/multikey/chain_type_test.go similarity index 100% rename from crypto/multikey/chain-type_test.go rename to crypto/multikey/chain_type_test.go diff --git a/internal/mail/id.go b/internal/mail/id.go index 2b39fb14a..61455898c 100644 --- a/internal/mail/id.go +++ b/internal/mail/id.go @@ -16,8 +16,8 @@ package mail import ( "crypto/rand" + "encoding/hex" - "github.com/multiformats/go-multihash" "github.com/pkg/errors" ) @@ -28,19 +28,17 @@ func NewID() (ID, error) { } // FromHexString create ID from multihash hex string -func FromHexString(hex string) (ID, error) { - id, err := multihash.FromHexString(hex) - return ID(id), errors.WithMessage(err, "could not generate ID") +func FromHexString(h string) (ID, error) { + return hex.DecodeString(h) } // HexString create a multihash representation of ID as hex string func (id ID) HexString() string { - mh := multihash.Multihash(id) - return mh.HexString() + return hex.EncodeToString(id) } // ID create the mail message ID header -type ID multihash.Multihash +type ID []byte // generateRandomID returns a securely generated random bytes encoded with multihash 0x00 prefix. // It will return an error if the system's secure random @@ -49,9 +47,5 @@ type ID multihash.Multihash func generateRandomID(n int) (ID, error) { bytes := make([]byte, n) _, err := rand.Read(bytes) - if err != nil { - return nil, err - } - - return multihash.Encode(bytes, multihash.ID) + return bytes, errors.WithStack(err) } diff --git a/internal/mail/id_test.go b/internal/mail/id_test.go index 95846c13c..a4be1430b 100644 --- a/internal/mail/id_test.go +++ b/internal/mail/id_test.go @@ -17,7 +17,6 @@ package mail import ( "testing" - "github.com/multiformats/go-multihash" "github.com/stretchr/testify/assert" ) @@ -25,8 +24,7 @@ func TestNewID(t *testing.T) { assert := assert.New(t) id, err := NewID() assert.NoError(err) - assert.Len(id, 46) - assert.Equal(byte(multihash.ID), id[0]) + assert.Len(id, 44) } func TestFromHexString(t *testing.T) {