mirror of https://github.com/mitchell/selfpass.git
147 lines
3.7 KiB
Go
147 lines
3.7 KiB
Go
|
package cmds
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"encoding/base64"
|
||
|
"encoding/hex"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"math/rand"
|
||
|
"os"
|
||
|
"time"
|
||
|
|
||
|
"github.com/atotto/clipboard"
|
||
|
"github.com/spf13/cobra"
|
||
|
"github.com/spf13/viper"
|
||
|
"gopkg.in/AlecAivazis/survey.v1"
|
||
|
|
||
|
"github.com/mitchell/selfpass/credentials/types"
|
||
|
"github.com/mitchell/selfpass/crypto"
|
||
|
)
|
||
|
|
||
|
func MakeCreateCmd(masterpass string, cfg *viper.Viper, initClient CredentialClientInit) *cobra.Command {
|
||
|
createCmd := &cobra.Command{
|
||
|
Use: "create",
|
||
|
Short: "Create a credential in Selfpass",
|
||
|
Long: `Create a credential in Selfpass, and save it to the server after encrypting the
|
||
|
password.`,
|
||
|
|
||
|
Run: func(_ *cobra.Command, args []string) {
|
||
|
mdqs := []*survey.Question{
|
||
|
{
|
||
|
Name: "primary",
|
||
|
Prompt: &survey.Input{Message: "Primary user key:"},
|
||
|
},
|
||
|
{
|
||
|
Name: "sourceHost",
|
||
|
Prompt: &survey.Input{Message: "Source host:"},
|
||
|
},
|
||
|
{
|
||
|
Name: "loginURL",
|
||
|
Prompt: &survey.Input{Message: "Login url:"},
|
||
|
},
|
||
|
{
|
||
|
Name: "tag",
|
||
|
Prompt: &survey.Input{Message: "Tag:"},
|
||
|
},
|
||
|
}
|
||
|
cqs := []*survey.Question{
|
||
|
{
|
||
|
Name: "username",
|
||
|
Prompt: &survey.Input{Message: "Username:"},
|
||
|
},
|
||
|
{
|
||
|
Name: "email",
|
||
|
Prompt: &survey.Input{Message: "Email:"},
|
||
|
},
|
||
|
}
|
||
|
var ci types.CredentialInput
|
||
|
|
||
|
check(survey.Ask(mdqs, &ci.MetadataInput))
|
||
|
check(survey.Ask(cqs, &ci))
|
||
|
|
||
|
var newpass bool
|
||
|
prompt := &survey.Confirm{Message: "Do you want a random password?", Default: true}
|
||
|
check(survey.AskOne(prompt, &newpass, nil))
|
||
|
|
||
|
if newpass {
|
||
|
ci.Password = generatePassword(16, true, true)
|
||
|
|
||
|
var copypass bool
|
||
|
prompt = &survey.Confirm{Message: "Copy new pass to clipboard?", Default: true}
|
||
|
check(survey.AskOne(prompt, ©pass, nil))
|
||
|
|
||
|
if copypass {
|
||
|
check(clipboard.WriteAll(ci.Password))
|
||
|
}
|
||
|
} else {
|
||
|
prompt := &survey.Password{Message: "Password: "}
|
||
|
check(survey.AskOne(prompt, &ci.Password, nil))
|
||
|
|
||
|
var cpass string
|
||
|
prompt = &survey.Password{Message: "Confirm assword: "}
|
||
|
check(survey.AskOne(prompt, &cpass, nil))
|
||
|
|
||
|
if ci.Password != cpass {
|
||
|
fmt.Println("passwords didn't match'")
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
key, err := hex.DecodeString(cfg.GetString(KeyPrivateKey))
|
||
|
check(err)
|
||
|
|
||
|
keypass, err := crypto.CombinePasswordAndKey([]byte(masterpass), []byte(key))
|
||
|
check(err)
|
||
|
|
||
|
cipherpass, err := crypto.CBCEncrypt(keypass, []byte(ci.Password))
|
||
|
check(err)
|
||
|
|
||
|
ci.Password = base64.StdEncoding.EncodeToString(cipherpass)
|
||
|
|
||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||
|
defer cancel()
|
||
|
|
||
|
c, err := initClient(ctx).Create(ctx, ci)
|
||
|
check(err)
|
||
|
|
||
|
mdjson, err := json.MarshalIndent(c.Metadata, "", " ")
|
||
|
check(err)
|
||
|
fmt.Println(string(mdjson))
|
||
|
},
|
||
|
}
|
||
|
|
||
|
return createCmd
|
||
|
}
|
||
|
|
||
|
const alphas = "abcdefghijklmnopqrstuvABCDEFGHIJKLMNOPQRSTUV"
|
||
|
const alphanumerics = "abcdefghijklmnopqrstuvABCDEFGHIJKLMNOPQRSTUV1234567890"
|
||
|
const alphasAndSpecials = "abcdefghijklmnopqrstuvABCDEFGHIJKLMNOPQRSTUV1234567890!@#$%^&*()"
|
||
|
const alphanumericsAndSpecials = "abcdefghijklmnopqrstuvABCDEFGHIJKLMNOPQRSTUV1234567890!@#$%^&*()"
|
||
|
|
||
|
func generatePassword(length int, numbers, specials bool) string {
|
||
|
rand.Seed(time.Now().UnixNano())
|
||
|
pass := make([]byte, length)
|
||
|
|
||
|
switch {
|
||
|
case numbers && specials:
|
||
|
for idx := 0; idx < length; idx++ {
|
||
|
pass[idx] = alphanumericsAndSpecials[rand.Int63()%int64(len(alphanumericsAndSpecials))]
|
||
|
}
|
||
|
case numbers:
|
||
|
for idx := 0; idx < length; idx++ {
|
||
|
pass[idx] = alphanumerics[rand.Int63()%int64(len(alphanumerics))]
|
||
|
}
|
||
|
case specials:
|
||
|
for idx := 0; idx < length; idx++ {
|
||
|
pass[idx] = alphasAndSpecials[rand.Int63()%int64(len(alphasAndSpecials))]
|
||
|
}
|
||
|
default:
|
||
|
for idx := 0; idx < length; idx++ {
|
||
|
pass[idx] = alphas[rand.Int63()%int64(len(alphas))]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return string(pass)
|
||
|
}
|