Skip to content

Commit dfb5f83

Browse files
authored
Merge pull request #109 from msladek/cmd-pass
new command 'pass' / non-interactive mode / README improvements
2 parents ff67a82 + 68c6a79 commit dfb5f83

File tree

3 files changed

+104
-37
lines changed

3 files changed

+104
-37
lines changed

README.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,42 @@ CLI Usage
1313
$ # set an alias to easily reuse
1414
$ alias enp="enpasscli -vault=/my-vault-dir/ -sort"
1515

16+
$ # list anything containing 'twitter' (without password)
17+
$ enp list twitter
18+
1619
$ # show passwords of 'enpass.com'
1720
$ enp show enpass.com
1821

1922
$ # copy password of 'reddit.com' entry to clipboard
2023
$ enp copy reddit.com
2124

22-
$ # or list anything containing 'twitter' (without password)
23-
$ enp list twitter
25+
$ # print password of 'github.com' to stdout, useful for scripting
26+
$ password=$(enp pass github.com)
2427
```
2528

29+
Commands
30+
-----
31+
| Name | Description |
32+
| :---: | --- |
33+
| `list FILTER` | List vault entries matching FILTER without password |
34+
| `show FILTER` | List vault entries matching FILTER with password |
35+
| `copy FILTER` | Copy the password of a vault entry matching FILTER to the clipboard |
36+
| `pass FILTER` | Print the password of a vaulty entry matching FILTER to stdout |
37+
| `version` | Print the version |
38+
39+
Flags
40+
-----
41+
| Name | Description |
42+
| :---: | --- |
43+
| `-vault=PATH` | Path to your Enpass vault |
44+
| `-keyfile=PATH` | Path to your Enpass vault keyfile |
45+
| `-type=TYPE` | The type of your card (password, ...) |
46+
| `-log=LEVEL` | The log level from debug (5) to error (1) |
47+
| `-nonInteractive` | Disable prompts and fail instead |
48+
| `-sort` | Sort the output by title and username of the `list` and `show` command |
49+
| `-trashed` | Show trashed items in the `list` and `show` command |
50+
| `-clipboardPrimary` | Use primary X selection instead of clipboard for the `copy` command |
51+
2652
Testing Code
2753
-------
2854
```shell

cmd/enpasscli/main.go

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@ package main
33
import (
44
"flag"
55
"fmt"
6-
"github.com/atotto/clipboard"
7-
"github.com/hazcod/enpass-cli/pkg/enpass"
8-
"github.com/miquella/ask"
9-
"github.com/sirupsen/logrus"
106
"os"
117
"path/filepath"
128
"runtime"
139
s "sort"
1410
"strings"
11+
12+
"github.com/atotto/clipboard"
13+
"github.com/hazcod/enpass-cli/pkg/enpass"
14+
"github.com/miquella/ask"
15+
"github.com/sirupsen/logrus"
1516
)
1617

