lockbox

password manager
Log | Files | Refs | README | LICENSE

commit af6dc8e4f4c9860d6245875a43dfc9bd0f9c74e2
parent 7364430a241411ff44467bd90c869ae86a2639b3
Author: Sean Enck <sean@ttypty.com>
Date:   Thu,  2 Mar 2023 18:13:51 -0500

move app code

Diffstat:
MMakefile | 2+-
Mcmd/main.go | 361+------------------------------------------------------------------------------
Dcmd/vers.txt | 2--
Ainternal/app/core.go | 363+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainternal/app/vers.txt | 2++
Minternal/util/core.go | 5-----
6 files changed, 369 insertions(+), 366 deletions(-)

diff --git a/Makefile b/Makefile @@ -7,7 +7,7 @@ all: $(TARGET) build: $(TARGET) $(TARGET): cmd/main.go internal/**/*.go go.* internal/cli/completions* - ./scripts/version/configure cmd/vers.txt + ./scripts/version/configure internal/app/vers.txt go build $(GOFLAGS) -o $@ cmd/main.go unittest: diff --git a/cmd/main.go b/cmd/main.go @@ -2,367 +2,12 @@ package main import ( - _ "embed" - "errors" - "fmt" - "os" - "os/exec" - "strings" - "time" - - "github.com/enckse/lockbox/internal/backend" - "github.com/enckse/lockbox/internal/cli" - "github.com/enckse/lockbox/internal/inputs" - "github.com/enckse/lockbox/internal/platform" - "github.com/enckse/lockbox/internal/totp" + "github.com/enckse/lockbox/internal/app" "github.com/enckse/lockbox/internal/util" ) -//go:embed "vers.txt" -var version string - -type ( - callbackFunction func([]string) error -) - -func internalCallback(name string) callbackFunction { - switch name { - case cli.TOTPCommand: - return totp.Call - case cli.HashCommand: - return hashText - case cli.ClearCommand: - return clearClipboard - } - return nil -} - func main() { - if err := run(); err != nil { - util.Fatal(err) - } -} - -func getInfoDefault(args []string, possibleArg string) (bool, error) { - defaults := false - invalid := false - switch len(args) { - case 2: - break - case 3: - if args[2] == possibleArg { - defaults = true - } else { - invalid = true - } - default: - invalid = true - } - if invalid { - return false, errors.New("invalid argument") - } - return defaults, nil -} - -func processInfoCommands(command string, args []string) ([]string, error) { - switch command { - case cli.HelpCommand: - if len(args) > 3 { - return nil, errors.New("invalid help command") - } - isAdvanced := false - if len(args) == 3 { - if args[2] == cli.HelpAdvancedCommand { - isAdvanced = true - } else { - return nil, errors.New("invalid help option") - } - } - results, err := cli.Usage(isAdvanced) - if err != nil { - return nil, err - } - return results, nil - case cli.VersionCommand: - return []string{fmt.Sprintf("version: %s", version)}, nil - case cli.EnvCommand, cli.BashCommand: - defaultFlag := cli.BashDefaultsCommand - isEnv := command == cli.EnvCommand - if isEnv { - defaultFlag = cli.EnvDefaultsCommand - } - defaults, err := getInfoDefault(args, defaultFlag) - if err != nil { - return nil, err - } - if isEnv { - return inputs.ListEnvironmentVariables(!defaults), nil - } - return cli.BashCompletions(defaults) - } - return nil, nil -} - -func wrapped(message string, err error) error { - return fmt.Errorf("%s (%w)", message, err) -} - -func run() error { - args := os.Args - if len(args) < 2 { - return errors.New("requires subcommand") - } - command := args[1] - info, err := processInfoCommands(command, args) - if err != nil { - return err - } - if info != nil { - fmt.Println(strings.Join(info, "\n")) - return nil - } - t, err := backend.NewTransaction() - if err != nil { - return wrapped("unable to build transaction model", err) - } - switch command { - case cli.ReKeyCommand: - if confirm("proceed with rekey") { - if err := t.ReKey(); err != nil { - return wrapped("unable to rekey", err) - } - } - case cli.ListCommand, cli.FindCommand: - opts := backend.QueryOptions{} - opts.Mode = backend.ListMode - if command == cli.FindCommand { - opts.Mode = backend.FindMode - if len(args) < 3 { - return errors.New("find requires search term") - } - opts.Criteria = args[2] - } - e, err := t.QueryCallback(opts) - if err != nil { - return wrapped("unable to list files", err) - } - for _, f := range e { - fmt.Println(f.Path) - } - case cli.MoveCommand: - if len(args) != 4 { - return errors.New("src/dst required for move") - } - src := args[2] - dst := args[3] - srcExists, err := t.Get(src, backend.SecretValue) - if err != nil { - return errors.New("unable to get source entry") - } - if srcExists == nil { - return errors.New("no source object found") - } - dstExists, err := t.Get(dst, backend.BlankValue) - if err != nil { - return errors.New("unable to get destination object") - } - if dstExists != nil { - if !confirm("overwrite destination") { - return nil - } - } - if err := t.Move(*srcExists, dst); err != nil { - return wrapped("unable to move object", err) - } - case cli.InsertCommand: - multi := false - isTOTP := false - idx := 2 - switch len(args) { - case 2: - return errors.New("insert requires an entry") - case 3: - case 4: - opt := args[2] - switch opt { - case cli.InsertMultiCommand: - multi = true - case cli.InsertTOTPCommand: - off, err := inputs.IsNoTOTP() - if err != nil { - return err - } - if off { - return totp.ErrNoTOTP - } - isTOTP = true - default: - return errors.New("unknown argument") - } - multi = true - idx = 3 - default: - return errors.New("too many arguments") - } - isPipe := inputs.IsInputFromPipe() - entry := args[idx] - if isTOTP { - totpToken := inputs.TOTPToken() - if !strings.HasSuffix(entry, backend.NewSuffix(totpToken)) { - entry = backend.NewPath(entry, totpToken) - } - } - existing, err := t.Get(entry, backend.BlankValue) - if err != nil { - return wrapped("unable to check for existing entry", err) - } - if existing != nil { - if !isPipe { - if !confirm("overwrite existing") { - return nil - } - } - } - password, err := inputs.GetUserInputPassword(isPipe, multi) - if err != nil { - return wrapped("invalid input", err) - } - p := strings.TrimSpace(string(password)) - if err := t.Insert(entry, p); err != nil { - return wrapped("failed to insert", err) - } - if !isPipe { - fmt.Println() - } - case cli.RemoveCommand: - if len(args) != 3 { - return errors.New("remove requires an entry") - } - deleting := args[2] - postfixRemove := "y" - existings, err := t.MatchPath(deleting) - if err != nil { - return wrapped("unable to get entry", err) - } - - if len(existings) > 1 { - postfixRemove = "ies" - fmt.Println("selected entities:") - for _, e := range existings { - fmt.Printf(" %s\n", e.Path) - } - fmt.Println("") - } - if confirm(fmt.Sprintf("delete entr%s", postfixRemove)) { - if err := t.RemoveAll(existings); err != nil { - return wrapped("unable to remove entry", err) - } - } - case cli.ShowCommand, cli.ClipCommand, cli.StatsCommand: - if len(args) != 3 { - return errors.New("entry required") - } - entry := args[2] - if command == cli.StatsCommand { - v, err := t.Get(entry, backend.StatsValue) - if err != nil { - return wrapped("unable to get stats", err) - } - if v != nil { - fmt.Println(v.Value) - } - return nil - } - clipboard := platform.Clipboard{} - isShow := command == cli.ShowCommand - if !isShow { - clipboard, err = platform.NewClipboard() - if err != nil { - return wrapped("unable to get clipboard", err) - } - } - existing, err := t.Get(entry, backend.SecretValue) - if err != nil { - return wrapped("unable to get entry", err) - } - if existing == nil { - return errors.New("entry not found") - } - if isShow { - fmt.Println(existing.Value) - return nil - } - if err := clipboard.CopyTo(existing.Value); err != nil { - return wrapped("clipboard operation failed", err) - } - default: - if len(args) < 2 { - return errors.New("missing required arguments") - } - a := args[2:] - callback := internalCallback(command) - if callback != nil { - if err := callback(a); err != nil { - return wrapped(fmt.Sprintf("%s command failure", command), err) - } - return nil - } - return fmt.Errorf("unknown command: %s", command) - } - return nil -} - -func hashText(args []string) error { - if len(args) == 0 { - return errors.New("hash requires a file") - } - t, err := backend.Load(args[len(args)-1]) - if err != nil { - return err - } - e, err := t.QueryCallback(backend.QueryOptions{Mode: backend.ListMode, Values: backend.HashedValue}) - if err != nil { - return err - } - for _, item := range e { - fmt.Printf("%s:\n %s\n\n", item.Path, strings.ReplaceAll(item.Value, "\n", "\n ")) - } - return nil -} - -func clearClipboard(args []string) error { - idx := 0 - val, err := inputs.Stdin(false) - if err != nil { - return err - } - clipboard, err := platform.NewClipboard() - if err != nil { - return err - } - pCmd, pArgs, valid := clipboard.Args(false) - if !valid { - return nil - } - val = strings.TrimSpace(val) - for idx < clipboard.MaxTime { - idx++ - time.Sleep(1 * time.Second) - out, err := exec.Command(pCmd, pArgs...).Output() - if err != nil { - continue - } - if strings.TrimSpace(string(out)) != val { - return nil - } - } - return clipboard.CopyTo("") -} - -func confirm(prompt string) bool { - yesNo, err := inputs.ConfirmYesNoPrompt(prompt) - if err != nil { - util.Dief("failed to read stdin for confirmation: %v", err) + if err := app.Run(); err != nil { + util.Die(err) } - return yesNo } diff --git a/cmd/vers.txt b/cmd/vers.txt @@ -1 +0,0 @@ -v23.03.00 -\ No newline at end of file diff --git a/internal/app/core.go b/internal/app/core.go @@ -0,0 +1,363 @@ +// Package app runs the commands/ui +package app + +import ( + _ "embed" + "errors" + "fmt" + "os" + "os/exec" + "strings" + "time" + + "github.com/enckse/lockbox/internal/backend" + "github.com/enckse/lockbox/internal/cli" + "github.com/enckse/lockbox/internal/inputs" + "github.com/enckse/lockbox/internal/platform" + "github.com/enckse/lockbox/internal/totp" + "github.com/enckse/lockbox/internal/util" +) + +//go:embed "vers.txt" +var version string + +type ( + callbackFunction func([]string) error +) + +func internalCallback(name string) callbackFunction { + switch name { + case cli.TOTPCommand: + return totp.Call + case cli.HashCommand: + return hashText + case cli.ClearCommand: + return clearClipboard + } + return nil +} + +func getInfoDefault(args []string, possibleArg string) (bool, error) { + defaults := false + invalid := false + switch len(args) { + case 2: + break + case 3: + if args[2] == possibleArg { + defaults = true + } else { + invalid = true + } + default: + invalid = true + } + if invalid { + return false, errors.New("invalid argument") + } + return defaults, nil +} + +func processInfoCommands(command string, args []string) ([]string, error) { + switch command { + case cli.HelpCommand: + if len(args) > 3 { + return nil, errors.New("invalid help command") + } + isAdvanced := false + if len(args) == 3 { + if args[2] == cli.HelpAdvancedCommand { + isAdvanced = true + } else { + return nil, errors.New("invalid help option") + } + } + results, err := cli.Usage(isAdvanced) + if err != nil { + return nil, err + } + return results, nil + case cli.VersionCommand: + return []string{fmt.Sprintf("version: %s", version)}, nil + case cli.EnvCommand, cli.BashCommand: + defaultFlag := cli.BashDefaultsCommand + isEnv := command == cli.EnvCommand + if isEnv { + defaultFlag = cli.EnvDefaultsCommand + } + defaults, err := getInfoDefault(args, defaultFlag) + if err != nil { + return nil, err + } + if isEnv { + return inputs.ListEnvironmentVariables(!defaults), nil + } + return cli.BashCompletions(defaults) + } + return nil, nil +} + +func wrapped(message string, err error) error { + return fmt.Errorf("%s (%w)", message, err) +} + +// Run invokes the app +func Run() error { + args := os.Args + if len(args) < 2 { + return errors.New("requires subcommand") + } + command := args[1] + info, err := processInfoCommands(command, args) + if err != nil { + return err + } + if info != nil { + fmt.Println(strings.Join(info, "\n")) + return nil + } + t, err := backend.NewTransaction() + if err != nil { + return wrapped("unable to build transaction model", err) + } + switch command { + case cli.ReKeyCommand: + if confirm("proceed with rekey") { + if err := t.ReKey(); err != nil { + return wrapped("unable to rekey", err) + } + } + case cli.ListCommand, cli.FindCommand: + opts := backend.QueryOptions{} + opts.Mode = backend.ListMode + if command == cli.FindCommand { + opts.Mode = backend.FindMode + if len(args) < 3 { + return errors.New("find requires search term") + } + opts.Criteria = args[2] + } + e, err := t.QueryCallback(opts) + if err != nil { + return wrapped("unable to list files", err) + } + for _, f := range e { + fmt.Println(f.Path) + } + case cli.MoveCommand: + if len(args) != 4 { + return errors.New("src/dst required for move") + } + src := args[2] + dst := args[3] + srcExists, err := t.Get(src, backend.SecretValue) + if err != nil { + return errors.New("unable to get source entry") + } + if srcExists == nil { + return errors.New("no source object found") + } + dstExists, err := t.Get(dst, backend.BlankValue) + if err != nil { + return errors.New("unable to get destination object") + } + if dstExists != nil { + if !confirm("overwrite destination") { + return nil + } + } + if err := t.Move(*srcExists, dst); err != nil { + return wrapped("unable to move object", err) + } + case cli.InsertCommand: + multi := false + isTOTP := false + idx := 2 + switch len(args) { + case 2: + return errors.New("insert requires an entry") + case 3: + case 4: + opt := args[2] + switch opt { + case cli.InsertMultiCommand: + multi = true + case cli.InsertTOTPCommand: + off, err := inputs.IsNoTOTP() + if err != nil { + return err + } + if off { + return totp.ErrNoTOTP + } + isTOTP = true + default: + return errors.New("unknown argument") + } + multi = true + idx = 3 + default: + return errors.New("too many arguments") + } + isPipe := inputs.IsInputFromPipe() + entry := args[idx] + if isTOTP { + totpToken := inputs.TOTPToken() + if !strings.HasSuffix(entry, backend.NewSuffix(totpToken)) { + entry = backend.NewPath(entry, totpToken) + } + } + existing, err := t.Get(entry, backend.BlankValue) + if err != nil { + return wrapped("unable to check for existing entry", err) + } + if existing != nil { + if !isPipe { + if !confirm("overwrite existing") { + return nil + } + } + } + password, err := inputs.GetUserInputPassword(isPipe, multi) + if err != nil { + return wrapped("invalid input", err) + } + p := strings.TrimSpace(string(password)) + if err := t.Insert(entry, p); err != nil { + return wrapped("failed to insert", err) + } + if !isPipe { + fmt.Println() + } + case cli.RemoveCommand: + if len(args) != 3 { + return errors.New("remove requires an entry") + } + deleting := args[2] + postfixRemove := "y" + existings, err := t.MatchPath(deleting) + if err != nil { + return wrapped("unable to get entry", err) + } + + if len(existings) > 1 { + postfixRemove = "ies" + fmt.Println("selected entities:") + for _, e := range existings { + fmt.Printf(" %s\n", e.Path) + } + fmt.Println("") + } + if confirm(fmt.Sprintf("delete entr%s", postfixRemove)) { + if err := t.RemoveAll(existings); err != nil { + return wrapped("unable to remove entry", err) + } + } + case cli.ShowCommand, cli.ClipCommand, cli.StatsCommand: + if len(args) != 3 { + return errors.New("entry required") + } + entry := args[2] + if command == cli.StatsCommand { + v, err := t.Get(entry, backend.StatsValue) + if err != nil { + return wrapped("unable to get stats", err) + } + if v != nil { + fmt.Println(v.Value) + } + return nil + } + clipboard := platform.Clipboard{} + isShow := command == cli.ShowCommand + if !isShow { + clipboard, err = platform.NewClipboard() + if err != nil { + return wrapped("unable to get clipboard", err) + } + } + existing, err := t.Get(entry, backend.SecretValue) + if err != nil { + return wrapped("unable to get entry", err) + } + if existing == nil { + return errors.New("entry not found") + } + if isShow { + fmt.Println(existing.Value) + return nil + } + if err := clipboard.CopyTo(existing.Value); err != nil { + return wrapped("clipboard operation failed", err) + } + default: + if len(args) < 2 { + return errors.New("missing required arguments") + } + a := args[2:] + callback := internalCallback(command) + if callback != nil { + if err := callback(a); err != nil { + return wrapped(fmt.Sprintf("%s command failure", command), err) + } + return nil + } + return fmt.Errorf("unknown command: %s", command) + } + return nil +} + +func hashText(args []string) error { + if len(args) == 0 { + return errors.New("hash requires a file") + } + t, err := backend.Load(args[len(args)-1]) + if err != nil { + return err + } + e, err := t.QueryCallback(backend.QueryOptions{Mode: backend.ListMode, Values: backend.HashedValue}) + if err != nil { + return err + } + for _, item := range e { + fmt.Printf("%s:\n %s\n\n", item.Path, strings.ReplaceAll(item.Value, "\n", "\n ")) + } + return nil +} + +func clearClipboard(args []string) error { + idx := 0 + val, err := inputs.Stdin(false) + if err != nil { + return err + } + clipboard, err := platform.NewClipboard() + if err != nil { + return err + } + pCmd, pArgs, valid := clipboard.Args(false) + if !valid { + return nil + } + val = strings.TrimSpace(val) + for idx < clipboard.MaxTime { + idx++ + time.Sleep(1 * time.Second) + out, err := exec.Command(pCmd, pArgs...).Output() + if err != nil { + continue + } + if strings.TrimSpace(string(out)) != val { + return nil + } + } + return clipboard.CopyTo("") +} + +func confirm(prompt string) bool { + yesNo, err := inputs.ConfirmYesNoPrompt(prompt) + if err != nil { + util.Dief("failed to read stdin for confirmation: %v", err) + } + return yesNo +} diff --git a/internal/app/vers.txt b/internal/app/vers.txt @@ -0,0 +1 @@ +v23.03.01 +\ No newline at end of file diff --git a/internal/util/core.go b/internal/util/core.go @@ -18,11 +18,6 @@ func PathExists(file string) bool { return true } -// Fatal will call Die but without a message -func Fatal(err error) { - Die(err) -} - // Dief will for a message and die func Dief(format string, a ...any) { Die(fmt.Sprintf(format, a...))