diff --git a/README.md b/README.md index 2dcea02..09f9632 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,10 @@ Uses [fzf](https://github.com/junegunn/fzf) to select the desired OTP. The `fzf` Without any special options, **termotp** shows a formatted table of your TOTP providers and the calculated tokens. This option shows a simple TUI with a fuzzy selector. Hitting enter on an entry will print the otp to the standard output. +**--json** + +Emits the output in JSON format. + **--plain** Produces a plain listing of the vault. @@ -112,7 +116,7 @@ Add support for other OTP programs, like AndOTP, 2FA, etc. I'll proceed to do th ## Thanks * https://github.com/zalando/ for their Keyring manipulation library. -* https://github.com/sam-artuso for his ideas on using fzf (as an external program) and keyring support. +* https://github.com/sam-artuso and http://github.com/timkgh for their great feature requests. ## Author diff --git a/aegis.go b/aegis.go index 1a27549..f8beec8 100644 --- a/aegis.go +++ b/aegis.go @@ -135,9 +135,9 @@ func filterAegisVault(plainJSON []byte, rematch *regexp.Regexp) ([]otpEntry, err } if rematch.MatchString(entry.Issuer) || rematch.MatchString(entry.Name) { ret = append(ret, otpEntry{ - issuer: entry.Issuer, - account: entry.Name, - token: token, + Issuer: entry.Issuer, + Account: entry.Name, + Token: token, }) } } diff --git a/fuzzy.go b/fuzzy.go index 4fc25d9..299376d 100644 --- a/fuzzy.go +++ b/fuzzy.go @@ -32,27 +32,27 @@ func maxlen(vault []otpEntry, f func(v otpEntry) string) int { // fuzzyFind opens a fuzzy finder window and allows the user to select the // desired issuer/account and returns the token. func fuzzyFind(vault []otpEntry) (string, error) { - maxIssuerLen := maxlen(vault, func(otp otpEntry) string { return otp.issuer }) - maxAccountLen := maxlen(vault, func(otp otpEntry) string { return otp.account }) - maxTokenLen := maxlen(vault, func(otp otpEntry) string { return otp.token }) + maxIssuerLen := maxlen(vault, func(otp otpEntry) string { return otp.Issuer }) + maxAccountLen := maxlen(vault, func(otp otpEntry) string { return otp.Account }) + maxTokenLen := maxlen(vault, func(otp otpEntry) string { return otp.Token }) idx, err := fuzzyfinder.Find( vault, func(i int) string { // Default issuer name issuer := defaultIssuerName - if vault[i].issuer != "" { - issuer = vault[i].issuer + if vault[i].Issuer != "" { + issuer = vault[i].Issuer } issuer = fmt.Sprintf("%-[1]*s", maxIssuerLen+fuzzyPadding, issuer) - account := fmt.Sprintf("%-[1]*s", maxAccountLen+fuzzyPadding, vault[i].account) - token := fmt.Sprintf("%-[1]*s", maxTokenLen+fuzzyPadding, vault[i].token) + account := fmt.Sprintf("%-[1]*s", maxAccountLen+fuzzyPadding, vault[i].Account) + token := fmt.Sprintf("%-[1]*s", maxTokenLen+fuzzyPadding, vault[i].Token) return fmt.Sprintf("%s %s %s", issuer, account, token) }) if err != nil { return "", err } - return vault[idx].token, nil + return vault[idx].Token, nil } diff --git a/main.go b/main.go index 714ed10..19aff3f 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ package main import ( + "encoding/json" "errors" "flag" "fmt" @@ -27,9 +28,9 @@ var BuildVersion string // otpEntry holds the representation of the internal vault. type otpEntry struct { - issuer string - account string - token string + Issuer string + Account string + Token string } // Keyring constants. User is not your user. @@ -44,6 +45,7 @@ type cmdLineFlags struct { fuzzy bool fzf bool plain bool + json bool setkeyring bool usekeyring bool version bool @@ -137,7 +139,7 @@ func outputTable(vault []otpEntry, flags cmdLineFlags) string { } for _, v := range vault { - tbl.AppendRow(table.Row{v.issuer, v.account, v.token}) + tbl.AppendRow(table.Row{v.Issuer, v.Account, v.Token}) } tbl.SortBy([]table.SortBy{ @@ -149,6 +151,15 @@ func outputTable(vault []otpEntry, flags cmdLineFlags) string { return tbl.Render() } +// outputJSON outputs a JSON representation of the decrypted vault. +func outputJSON(vault []otpEntry) (string, error) { + output, err := json.Marshal(vault) + if err != nil { + return "", err + } + return string(output), nil +} + // parseFlags parses the command line flags and returns a cmdLineFlag struct. func parseFlags() (cmdLineFlags, error) { flags := cmdLineFlags{} @@ -156,6 +167,7 @@ func parseFlags() (cmdLineFlags, error) { flag.StringVar(&flags.input, "input", "", "Input (encrypted) JSON file glob.") flag.BoolVar(&flags.fuzzy, "fuzzy", false, "Use interactive fuzzy finder.") flag.BoolVar(&flags.fzf, "fzf", false, "Use fzf (needs external binary in path).") + flag.BoolVar(&flags.json, "json", false, "Use JSON output.") flag.BoolVar(&flags.plain, "plain", false, "Use plain output (disables fuzzy finder and tabular output.)") flag.BoolVar(&flags.version, "version", false, "Show program version and exit.") flag.BoolVar(&flags.setkeyring, "set-keyring", false, "Set the keyring password and exit.") @@ -180,7 +192,7 @@ func parseFlags() (cmdLineFlags, error) { // Only one output format allowed. n := 0 - for _, v := range []bool{flags.plain, flags.fuzzy, flags.fzf} { + for _, v := range []bool{flags.fuzzy, flags.fzf, flags.json, flags.plain} { if v { n++ } @@ -321,8 +333,8 @@ func main() { os.Exit(1) } sort.Slice(vault, func(i, j int) bool { - key1 := vault[i].issuer + "/" + vault[i].account - key2 := vault[j].issuer + "/" + vault[j].account + key1 := vault[i].Issuer + "/" + vault[i].Account + key2 := vault[j].Issuer + "/" + vault[j].Account return key1 > key2 }) @@ -342,6 +354,12 @@ func main() { die(err) } fmt.Println(t) + case flags.json: + output, err := outputJSON(vault) + if err != nil { + die(err) + } + fmt.Println(output) default: fmt.Println(outputTable(vault, flags)) }