1718
const (
@@ -21,8 +22,21 @@ const (
2122
var (
2223
// overwritten by go build
2324
version = "dev"
25+
// enables prompts
26+
interactive = true
2427
)
2528

29+
func prompt(logger *logrus.Logger, msg string) string {
30+
if interactive {
31+
if response, err := ask.HiddenAsk("Enter " + msg + ": "); err != nil {
32+
logger.WithError(err).Fatal("could not prompt for " + msg)
33+
} else {
34+
return response
35+
}
36+
}
37+
return ""
38+
}
39+
2640
func sortEntries(cards []enpass.Card) {
2741
// Sort by username preserving original order
2842
s.SliceStable(cards, func(i, j int) bool {
@@ -89,26 +103,31 @@ func showEntries(logger *logrus.Logger, vault *enpass.Vault, cardType string, so
89103
}
90104

91105
func copyEntry(logger *logrus.Logger, vault *enpass.Vault, cardType string, filters []string) {
92-
cards, err := vault.GetEntries(cardType, filters)
106+
card, err := vault.GetUniqueEntry(cardType, filters)
93107
if err != nil {
94-
logger.WithError(err).Fatal("could not retrieve cards")
108+
logger.WithError(err).Fatal("could not retrieve unique card")
95109
}
96110

97-
if len(cards) == 0 {
98-
logger.Fatal("card not found")
111+
password, err := card.Decrypt()
112+
if err != nil {
113+
logger.WithError(err).Fatal("could not decrypt card")
99114
}
100115

101-
if len(cards) > 1 {
102-
logger.WithField("cards", len(cards)).Fatal("multiple cards match that title")
116+
if err := clipboard.WriteAll(password); err != nil {
117+
logger.WithError(err).Fatal("could not copy password to clipboard")
103118
}
119+
}
104120

105-
password, err := cards[0].Decrypt()
121+
func entryPassword(logger *logrus.Logger, vault *enpass.Vault, cardType string, filters []string) {
122+
card, err := vault.GetUniqueEntry(cardType, filters)
106123
if err != nil {
107-
logger.WithError(err).Fatal("could not decrypt card")
124+
logger.WithError(err).Fatal("could not retrieve unique card")
108125
}
109126

110-
if err := clipboard.WriteAll(password); err != nil {
111-
logger.WithError(err).Fatal("could not copy password to clipboard")
127+
if password, err := card.Decrypt(); err != nil {
128+
logger.WithError(err).Fatal("could not decrypt card")
129+
} else {
130+
fmt.Println(password)
112131
}
113132
}
114133

@@ -117,14 +136,15 @@ func main() {
117136
cardType := flag.String("type", "password", "The type of your card. (password, ...)")
118137
keyFilePath := flag.String("keyfile", "", "Path to your Enpass vault keyfile.")
119138
logLevelStr := flag.String("log", defaultLogLevel.String(), "The log level from debug (5) to error (1).")
120-
sort := flag.Bool("sort", false, "Sort the output by title and username.")
121-
trashed := flag.Bool("trashed", false, "Show trashed items in output.")
122-
clipboardPrimary := flag.Bool("clipboardPrimary", false, "Use primary X selection instead of clipboard.")
139+
nonInteractive := flag.Bool("nonInteractive", false, "Disable prompts and fail instead.")
140+
sort := flag.Bool("sort", false, "Sort the output by title and username of the 'list' and 'show' command.")
141+
trashed := flag.Bool("trashed", false, "Show trashed items in the 'list' and 'show' command.")
142+
clipboardPrimary := flag.Bool("clipboardPrimary", false, "Use primary X selection instead of clipboard for the 'copy' command.")
123143

124144
flag.Parse()
125145

126146
if flag.NArg() == 0 {
127-
fmt.Println("Specify a command: version, list, open, copy")
147+
fmt.Println("Specify a command: version, list, show, copy, pass")
128148
flag.Usage()
129149
os.Exit(1)
130150
}
@@ -139,6 +159,8 @@ func main() {
139159
command := strings.ToLower(flag.Arg(0))
140160
filters := flag.Args()[1:]
141161

162+
interactive = !*nonInteractive
163+
142164
if *clipboardPrimary {
143165
clipboard.Primary = true
144166
logger.Debug("primary X selection enabled")
@@ -154,9 +176,7 @@ func main() {
154176

155177
masterPassword := os.Getenv("MASTERPW")
156178
if masterPassword == "" {
157-
if masterPassword, err = ask.HiddenAsk("Enter master password: "); err != nil {
158-
logger.WithError(err).Fatal("could not prompt for master password")
159-
}
179+
masterPassword = prompt(logger, "master password")
160180
}
161181

162182
if masterPassword == "" {
@@ -168,25 +188,22 @@ func main() {
168188

169189
if err := vault.Initialize(*vaultPath, *keyFilePath, masterPassword); err != nil {
170190
logger.WithError(err).Error("could not open vault")
171-
os.Exit(2)
191+
logger.Exit(2)
172192
}
173193
defer func() { _ = vault.Close() }()
174-
vault.Logger.SetLevel(logLevel)
175194

176195
logger.Debug("initialized vault")
177196

178-
switch strings.ToLower(command) {
197+
switch command {
179198
case "list":
180199
listEntries(logger, &vault, *cardType, *sort, *trashed, filters)
181-
return
182-
183200
case "show":
184201
showEntries(logger, &vault, *cardType, *sort, *trashed, filters)
185-
return
186-
187202
case "copy":
188203
copyEntry(logger, &vault, *cardType, filters)
189-
return
204+
case "pass":
205+
entryPassword(logger, &vault, *cardType, filters)
206+
default:
207+
logger.WithField("command", command).Fatal("unknown command")
190208
}
191-
192209
}

pkg/enpass/vault.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,19 @@ func (v *Vault) checkPaths() error {
7474
// Initialize : setup a connection to the Enpass database. Call this before doing anything.
7575
func (v *Vault) Initialize(databasePath string, keyfilePath string, password string) error {
7676
if databasePath == "" {
77-
return errors.New("empty v path provided")
77+
return errors.New("empty vault path provided")
7878
}
7979

8080
if password == "" {
81-
return errors.New("empty v password provided")
81+
return errors.New("empty vault password provided")
8282
}
8383

8484
v.databaseFilename = filepath.Join(databasePath, vaultFileName)
8585
v.vaultInfoFilename = filepath.Join(databasePath, vaultInfoFileName)
8686

87-
v.Logger.Debug("checking provided v paths")
87+
v.Logger.Debug("checking provided vault paths")
8888
if err := v.checkPaths(); err != nil {
89-
return errors.Wrap(err, "invalid v path provided")
89+
return errors.Wrap(err, "invalid vault path provided")
9090
}
9191

9292
v.Logger.Debug("loading vault info")
@@ -112,7 +112,7 @@ func (v *Vault) Initialize(databasePath string, keyfilePath string, password str
112112
v.Logger.Debug("generating master password")
113113
masterPassword, err := v.generateMasterPassword([]byte(password), keyfilePath)
114114
if err != nil {
115-
return errors.Wrap(err, "could not generate v unlock key")
115+
return errors.Wrap(err, "could not generate vault unlock key")
116116
}
117117

118118
v.Logger.Debug("extracting salt from database")
@@ -215,3 +215,27 @@ func (v *Vault) GetEntries(cardType string, filters []string) ([]Card, error) {
215215

216216
return cards, nil
217217
}
218+
219+
func (v *Vault) GetUniqueEntry(cardType string, filters []string) (*Card, error) {
220+
cards, err := v.GetEntries(cardType, filters)
221+
if err != nil {
222+
return nil, errors.Wrap(err, "could not retrieve cards")
223+
}
224+
225+
if len(cards) == 0 {
226+
return nil, errors.New("card not found")
227+
}
228+
229+
var uniqueCard *Card
230+
for _, card := range cards {
231+
if card.IsTrashed() || card.IsDeleted() {
232+
continue
233+
} else if uniqueCard == nil {
234+
uniqueCard = &card
235+
} else {
236+
return nil, errors.New("multiple cards match that title")
237+
}
238+
}
239+
240+
return uniqueCard, nil
241+
}

0 commit comments

Comments
 (0)