lockbox

password manager
Log | Files | Refs | README | LICENSE

commit 1f22483bf032b0aee24100f34500d93dd876ba85
parent e0a56b3d1f22034fb2119ae30b49e3c321d1157c
Author: Sean Enck <sean@ttypty.com>
Date:   Sat,  1 Oct 2022 14:07:39 -0400

move totp to an area split from the rest

Diffstat:
Mcmd/main.go | 4++--
Dinternal/subcommands/totp.go | 216-------------------------------------------------------------------------------
Ainternal/totp/core.go | 216+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 218 insertions(+), 218 deletions(-)

diff --git a/cmd/main.go b/cmd/main.go @@ -14,7 +14,7 @@ import ( "github.com/enckse/lockbox/internal/cli" "github.com/enckse/lockbox/internal/inputs" "github.com/enckse/lockbox/internal/platform" - "github.com/enckse/lockbox/internal/subcommands" + "github.com/enckse/lockbox/internal/totp" ) var ( @@ -40,7 +40,7 @@ func getEntry(args []string, idx int) string { func internalCallback(name string) callbackFunction { switch name { case "totp": - return subcommands.TOTP + return totp.Call case "hash": return hashText case "clear": diff --git a/internal/subcommands/totp.go b/internal/subcommands/totp.go @@ -1,216 +0,0 @@ -// Package subcommands handles TOTP tokens. -package subcommands - -import ( - "errors" - "fmt" - "os" - "os/exec" - "strconv" - "strings" - "time" - - "github.com/enckse/lockbox/internal/backend" - "github.com/enckse/lockbox/internal/cli" - "github.com/enckse/lockbox/internal/colors" - "github.com/enckse/lockbox/internal/inputs" - "github.com/enckse/lockbox/internal/platform" - otp "github.com/pquerna/otp/totp" -) - -type ( - colorWhen struct { - start int - end int - } -) - -func clear() { - cmd := exec.Command("clear") - cmd.Stdout = os.Stdout - if err := cmd.Run(); err != nil { - fmt.Printf("unable to clear screen: %v\n", err) - } -} - -func totpEnv() string { - return inputs.EnvOrDefault(inputs.TotpEnv, "totp") -} - -func colorWhenRules() ([]colorWhen, error) { - envTime := os.Getenv(inputs.ColorBetweenEnv) - if envTime == "" { - return []colorWhen{ - {start: 0, end: 5}, - {start: 30, end: 35}, - }, nil - } - var rules []colorWhen - for _, item := range strings.Split(envTime, ",") { - line := strings.TrimSpace(item) - if line == "" { - continue - } - parts := strings.Split(line, ":") - if len(parts) != 2 { - return nil, fmt.Errorf("invalid colorization rule found: %s", line) - } - s, err := strconv.Atoi(parts[0]) - if err != nil { - return nil, err - } - e, err := strconv.Atoi(parts[1]) - if err != nil { - return nil, err - } - if s < 0 || e < 0 || e < s || s > 59 || e > 59 { - return nil, fmt.Errorf("invalid time found for colorization rule: %s", line) - } - rules = append(rules, colorWhen{start: s, end: e}) - } - if len(rules) == 0 { - return nil, errors.New("invalid colorization rules for totp, none found") - } - return rules, nil -} - -func display(token string, args cli.Arguments) error { - interactive, err := inputs.IsInteractive() - if err != nil { - return err - } - if args.Short { - interactive = false - } - if !interactive && args.Clip { - return errors.New("clipboard not available in non-interactive mode") - } - coloring, err := colors.NewTerminal(colors.Red) - if err != nil { - return err - } - t, err := backend.NewTransaction() - if err != nil { - return err - } - entity, err := t.Get(backend.NewPath(token, totpEnv()), backend.SecretValue) - if err != nil { - return err - } - if entity == nil { - return errors.New("object does not exist") - } - totpToken := string(entity.Value) - if !interactive { - code, err := otp.GenerateCode(totpToken, time.Now()) - if err != nil { - return err - } - fmt.Println(code) - return nil - } - first := true - running := 0 - lastSecond := -1 - if !args.Clip { - if !args.Once { - clear() - } - } - clipboard := platform.Clipboard{} - if args.Clip { - clipboard, err = platform.NewClipboard() - if err != nil { - return err - } - } - colorRules, err := colorWhenRules() - if err != nil { - return err - } - for { - if !first { - time.Sleep(500 * time.Millisecond) - } - first = false - running++ - if running > 120 { - fmt.Println("exiting (timeout)") - return nil - } - now := time.Now() - last := now.Second() - if last == lastSecond { - continue - } - lastSecond = last - left := 60 - last - code, err := otp.GenerateCode(totpToken, now) - if err != nil { - return err - } - startColor := "" - endColor := "" - for _, when := range colorRules { - if left < when.end && left >= when.start { - startColor = coloring.Start - endColor = coloring.End - } - } - leftString := fmt.Sprintf("%d", left) - if len(leftString) < 2 { - leftString = "0" + leftString - } - expires := fmt.Sprintf("%s%s (%s)%s", startColor, now.Format("15:04:05"), leftString, endColor) - outputs := []string{expires} - if !args.Clip { - outputs = append(outputs, fmt.Sprintf("%s\n %s", token, code)) - if !args.Once { - outputs = append(outputs, "-> CTRL+C to exit") - } - } else { - fmt.Printf("-> %s\n", expires) - return clipboard.CopyTo(code) - } - if !args.Once { - clear() - } - fmt.Printf("%s\n", strings.Join(outputs, "\n\n")) - if args.Once { - return nil - } - } -} - -// TOTP handles UI for TOTP tokens. -func TOTP(args []string) error { - if len(args) > 2 || len(args) < 1 { - return errors.New("invalid arguments, subkey and entry required") - } - cmd := args[0] - options := cli.ParseArgs(cmd) - if options.List { - t, err := backend.NewTransaction() - if err != nil { - return err - } - e, err := t.QueryCallback(backend.QueryOptions{Mode: backend.SuffixMode, Criteria: backend.NewSuffix(totpEnv())}) - if err != nil { - return err - } - for _, entry := range e { - fmt.Println(entry.Directory()) - } - return nil - } - if len(args) == 2 { - if !options.Clip && !options.Short && !options.Once { - return errors.New("invalid sub command") - } - cmd = args[1] - } - if err := display(cmd, options); err != nil { - return err - } - return nil -} diff --git a/internal/totp/core.go b/internal/totp/core.go @@ -0,0 +1,216 @@ +// Package totp handles TOTP tokens. +package totp + +import ( + "errors" + "fmt" + "os" + "os/exec" + "strconv" + "strings" + "time" + + "github.com/enckse/lockbox/internal/backend" + "github.com/enckse/lockbox/internal/cli" + "github.com/enckse/lockbox/internal/colors" + "github.com/enckse/lockbox/internal/inputs" + "github.com/enckse/lockbox/internal/platform" + otp "github.com/pquerna/otp/totp" +) + +type ( + colorWhen struct { + start int + end int + } +) + +func clear() { + cmd := exec.Command("clear") + cmd.Stdout = os.Stdout + if err := cmd.Run(); err != nil { + fmt.Printf("unable to clear screen: %v\n", err) + } +} + +func totpEnv() string { + return inputs.EnvOrDefault(inputs.TotpEnv, "totp") +} + +func colorWhenRules() ([]colorWhen, error) { + envTime := os.Getenv(inputs.ColorBetweenEnv) + if envTime == "" { + return []colorWhen{ + {start: 0, end: 5}, + {start: 30, end: 35}, + }, nil + } + var rules []colorWhen + for _, item := range strings.Split(envTime, ",") { + line := strings.TrimSpace(item) + if line == "" { + continue + } + parts := strings.Split(line, ":") + if len(parts) != 2 { + return nil, fmt.Errorf("invalid colorization rule found: %s", line) + } + s, err := strconv.Atoi(parts[0]) + if err != nil { + return nil, err + } + e, err := strconv.Atoi(parts[1]) + if err != nil { + return nil, err + } + if s < 0 || e < 0 || e < s || s > 59 || e > 59 { + return nil, fmt.Errorf("invalid time found for colorization rule: %s", line) + } + rules = append(rules, colorWhen{start: s, end: e}) + } + if len(rules) == 0 { + return nil, errors.New("invalid colorization rules for totp, none found") + } + return rules, nil +} + +func display(token string, args cli.Arguments) error { + interactive, err := inputs.IsInteractive() + if err != nil { + return err + } + if args.Short { + interactive = false + } + if !interactive && args.Clip { + return errors.New("clipboard not available in non-interactive mode") + } + coloring, err := colors.NewTerminal(colors.Red) + if err != nil { + return err + } + t, err := backend.NewTransaction() + if err != nil { + return err + } + entity, err := t.Get(backend.NewPath(token, totpEnv()), backend.SecretValue) + if err != nil { + return err + } + if entity == nil { + return errors.New("object does not exist") + } + totpToken := string(entity.Value) + if !interactive { + code, err := otp.GenerateCode(totpToken, time.Now()) + if err != nil { + return err + } + fmt.Println(code) + return nil + } + first := true + running := 0 + lastSecond := -1 + if !args.Clip { + if !args.Once { + clear() + } + } + clipboard := platform.Clipboard{} + if args.Clip { + clipboard, err = platform.NewClipboard() + if err != nil { + return err + } + } + colorRules, err := colorWhenRules() + if err != nil { + return err + } + for { + if !first { + time.Sleep(500 * time.Millisecond) + } + first = false + running++ + if running > 120 { + fmt.Println("exiting (timeout)") + return nil + } + now := time.Now() + last := now.Second() + if last == lastSecond { + continue + } + lastSecond = last + left := 60 - last + code, err := otp.GenerateCode(totpToken, now) + if err != nil { + return err + } + startColor := "" + endColor := "" + for _, when := range colorRules { + if left < when.end && left >= when.start { + startColor = coloring.Start + endColor = coloring.End + } + } + leftString := fmt.Sprintf("%d", left) + if len(leftString) < 2 { + leftString = "0" + leftString + } + expires := fmt.Sprintf("%s%s (%s)%s", startColor, now.Format("15:04:05"), leftString, endColor) + outputs := []string{expires} + if !args.Clip { + outputs = append(outputs, fmt.Sprintf("%s\n %s", token, code)) + if !args.Once { + outputs = append(outputs, "-> CTRL+C to exit") + } + } else { + fmt.Printf("-> %s\n", expires) + return clipboard.CopyTo(code) + } + if !args.Once { + clear() + } + fmt.Printf("%s\n", strings.Join(outputs, "\n\n")) + if args.Once { + return nil + } + } +} + +// Call handles UI for TOTP tokens. +func Call(args []string) error { + if len(args) > 2 || len(args) < 1 { + return errors.New("invalid arguments, subkey and entry required") + } + cmd := args[0] + options := cli.ParseArgs(cmd) + if options.List { + t, err := backend.NewTransaction() + if err != nil { + return err + } + e, err := t.QueryCallback(backend.QueryOptions{Mode: backend.SuffixMode, Criteria: backend.NewSuffix(totpEnv())}) + if err != nil { + return err + } + for _, entry := range e { + fmt.Println(entry.Directory()) + } + return nil + } + if len(args) == 2 { + if !options.Clip && !options.Short && !options.Once { + return errors.New("invalid sub command") + } + cmd = args[1] + } + if err := display(cmd, options); err != nil { + return err + } + return nil +}