diff --git a/.schemastore/config.schema.json b/.schemastore/config.schema.json index 829b71bae3fb..2bf9c3618bee 100644 --- a/.schemastore/config.schema.json +++ b/.schemastore/config.schema.json @@ -1524,6 +1524,13 @@ "default": 8, "minimum": 6 }, + "max_password_length": { + "title": "Maximum Password Length", + "description": "Defines the maximum length of the password.", + "type": "integer", + "default": 1024, + "minimum": 20 + }, "identifier_similarity_check_enabled": { "title": "Enable password-identifier similarity check", "description": "If set to false the password validation does not check for similarity between the password and the user identifier.", diff --git a/driver/config/config.go b/driver/config/config.go index 0d755a11ba63..113a1ddfbed4 100644 --- a/driver/config/config.go +++ b/driver/config/config.go @@ -182,6 +182,7 @@ const ( ViperKeyPasswordHaveIBeenPwnedEnabled = "selfservice.methods.password.config.haveibeenpwned_enabled" ViperKeyPasswordMaxBreaches = "selfservice.methods.password.config.max_breaches" ViperKeyPasswordMinLength = "selfservice.methods.password.config.min_password_length" + ViperKeyPasswordMaxLength = "selfservice.methods.password.config.max_password_length" ViperKeyPasswordIdentifierSimilarityCheckEnabled = "selfservice.methods.password.config.identifier_similarity_check_enabled" ViperKeyIgnoreNetworkErrors = "selfservice.methods.password.config.ignore_network_errors" ViperKeyTOTPIssuer = "selfservice.methods.totp.config.issuer" @@ -256,6 +257,7 @@ type ( MaxBreaches uint `json:"max_breaches"` IgnoreNetworkErrors bool `json:"ignore_network_errors"` MinPasswordLength uint `json:"min_password_length"` + MaxPasswordLength uint `json:"max_password_length"` IdentifierSimilarityCheckEnabled bool `json:"identifier_similarity_check_enabled"` } Schemas []Schema @@ -1445,6 +1447,7 @@ func (p *Config) PasswordPolicyConfig(ctx context.Context) *PasswordPolicy { MaxBreaches: uint(p.GetProvider(ctx).Int(ViperKeyPasswordMaxBreaches)), IgnoreNetworkErrors: p.GetProvider(ctx).BoolF(ViperKeyIgnoreNetworkErrors, true), MinPasswordLength: uint(p.GetProvider(ctx).IntF(ViperKeyPasswordMinLength, 8)), + MaxPasswordLength: uint(p.GetProvider(ctx).IntF(ViperKeyPasswordMaxLength, 20)), IdentifierSimilarityCheckEnabled: p.GetProvider(ctx).BoolF(ViperKeyPasswordIdentifierSimilarityCheckEnabled, true), } } diff --git a/driver/config/config_test.go b/driver/config/config_test.go index 6cb37f100850..550f95648774 100644 --- a/driver/config/config_test.go +++ b/driver/config/config_test.go @@ -211,7 +211,7 @@ func TestViperProvider(t *testing.T) { config string enabled bool }{ - {id: "password", enabled: true, config: `{"haveibeenpwned_host":"api.pwnedpasswords.com","haveibeenpwned_enabled":true,"ignore_network_errors":true,"max_breaches":0,"min_password_length":8,"identifier_similarity_check_enabled":true}`}, + {id: "password", enabled: true, config: `{"haveibeenpwned_host":"api.pwnedpasswords.com","haveibeenpwned_enabled":true,"ignore_network_errors":true,"max_breaches":0,"min_password_length":8,"max_password_length":1024,"identifier_similarity_check_enabled":true}`}, {id: "oidc", enabled: true, config: `{"providers":[{"client_id":"a","client_secret":"b","id":"github","provider":"github","mapper_url":"http://test.kratos.ory.sh/default-identity.schema.json"}]}`}, {id: "totp", enabled: true, config: `{"issuer":"issuer.ory.sh"}`}, } { diff --git a/embedx/config.schema.json b/embedx/config.schema.json index 79bc83c88b1d..b501558edbc3 100644 --- a/embedx/config.schema.json +++ b/embedx/config.schema.json @@ -1570,6 +1570,13 @@ "default": 8, "minimum": 6 }, + "max_password_length": { + "title": "Maximum Password Length", + "description": "Defines the maximum length of the password.", + "type": "integer", + "default": 1024, + "minimum": 20 + }, "identifier_similarity_check_enabled": { "title": "Enable password-identifier similarity check", "description": "If set to false the password validation does not check for similarity between the password and the user identifier.", diff --git a/internal/client-go/go.sum b/internal/client-go/go.sum index c966c8ddfd0d..6cc3f5911d11 100644 --- a/internal/client-go/go.sum +++ b/internal/client-go/go.sum @@ -4,6 +4,7 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/client-go/model_ui_node_input_attributes.go b/internal/client-go/model_ui_node_input_attributes.go index b373dda7ccfd..00a6e22c3f00 100644 --- a/internal/client-go/model_ui_node_input_attributes.go +++ b/internal/client-go/model_ui_node_input_attributes.go @@ -19,6 +19,10 @@ import ( type UiNodeInputAttributes struct { // The autocomplete attribute for the input. email InputAttributeAutocompleteEmail tel InputAttributeAutocompleteTel url InputAttributeAutocompleteUrl current-password InputAttributeAutocompleteCurrentPassword new-password InputAttributeAutocompleteNewPassword one-time-code InputAttributeAutocompleteOneTimeCode Autocomplete *string `json:"autocomplete,omitempty"` + // The minlength attribute for the input. + MinLength uint `json:"minlength,omitempty"` + // The maxlength attribute for the input. + MaxLength uint `json:"maxlength,omitempty"` // Sets the input's disabled field to true or false. Disabled bool `json:"disabled"` Label *UiText `json:"label,omitempty"` diff --git a/internal/httpclient/model_ui_node_input_attributes.go b/internal/httpclient/model_ui_node_input_attributes.go index b373dda7ccfd..00a6e22c3f00 100644 --- a/internal/httpclient/model_ui_node_input_attributes.go +++ b/internal/httpclient/model_ui_node_input_attributes.go @@ -19,6 +19,10 @@ import ( type UiNodeInputAttributes struct { // The autocomplete attribute for the input. email InputAttributeAutocompleteEmail tel InputAttributeAutocompleteTel url InputAttributeAutocompleteUrl current-password InputAttributeAutocompleteCurrentPassword new-password InputAttributeAutocompleteNewPassword one-time-code InputAttributeAutocompleteOneTimeCode Autocomplete *string `json:"autocomplete,omitempty"` + // The minlength attribute for the input. + MinLength uint `json:"minlength,omitempty"` + // The maxlength attribute for the input. + MaxLength uint `json:"maxlength,omitempty"` // Sets the input's disabled field to true or false. Disabled bool `json:"disabled"` Label *UiText `json:"label,omitempty"` diff --git a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_link_a_connection-flow=fetch.json b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_link_a_connection-flow=fetch.json index b775cb07f8b3..5fd9d647e683 100644 --- a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_link_a_connection-flow=fetch.json +++ b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_link_a_connection-flow=fetch.json @@ -60,6 +60,8 @@ { "attributes": { "autocomplete": "new-password", + "minlength": 8, + "maxlength": 1024, "disabled": false, "name": "password", "node_type": "input", diff --git a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_link_a_connection-flow=original.json b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_link_a_connection-flow=original.json index 19da7fb7f971..7c9c1ba06edd 100644 --- a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_link_a_connection-flow=original.json +++ b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_link_a_connection-flow=original.json @@ -60,6 +60,8 @@ { "attributes": { "autocomplete": "new-password", + "minlength": 8, + "maxlength": 1024, "disabled": false, "name": "password", "node_type": "input", diff --git a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_link_a_connection-flow=response.json b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_link_a_connection-flow=response.json index cda03ca13acb..31e953d9429f 100644 --- a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_link_a_connection-flow=response.json +++ b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_link_a_connection-flow=response.json @@ -65,6 +65,8 @@ "type": "password", "required": true, "autocomplete": "new-password", + "minlength": 8, + "maxlength": 1024, "disabled": false, "node_type": "input" }, diff --git a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_link_a_connection_even_if_user_does_not_have_oidc_credentials_yet.json b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_link_a_connection_even_if_user_does_not_have_oidc_credentials_yet.json index 1763aae80238..07846c809d46 100644 --- a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_link_a_connection_even_if_user_does_not_have_oidc_credentials_yet.json +++ b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_link_a_connection_even_if_user_does_not_have_oidc_credentials_yet.json @@ -60,6 +60,8 @@ { "attributes": { "autocomplete": "new-password", + "minlength": 8, + "maxlength": 1024, "disabled": false, "name": "password", "node_type": "input", diff --git a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_not_be_able_to_link_a_connection_which_already_exists.json b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_not_be_able_to_link_a_connection_which_already_exists.json index a8b9407aab8a..340d6133d6ac 100644 --- a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_not_be_able_to_link_a_connection_which_already_exists.json +++ b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_not_be_able_to_link_a_connection_which_already_exists.json @@ -65,6 +65,8 @@ "type": "password", "required": true, "autocomplete": "new-password", + "minlength": 8, + "maxlength": 1024, "disabled": false, "node_type": "input" }, diff --git a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_not_be_able_to_link_an_non-existing_connection.json b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_not_be_able_to_link_an_non-existing_connection.json index abb1fa20bd40..b48a03ba79db 100644 --- a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_not_be_able_to_link_an_non-existing_connection.json +++ b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=link-case=should_not_be_able_to_link_an_non-existing_connection.json @@ -65,6 +65,8 @@ "type": "password", "required": true, "autocomplete": "new-password", + "minlength": 8, + "maxlength": 1024, "disabled": false, "node_type": "input" }, diff --git a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_a_connection_not_yet_linked-flow=json.json b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_a_connection_not_yet_linked-flow=json.json index a8b9407aab8a..340d6133d6ac 100644 --- a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_a_connection_not_yet_linked-flow=json.json +++ b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_a_connection_not_yet_linked-flow=json.json @@ -65,6 +65,8 @@ "type": "password", "required": true, "autocomplete": "new-password", + "minlength": 8, + "maxlength": 1024, "disabled": false, "node_type": "input" }, diff --git a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_an_non-existing_connection-flow=json.json b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_an_non-existing_connection-flow=json.json index a8b9407aab8a..340d6133d6ac 100644 --- a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_an_non-existing_connection-flow=json.json +++ b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_an_non-existing_connection-flow=json.json @@ -65,6 +65,8 @@ "type": "password", "required": true, "autocomplete": "new-password", + "minlength": 8, + "maxlength": 1024, "disabled": false, "node_type": "input" }, diff --git a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_the_last_remaining_connection-flow=json.json b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_the_last_remaining_connection-flow=json.json index abb1fa20bd40..b48a03ba79db 100644 --- a/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_the_last_remaining_connection-flow=json.json +++ b/selfservice/strategy/oidc/.snapshots/TestSettingsStrategy-suite=unlink-case=should_not_be_able_to_unlink_the_last_remaining_connection-flow=json.json @@ -65,6 +65,8 @@ "type": "password", "required": true, "autocomplete": "new-password", + "minlength": 8, + "maxlength": 1024, "disabled": false, "node_type": "input" }, diff --git a/selfservice/strategy/password/login.go b/selfservice/strategy/password/login.go index 8c91d7e6c4f9..a2eba067a445 100644 --- a/selfservice/strategy/password/login.go +++ b/selfservice/strategy/password/login.go @@ -69,6 +69,12 @@ func (s *Strategy) Login(w http.ResponseWriter, r *http.Request, f *login.Flow, return nil, s.handleLoginError(w, r, f, &p, err) } + // Reduce possibility of DDoS attacks with long password + if len([]byte(p.Password)) > int(s.d.Config().PasswordPolicyConfig(r.Context()).MaxPasswordLength) { + time.Sleep(x.RandomDelay(s.d.Config().HasherArgon2(r.Context()).ExpectedDuration, s.d.Config().HasherArgon2(r.Context()).ExpectedDeviation)) + return nil, s.handleLoginError(w, r, f, &p, errors.WithStack(schema.NewInvalidCredentialsError())) + } + i, c, err := s.d.PrivilegedIdentityPool().FindByCredentialsIdentifier(r.Context(), s.ID(), stringsx.Coalesce(p.Identifier, p.LegacyIdentifier)) if err != nil { time.Sleep(x.RandomDelay(s.d.Config().HasherArgon2(r.Context()).ExpectedDuration, s.d.Config().HasherArgon2(r.Context()).ExpectedDeviation)) @@ -161,7 +167,7 @@ func (s *Strategy) PopulateLoginMethod(r *http.Request, requestedAAL identity.Au } sr.UI.SetCSRF(s.d.GenerateCSRFToken(r)) - sr.UI.SetNode(NewPasswordNode("password", node.InputAttributeAutocompleteCurrentPassword)) + sr.UI.SetNode(NewPasswordNode("password", node.InputAttributeAutocompleteCurrentPassword, s.d.Config().PasswordPolicyConfig(r.Context()))) sr.UI.GetNodes().Append(node.NewInputField("method", "password", node.PasswordGroup, node.InputAttributeTypeSubmit).WithMetaLabel(text.NewInfoLogin())) return nil diff --git a/selfservice/strategy/password/nodes.go b/selfservice/strategy/password/nodes.go index f45a9a5f3ba4..54c0d2be216f 100644 --- a/selfservice/strategy/password/nodes.go +++ b/selfservice/strategy/password/nodes.go @@ -4,15 +4,18 @@ package password import ( + "github.com/ory/kratos/driver/config" "github.com/ory/kratos/text" "github.com/ory/kratos/ui/node" ) -func NewPasswordNode(name string, autocomplete node.UiNodeInputAttributeAutocomplete) *node.Node { +func NewPasswordNode(name string, autocomplete node.UiNodeInputAttributeAutocomplete, passwordPolicy *config.PasswordPolicy) *node.Node { return node.NewInputField(name, nil, node.PasswordGroup, node.InputAttributeTypePassword, node.WithRequiredInputAttribute, node.WithInputAttributes(func(a *node.InputAttributes) { + a.MinLength = passwordPolicy.MinPasswordLength + a.MaxLength = passwordPolicy.MaxPasswordLength a.Autocomplete = autocomplete })). WithMetaLabel(text.NewInfoNodeInputPassword()) diff --git a/selfservice/strategy/password/registration.go b/selfservice/strategy/password/registration.go index 55970fdf0b5e..3f32a0afaadc 100644 --- a/selfservice/strategy/password/registration.go +++ b/selfservice/strategy/password/registration.go @@ -188,7 +188,7 @@ func (s *Strategy) PopulateRegistrationMethod(r *http.Request, f *registration.F } f.UI.SetCSRF(s.d.GenerateCSRFToken(r)) - f.UI.Nodes.Upsert(NewPasswordNode("password", node.InputAttributeAutocompleteNewPassword)) + f.UI.Nodes.Upsert(NewPasswordNode("password", node.InputAttributeAutocompleteNewPassword, s.d.Config().PasswordPolicyConfig(r.Context()))) f.UI.Nodes.Append(node.NewInputField("method", "password", node.PasswordGroup, node.InputAttributeTypeSubmit).WithMetaLabel(text.NewInfoRegistration())) return nil diff --git a/selfservice/strategy/password/settings.go b/selfservice/strategy/password/settings.go index f763163d3180..8b84d151ef7b 100644 --- a/selfservice/strategy/password/settings.go +++ b/selfservice/strategy/password/settings.go @@ -169,7 +169,7 @@ func (s *Strategy) continueSettingsFlow( func (s *Strategy) PopulateSettingsMethod(r *http.Request, _ *identity.Identity, f *settings.Flow) error { f.UI.SetCSRF(s.d.GenerateCSRFToken(r)) - f.UI.Nodes.Upsert(NewPasswordNode("password", node.InputAttributeAutocompleteNewPassword).WithMetaLabel(text.NewInfoNodeInputPassword())) + f.UI.Nodes.Upsert(NewPasswordNode("password", node.InputAttributeAutocompleteNewPassword, s.d.Config().PasswordPolicyConfig(r.Context())).WithMetaLabel(text.NewInfoNodeInputPassword())) f.UI.Nodes.Append(node.NewInputField("method", "password", node.PasswordGroup, node.InputAttributeTypeSubmit).WithMetaLabel(text.NewInfoNodeLabelSave())) return nil diff --git a/selfservice/strategy/password/validator.go b/selfservice/strategy/password/validator.go index be1aac3c3497..3925cf8a1fad 100644 --- a/selfservice/strategy/password/validator.go +++ b/selfservice/strategy/password/validator.go @@ -184,6 +184,10 @@ func (s *DefaultPasswordValidator) validate(ctx context.Context, identifier, pas return text.NewErrorValidationPasswordMinLength(int(passwordPolicyConfig.MinPasswordLength), len(password)) } + if len(password) > int(passwordPolicyConfig.MaxPasswordLength) { + return errors.Errorf("password length cannot exceed %d characters", passwordPolicyConfig.MaxPasswordLength) + } + if passwordPolicyConfig.IdentifierSimilarityCheckEnabled && len(identifier) > 0 { compIdentifier, compPassword := strings.ToLower(identifier), strings.ToLower(password) dist := levenshtein.Distance(compIdentifier, compPassword) diff --git a/selfservice/strategy/profile/.snapshots/TestStrategyTraits-description=hydrate_the_proper_fields-type=api#01.json b/selfservice/strategy/profile/.snapshots/TestStrategyTraits-description=hydrate_the_proper_fields-type=api#01.json index d6665e756663..411d6bea2452 100644 --- a/selfservice/strategy/profile/.snapshots/TestStrategyTraits-description=hydrate_the_proper_fields-type=api#01.json +++ b/selfservice/strategy/profile/.snapshots/TestStrategyTraits-description=hydrate_the_proper_fields-type=api#01.json @@ -113,6 +113,8 @@ { "attributes": { "autocomplete": "new-password", + "minlength": 8, + "maxlength": 1024, "disabled": false, "name": "password", "node_type": "input", diff --git a/selfservice/strategy/profile/.snapshots/TestStrategyTraits-description=hydrate_the_proper_fields-type=api.json b/selfservice/strategy/profile/.snapshots/TestStrategyTraits-description=hydrate_the_proper_fields-type=api.json index d6665e756663..411d6bea2452 100644 --- a/selfservice/strategy/profile/.snapshots/TestStrategyTraits-description=hydrate_the_proper_fields-type=api.json +++ b/selfservice/strategy/profile/.snapshots/TestStrategyTraits-description=hydrate_the_proper_fields-type=api.json @@ -113,6 +113,8 @@ { "attributes": { "autocomplete": "new-password", + "minlength": 8, + "maxlength": 1024, "disabled": false, "name": "password", "node_type": "input", diff --git a/selfservice/strategy/profile/.snapshots/TestStrategyTraits-description=hydrate_the_proper_fields-type=browser.json b/selfservice/strategy/profile/.snapshots/TestStrategyTraits-description=hydrate_the_proper_fields-type=browser.json index d6665e756663..411d6bea2452 100644 --- a/selfservice/strategy/profile/.snapshots/TestStrategyTraits-description=hydrate_the_proper_fields-type=browser.json +++ b/selfservice/strategy/profile/.snapshots/TestStrategyTraits-description=hydrate_the_proper_fields-type=browser.json @@ -113,6 +113,8 @@ { "attributes": { "autocomplete": "new-password", + "minlength": 8, + "maxlength": 1024, "disabled": false, "name": "password", "node_type": "input", diff --git a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_does_not_exist_when_passwordless_is_disabled-browser.json b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_does_not_exist_when_passwordless_is_disabled-browser.json index cd42a6256ce0..0c9a1000f2de 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_does_not_exist_when_passwordless_is_disabled-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_does_not_exist_when_passwordless_is_disabled-browser.json @@ -28,6 +28,8 @@ { "attributes": { "autocomplete": "new-password", + "minlength": 8, + "maxlength": 1024, "disabled": false, "name": "password", "node_type": "input", diff --git a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_does_not_exist_when_passwordless_is_disabled-spa.json b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_does_not_exist_when_passwordless_is_disabled-spa.json index cd42a6256ce0..0c9a1000f2de 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_does_not_exist_when_passwordless_is_disabled-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_does_not_exist_when_passwordless_is_disabled-spa.json @@ -28,6 +28,8 @@ { "attributes": { "autocomplete": "new-password", + "minlength": 8, + "maxlength": 1024, "disabled": false, "name": "password", "node_type": "input", diff --git a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-browser.json b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-browser.json index 14a920d0a18d..c4b9c9b20bb3 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-browser.json +++ b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-browser.json @@ -107,6 +107,8 @@ { "attributes": { "autocomplete": "new-password", + "minlength": 8, + "maxlength": 1024, "disabled": false, "name": "password", "node_type": "input", diff --git a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-spa.json b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-spa.json index 14a920d0a18d..c4b9c9b20bb3 100644 --- a/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-spa.json +++ b/selfservice/strategy/webauthn/.snapshots/TestRegistration-case=webauthn_button_exists-spa.json @@ -107,6 +107,8 @@ { "attributes": { "autocomplete": "new-password", + "minlength": 8, + "maxlength": 1024, "disabled": false, "name": "password", "node_type": "input", diff --git a/test/e2e/cypress/support/config.d.ts b/test/e2e/cypress/support/config.d.ts index c7e9742aed39..9e285f7928d5 100644 --- a/test/e2e/cypress/support/config.d.ts +++ b/test/e2e/cypress/support/config.d.ts @@ -139,6 +139,10 @@ export type IgnoreLookupNetworkErrors = boolean * Defines the minimum length of the password. */ export type MinimumPasswordLength = number +/** + * Defines the maximum length of the password. + */ +export type MaximumPasswordLength = number /** * If set to false the password validation does not check for similarity between the password and the user identifier. */ @@ -875,6 +879,7 @@ export interface PasswordConfiguration { max_breaches?: AllowPasswordBreaches ignore_network_errors?: IgnoreLookupNetworkErrors min_password_length?: MinimumPasswordLength + max_password_length?: MaximumPasswordLength identifier_similarity_check_enabled?: EnablePasswordIdentifierSimilarityCheck } export interface TOTPConfiguration { diff --git a/ui/node/attributes.go b/ui/node/attributes.go index 9611b5828dff..3af6f8364670 100644 --- a/ui/node/attributes.go +++ b/ui/node/attributes.go @@ -78,6 +78,12 @@ type InputAttributes struct { // The autocomplete attribute for the input. Autocomplete UiNodeInputAttributeAutocomplete `json:"autocomplete,omitempty"` + // The minlength attribute for the input. + MinLength uint `json:"minlength,omitempty"` + + // The maxlength attribute for the input. + MaxLength uint `json:"maxlength,omitempty"` + // The input's label text. Label *text.Message `json:"label,omitempty"` diff --git a/ui/node/fixtures/sort/expected/1.json b/ui/node/fixtures/sort/expected/1.json index 4ab9da27b06e..4338e37a7e8f 100644 --- a/ui/node/fixtures/sort/expected/1.json +++ b/ui/node/fixtures/sort/expected/1.json @@ -85,6 +85,8 @@ "attributes": { "name": "password", "type": "password", + "minlength": 8, + "maxlength": 1024, "required": true, "disabled": false, "node_type": "input" diff --git a/ui/node/fixtures/sort/expected/2.json b/ui/node/fixtures/sort/expected/2.json index a27169677eaf..9ac9f1fb4dd0 100644 --- a/ui/node/fixtures/sort/expected/2.json +++ b/ui/node/fixtures/sort/expected/2.json @@ -83,6 +83,8 @@ "attributes": { "name": "password", "type": "password", + "minlength": 8, + "maxlength": 1024, "required": true, "disabled": false, "node_type": "input" diff --git a/ui/node/fixtures/sort/expected/4.json b/ui/node/fixtures/sort/expected/4.json index ae0f68d0fb19..c5a1683ffb0b 100644 --- a/ui/node/fixtures/sort/expected/4.json +++ b/ui/node/fixtures/sort/expected/4.json @@ -63,6 +63,8 @@ "attributes": { "name": "password", "type": "password", + "minlength": 8, + "maxlength": 1024, "required": true, "disabled": false, "node_type": "input" diff --git a/ui/node/fixtures/sort/input/1.json b/ui/node/fixtures/sort/input/1.json index 74eb0b68c005..e7e63b483344 100644 --- a/ui/node/fixtures/sort/input/1.json +++ b/ui/node/fixtures/sort/input/1.json @@ -84,6 +84,8 @@ "attributes": { "name": "password", "type": "password", + "minlength": 8, + "maxlength": 1024, "required": true, "disabled": false }, diff --git a/ui/node/fixtures/sort/input/2.json b/ui/node/fixtures/sort/input/2.json index fd7688711dc7..d904d2809cb2 100644 --- a/ui/node/fixtures/sort/input/2.json +++ b/ui/node/fixtures/sort/input/2.json @@ -16,6 +16,8 @@ "attributes": { "name": "password", "type": "password", + "minlength": 8, + "maxlength": 1024, "required": true, "disabled": false }, diff --git a/ui/node/fixtures/sort/input/4.json b/ui/node/fixtures/sort/input/4.json index 98ea85794c4c..727ad7653585 100644 --- a/ui/node/fixtures/sort/input/4.json +++ b/ui/node/fixtures/sort/input/4.json @@ -57,6 +57,8 @@ "attributes": { "disabled": false, "name": "password", + "minlength": 8, + "maxlength": 1024, "required": true, "type": "password" },