From 486bc4516ea7d1e8de6ca2a64231b54fa7bac9b8 Mon Sep 17 00:00:00 2001 From: Abhishek Shroff Date: Thu, 29 May 2025 00:24:29 +0530 Subject: [PATCH] [client] Password strength validation --- server/internal/command/config.defaults.yml | 5 ++ server/internal/core/user/config.go | 5 ++ server/internal/core/user/password.go | 59 +++++++++++++++++++++ server/internal/core/user/update.go | 4 ++ 4 files changed, 73 insertions(+) create mode 100644 server/internal/core/user/password.go diff --git a/server/internal/command/config.defaults.yml b/server/internal/command/config.defaults.yml index ca961b5d..85328179 100644 --- a/server/internal/command/config.defaults.yml +++ b/server/internal/command/config.defaults.yml @@ -12,6 +12,11 @@ storage: user: password: + length: 12 + lower: 1 + upper: 1 + numeric: 1 + symbols: 1 basedir: /home perimission: 0 diff --git a/server/internal/core/user/config.go b/server/internal/core/user/config.go index ce8973e6..4dba23cb 100644 --- a/server/internal/core/user/config.go +++ b/server/internal/core/user/config.go @@ -7,4 +7,9 @@ type Config struct { } type PasswordConfig struct { + Length int `koanf:"length"` + Lower int `koanf:"lower"` + Upper int `koanf:"upper"` + Numeric int `koanf:"numeric"` + Symbols int `koanf:"symbols"` } diff --git a/server/internal/core/user/password.go b/server/internal/core/user/password.go new file mode 100644 index 00000000..72424dbf --- /dev/null +++ b/server/internal/core/user/password.go @@ -0,0 +1,59 @@ +package user + +import ( + "net/http" + "strconv" + + "github.com/shroff/phylum/server/internal/core/errors" +) + +type charType int + +const ( + charTypeOther charType = iota + charTypeLower + charTypeUpper + charTypeNumeric + charTypeSymbol +) + +var charTypes = map[rune]charType{} + +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) < Cfg.Password.Length { + return errors.NewError(http.StatusBadRequest, "password_invalid", "Must be at least "+strconv.Itoa(Cfg.Password.Length)+" characters long.") + } + count := map[charType]int{} + for _, c := range password { + count[charTypes[c]]++ + } + + if count[charTypeLower] < Cfg.Password.Lower { + return errors.NewError(http.StatusBadRequest, "password_invalid", "Must have at least "+strconv.Itoa(Cfg.Password.Lower)+" lower case.") + } + if count[charTypeUpper] < Cfg.Password.Upper { + return errors.NewError(http.StatusBadRequest, "password_invalid", "Must have at least "+strconv.Itoa(Cfg.Password.Upper)+" upper case.") + } + if count[charTypeNumeric] < Cfg.Password.Numeric { + return errors.NewError(http.StatusBadRequest, "password_invalid", "Must have at least "+strconv.Itoa(Cfg.Password.Numeric)+" numeric.") + } + if count[charTypeSymbol] < Cfg.Password.Symbols { + return errors.NewError(http.StatusBadRequest, "password_invalid", "Must have at least "+strconv.Itoa(Cfg.Password.Symbols)+" symbols.") + } + return nil +} diff --git a/server/internal/core/user/update.go b/server/internal/core/user/update.go index 694c4409..c10174f4 100644 --- a/server/internal/core/user/update.go +++ b/server/internal/core/user/update.go @@ -22,6 +22,10 @@ func (m manager) UpdateUserName(user User, name string) error { } func (m manager) UpdateUserPassword(user User, password string) error { + if err := checkPasswordStrength(password); err != nil { + return err + } + const q = "UPDATE users SET password_hash = $2::TEXT, modified = NOW() WHERE id = $1::INT" if hash, err := crypt.Generate(password); err != nil { return err