refactor: misc password policy refactoring (#3102)
Add tests and makes the password policy a provider so the configuration can be loaded to memory on startup.pull/3100/head^2
parent
8659ba394d
commit
36cf662458
|
@ -321,6 +321,25 @@ authentication_backend:
|
||||||
# memory: 1024
|
# memory: 1024
|
||||||
# parallelism: 8
|
# parallelism: 8
|
||||||
|
|
||||||
|
##
|
||||||
|
## Password Policy Configuration.
|
||||||
|
##
|
||||||
|
password_policy:
|
||||||
|
|
||||||
|
## The standard policy allows you to tune individual settings manually.
|
||||||
|
standard:
|
||||||
|
enabled: false
|
||||||
|
min_length: 8
|
||||||
|
max_length: 0
|
||||||
|
require_uppercase: true
|
||||||
|
require_lowercase: true
|
||||||
|
require_number: true
|
||||||
|
require_special: true
|
||||||
|
|
||||||
|
## zxcvbn is a well known and used password strength algorithm. It does not have tunable settings.
|
||||||
|
zxcvbn:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
##
|
##
|
||||||
## Access Control Configuration
|
## Access Control Configuration
|
||||||
##
|
##
|
||||||
|
|
|
@ -6,6 +6,7 @@ nav_order: 17
|
||||||
---
|
---
|
||||||
|
|
||||||
# Password Policy
|
# Password Policy
|
||||||
|
|
||||||
_Authelia_ allows administrators to configure an enforced password policy.
|
_Authelia_ allows administrators to configure an enforced password policy.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
@ -13,7 +14,7 @@ _Authelia_ allows administrators to configure an enforced password policy.
|
||||||
```yaml
|
```yaml
|
||||||
password_policy:
|
password_policy:
|
||||||
standard:
|
standard:
|
||||||
enabled: false
|
enabled: false
|
||||||
min_length: 8
|
min_length: 8
|
||||||
max_length: 0
|
max_length: 0
|
||||||
require_uppercase: true
|
require_uppercase: true
|
||||||
|
@ -34,77 +35,95 @@ required: no
|
||||||
{: .label .label-config .label-green }
|
{: .label .label-config .label-green }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
This section allows you to enable standard security policies.
|
This section allows you to enable standard security policies.
|
||||||
#### enabled
|
|
||||||
|
#### enabled
|
||||||
|
<div markdown="1">
|
||||||
type: bool
|
type: bool
|
||||||
{: .label .label-config .label-purple }
|
{: .label .label-config .label-purple }
|
||||||
required: no
|
required: no
|
||||||
{: .label .label-config .label-green }
|
{: .label .label-config .label-green }
|
||||||
</div>
|
</div>
|
||||||
Enables standard password policy
|
|
||||||
|
|
||||||
#### min_length
|
Enables standard password policy.
|
||||||
|
|
||||||
|
#### min_length
|
||||||
|
<div markdown="1">
|
||||||
type: integer
|
type: integer
|
||||||
{: .label .label-config .label-purple }
|
{: .label .label-config .label-purple }
|
||||||
required: no
|
required: no
|
||||||
{: .label .label-config .label-green }
|
{: .label .label-config .label-green }
|
||||||
</div>
|
</div>
|
||||||
Determines the minimum allowed password length
|
|
||||||
|
|
||||||
#### max_length
|
Determines the minimum allowed password length.
|
||||||
|
|
||||||
|
#### max_length
|
||||||
|
<div markdown="1">
|
||||||
type: integer
|
type: integer
|
||||||
{: .label .label-config .label-purple }
|
{: .label .label-config .label-purple }
|
||||||
required: no
|
required: no
|
||||||
{: .label .label-config .label-green }
|
{: .label .label-config .label-green }
|
||||||
</div>
|
</div>
|
||||||
Determines the maximum allowed password length
|
|
||||||
|
|
||||||
#### require_uppercase
|
Determines the maximum allowed password length.
|
||||||
|
|
||||||
|
#### require_uppercase
|
||||||
|
<div markdown="1">
|
||||||
type: bool
|
type: bool
|
||||||
{: .label .label-config .label-purple }
|
{: .label .label-config .label-purple }
|
||||||
required: no
|
required: no
|
||||||
{: .label .label-config .label-green }
|
{: .label .label-config .label-green }
|
||||||
</div>
|
</div>
|
||||||
Indicates that at least one UPPERCASE letter must be provided as part of the password
|
|
||||||
|
|
||||||
#### require_lowercase
|
Indicates that at least one UPPERCASE letter must be provided as part of the password.
|
||||||
|
|
||||||
|
#### require_lowercase
|
||||||
|
<div markdown="1">
|
||||||
type: bool
|
type: bool
|
||||||
{: .label .label-config .label-purple }
|
{: .label .label-config .label-purple }
|
||||||
required: no
|
required: no
|
||||||
{: .label .label-config .label-green }
|
{: .label .label-config .label-green }
|
||||||
</div>
|
</div>
|
||||||
Indicates that at least one lowercase letter must be provided as part of the password
|
|
||||||
|
|
||||||
#### require_number
|
Indicates that at least one lowercase letter must be provided as part of the password.
|
||||||
|
|
||||||
|
#### require_number
|
||||||
|
<div markdown="1">
|
||||||
type: bool
|
type: bool
|
||||||
{: .label .label-config .label-purple }
|
{: .label .label-config .label-purple }
|
||||||
required: no
|
required: no
|
||||||
{: .label .label-config .label-green }
|
{: .label .label-config .label-green }
|
||||||
</div>
|
</div>
|
||||||
Indicates that at least one number must be provided as part of the password
|
|
||||||
|
|
||||||
#### require_special
|
Indicates that at least one number must be provided as part of the password.
|
||||||
|
|
||||||
|
#### require_special
|
||||||
|
<div markdown="1">
|
||||||
type: bool
|
type: bool
|
||||||
{: .label .label-config .label-purple }
|
{: .label .label-config .label-purple }
|
||||||
required: no
|
required: no
|
||||||
{: .label .label-config .label-green }
|
{: .label .label-config .label-green }
|
||||||
</div>
|
</div>
|
||||||
Indicates that at least one special character must be provided as part of the password
|
|
||||||
|
|
||||||
|
Indicates that at least one special character must be provided as part of the password.
|
||||||
|
|
||||||
### zxcvbn
|
### zxcvbn
|
||||||
This password policy enables advanced password strengh metering, using [Dropbox zxcvbn package](https://github.com/dropbox/zxcvbn).
|
|
||||||
|
|
||||||
Note that this password policy do not restrict the user's entry, just warns the user that if their password is too weak
|
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
|
#### enabled
|
||||||
|
<div markdown="1">
|
||||||
type: bool
|
type: bool
|
||||||
{: .label .label-config .label-purple }
|
{: .label .label-config .label-purple }
|
||||||
required: no
|
required: no
|
||||||
{: .label .label-config .label-green }
|
{: .label .label-config .label-green }
|
||||||
</div>
|
</div>
|
||||||
Enables standard password policy
|
|
||||||
|
|
||||||
Note:
|
_**Important Note:** only one password policy can be applied at a time._
|
||||||
* only one password policy can be applied at a time
|
|
||||||
|
Enables zxcvbn password policy.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,17 +6,22 @@ nav_order: 8
|
||||||
---
|
---
|
||||||
|
|
||||||
# Password Policy
|
# Password Policy
|
||||||
Password policy enforces the security by requering the users to use strong passwords
|
|
||||||
|
Password policy enforces the security by requiring the users to use strong passwords.
|
||||||
|
|
||||||
Currently, two methods are supported:
|
Currently, two methods are supported:
|
||||||
|
|
||||||
## classic
|
## classic
|
||||||
* this mode of operation allows administrators to set the rules that user passwords must comply with
|
|
||||||
* the available options are:
|
This mode of operation allows administrators to set the rules that user passwords must comply with when changing their
|
||||||
* Minimum password length
|
password.
|
||||||
* Require Uppercase
|
|
||||||
* Require Lowercase
|
The available options are:
|
||||||
* Require Numbers
|
* Minimum password length
|
||||||
* Require Special characters
|
* Require Uppercase
|
||||||
* when changing the password users must meet these rules
|
* Require Lowercase
|
||||||
|
* Require Numbers
|
||||||
|
* Require Special characters
|
||||||
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
@ -25,8 +30,10 @@ Currently, two methods are supported:
|
||||||
|
|
||||||
|
|
||||||
## zxcvbn
|
## zxcvbn
|
||||||
* this mode uses zxcvbn for password strength checking (see: https://github.com/dropbox/zxcvbn)
|
|
||||||
* in this mode of operation, the user is not forced to follow any rules. the user is notified if their passwords is weak or strong
|
This mode uses [zxcvbn](https://github.com/dropbox/zxcvbn) for password strength checking. In this mode of operation,
|
||||||
|
the user is not forced to follow any rules. The user is notified if their passwords is weak or strong.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="../images/password-policy-zxcvbn-1.png" width="400">
|
<img src="../images/password-policy-zxcvbn-1.png" width="400">
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -71,6 +71,8 @@ func getProviders() (providers middlewares.Providers, warnings []error, errors [
|
||||||
|
|
||||||
totpProvider := totp.NewTimeBasedProvider(config.TOTP)
|
totpProvider := totp.NewTimeBasedProvider(config.TOTP)
|
||||||
|
|
||||||
|
passwordPolicyProvider := middlewares.NewPasswordPolicyProvider(config.PasswordPolicy)
|
||||||
|
|
||||||
return middlewares.Providers{
|
return middlewares.Providers{
|
||||||
Authorizer: authorizer,
|
Authorizer: authorizer,
|
||||||
UserProvider: userProvider,
|
UserProvider: userProvider,
|
||||||
|
@ -81,5 +83,6 @@ func getProviders() (providers middlewares.Providers, warnings []error, errors [
|
||||||
Notifier: notifier,
|
Notifier: notifier,
|
||||||
SessionProvider: sessionProvider,
|
SessionProvider: sessionProvider,
|
||||||
TOTP: totpProvider,
|
TOTP: totpProvider,
|
||||||
|
PasswordPolicy: passwordPolicyProvider,
|
||||||
}, warnings, errors
|
}, warnings, errors
|
||||||
}
|
}
|
||||||
|
|
|
@ -321,6 +321,25 @@ authentication_backend:
|
||||||
# memory: 1024
|
# memory: 1024
|
||||||
# parallelism: 8
|
# parallelism: 8
|
||||||
|
|
||||||
|
##
|
||||||
|
## Password Policy Configuration.
|
||||||
|
##
|
||||||
|
password_policy:
|
||||||
|
|
||||||
|
## The standard policy allows you to tune individual settings manually.
|
||||||
|
standard:
|
||||||
|
enabled: false
|
||||||
|
min_length: 8
|
||||||
|
max_length: 0
|
||||||
|
require_uppercase: true
|
||||||
|
require_lowercase: true
|
||||||
|
require_number: true
|
||||||
|
require_special: true
|
||||||
|
|
||||||
|
## zxcvbn is a well known and used password strength algorithm. It does not have tunable settings.
|
||||||
|
zxcvbn:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
##
|
##
|
||||||
## Access Control Configuration
|
## Access Control Configuration
|
||||||
##
|
##
|
||||||
|
|
|
@ -10,16 +10,15 @@ type Configuration struct {
|
||||||
Log LogConfiguration `koanf:"log"`
|
Log LogConfiguration `koanf:"log"`
|
||||||
IdentityProviders IdentityProvidersConfiguration `koanf:"identity_providers"`
|
IdentityProviders IdentityProvidersConfiguration `koanf:"identity_providers"`
|
||||||
AuthenticationBackend AuthenticationBackendConfiguration `koanf:"authentication_backend"`
|
AuthenticationBackend AuthenticationBackendConfiguration `koanf:"authentication_backend"`
|
||||||
|
Session SessionConfiguration `koanf:"session"`
|
||||||
TOTP TOTPConfiguration `koanf:"totp"`
|
TOTP TOTPConfiguration `koanf:"totp"`
|
||||||
Webauthn WebauthnConfiguration `koanf:"webauthn"`
|
|
||||||
DuoAPI *DuoAPIConfiguration `koanf:"duo_api"`
|
DuoAPI *DuoAPIConfiguration `koanf:"duo_api"`
|
||||||
AccessControl AccessControlConfiguration `koanf:"access_control"`
|
AccessControl AccessControlConfiguration `koanf:"access_control"`
|
||||||
|
NTP NTPConfiguration `koanf:"ntp"`
|
||||||
Regulation RegulationConfiguration `koanf:"regulation"`
|
Regulation RegulationConfiguration `koanf:"regulation"`
|
||||||
|
Storage StorageConfiguration `koanf:"storage"`
|
||||||
Server ServerConfiguration `koanf:"server"`
|
Notifier *NotifierConfiguration `koanf:"notifier"`
|
||||||
Session SessionConfiguration `koanf:"session"`
|
Server ServerConfiguration `koanf:"server"`
|
||||||
NTP NTPConfiguration `koanf:"ntp"`
|
Webauthn WebauthnConfiguration `koanf:"webauthn"`
|
||||||
Storage StorageConfiguration `koanf:"storage"`
|
PasswordPolicy PasswordPolicyConfiguration `koanf:"password_policy"`
|
||||||
Notifier *NotifierConfiguration `koanf:"notifier"`
|
|
||||||
PasswordPolicy PasswordPolicyConfiguration `koanf:"password_policy"`
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
package handlers
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var errPasswordPolicyNoMet = errors.New("the supplied password does not met the security policy")
|
|
|
@ -2,7 +2,6 @@ package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
|
@ -28,7 +27,7 @@ func ResetPasswordPost(ctx *middlewares.AutheliaCtx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validatePassword(ctx, requestBody.Password); err != nil {
|
if err = ctx.Providers.PasswordPolicy.Check(requestBody.Password); err != nil {
|
||||||
ctx.Error(err, messagePasswordWeak)
|
ctx.Error(err, messagePasswordWeak)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -60,54 +59,3 @@ func ResetPasswordPost(ctx *middlewares.AutheliaCtx) {
|
||||||
|
|
||||||
ctx.ReplyOK()
|
ctx.ReplyOK()
|
||||||
}
|
}
|
||||||
|
|
||||||
// validatePassword validates if the password met the password policy rules.
|
|
||||||
func validatePassword(ctx *middlewares.AutheliaCtx, password string) error {
|
|
||||||
// password validation applies only to standard passwor policy.
|
|
||||||
if !ctx.Configuration.PasswordPolicy.Standard.Enabled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
requireLowercase := ctx.Configuration.PasswordPolicy.Standard.RequireLowercase
|
|
||||||
requireUppercase := ctx.Configuration.PasswordPolicy.Standard.RequireUppercase
|
|
||||||
requireNumber := ctx.Configuration.PasswordPolicy.Standard.RequireNumber
|
|
||||||
requireSpecial := ctx.Configuration.PasswordPolicy.Standard.RequireSpecial
|
|
||||||
minLength := ctx.Configuration.PasswordPolicy.Standard.MinLength
|
|
||||||
maxlength := ctx.Configuration.PasswordPolicy.Standard.MaxLength
|
|
||||||
|
|
||||||
var patterns []string
|
|
||||||
|
|
||||||
if (minLength > 0 && len(password) < minLength) || (maxlength > 0 && len(password) > maxlength) {
|
|
||||||
return errPasswordPolicyNoMet
|
|
||||||
}
|
|
||||||
|
|
||||||
if requireLowercase {
|
|
||||||
patterns = append(patterns, "[a-z]+")
|
|
||||||
}
|
|
||||||
|
|
||||||
if requireUppercase {
|
|
||||||
patterns = append(patterns, "[A-Z]+")
|
|
||||||
}
|
|
||||||
|
|
||||||
if requireNumber {
|
|
||||||
patterns = append(patterns, "[0-9]+")
|
|
||||||
}
|
|
||||||
|
|
||||||
if requireSpecial {
|
|
||||||
patterns = append(patterns, "[^a-zA-Z0-9]+")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pattern := range patterns {
|
|
||||||
re, err := regexp.Compile(pattern)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if found := re.MatchString(password); !found {
|
|
||||||
return errPasswordPolicyNoMet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package middlewares
|
package middlewares
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -56,3 +58,5 @@ const (
|
||||||
|
|
||||||
var protoHostSeparator = []byte("://")
|
var protoHostSeparator = []byte("://")
|
||||||
var validOverrideAssets = []string{"favicon.ico", "logo.png"}
|
var validOverrideAssets = []string{"favicon.ico", "logo.png"}
|
||||||
|
|
||||||
|
var errPasswordPolicyNoMet = errors.New("the supplied password does not met the security policy")
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"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 handles password policy checking.
|
||||||
|
type PasswordPolicyProvider struct {
|
||||||
|
patterns []regexp.Regexp
|
||||||
|
min, max int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check checks the password against the policy.
|
||||||
|
func (p PasswordPolicyProvider) Check(password string) (err error) {
|
||||||
|
patterns := len(p.patterns)
|
||||||
|
|
||||||
|
if (p.min > 0 && len(password) < p.min) || (p.max > 0 && len(password) > p.max) {
|
||||||
|
return errPasswordPolicyNoMet
|
||||||
|
}
|
||||||
|
|
||||||
|
if patterns == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < patterns; i++ {
|
||||||
|
if !p.patterns[i].MatchString(password) {
|
||||||
|
return errPasswordPolicyNoMet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewPasswordPolicyProvider(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
have schema.PasswordPolicyConfiguration
|
||||||
|
expected PasswordPolicyProvider
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "ShouldReturnUnconfiguredProvider",
|
||||||
|
have: schema.PasswordPolicyConfiguration{},
|
||||||
|
expected: PasswordPolicyProvider{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldReturnUnconfiguredProviderWhenZxcvbn",
|
||||||
|
have: schema.PasswordPolicyConfiguration{Zxcvbn: schema.PasswordPolicyZxcvbnParams{Enabled: true}},
|
||||||
|
expected: PasswordPolicyProvider{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldReturnConfiguredProviderWithMin",
|
||||||
|
have: schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8}},
|
||||||
|
expected: PasswordPolicyProvider{min: 8},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldReturnConfiguredProviderWitHMinMax",
|
||||||
|
have: schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8, MaxLength: 100}},
|
||||||
|
expected: PasswordPolicyProvider{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]+`)}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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]+`)}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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]+`)}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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]+`)}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
actual := NewPasswordPolicyProvider(tc.have)
|
||||||
|
assert.Equal(t, tc.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPasswordPolicyProvider_Validate(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
config schema.PasswordPolicyConfiguration
|
||||||
|
have []string
|
||||||
|
expected []error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "ShouldValidateAllPasswords",
|
||||||
|
config: schema.PasswordPolicyConfiguration{},
|
||||||
|
have: []string{"a", "1", "a really str0ng pass12nm3kjl12word@@#4"},
|
||||||
|
expected: []error{nil, nil, nil},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldValidatePasswordMinLength",
|
||||||
|
config: schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8}},
|
||||||
|
have: []string{"a", "b123", "1111111", "aaaaaaaa", "1o23nm1kio2n3k12jn"},
|
||||||
|
expected: []error{errPasswordPolicyNoMet, errPasswordPolicyNoMet, errPasswordPolicyNoMet, nil, nil},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldValidatePasswordMaxLength",
|
||||||
|
config: schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MaxLength: 30}},
|
||||||
|
have: []string{
|
||||||
|
"a1234567894654wkjnkjasnskjandkjansdkjnas",
|
||||||
|
"012345678901234567890123456789a",
|
||||||
|
"0123456789012345678901234567890123456789",
|
||||||
|
"012345678901234567890123456789",
|
||||||
|
"1o23nm1kio2n3k12jn",
|
||||||
|
},
|
||||||
|
expected: []error{errPasswordPolicyNoMet, errPasswordPolicyNoMet, errPasswordPolicyNoMet, nil, nil},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldValidatePasswordAdvancedLowerUpperMin8",
|
||||||
|
config: schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8, RequireLowercase: true, RequireUppercase: true}},
|
||||||
|
have: []string{"a", "b123", "1111111", "aaaaaaaa", "1o23nm1kio2n3k12jn", "ANJKJQ@#NEK!@#NJK!@#", "qjik2nkjAkjlmn123"},
|
||||||
|
expected: []error{errPasswordPolicyNoMet, errPasswordPolicyNoMet, errPasswordPolicyNoMet, errPasswordPolicyNoMet, errPasswordPolicyNoMet, errPasswordPolicyNoMet, nil},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldValidatePasswordAdvancedAllMax100Min8",
|
||||||
|
config: schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8, MaxLength: 100, RequireLowercase: true, RequireUppercase: true, RequireNumber: true, RequireSpecial: true}},
|
||||||
|
have: []string{
|
||||||
|
"a",
|
||||||
|
"b123",
|
||||||
|
"1111111",
|
||||||
|
"aaaaaaaa",
|
||||||
|
"1o23nm1kio2n3k12jn",
|
||||||
|
"ANJKJQ@#NEK!@#NJK!@#",
|
||||||
|
"qjik2nkjAkjlmn123",
|
||||||
|
"qjik2n@jAkjlmn123",
|
||||||
|
"qjik2n@jAkjlmn123qjik2n@jAkjlmn123qjik2n@jAkjlmn123qjik2n@jAkjlmn123qjik2n@jAkjlmn123qjik2n@jAkjlmn123",
|
||||||
|
},
|
||||||
|
expected: []error{
|
||||||
|
errPasswordPolicyNoMet,
|
||||||
|
errPasswordPolicyNoMet,
|
||||||
|
errPasswordPolicyNoMet,
|
||||||
|
errPasswordPolicyNoMet,
|
||||||
|
errPasswordPolicyNoMet,
|
||||||
|
errPasswordPolicyNoMet,
|
||||||
|
errPasswordPolicyNoMet,
|
||||||
|
nil,
|
||||||
|
errPasswordPolicyNoMet,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
require.Equal(t, len(tc.have), len(tc.expected))
|
||||||
|
for i := 0; i < len(tc.have); i++ {
|
||||||
|
provider := NewPasswordPolicyProvider(tc.config)
|
||||||
|
t.Run(tc.have[i], func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.expected[i], provider.Check(tc.have[i]))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,6 +39,7 @@ type Providers struct {
|
||||||
StorageProvider storage.Provider
|
StorageProvider storage.Provider
|
||||||
Notifier notification.Notifier
|
Notifier notification.Notifier
|
||||||
TOTP totp.Provider
|
TOTP totp.Provider
|
||||||
|
PasswordPolicy PasswordPolicyProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestHandler represents an Authelia request handler.
|
// RequestHandler represents an Authelia request handler.
|
||||||
|
|
|
@ -103,7 +103,7 @@ ntp:
|
||||||
password_policy:
|
password_policy:
|
||||||
standard:
|
standard:
|
||||||
# Enables standard password Policy
|
# Enables standard password Policy
|
||||||
enabled: false
|
enabled: false
|
||||||
min_length: 8
|
min_length: 8
|
||||||
max_length: 0
|
max_length: 0
|
||||||
require_uppercase: true
|
require_uppercase: true
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
"react-loading": "2.0.3",
|
"react-loading": "2.0.3",
|
||||||
"react-otp-input": "2.4.0",
|
"react-otp-input": "2.4.0",
|
||||||
"react-router-dom": "6.3.0",
|
"react-router-dom": "6.3.0",
|
||||||
"zxcvbn": "^4.4.2"
|
"zxcvbn": "4.4.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "cd .. && husky install .github",
|
"prepare": "cd .. && husky install .github",
|
||||||
|
|
|
@ -56,7 +56,7 @@ specifiers:
|
||||||
vite-plugin-istanbul: 2.5.1
|
vite-plugin-istanbul: 2.5.1
|
||||||
vite-plugin-svgr: 1.1.0
|
vite-plugin-svgr: 1.1.0
|
||||||
vite-tsconfig-paths: 3.4.1
|
vite-tsconfig-paths: 3.4.1
|
||||||
zxcvbn: ^4.4.2
|
zxcvbn: 4.4.2
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fortawesome/fontawesome-svg-core': 6.1.1
|
'@fortawesome/fontawesome-svg-core': 6.1.1
|
||||||
|
|
Loading…
Reference in New Issue