3
0

Add support for min upper/lower-case password constraints

This commit is contained in:
Denis Arh
2022-08-01 16:37:46 +02:00
parent 54060f5ff8
commit 507e5cd72f
3 changed files with 175 additions and 47 deletions

View File

@@ -16,7 +16,6 @@ import (
"github.com/cortezaproject/corteza-server/system/types"
"github.com/dgryski/dgoogauth"
"golang.org/x/crypto/bcrypt"
"math"
rand2 "math/rand"
"regexp"
"sort"
@@ -528,39 +527,7 @@ func (svc *auth) hashPassword(password string) (hash []byte, err error) {
}
func (svc *auth) CheckPasswordStrength(password string) bool {
pwdL := len(password)
// Ignore defined password constraints
if !svc.settings.Auth.Internal.PasswordConstraints.PasswordSecurity {
return true
}
// Check the password length
minL := math.Max(float64(passwordMinLength), float64(svc.settings.Auth.Internal.PasswordConstraints.MinLength))
if pwdL < int(minL) || pwdL > passwordMaxLength {
return false
}
// Check special constraints
// - numeric characters
count := svc.settings.Auth.Internal.PasswordConstraints.MinNumCount
if count > 0 {
rr := regexp.MustCompile("[0-9]")
if uint(len(rr.FindAllStringIndex(password, -1))) < count {
return false
}
}
// - special characters
count = svc.settings.Auth.Internal.PasswordConstraints.MinSpecialCount
if count > 0 {
rr := regexp.MustCompile("[^0-9a-zA-Z]")
if uint(len(rr.FindAllStringIndex(password, -1))) < count {
return false
}
}
return true
return checkPasswordStrength(password, svc.settings.Auth.Internal.PasswordConstraints)
}
// SetPasswordCredentials (soft) deletes old password entry and creates a new entry with new password on every change
@@ -1148,3 +1115,65 @@ func credentialsFilter(cc []*types.Credential, limit int, mm ...func(*types.Cred
return
}
func checkPasswordStrength(password string, pc types.PasswordConstraints) bool {
var (
length = len(password)
re *regexp.Regexp
mt [][]int
)
// Always check system constraints
if length < passwordMinLength || length > passwordMaxLength {
return false
}
// Ignore defined password constraints
if !pc.PasswordSecurity {
return true
}
// Check the password length
if length < int(pc.MinLength) {
return false
}
// Check special constraints
// - numeric characters
if count := int(pc.MinNumCount); count > 0 {
re = regexp.MustCompile("[0-9]")
mt = re.FindAllStringIndex(password, -1)
if len(mt) < count {
return false
}
}
// Check for lowercase characters
if count := int(pc.MinLowerCase); count > 0 {
re = regexp.MustCompile("[a-z]")
mt = re.FindAllStringIndex(password, -1)
if len(mt) < count {
return false
}
}
// Check for upper-case characters
if count := int(pc.MinUpperCase); count > 0 {
re = regexp.MustCompile("[A-Z]")
mt = re.FindAllStringIndex(password, -1)
if len(mt) < count {
return false
}
}
// - special characters
if count := int(pc.MinSpecialCount); count > 0 {
re = regexp.MustCompile("[^0-9a-zA-Z]")
mt = re.FindAllStringIndex(password, -1)
if len(mt) < count {
return false
}
}
return true
}

View File

@@ -4,6 +4,7 @@ import (
"github.com/cortezaproject/corteza-server/system/types"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/bcrypt"
"strings"
"testing"
"time"
)
@@ -196,3 +197,93 @@ func TestValidateToken(t *testing.T) {
})
}
}
func Test_checkPasswordStrength(t *testing.T) {
tests := []struct {
name string
pc types.PasswordConstraints
password string
want bool
}{
{
name: "empty",
pc: types.PasswordConstraints{},
password: "",
want: false,
},
{
name: "sys too short",
pc: types.PasswordConstraints{},
password: strings.Repeat("A", passwordMinLength-1),
want: false,
},
{
name: "sys too long",
pc: types.PasswordConstraints{},
password: strings.Repeat("A", passwordMaxLength+1),
want: false,
},
{
name: "too short",
pc: types.PasswordConstraints{MinLength: 10},
password: "123456789",
want: false,
},
{
name: "uc valid",
pc: types.PasswordConstraints{MinUpperCase: 2},
password: "aaaAAAaaa",
want: true,
},
{
name: "uc invalid",
pc: types.PasswordConstraints{MinUpperCase: 2},
password: "aaaaaaaa",
want: false,
},
{
name: "lc valid",
pc: types.PasswordConstraints{MinLowerCase: 2},
password: "AAAaaAAAA",
want: true,
},
{
name: "lc invalid",
pc: types.PasswordConstraints{MinLowerCase: 2},
password: "AAAAAAAAA",
want: false,
},
{
name: "digit valid",
pc: types.PasswordConstraints{MinNumCount: 2},
password: "AAA12AAAA",
want: true,
},
{
name: "digit invalid",
pc: types.PasswordConstraints{MinNumCount: 2},
password: "AAAaaAAAA",
want: false,
},
{
name: "special valid",
pc: types.PasswordConstraints{MinSpecialCount: 2},
password: "AAA!!AAAA",
want: true,
},
{
name: "special invalid",
pc: types.PasswordConstraints{MinSpecialCount: 2},
password: "AAAaaAAAA",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.pc.PasswordSecurity = true
if got := checkPasswordStrength(tt.password, tt.pc); got != tt.want {
t.Errorf("checkPasswordStrength() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -69,19 +69,7 @@ type (
// If only one ext. provider is enabled, user is automatically redirected there
SplitCredentialsCheck bool `json:"-" kv:"split-credentials-check"`
PasswordConstraints struct {
// Should the environment not enforce the constraints
PasswordSecurity bool `kv:"-" json:"passwordSecurity"`
// The min password length
MinLength uint `kv:"min-length"`
// The min number of numeric characters
MinNumCount uint `kv:"min-num-count"`
// The min number of special characters
MinSpecialCount uint `kv:"min-special-count"`
} `kv:"password-constraints" json:"passwordConstraints"`
PasswordConstraints PasswordConstraints `kv:"password-constraints" json:"passwordConstraints"`
} `json:"internal"`
External struct {
@@ -385,6 +373,26 @@ type (
TlsInsecure bool `json:"tlsInsecure"`
TlsServerName string `json:"tlsServerName"`
}
PasswordConstraints struct {
// Should the environment not enforce the constraints
PasswordSecurity bool `kv:"-" json:"passwordSecurity"`
// The min password length
MinLength uint `kv:"min-length"`
// Minimum number of uppercase letters in password
MinUpperCase uint `kv:"min-upper-case"`
// Minimum number of lowercase letters in password
MinLowerCase uint `kv:"min-lower-case"`
// The min number of numeric characters
MinNumCount uint `kv:"min-num-count"`
// The min number of special characters
MinSpecialCount uint `kv:"min-special-count"`
}
)
// WithDefaults sets defaults on copy (!!) of settings