From 1e1a26b09897fb330063f068049d439c3de01fcf Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Thu, 7 Mar 2024 16:04:46 -0500 Subject: [PATCH] Implement password generation on new entries --- main.go | 33 +++++++++++++++-------- src/cli/add.go | 15 ++++++++--- src/cli/utilitiesMisc.go | 20 +++++++++++--- src/offline/utilitiesMisc.go | 51 ++++++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 17 deletions(-) diff --git a/main.go b/main.go index 2d90002..d3aff66 100644 --- a/main.go +++ b/main.go @@ -35,7 +35,8 @@ func main() { cli.EntryReaderShortcut(targetLocation, false) case "shear": offline.Shear(targetLocation) - case "gen": // TODO offline.Gen(targetLocation), exit after run + case "gen": + cli.AddEntry(targetLocation, true, 1) case "copy": cli.HelpCopy() case "edit": @@ -87,30 +88,40 @@ func main() { switch args[3] { case "password", "-p": if argsCount == 4 { - cli.AddEntry(targetLocation, true, false) + cli.AddEntry(targetLocation, true, 0) } else { switch args[4] { case "show", "-s": - cli.AddEntry(targetLocation, false, false) + cli.AddEntry(targetLocation, false, 0) default: - cli.AddEntry(targetLocation, true, false) + cli.AddEntry(targetLocation, true, 0) } } case "note", "-n": - cli.AddEntry(targetLocation, true, true) + cli.AddEntry(targetLocation, true, 2) case "folder", "-f": offline.AddFolder(targetLocation) default: cli.HelpAdd() } case "gen": - var field rune - field = field // TODO temporary to avoid unused variable error - switch args[3] { - case "update", "-u": // TODO offline.GenUpdate(targetLocation), exit after run - default: - cli.HelpGen() + if argsCount == 4 { + switch args[3] { + case "show", "-s": + cli.AddEntry(targetLocation, false, 1) + case "update", "-u": // TODO offline.GenUpdate(targetLocation), exit after run + default: + cli.HelpGen() + } + } else if args[3] == "update" || args[3] == "-u" { + switch args[4] { + case "show", "-s": + cli.AddEntry(targetLocation, false, 1) + default: + cli.AddEntry(targetLocation, true, 1) + } } + cli.HelpGen() default: cli.HelpMain() } diff --git a/src/cli/add.go b/src/cli/add.go index 9566b32..bb02e76 100644 --- a/src/cli/add.go +++ b/src/cli/add.go @@ -7,7 +7,8 @@ import ( ) // AddEntry creates a new entry at targetLocation by taking user input via CLI prompts -func AddEntry(targetLocation string, hidePassword bool, isNote bool) { +// entryType: 0 = standard (password), 1 = auto-generated password, 2 = note +func AddEntry(targetLocation string, hidePassword bool, entryType uint8) { _, isAccessible := offline.TargetIsFile(targetLocation, false, 0) if isAccessible { fmt.Println(offline.AnsiError + "Target location already exists" + offline.AnsiReset) @@ -16,9 +17,17 @@ func AddEntry(targetLocation string, hidePassword bool, isNote bool) { var unencryptedEntry []string - if !isNote { + if entryType < 2 { username := input("Username:") - password := inputHidden("Password:") + + // determine whether to generate the password + var password string + if entryType == 0 { + password = inputHidden("Password:") + } else { + password = offline.StringGen(inputInt("Password length:"), inputBinary("Generate a complex (special characters) password?"), 0.2) + } + url := input("URL:") if inputBinary("Add a note to this entry?") { note := newNote() diff --git a/src/cli/utilitiesMisc.go b/src/cli/utilitiesMisc.go index ba137bb..7fb317d 100644 --- a/src/cli/utilitiesMisc.go +++ b/src/cli/utilitiesMisc.go @@ -12,9 +12,9 @@ import ( // input prompts the user for input and returns the input as a string func input(prompt string) string { fmt.Print("\n" + prompt + " ") - var input string - fmt.Scanln(&input) - return input + var userInput string + fmt.Scanln(&userInput) + return userInput } // inputHidden prompts the user for input and returns the input as a string, hiding the input from the terminal @@ -26,6 +26,20 @@ func inputHidden(prompt string) string { return password } +// inputInt prompts the user for input and returns the input as an integer +func inputInt(prompt string) int { + + // loop until a valid integer is entered + for { + fmt.Print("\n" + prompt + " ") + var userInput int + _, err := fmt.Scanln(&userInput) + if err == nil { + return userInput + } + } +} + // inputBinary prompts the user with a yes/no question and returns the response as a boolean func inputBinary(prompt string) bool { reader := bufio.NewReader(os.Stdin) diff --git a/src/offline/utilitiesMisc.go b/src/offline/utilitiesMisc.go index d9c37b6..3663eb7 100644 --- a/src/offline/utilitiesMisc.go +++ b/src/offline/utilitiesMisc.go @@ -1,10 +1,14 @@ package offline import ( + "crypto/rand" "fmt" "io" + "math" + "math/big" "os" "os/exec" + "strings" ) // TargetIsFile TargetStatusCheck checks if the targetLocation is a file, directory, or is inaccessible @@ -63,3 +67,50 @@ func RemoveTrailingEmptyStrings(slice []string) []string { } return []string{} } + +// StringGen generates a random string of a specified length and complexity +// complexity: minimum percentage of special characters to be returned in the generated string (only impacts complex strings) +func StringGen(length int, complex bool, complexity float64) string { + var extendedCharset string // hold extended character set used for complex strings + var actualSpecialChars int // track the number of special characters in the generated string + var minSpecialChars int // track the minimum number of special characters to accept + + charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" // default character set used for all strings + if complex { + minSpecialChars = int(math.Round(float64(length) * complexity)) // determine minimum number of special characters to accept + extendedCharset = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" + charset = charset + extendedCharset + } else { + extendedCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + } + + for { + // generate a random string + result := make([]byte, length) + for i := range result { + val, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset)))) + result[i] = charset[val.Int64()] + } + + // return early if the string is not complex + if !complex { + return string(result) + } + + // count the number of special characters in the generated string + for _, char := range string(result) { + if strings.ContainsRune(extendedCharset, char) { + actualSpecialChars++ + } + } + + // return the generated string if it contains enough special characters + if actualSpecialChars >= minSpecialChars { + return string(result) + } + + // reset special character counter + fmt.Println("Regenerating string until desired complexity is achieved...") + actualSpecialChars = 0 + } +}