diff --git a/CHANGELOG.md b/CHANGELOG.md index cbbeafe..35d0fea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## To be Released +* chore(go): use go 1.22 +* Raise default length from 20 to 64 +* Allow `_` only as special character + ## 1.0.3 * chore(go): use go 1.20 diff --git a/README.md b/README.md index 936ed2f..2d63dc4 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Simple password generator in Go. Use `crypto/rand` ```go -// Passowrd of 20 characters +// Password of 64 characters gopassword.Generate() // Password of 42 characters diff --git a/go.mod b/go.mod index d02acfe..a80f1a0 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Scalingo/gopassword -go 1.20 +go 1.22 require github.com/stretchr/testify v1.9.0 diff --git a/gopassword.go b/gopassword.go index 7a89347..43c679a 100644 --- a/gopassword.go +++ b/gopassword.go @@ -3,12 +3,14 @@ package gopassword import ( "crypto/rand" "encoding/base64" - "fmt" "strings" ) +const defaultLength = 64 +const defaultSpecialChar = "_" + func Generate(n ...int) string { - length := 20 + length := defaultLength if len(n) > 0 { length = n[0] } @@ -23,15 +25,8 @@ func Generate(n ...int) string { randString := base64.StdEncoding.EncodeToString(randBytes) password := randString[:length] - password = strings.Replace(password, "+", "_", -1) - password = strings.Replace(password, "/", "-", -1) - - if password[0] == '-' { - password = fmt.Sprintf("_%s", password[1:]) - } - if password[length-1] == '-' { - password = fmt.Sprintf("%s_", password[:length-1]) - } + password = strings.ReplaceAll(password, "+", defaultSpecialChar) + password = strings.ReplaceAll(password, "/", defaultSpecialChar) return password } diff --git a/gopassword_test.go b/gopassword_test.go index d846ba6..879204a 100644 --- a/gopassword_test.go +++ b/gopassword_test.go @@ -1,6 +1,7 @@ package gopassword import ( + "regexp" "testing" "github.com/stretchr/testify/assert" @@ -8,22 +9,34 @@ import ( func TestGenerate(t *testing.T) { t.Run("When we want to generate a password", func(t *testing.T) { - t.Run("By default, it should be 20 characters", func(t *testing.T) { - assert.Len(t, Generate(), 20) + t.Run("By default, it must be 64 characters", func(t *testing.T) { + assert.Len(t, Generate(), 64) }) - t.Run("With an argument, the generated password should have its length", func(t *testing.T) { + t.Run("With an argument, the generated password must have its length", func(t *testing.T) { + assert.Len(t, Generate(10), 10) assert.Len(t, Generate(42), 42) + assert.Len(t, Generate(999), 999) }) - t.Run("With several arguments, only the first should be considered", func(t *testing.T) { + t.Run("With several arguments, only the first must be considered", func(t *testing.T) { assert.Len(t, Generate(10, 20, 30), 10) }) + + t.Run("It must contain only alphanumeric or underscore characters", func(t *testing.T) { + allowedCharacters := regexp.MustCompile("^[a-zA-Z0-9_]+$") + + // Try various times to ensure the result is not casual + for range 1000 { + passwd := Generate(99) + assert.True(t, allowedCharacters.MatchString(passwd)) + } + }) }) t.Run("Given a generated password", func(t *testing.T) { passwd := Generate(20) - t.Run("The character frequency should be low", func(t *testing.T) { + t.Run("The character frequency must be low", func(t *testing.T) { fm := frequencyMap(passwd) maxFreq := max(fm) assert.LessOrEqual(t, maxFreq, 3)