diff --git a/internal/api/magic_link.go b/internal/api/magic_link.go index 791ce0327..36840da0a 100644 --- a/internal/api/magic_link.go +++ b/internal/api/magic_link.go @@ -83,7 +83,11 @@ func (a *API) MagicLink(w http.ResponseWriter, r *http.Request) error { if isNewUser { // User either doesn't exist or hasn't completed the signup process. // Sign them up with temporary password. - password := utilities.GeneratePassword(config.Password.RequiredCharacters, 33) + password, err := utilities.GeneratePassword(config.Password.RequiredCharacters, 33) + if err != nil { + // password generation must succeed + panic(err) + } signUpParams := &SignupParams{ Email: params.Email, diff --git a/internal/utilities/password.go b/internal/utilities/password.go index 52585caf3..6077b80aa 100644 --- a/internal/utilities/password.go +++ b/internal/utilities/password.go @@ -1,7 +1,8 @@ package utilities import ( - "math/rand" + "crypto/rand" + "math/big" "strings" ) @@ -12,29 +13,53 @@ func parseGroups(requiredChars []string) []string { return groups } -func GeneratePassword(requiredChars []string, length int) string { - +func GeneratePassword(requiredChars []string, length int) (string, error) { groups := parseGroups(requiredChars) passwordBuilder := strings.Builder{} + passwordBuilder.Grow(length) + // Add required characters for _, group := range groups { if len(group) > 0 { - passwordBuilder.WriteString(string(group[rand.Intn(len(group))])) + randomIndex, err := secureRandomInt(len(group)) + if err != nil { + return "", err + } + passwordBuilder.WriteByte(group[randomIndex]) } } // Define a default character set for random generation (if needed) allChars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + // Fill the rest of the password for passwordBuilder.Len() < length { - passwordBuilder.WriteString(string(allChars[rand.Intn(len(allChars))])) + randomIndex, err := secureRandomInt(len(allChars)) + if err != nil { + return "", err + } + passwordBuilder.WriteByte(allChars[randomIndex]) } - password := passwordBuilder.String() - passwordBytes := []byte(password) - rand.Shuffle(len(passwordBytes), func(i, j int) { + // Convert to byte slice for shuffling + passwordBytes := []byte(passwordBuilder.String()) + + // Secure shuffling + for i := len(passwordBytes) - 1; i > 0; i-- { + j, err := secureRandomInt(i + 1) + if err != nil { + return "", err + } passwordBytes[i], passwordBytes[j] = passwordBytes[j], passwordBytes[i] - }) + } + + return string(passwordBytes), nil +} - return string(passwordBytes) +func secureRandomInt(max int) (int, error) { + randomInt, err := rand.Int(rand.Reader, big.NewInt(int64(max))) + if err != nil { + return 0, err + } + return int(randomInt.Int64()), nil }