diff --git a/lib/generator.go b/lib/generator.go index b616e4a..b8bc13e 100644 --- a/lib/generator.go +++ b/lib/generator.go @@ -1,6 +1,8 @@ +// Package securepassword implements a password generator and check. package securepassword // import "github.com/Luzifer/password/lib" import ( + "errors" "fmt" "math/rand" "regexp" @@ -8,12 +10,21 @@ import ( "time" ) +// SecurePassword provides methods for generating secure passwords and +// checking the security requirements of passwords type SecurePassword struct { characterTables map[string]string insecurePattern []string badCharacters []string } +var ( + // ErrLengthTooLow represents an error thrown if the password will + // never be able match the security considerations in this package + ErrLengthTooLow = errors.New("Passwords with a length lower than 4 will never meet the security requirements") +) + +// NewSecurePassword initializes a new SecurePassword generator func NewSecurePassword() *SecurePassword { return &SecurePassword{ characterTables: map[string]string{ @@ -36,7 +47,16 @@ func NewSecurePassword() *SecurePassword { } } -func (s *SecurePassword) GeneratePassword(length int, special bool) string { +// GeneratePassword generates a new password with a given length and +// optional special characters in it. The password is automatically +// checked against CheckPasswordSecurity in order to only deliver secure +// passwords. +func (s *SecurePassword) GeneratePassword(length int, special bool) (string, error) { + // Sanity check + if length < 4 { + return "", ErrLengthTooLow + } + characterTable := strings.Join([]string{ s.characterTables["simple"], strings.ToUpper(s.characterTables["simple"]), @@ -60,9 +80,14 @@ func (s *SecurePassword) GeneratePassword(length int, special bool) string { password = "" } } - return password + return password, nil } +// CheckPasswordSecurity executes three checks to ensure the passwords +// meet the security considerations in this package: +// - The password may not contain pattern found on the keyboard or in alphabet +// - The password must have 3 or 4 different character groups in it +// - The password may not have repeating characters func (s *SecurePassword) CheckPasswordSecurity(password string, needsSpecialCharacters bool) bool { return !s.hasInsecurePattern(password) && s.matchesBasicSecurity(password, needsSpecialCharacters) && diff --git a/lib/generator_test.go b/lib/generator_test.go index 30e55e1..ac50c13 100644 --- a/lib/generator_test.go +++ b/lib/generator_test.go @@ -80,6 +80,15 @@ func TestSecurePasswordWithSpecialCharacter(t *testing.T) { } } +func TestImpossiblePasswords(t *testing.T) { + for i := 0; i < 4; i++ { + _, err := NewSecurePassword().GeneratePassword(i, false) + if err != ErrLengthTooLow { + t.Errorf("Password with a length of %d did not throw as ErrLengthTooLow error", i) + } + } +} + func BenchmarkGeneratePasswords8Char(b *testing.B) { pwd := NewSecurePassword() for i := 0; i < b.N; i++ { diff --git a/main.go b/main.go index 4d4887d..a164019 100644 --- a/main.go +++ b/main.go @@ -65,12 +65,18 @@ func startAPIServer(c *cli.Context) { } func printPassword(c *cli.Context) { - if c.Int("length") < 5 { - fmt.Println("Passwords with fewer than 5 characters are too insecure.") + password, err := pwd.GeneratePassword(c.Int("length"), c.Bool("special")) + if err != nil { + switch { + case err == securepassword.ErrLengthTooLow: + fmt.Println("The password has to be more than 4 characters long to meet the security considerations") + default: + fmt.Println("An unknown error occured") + } os.Exit(1) } - fmt.Println(pwd.GeneratePassword(c.Int("length"), c.Bool("special"))) + fmt.Println(password) } func handleAPIGetPasswordv1(res http.ResponseWriter, r *http.Request) { @@ -80,12 +86,14 @@ func handleAPIGetPasswordv1(res http.ResponseWriter, r *http.Request) { } special := r.URL.Query().Get("special") == "true" - if length > 128 || length < 5 { - http.Error(res, "Please do not use length with more than 128 or fewer than 5 characters!", http.StatusNotAcceptable) + if length > 128 || length < 4 { + http.Error(res, "Please do not use length with more than 128 or fewer than 4 characters!", http.StatusNotAcceptable) return } + password, err := pwd.GeneratePassword(length, special) + res.Header().Add("Content-Type", "text/plain") res.Header().Add("Cache-Control", "no-cache") - res.Write([]byte(pwd.GeneratePassword(length, special))) + res.Write([]byte(password)) }