diff --git a/cmd/goaegis/app.go b/cmd/goaegis/app.go index b9aeb95..9aca2dc 100644 --- a/cmd/goaegis/app.go +++ b/cmd/goaegis/app.go @@ -10,13 +10,15 @@ import ( "github.com/urfave/cli/v2" ) +const aegisVaultFileName = ".aegis_vault.json" + var ( appFlagPath = &cli.PathFlag{ Name: "path", Aliases: []string{"p"}, Usage: "path to vault file", Required: false, - DefaultText: "$HOME/.aegis_vault.json", + DefaultText: "$HOME/" + aegisVaultFileName, } appFlags = []cli.Flag{appFlagPath} ) @@ -31,6 +33,7 @@ func newApp(name, version string) *appWrapper { app.Name = name app.HelpName = name app.Version = version + app.Usage = "Two-Factor Authentication (2FA) App compatible with Aegis vault format" app.Commands = []*cli.Command{cmdHook, cmdTUI} app.DefaultCommand = cmdTUI.Name app.EnableBashCompletion = true @@ -59,5 +62,5 @@ func getVaultPath(c *cli.Context) (string, error) { return "", fmt.Errorf("couldn't get user home dir: %w", err) } - return path.Join(dirname, ".aegis_vault.json"), nil + return path.Join(dirname, aegisVaultFileName), nil } diff --git a/cmd/goaegis/cmd_hook.go b/cmd/goaegis/cmd_hook.go index 573f740..f3cb735 100644 --- a/cmd/goaegis/cmd_hook.go +++ b/cmd/goaegis/cmd_hook.go @@ -9,7 +9,7 @@ import ( var cmdHook = &cli.Command{ Name: "hook", - Usage: "Print the shell function used to execute goaegis", + Usage: "Prints the shell function used to execute " + appName, Subcommands: []*cli.Command{ { Name: "bash", diff --git a/cmd/goaegis/cmd_tui.go b/cmd/goaegis/cmd_tui.go index 0857115..6a518ee 100644 --- a/cmd/goaegis/cmd_tui.go +++ b/cmd/goaegis/cmd_tui.go @@ -28,7 +28,7 @@ var cmdTUI = &cli.Command{ logger.Debug("vault file read successfully", slog.String("path", path)) - if _, err := tea.NewProgram(internal.NewUI(vault)).Run(); err != nil { + if _, err := tea.NewProgram(internal.NewUI(c.App.Name, vault)).Run(); err != nil { return err } diff --git a/cmd/goaegis/main.go b/cmd/goaegis/main.go index 559861b..c00d8f5 100644 --- a/cmd/goaegis/main.go +++ b/cmd/goaegis/main.go @@ -3,17 +3,34 @@ package main import ( "log/slog" "os" + "runtime/debug" + "slices" ) const appName = "goaegis" +const defaultVersion = "development" + // this flag will be set by the build flags -var version = "development" +var version = defaultVersion func main() { - app := newApp(appName, version) + app := newApp(appName, getVersion()) if err := app.Run(os.Args); err != nil { app.logger.Error("app run failed", slog.Any("error", err)) os.Exit(1) } } + +func getVersion() string { + if version != defaultVersion { + return version + } + + buildInfo, ok := debug.ReadBuildInfo() + if !ok || slices.Contains([]string{"", "(devel)"}, buildInfo.Main.Version) { + return defaultVersion + } + + return buildInfo.Main.Version +} diff --git a/internal/ui.go b/internal/ui.go index b0d6140..073b894 100644 --- a/internal/ui.go +++ b/internal/ui.go @@ -1,7 +1,10 @@ package internal import ( + "errors" "fmt" + "slices" + "strings" "time" "github.com/charmbracelet/bubbles/list" @@ -23,9 +26,10 @@ type UI struct { db DB list list.Model passwordInput textinput.Model + passwordError error } -func NewUI(vault Vault) UI { +func NewUI(appName string, vault Vault) UI { passwordInput := textinput.New() passwordInput.Focus() passwordInput.EchoMode = textinput.EchoPassword @@ -33,7 +37,7 @@ func NewUI(vault Vault) UI { passwordInput.Width = 20 l := list.New(nil, list.NewDefaultDelegate(), 0, 0) - l.Title = "GoAegis" + l.Title = appName l.SetShowPagination(false) return UI{ @@ -55,23 +59,37 @@ type refreshListMsg struct { } func (m UI) Update(teaMsg tea.Msg) (tea.Model, tea.Cmd) { + if msg, ok := teaMsg.(tea.KeyMsg); ok { + if slices.Contains([]tea.KeyType{tea.KeyCtrlC, tea.KeyEsc}, msg.Type) { + return m, tea.Quit + } + } + + switch m.view { + case viewPassword: + return m.updatePasswordView(teaMsg) + case viewList: + return m.updateListView(teaMsg) + default: + return m, tea.Quit + } +} + +func (m UI) updatePasswordView(teaMsg tea.Msg) (tea.Model, tea.Cmd) { var passwordInputCmd tea.Cmd //nolint:revive m.passwordInput, passwordInputCmd = m.passwordInput.Update(teaMsg) var cmd tea.Cmd - switch msg := teaMsg.(type) { - case tea.KeyMsg: - //nolint:exhaustive - switch msg.Type { - case tea.KeyCtrlC, tea.KeyEsc: - return m, tea.Quit - case tea.KeyEnter: + if msg, ok := teaMsg.(tea.KeyMsg); ok { + if msg.Type == tea.KeyEnter { var err error //nolint:revive m.db, err = m.vault.DecryptDB([]byte(m.passwordInput.Value())) if err != nil { + //nolint:revive + m.passwordError = errors.New("invalid password") m.passwordInput.Reset() } else { //nolint:revive @@ -82,8 +100,22 @@ func (m UI) Update(teaMsg tea.Msg) (tea.Model, tea.Cmd) { ) } } + } + + if m.passwordInput.Value() != "" && m.passwordError != nil { + //nolint:revive + m.passwordError = nil + } + + return m, tea.Batch(passwordInputCmd, cmd) +} + +func (m UI) updateListView(teaMsg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + + switch msg := teaMsg.(type) { case tea.WindowSizeMsg: - h, v := docStyle.GetFrameSize() + h, v := listStyle.GetFrameSize() m.list.SetSize(msg.Width-h, msg.Height-v) case refreshListMsg: cmd = tea.Batch( @@ -96,7 +128,7 @@ func (m UI) Update(teaMsg tea.Msg) (tea.Model, tea.Cmd) { //nolint:revive m.list, listCmd = m.list.Update(teaMsg) - return m, tea.Batch(passwordInputCmd, cmd, listCmd) + return m, tea.Batch(cmd, listCmd) } const tickDuration = time.Second @@ -107,18 +139,29 @@ func (m UI) tick() tea.Cmd { }) } -var docStyle = lipgloss.NewStyle().Margin(1, 2) +var ( + listStyle = lipgloss.NewStyle().Margin(1, 2) + helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Margin(1, 0) + errStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF0000")) +) func (m UI) View() string { if m.view == viewList { - return docStyle.Render(m.list.View()) + return listStyle.Render(m.list.View()) } - return fmt.Sprintf( - "Enter password:\n\n%s\n\n%s", - m.passwordInput.View(), - "(ctrl+c to quit)", - ) + "\n" + var b strings.Builder + + b.WriteString("Enter password:\n\n") + b.WriteString(m.passwordInput.View()) + b.WriteString("\n") + if m.passwordError != nil { + b.WriteString(errStyle.Render(m.passwordError.Error())) + } + b.WriteString("\n") + b.WriteString(helpStyle.Render("(ctrl+c to quit)")) + + return b.String() } type listItem struct {