diff --git a/api/openapi.yml b/api/openapi.yml
index 62c8ee3b4..10ec36b9d 100644
--- a/api/openapi.yml
+++ b/api/openapi.yml
@@ -730,6 +730,9 @@ components:
max_length:
type: integer
description: The maximum password length when using the standard mode.
+ min_score:
+ type: integer
+ description: The minimum password score when using the zxcvbn mode.
require_uppercase:
type: boolean
description: If uppercase characters are required when using the standard mode.
diff --git a/config.template.yml b/config.template.yml
index 1cc564f53..1739cec81 100644
--- a/config.template.yml
+++ b/config.template.yml
@@ -365,6 +365,9 @@ password_policy:
zxcvbn:
enabled: false
+ ## Configures the minimum score allowed.
+ min_score: 3
+
##
## Access Control Configuration
##
diff --git a/docs/configuration/password_policy.md b/docs/configuration/password_policy.md
index 0661d7222..71f483a5f 100644
--- a/docs/configuration/password_policy.md
+++ b/docs/configuration/password_policy.md
@@ -23,6 +23,7 @@ password_policy:
require_special: false
zxcvbn:
enabled: false
+ min_score: 3
```
## Options
@@ -39,8 +40,10 @@ This section allows you to enable standard security policies.
#### enabled
-type: bool
+type: boolean
{: .label .label-config .label-purple }
+default: false
+{: .label .label-config .label-blue }
required: no
{: .label .label-config .label-green }
@@ -73,8 +76,10 @@ Determines the maximum allowed password length.
#### require_uppercase
-type: bool
+type: boolean
{: .label .label-config .label-purple }
+default: false
+{: .label .label-config .label-blue }
required: no
{: .label .label-config .label-green }
@@ -83,8 +88,10 @@ Indicates that at least one UPPERCASE letter must be provided as part of the pas
#### require_lowercase
-type: bool
+type: boolean
{: .label .label-config .label-purple }
+default: false
+{: .label .label-config .label-blue }
required: no
{: .label .label-config .label-green }
@@ -93,8 +100,10 @@ Indicates that at least one lowercase letter must be provided as part of the pas
#### require_number
-type: bool
+type: boolean
{: .label .label-config .label-purple }
+default: false
+{: .label .label-config .label-blue }
required: no
{: .label .label-config .label-green }
@@ -103,8 +112,10 @@ Indicates that at least one number must be provided as part of the password.
#### require_special
-type: bool
+type: boolean
{: .label .label-config .label-purple }
+default: false
+{: .label .label-config .label-blue }
required: no
{: .label .label-config .label-green }
@@ -115,13 +126,12 @@ Indicates that at least one special character must be provided as part of the pa
This password policy enables advanced password strength metering, using [zxcvbn](https://github.com/dropbox/zxcvbn).
-Note that this password policy do not restrict the user's entry it just gives the user feedback as to how strong their
-password is.
-
#### enabled
-type: bool
+type: boolean
{: .label .label-config .label-purple }
+default: false
+{: .label .label-config .label-blue }
required: no
{: .label .label-config .label-green }
@@ -130,4 +140,22 @@ _**Important Note:** only one password policy can be applied at a time._
Enables zxcvbn password policy.
+#### min_score
+
+type: integer
+{: .label .label-config .label-purple }
+default: 3
+{: .label .label-config .label-blue }
+required: no
+{: .label .label-config .label-green }
+
+Configures the minimum zxcvbn score allowed for new passwords. There are 5 levels in the zxcvbn score system (taken from [github.com/dropbox/zxcvbn](https://github.com/dropbox/zxcvbn#usage)):
+
+- score 0: too guessable: risky password (guesses < 10^3)
+- score 1: very guessable: protection from throttled online attacks (guesses < 10^6)
+- score 2: somewhat guessable: protection from unthrottled online attacks. (guesses < 10^8)
+- score 3: safely unguessable: moderate protection from offline slow-hash scenario. (guesses < 10^10)
+- score 4: very unguessable: strong protection from offline slow-hash scenario. (guesses >= 10^10)
+
+We do not allow score 0, if you set the `min_score` value to 0 instead the default will be chosen.
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 97ef1e098..50cda5e9e 100644
--- a/go.mod
+++ b/go.mod
@@ -31,6 +31,7 @@ require (
github.com/spf13/cobra v1.4.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.1
+ github.com/trustelem/zxcvbn v1.0.1
github.com/valyala/fasthttp v1.35.0
golang.org/x/text v0.3.7
gopkg.in/square/go-jose.v2 v2.6.0
@@ -45,6 +46,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+ github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
@@ -85,6 +87,7 @@ require (
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
+ github.com/test-go/testify v1.1.4 // indirect
github.com/tinylib/msgp v1.1.6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
diff --git a/go.sum b/go.sum
index 9caf9f462..5d3add6d6 100644
--- a/go.sum
+++ b/go.sum
@@ -160,6 +160,8 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
+github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v17.12.0-ce-rc1.0.20201201034508-7d75c1d40d88+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
@@ -1243,6 +1245,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
+github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
github.com/tidwall/gjson v1.11.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
@@ -1255,6 +1259,8 @@ github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/trustelem/zxcvbn v1.0.1 h1:mp4JFtzdDYGj9WYSD3KQSkwwUumWNFzXaAjckaTYpsc=
+github.com/trustelem/zxcvbn v1.0.1/go.mod h1:zonUyKeh7sw6psPf/e3DtRqkRyZvAbOfjNz/aO7YQ5s=
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-client-go v2.22.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
diff --git a/internal/commands/helpers.go b/internal/commands/helpers.go
index d5c3990bb..178dde8ef 100644
--- a/internal/commands/helpers.go
+++ b/internal/commands/helpers.go
@@ -71,7 +71,7 @@ func getProviders() (providers middlewares.Providers, warnings []error, errors [
totpProvider := totp.NewTimeBasedProvider(config.TOTP)
- passwordPolicyProvider := middlewares.NewPasswordPolicyProvider(config.PasswordPolicy)
+ ppolicyProvider := middlewares.NewPasswordPolicyProvider(config.PasswordPolicy)
return middlewares.Providers{
Authorizer: authorizer,
@@ -83,6 +83,6 @@ func getProviders() (providers middlewares.Providers, warnings []error, errors [
Notifier: notifier,
SessionProvider: sessionProvider,
TOTP: totpProvider,
- PasswordPolicy: passwordPolicyProvider,
+ PasswordPolicy: ppolicyProvider,
}, warnings, errors
}
diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml
index 1cc564f53..1739cec81 100644
--- a/internal/configuration/config.template.yml
+++ b/internal/configuration/config.template.yml
@@ -365,6 +365,9 @@ password_policy:
zxcvbn:
enabled: false
+ ## Configures the minimum score allowed.
+ min_score: 3
+
##
## Access Control Configuration
##
diff --git a/internal/configuration/schema/password_policy.go b/internal/configuration/schema/password_policy.go
index 7b247725a..303618fa9 100644
--- a/internal/configuration/schema/password_policy.go
+++ b/internal/configuration/schema/password_policy.go
@@ -13,7 +13,8 @@ type PasswordPolicyStandardParams struct {
// PasswordPolicyZXCVBNParams represents the configuration related to ZXCVBN parameters of password policy.
type PasswordPolicyZXCVBNParams struct {
- Enabled bool `koanf:"enabled"`
+ Enabled bool `koanf:"enabled"`
+ MinScore int `koanf:"min_score"`
}
// PasswordPolicyConfiguration represents the configuration related to password policy.
@@ -30,6 +31,7 @@ var DefaultPasswordPolicyConfiguration = PasswordPolicyConfiguration{
MaxLength: 0,
},
ZXCVBN: PasswordPolicyZXCVBNParams{
- Enabled: false,
+ Enabled: false,
+ MinScore: 3,
},
}
diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go
index 02f48f875..9eb02cac0 100644
--- a/internal/configuration/validator/const.go
+++ b/internal/configuration/validator/const.go
@@ -244,8 +244,9 @@ const (
)
const (
- errFmtPasswordPolicyMinLengthNotGreaterThanZero = "password_policy: standard: option 'min_length' must be greater than 0 but is configured as %d"
- errPasswordPolicyMultipleDefined = "password_policy: only a single password policy mechanism can be specified"
+ errPasswordPolicyMultipleDefined = "password_policy: only a single password policy mechanism can be specified"
+ errFmtPasswordPolicyStandardMinLengthNotGreaterThanZero = "password_policy: standard: option 'min_length' must be greater than 0 but is configured as %d"
+ errFmtPasswordPolicyZXCVBNMinScoreInvalid = "password_policy: zxcvbn: option 'min_score' is invalid: must be between 1 and 4 but it's configured as %d"
)
// Error constants.
@@ -524,6 +525,7 @@ var ValidKeys = []string{
"password_policy.standard.require_number",
"password_policy.standard.require_special",
"password_policy.zxcvbn.enabled",
+ "password_policy.zxcvbn.min_score",
}
var replacedKeys = map[string]string{
diff --git a/internal/configuration/validator/keys_test.go b/internal/configuration/validator/keys_test.go
index 57fd0e06b..23153666b 100644
--- a/internal/configuration/validator/keys_test.go
+++ b/internal/configuration/validator/keys_test.go
@@ -54,7 +54,7 @@ func TestAllSpecificErrorKeys(t *testing.T) {
var uniqueValues []string
- // Setup configKeys and uniqueValues expectedErrs.
+ // Setup configKeys and uniqueValues expected.
for key, value := range specificErrorKeys {
configKeys = append(configKeys, key)
diff --git a/internal/configuration/validator/password_policy.go b/internal/configuration/validator/password_policy.go
index 0e7408f6b..0951c4bf5 100644
--- a/internal/configuration/validator/password_policy.go
+++ b/internal/configuration/validator/password_policy.go
@@ -17,11 +17,20 @@ func ValidatePasswordPolicy(config *schema.PasswordPolicyConfiguration, validato
if config.Standard.MinLength == 0 {
config.Standard.MinLength = schema.DefaultPasswordPolicyConfiguration.Standard.MinLength
} else if config.Standard.MinLength < 0 {
- validator.Push(fmt.Errorf(errFmtPasswordPolicyMinLengthNotGreaterThanZero, config.Standard.MinLength))
+ validator.Push(fmt.Errorf(errFmtPasswordPolicyStandardMinLengthNotGreaterThanZero, config.Standard.MinLength))
}
if config.Standard.MaxLength == 0 {
config.Standard.MaxLength = schema.DefaultPasswordPolicyConfiguration.Standard.MaxLength
}
}
+
+ if config.ZXCVBN.Enabled {
+ switch {
+ case config.ZXCVBN.MinScore == 0:
+ config.ZXCVBN.MinScore = schema.DefaultPasswordPolicyConfiguration.ZXCVBN.MinScore
+ case config.ZXCVBN.MinScore < 0, config.ZXCVBN.MinScore > 4:
+ validator.Push(fmt.Errorf(errFmtPasswordPolicyZXCVBNMinScoreInvalid, config.ZXCVBN.MinScore))
+ }
+ }
}
diff --git a/internal/configuration/validator/password_policy_test.go b/internal/configuration/validator/password_policy_test.go
index 5f341672d..3d27f08d6 100644
--- a/internal/configuration/validator/password_policy_test.go
+++ b/internal/configuration/validator/password_policy_test.go
@@ -33,7 +33,8 @@ func TestValidatePasswordPolicy(t *testing.T) {
MinLength: -1,
},
ZXCVBN: schema.PasswordPolicyZXCVBNParams{
- Enabled: true,
+ Enabled: true,
+ MinScore: 3,
},
},
expectedErrs: []string{
@@ -65,7 +66,8 @@ func TestValidatePasswordPolicy(t *testing.T) {
},
expected: &schema.PasswordPolicyConfiguration{
ZXCVBN: schema.PasswordPolicyZXCVBNParams{
- Enabled: true,
+ Enabled: true,
+ MinScore: 3,
},
},
},
@@ -84,6 +86,42 @@ func TestValidatePasswordPolicy(t *testing.T) {
},
},
},
+ {
+ desc: "ShouldRaiseErrorsZXCVBNTooLow",
+ have: &schema.PasswordPolicyConfiguration{
+ ZXCVBN: schema.PasswordPolicyZXCVBNParams{
+ Enabled: true,
+ MinScore: -1,
+ },
+ },
+ expected: &schema.PasswordPolicyConfiguration{
+ ZXCVBN: schema.PasswordPolicyZXCVBNParams{
+ Enabled: true,
+ MinScore: -1,
+ },
+ },
+ expectedErrs: []string{
+ "password_policy: zxcvbn: option 'min_score' is invalid: must be between 1 and 4 but it's configured as -1",
+ },
+ },
+ {
+ desc: "ShouldRaiseErrorsZXCVBNTooHigh",
+ have: &schema.PasswordPolicyConfiguration{
+ ZXCVBN: schema.PasswordPolicyZXCVBNParams{
+ Enabled: true,
+ MinScore: 5,
+ },
+ },
+ expected: &schema.PasswordPolicyConfiguration{
+ ZXCVBN: schema.PasswordPolicyZXCVBNParams{
+ Enabled: true,
+ MinScore: 5,
+ },
+ },
+ expectedErrs: []string{
+ "password_policy: zxcvbn: option 'min_score' is invalid: must be between 1 and 4 but it's configured as 5",
+ },
+ },
}
for _, tc := range testCases {
@@ -99,6 +137,7 @@ func TestValidatePasswordPolicy(t *testing.T) {
assert.Equal(t, tc.expected.Standard.RequireSpecial, tc.have.Standard.RequireSpecial)
assert.Equal(t, tc.expected.Standard.RequireUppercase, tc.have.Standard.RequireUppercase)
assert.Equal(t, tc.expected.Standard.RequireLowercase, tc.have.Standard.RequireLowercase)
+ assert.Equal(t, tc.expected.ZXCVBN.MinScore, tc.have.ZXCVBN.MinScore)
errs := validator.Errors()
require.Len(t, errs, len(tc.expectedErrs))
diff --git a/internal/handlers/types.go b/internal/handlers/types.go
index 6b7a4d8da..33e096c25 100644
--- a/internal/handlers/types.go
+++ b/internal/handlers/types.go
@@ -120,6 +120,7 @@ type PassworPolicyBody struct {
Mode string `json:"mode"`
MinLength int `json:"min_length"`
MaxLength int `json:"max_length"`
+ MinScore int `json:"min_score"`
RequireUppercase bool `json:"require_uppercase"`
RequireLowercase bool `json:"require_lowercase"`
RequireNumber bool `json:"require_number"`
diff --git a/internal/middlewares/password_policy.go b/internal/middlewares/password_policy.go
index f2894361c..7a3aba798 100644
--- a/internal/middlewares/password_policy.go
+++ b/internal/middlewares/password_policy.go
@@ -3,44 +3,77 @@ package middlewares
import (
"regexp"
+ "github.com/trustelem/zxcvbn"
+
"github.com/authelia/authelia/v4/internal/configuration/schema"
)
-// NewPasswordPolicyProvider returns a new password policy provider.
-func NewPasswordPolicyProvider(config schema.PasswordPolicyConfiguration) (provider PasswordPolicyProvider) {
- if !config.Standard.Enabled {
- return provider
- }
-
- provider.min, provider.max = config.Standard.MinLength, config.Standard.MaxLength
-
- if config.Standard.RequireLowercase {
- provider.patterns = append(provider.patterns, *regexp.MustCompile(`[a-z]+`))
- }
-
- if config.Standard.RequireUppercase {
- provider.patterns = append(provider.patterns, *regexp.MustCompile(`[A-Z]+`))
- }
-
- if config.Standard.RequireNumber {
- provider.patterns = append(provider.patterns, *regexp.MustCompile(`[0-9]+`))
- }
-
- if config.Standard.RequireSpecial {
- provider.patterns = append(provider.patterns, *regexp.MustCompile(`[^a-zA-Z0-9]+`))
- }
-
- return provider
+// PasswordPolicyProvider represents an implementation of a password policy provider.
+type PasswordPolicyProvider interface {
+ Check(password string) (err error)
}
-// PasswordPolicyProvider handles password policy checking.
-type PasswordPolicyProvider struct {
+// NewPasswordPolicyProvider returns a new password policy provider.
+func NewPasswordPolicyProvider(config schema.PasswordPolicyConfiguration) (provider PasswordPolicyProvider) {
+ if !config.Standard.Enabled && !config.ZXCVBN.Enabled {
+ return &StandardPasswordPolicyProvider{}
+ }
+
+ if config.Standard.Enabled {
+ p := &StandardPasswordPolicyProvider{}
+
+ p.min, p.max = config.Standard.MinLength, config.Standard.MaxLength
+
+ if config.Standard.RequireLowercase {
+ p.patterns = append(p.patterns, *regexp.MustCompile(`[a-z]+`))
+ }
+
+ if config.Standard.RequireUppercase {
+ p.patterns = append(p.patterns, *regexp.MustCompile(`[A-Z]+`))
+ }
+
+ if config.Standard.RequireNumber {
+ p.patterns = append(p.patterns, *regexp.MustCompile(`[0-9]+`))
+ }
+
+ if config.Standard.RequireSpecial {
+ p.patterns = append(p.patterns, *regexp.MustCompile(`[^a-zA-Z0-9]+`))
+ }
+
+ return p
+ }
+
+ if config.ZXCVBN.Enabled {
+ return &ZXCVBNPasswordPolicyProvider{minScore: config.ZXCVBN.MinScore}
+ }
+
+ return &StandardPasswordPolicyProvider{}
+}
+
+// ZXCVBNPasswordPolicyProvider handles zxcvbn password policy checking.
+type ZXCVBNPasswordPolicyProvider struct {
+ minScore int
+}
+
+// Check checks the password against the policy.
+func (p ZXCVBNPasswordPolicyProvider) Check(password string) (err error) {
+ result := zxcvbn.PasswordStrength(password, nil)
+
+ if result.Score < p.minScore {
+ return errPasswordPolicyNoMet
+ }
+
+ return nil
+}
+
+// StandardPasswordPolicyProvider handles standard password policy checking.
+type StandardPasswordPolicyProvider struct {
patterns []regexp.Regexp
min, max int
}
// Check checks the password against the policy.
-func (p PasswordPolicyProvider) Check(password string) (err error) {
+func (p StandardPasswordPolicyProvider) Check(password string) (err error) {
patterns := len(p.patterns)
if (p.min > 0 && len(password) < p.min) || (p.max > 0 && len(password) > p.max) {
diff --git a/internal/middlewares/password_policy_test.go b/internal/middlewares/password_policy_test.go
index 8828f63a9..0ce9ff899 100644
--- a/internal/middlewares/password_policy_test.go
+++ b/internal/middlewares/password_policy_test.go
@@ -19,42 +19,42 @@ func TestNewPasswordPolicyProvider(t *testing.T) {
{
desc: "ShouldReturnUnconfiguredProvider",
have: schema.PasswordPolicyConfiguration{},
- expected: PasswordPolicyProvider{},
+ expected: &StandardPasswordPolicyProvider{},
},
{
- desc: "ShouldReturnUnconfiguredProviderWhenZxcvbn",
- have: schema.PasswordPolicyConfiguration{ZXCVBN: schema.PasswordPolicyZXCVBNParams{Enabled: true}},
- expected: PasswordPolicyProvider{},
+ desc: "ShouldReturnProviderWhenZxcvbn",
+ have: schema.PasswordPolicyConfiguration{ZXCVBN: schema.PasswordPolicyZXCVBNParams{Enabled: true, MinScore: 10}},
+ expected: &ZXCVBNPasswordPolicyProvider{minScore: 10},
},
{
desc: "ShouldReturnConfiguredProviderWithMin",
have: schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8}},
- expected: PasswordPolicyProvider{min: 8},
+ expected: &StandardPasswordPolicyProvider{min: 8},
},
{
desc: "ShouldReturnConfiguredProviderWitHMinMax",
have: schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8, MaxLength: 100}},
- expected: PasswordPolicyProvider{min: 8, max: 100},
+ expected: &StandardPasswordPolicyProvider{min: 8, max: 100},
},
{
desc: "ShouldReturnConfiguredProviderWithMinLowercase",
have: schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8, RequireLowercase: true}},
- expected: PasswordPolicyProvider{min: 8, patterns: []regexp.Regexp{*regexp.MustCompile(`[a-z]+`)}},
+ expected: &StandardPasswordPolicyProvider{min: 8, patterns: []regexp.Regexp{*regexp.MustCompile(`[a-z]+`)}},
},
{
desc: "ShouldReturnConfiguredProviderWithMinLowercaseUppercase",
have: schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8, RequireLowercase: true, RequireUppercase: true}},
- expected: PasswordPolicyProvider{min: 8, patterns: []regexp.Regexp{*regexp.MustCompile(`[a-z]+`), *regexp.MustCompile(`[A-Z]+`)}},
+ expected: &StandardPasswordPolicyProvider{min: 8, patterns: []regexp.Regexp{*regexp.MustCompile(`[a-z]+`), *regexp.MustCompile(`[A-Z]+`)}},
},
{
desc: "ShouldReturnConfiguredProviderWithMinLowercaseUppercaseNumber",
have: schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8, RequireLowercase: true, RequireUppercase: true, RequireNumber: true}},
- expected: PasswordPolicyProvider{min: 8, patterns: []regexp.Regexp{*regexp.MustCompile(`[a-z]+`), *regexp.MustCompile(`[A-Z]+`), *regexp.MustCompile(`[0-9]+`)}},
+ expected: &StandardPasswordPolicyProvider{min: 8, patterns: []regexp.Regexp{*regexp.MustCompile(`[a-z]+`), *regexp.MustCompile(`[A-Z]+`), *regexp.MustCompile(`[0-9]+`)}},
},
{
desc: "ShouldReturnConfiguredProviderWithMinLowercaseUppercaseSpecial",
have: schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8, RequireLowercase: true, RequireUppercase: true, RequireSpecial: true}},
- expected: PasswordPolicyProvider{min: 8, patterns: []regexp.Regexp{*regexp.MustCompile(`[a-z]+`), *regexp.MustCompile(`[A-Z]+`), *regexp.MustCompile(`[^a-zA-Z0-9]+`)}},
+ expected: &StandardPasswordPolicyProvider{min: 8, patterns: []regexp.Regexp{*regexp.MustCompile(`[a-z]+`), *regexp.MustCompile(`[A-Z]+`), *regexp.MustCompile(`[^a-zA-Z0-9]+`)}},
},
}
diff --git a/web/src/components/PasswordMeter.test.tsx b/web/src/components/PasswordMeter.test.tsx
index cc77ab77e..790ddd6f3 100644
--- a/web/src/components/PasswordMeter.test.tsx
+++ b/web/src/components/PasswordMeter.test.tsx
@@ -12,6 +12,7 @@ it("renders without crashing", () => {
policy={{
max_length: 0,
min_length: 4,
+ min_score: 0,
require_lowercase: false,
require_number: false,
require_special: false,
@@ -29,6 +30,7 @@ it("renders adjusted height without crashing", () => {
policy={{
max_length: 0,
min_length: 4,
+ min_score: 0,
require_lowercase: false,
require_number: false,
require_special: false,
diff --git a/web/src/models/PasswordPolicy.ts b/web/src/models/PasswordPolicy.ts
index 454a90ffa..32bec62f3 100644
--- a/web/src/models/PasswordPolicy.ts
+++ b/web/src/models/PasswordPolicy.ts
@@ -8,6 +8,7 @@ export interface PasswordPolicyConfiguration {
mode: PasswordPolicyMode;
min_length: number;
max_length: number;
+ min_score: number;
require_uppercase: boolean;
require_lowercase: boolean;
require_number: boolean;
diff --git a/web/src/services/PasswordPolicyConfiguration.ts b/web/src/services/PasswordPolicyConfiguration.ts
index a816d8b93..6b979c74d 100644
--- a/web/src/services/PasswordPolicyConfiguration.ts
+++ b/web/src/services/PasswordPolicyConfiguration.ts
@@ -6,6 +6,7 @@ interface PasswordPolicyConfigurationPayload {
mode: ModePasswordPolicy;
min_length: number;
max_length: number;
+ min_score: number;
require_uppercase: boolean;
require_lowercase: boolean;
require_number: boolean;
diff --git a/web/src/views/ResetPassword/ResetPasswordStep2.tsx b/web/src/views/ResetPassword/ResetPasswordStep2.tsx
index 809938a3d..bf1e61463 100644
--- a/web/src/views/ResetPassword/ResetPasswordStep2.tsx
+++ b/web/src/views/ResetPassword/ResetPasswordStep2.tsx
@@ -32,6 +32,7 @@ const ResetPasswordStep2 = function () {
const [pPolicy, setPPolicy] = useState({
max_length: 0,
min_length: 8,
+ min_score: 0,
require_lowercase: false,
require_number: false,
require_special: false,