From b26c2ce17c0491890de03dc131717d557876a3e4 Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Sun, 23 Nov 2025 13:40:41 -0500 Subject: [PATCH] Move username to top of copy menu; send user to copy menu after creating/editing an entry; allow copying old password from copy menu --- go.mod | 2 +- go.sum | 4 ++-- mutn.go | 2 +- src/cli/add.go | 4 ++-- src/cli/copyMenu.go | 42 ++++++++++++++++++++++++---------------- src/cli/edit.go | 9 ++++++--- src/cli/entryReader.go | 3 --- src/cli/utilitiesMisc.go | 5 ++++- 8 files changed, 41 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index bff31d8..cbbe8ff 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.25.4 require ( github.com/charmbracelet/glamour v0.7.0 github.com/rwinkhart/go-boilerplate v0.1.1-0.20251110055016-10ee4f91fcb6 - github.com/rwinkhart/libmutton v0.4.3-0.20251123083838-5dc3a5576d67 + github.com/rwinkhart/libmutton v0.4.3-0.20251123183956-95a877d0b300 golang.org/x/term v0.37.0 ) diff --git a/go.sum b/go.sum index e7d8655..327aedb 100644 --- a/go.sum +++ b/go.sum @@ -46,8 +46,8 @@ github.com/rwinkhart/go-highlite v0.1.1 h1:9TxbRhYVfD/3YaEgNk1BtEiZ0t8/5mxDUZqNM github.com/rwinkhart/go-highlite v0.1.1/go.mod h1:mWLMtCWcyV0BG4NeyPyAUGMjPvI0ds6vXmUq89QwBFA= github.com/rwinkhart/go-winio v0.1.0 h1:b72agLW+dETGmhR3VbcbwnStfgKfc5AfgJOXBJDkaHg= github.com/rwinkhart/go-winio v0.1.0/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0= -github.com/rwinkhart/libmutton v0.4.3-0.20251123083838-5dc3a5576d67 h1:lx+JG1J6vAMkYVaCjtDH79+CWYbGQWBuZwc52Umke0s= -github.com/rwinkhart/libmutton v0.4.3-0.20251123083838-5dc3a5576d67/go.mod h1:QFyn9hvMbSfcfXnUl139Cr/ZxR7DARpsMtynOk3CziY= +github.com/rwinkhart/libmutton v0.4.3-0.20251123183956-95a877d0b300 h1:zeFKfGRyFaKgbfEsyu9rfSz2yG5OvaekGFran0wyT1M= +github.com/rwinkhart/libmutton v0.4.3-0.20251123183956-95a877d0b300/go.mod h1:QFyn9hvMbSfcfXnUl139Cr/ZxR7DARpsMtynOk3CziY= github.com/rwinkhart/peercred-mini v0.1.2 h1:4cGWDbv0whvLeVvbUdx84V/9p+2fS+DEXgrA1KxlRFo= github.com/rwinkhart/peercred-mini v0.1.2/go.mod h1:LLHG7YshHEpbpJJP+Il9nx2dnGj5O3VGE32rWmflj0c= github.com/rwinkhart/rcw v0.2.3 h1:g1rVaspZnZM8nWupeSdVO827di4ORMZnczw6iXyo01Y= diff --git a/mutn.go b/mutn.go index 41c5963..92b617d 100644 --- a/mutn.go +++ b/mutn.go @@ -97,7 +97,7 @@ func main() { case "note", "-n": field = 4 case "menu", "-m": - cli.CopyMenu(realPath) + cli.CopyMenu(realPath, nil, "") default: cli.HelpCopy() } diff --git a/src/cli/add.go b/src/cli/add.go index c686658..418c6ce 100644 --- a/src/cli/add.go +++ b/src/cli/add.go @@ -44,9 +44,9 @@ func AddEntry(realPath string, hideSecrets bool, entryType uint8) { // write and preview the new entry if password != "" { - writeEntryCLI(realPath, decryptedEntry, hideSecrets, true) + writeEntryCLI(realPath, decryptedEntry, hideSecrets, true, "") } else { - writeEntryCLI(realPath, decryptedEntry, hideSecrets, false) + writeEntryCLI(realPath, decryptedEntry, hideSecrets, false, "") } } diff --git a/src/cli/copyMenu.go b/src/cli/copyMenu.go index 45d5f2b..e7dc036 100644 --- a/src/cli/copyMenu.go +++ b/src/cli/copyMenu.go @@ -16,25 +16,27 @@ import ( // CopyMenu decrypts an entry and allows the user to // interactively copy fields without having to re-decrypt each time. -func CopyMenu(realPath string) { - // decrypt entry - decSlice, err := crypt.DecryptFileToSlice(realPath) - if err != nil { - other.PrintError("Failed to decrypt entry: "+err.Error(), global.ErrorDecryption) +// Only one of realPath or decSlice should be provided. +func CopyMenu(realPath string, decSlice []string, oldPassword string) { + var err error + if decSlice == nil { + // decrypt entry + decSlice, err = crypt.DecryptFileToSlice(realPath) + if err != nil { + other.PrintError("Failed to decrypt entry: "+err.Error(), global.ErrorDecryption) + } } // determine populated fields in entry - fieldIndexToString := map[int]string{ - 0: "Password", - 1: "Username", - 2: "TOTP Code", - 3: "URL", - 4: "Note (first line)", - } + var fieldStrings = []string{"Username", "Password", "TOTP Code", "URL", "Note (first line)"} + var indices = []int{1, 0, 2, 3, 4} var fields []string - for i, _ := range decSlice[:min(5, len(decSlice))] { - if decSlice[i] != "" { - fields = append(fields, fieldIndexToString[i]) + for i := range indices { + if len(decSlice) > indices[i] && decSlice[indices[i]] != "" { + fields = append(fields, fieldStrings[i]) + if indices[i] == 0 && oldPassword != "" { + fields = append(fields, "Old Password") + } } } @@ -52,10 +54,16 @@ func CopyMenu(realPath string) { fmt.Println() choice := front.InputMenuGen("Field to copy:", fields) switch fields[choice-1] { - case "Password": - choice = 0 case "Username": choice = 1 + case "Password": + choice = 0 + case "Old Password": + err := clip.CopyString(false, oldPassword) + if err != nil { + other.PrintError("Failed to copy old password to clipboard: "+err.Error(), global.ErrorClipboard) + } + continue case "TOTP Code": choice = 2 case "URL": diff --git a/src/cli/edit.go b/src/cli/edit.go index fd0f369..7275b3a 100644 --- a/src/cli/edit.go +++ b/src/cli/edit.go @@ -35,8 +35,10 @@ func EditEntryField(realPath string, hideSecrets bool, field int) { } // edit the field + var oldPassword string switch field { case 0: + oldPassword = decryptedEntry[field] decryptedEntry[field] = string(front.InputHidden("Password:")) case 1: decryptedEntry[field] = front.Input("Username:") @@ -59,9 +61,9 @@ func EditEntryField(realPath string, hideSecrets bool, field int) { // write and preview the modified entry if field == 0 { - writeEntryCLI(realPath, decryptedEntry, hideSecrets, true) + writeEntryCLI(realPath, decryptedEntry, hideSecrets, true, oldPassword) } else { - writeEntryCLI(realPath, decryptedEntry, hideSecrets, false) + writeEntryCLI(realPath, decryptedEntry, hideSecrets, false, oldPassword) } } @@ -74,10 +76,11 @@ func GenUpdate(realPath string, hideSecrets bool) { } // generate a new password + oldPassword := decryptedEntry[0] decryptedEntry[0] = inputPasswordGen() // write and preview the modified entry - writeEntryCLI(realPath, decryptedEntry, hideSecrets, true) + writeEntryCLI(realPath, decryptedEntry, hideSecrets, true, oldPassword) } // editNote uses the user-specified text editor to edit an existing note (or create a new one if baseNote is empty). diff --git a/src/cli/entryReader.go b/src/cli/entryReader.go index 51c3f01..1eee939 100644 --- a/src/cli/entryReader.go +++ b/src/cli/entryReader.go @@ -2,7 +2,6 @@ package cli import ( "fmt" - "os" "strings" "github.com/charmbracelet/glamour" @@ -78,8 +77,6 @@ fieldLoop: other.PrintError("Failed to sync entries: "+err.Error(), global.ErrorSyncProcess) } } - - os.Exit(0) } // EntryReaderDecrypt is a wrapper for EntryReader that first decrypts an RCW-wrapped file before sending it to EntryReader. diff --git a/src/cli/utilitiesMisc.go b/src/cli/utilitiesMisc.go index c9d5bfc..dd45933 100644 --- a/src/cli/utilitiesMisc.go +++ b/src/cli/utilitiesMisc.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "os" "github.com/rwinkhart/go-boilerplate/back" "github.com/rwinkhart/go-boilerplate/front" @@ -28,7 +29,7 @@ func inputPasswordGen() string { } // writeEntryCLI writes an entry to realPath and previews it (errors if no data is supplied). -func writeEntryCLI(realPath string, decSlice []string, hideSecrets bool, passwordIsNew bool) { +func writeEntryCLI(realPath string, decSlice []string, hideSecrets bool, passwordIsNew bool, oldPassword string) { if core.EntryIsNotEmpty(decSlice) { err := core.WriteEntry(realPath, decSlice, passwordIsNew) if err != nil { @@ -37,6 +38,8 @@ func writeEntryCLI(realPath string, decSlice []string, hideSecrets bool, passwor // preview the entry fmt.Println(back.AnsiBold + "\nEntry Preview:" + back.AnsiReset) EntryReader(decSlice, hideSecrets, true) + CopyMenu("", decSlice, oldPassword) + os.Exit(0) } else { other.PrintError("No data supplied for entry", back.ErrorTargetNotFound) }