[server] Single error message for all unmet password requirements [#31]

This commit is contained in:
Abhishek Shroff
2025-07-19 11:09:34 +05:30
parent 3d7b58806b
commit 6cf0d0118b
3 changed files with 89 additions and 64 deletions
+3
View File
@@ -27,6 +27,9 @@ func handlePasswordChangeRoute(c *gin.Context) {
if errors.Is(err, auth.ErrCredentialsInvalid) {
panic(core.NewError(http.StatusBadRequest, "password_incorrect", "current password is incorrect"))
}
if e, ok := err.(*auth.PasswordRequirementsError); ok {
panic(core.NewError(http.StatusBadRequest, "new_password_invalid", e.Error()))
}
panic(err)
}
@@ -0,0 +1,86 @@
package auth
import (
"strconv"
"strings"
)
type charType int
const (
charTypeOther charType = iota
charTypeLower
charTypeUpper
charTypeNumeric
charTypeSymbol
)
var charTypes = map[rune]charType{}
type PasswordRequirementsError struct {
minimumLength int
missingRequirements []string
}
func (e PasswordRequirementsError) Error() string {
sb := strings.Builder{}
sb.WriteString("password must")
if e.missingRequirements != nil {
sb.WriteString(" have at least " + strings.Join(e.missingRequirements, ", "))
if e.minimumLength != 0 {
sb.WriteString(", and")
}
}
if e.minimumLength != 0 {
sb.WriteString(" be at least " + strconv.Itoa(e.minimumLength) + " characters long")
}
return sb.String()
}
func init() {
for _, c := range "abcdefghijklmnopqrstuvwxyz" {
charTypes[c] = charTypeLower
}
for _, c := range "ABCDEFGHIJKLMNOPQRSTUVWXYZ" {
charTypes[c] = charTypeUpper
}
for _, c := range "0123456789" {
charTypes[c] = charTypeNumeric
}
for _, c := range "`~!@#$%^&*()-_=+[]{}\\|;:'\",.<>/?" {
charTypes[c] = charTypeSymbol
}
}
func checkPasswordStrength(password string) error {
var minimumLength int
var errors []string
if len(password) < passwordRequirements.Length {
minimumLength = passwordRequirements.Length
}
count := map[charType]int{}
for _, c := range password {
count[charTypes[c]]++
}
if count[charTypeLower] < passwordRequirements.Lower {
errors = append(errors, strconv.Itoa(passwordRequirements.Lower)+" lower case characters")
}
if count[charTypeUpper] < passwordRequirements.Upper {
errors = append(errors, strconv.Itoa(passwordRequirements.Upper)+" upper case characters")
}
if count[charTypeNumeric] < passwordRequirements.Numeric {
errors = append(errors, strconv.Itoa(passwordRequirements.Numeric)+" numbers")
}
if count[charTypeSymbol] < passwordRequirements.Symbols {
errors = append(errors, strconv.Itoa(passwordRequirements.Symbols)+" symbols")
}
if minimumLength != 0 || errors != nil {
return &PasswordRequirementsError{
minimumLength: minimumLength,
missingRequirements: errors,
}
}
return nil
}
-64
View File
@@ -1,64 +0,0 @@
package auth
import (
"strconv"
)
type charType int
const (
charTypeOther charType = iota
charTypeLower
charTypeUpper
charTypeNumeric
charTypeSymbol
)
var charTypes = map[rune]charType{}
type PasswordStrengthError struct {
Reason string
}
func (e PasswordStrengthError) Error() string {
return e.Reason
}
func init() {
for _, c := range "abcdefghijklmnopqrstuvwxyz" {
charTypes[c] = charTypeLower
}
for _, c := range "ABCDEFGHIJKLMNOPQRSTUVWXYZ" {
charTypes[c] = charTypeUpper
}
for _, c := range "0123456789" {
charTypes[c] = charTypeNumeric
}
for _, c := range "`~!@#$%^&*()-_=+[]{}\\|;:'\",.<>/?" {
charTypes[c] = charTypeSymbol
}
}
func checkPasswordStrength(password string) error {
if len(password) < passwordRequirements.Length {
return &PasswordStrengthError{Reason: "password must be at least " + strconv.Itoa(passwordRequirements.Length) + " characters long."}
}
count := map[charType]int{}
for _, c := range password {
count[charTypes[c]]++
}
if count[charTypeLower] < passwordRequirements.Lower {
return &PasswordStrengthError{Reason: "password must have at least " + strconv.Itoa(passwordRequirements.Lower) + " lower case."}
}
if count[charTypeUpper] < passwordRequirements.Upper {
return &PasswordStrengthError{Reason: "password must have at least " + strconv.Itoa(passwordRequirements.Upper) + " upper case."}
}
if count[charTypeNumeric] < passwordRequirements.Numeric {
return &PasswordStrengthError{Reason: "password must have at least " + strconv.Itoa(passwordRequirements.Numeric) + " numeric."}
}
if count[charTypeSymbol] < passwordRequirements.Symbols {
return &PasswordStrengthError{Reason: "password must have at least " + strconv.Itoa(passwordRequirements.Symbols) + " symbols."}
}
return nil
}