diff --git a/config.template.yml b/config.template.yml index 9687b26fc7697..042aeecdd05bd 100644 --- a/config.template.yml +++ b/config.template.yml @@ -191,11 +191,12 @@ authentication_backend: ## Disable both the HTML element and the API for reset password functionality. disable_reset_password: false - ## Enable the use of external reset password link. - enable_external_reset_password: false + ## Password Reset Options. + password_reset: - ## External reset password url that redirects the user to an external reset portal. - external_reset_password_url: "" + ## External reset password url that redirects the user to an external reset portal. This disables the internal reset + ## functionality. + custom_url: "" ## The amount of time to wait before we refresh data from the authentication backend. Uses duration notation. ## To disable this feature set it to 'disable', this will slightly reduce security because for Authelia, users will diff --git a/docs/configuration/authentication/index.md b/docs/configuration/authentication/index.md index dd1037f05a20e..be1204443226a 100644 --- a/docs/configuration/authentication/index.md +++ b/docs/configuration/authentication/index.md @@ -18,8 +18,8 @@ There are two ways to store the users along with their password: ```yaml authentication_backend: disable_reset_password: false - enable_external_reset_password: false - external_reset_password_url: "" + password_reset: + custom_url: "" file: {} ldap: {} ``` @@ -38,29 +38,20 @@ required: no This setting controls if users can reset their password from the web frontend or not. -### enable_external_reset_password -
-type: boolean -{: .label .label-config .label-purple } -default: false -{: .label .label-config .label-blue } -required: no -{: .label .label-config .label-green } -
+### password_reset -This setting controls if users can reset their password from an external reset password portal. - -### external_reset_password_url +#### custom_url
type: string {: .label .label-config .label-purple } default: "" {: .label .label-config .label-blue } -required: no (yes if enable_external_reset_password is true) +required: no {: .label .label-config .label-green }
-The custom reset password URL, which redirects users to the custom reset password portal. +The custom password reset URL. This replaces the inbuilt password reset functionality and disables the endpoints if +this is configured to anything other than nothing or an empty string. ### file diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml index 9687b26fc7697..042aeecdd05bd 100644 --- a/internal/configuration/config.template.yml +++ b/internal/configuration/config.template.yml @@ -191,11 +191,12 @@ authentication_backend: ## Disable both the HTML element and the API for reset password functionality. disable_reset_password: false - ## Enable the use of external reset password link. - enable_external_reset_password: false + ## Password Reset Options. + password_reset: - ## External reset password url that redirects the user to an external reset portal. - external_reset_password_url: "" + ## External reset password url that redirects the user to an external reset portal. This disables the internal reset + ## functionality. + custom_url: "" ## The amount of time to wait before we refresh data from the authentication backend. Uses duration notation. ## To disable this feature set it to 'disable', this will slightly reduce security because for Authelia, users will diff --git a/internal/configuration/schema/authentication.go b/internal/configuration/schema/authentication.go index 538b6d29fddba..07e6f56dedf6f 100644 --- a/internal/configuration/schema/authentication.go +++ b/internal/configuration/schema/authentication.go @@ -1,6 +1,9 @@ package schema -import "time" +import ( + "net/url" + "time" +) // LDAPAuthenticationBackendConfiguration represents the configuration related to LDAP server. type LDAPAuthenticationBackendConfiguration struct { @@ -45,12 +48,18 @@ type PasswordConfiguration struct { // AuthenticationBackendConfiguration represents the configuration related to the authentication backend. type AuthenticationBackendConfiguration struct { - DisableResetPassword bool `koanf:"disable_reset_password"` - EnableExternalResetPassword bool `koanf:"enable_external_reset_password"` - ExternalResetPasswordURL string `koanf:"external_reset_password_url"` - RefreshInterval string `koanf:"refresh_interval"` - LDAP *LDAPAuthenticationBackendConfiguration `koanf:"ldap"` - File *FileAuthenticationBackendConfiguration `koanf:"file"` + LDAP *LDAPAuthenticationBackendConfiguration `koanf:"ldap"` + File *FileAuthenticationBackendConfiguration `koanf:"file"` + + PasswordReset PasswordResetAuthenticationBackendConfiguration `koanf:"password_reset"` + + DisableResetPassword bool `koanf:"disable_reset_password"` + RefreshInterval string `koanf:"refresh_interval"` +} + +// PasswordResetAuthenticationBackendConfiguration represents the configuration related to password reset functionality. +type PasswordResetAuthenticationBackendConfiguration struct { + CustomURL url.URL `koanf:"custom_url"` } // DefaultPasswordConfiguration represents the default configuration related to Argon2id hashing. diff --git a/internal/configuration/validator/access_control_test.go b/internal/configuration/validator/access_control_test.go index 3ce26fedfec00..5af97a0a9c739 100644 --- a/internal/configuration/validator/access_control_test.go +++ b/internal/configuration/validator/access_control_test.go @@ -32,8 +32,8 @@ func (suite *AccessControl) SetupTest() { func (suite *AccessControl) TestShouldValidateCompleteConfiguration() { ValidateAccessControl(suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) } func (suite *AccessControl) TestShouldValidateEitherDomainsOrDomainsRegex() { @@ -55,7 +55,7 @@ func (suite *AccessControl) TestShouldValidateEitherDomainsOrDomainsRegex() { ValidateRules(suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) assert.EqualError(suite.T(), suite.validator.Errors()[0], "access control: rule #3: rule is invalid: must have the option 'domain' or 'domain_regex' configured") @@ -66,7 +66,7 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidDefaultPolicy() { ValidateAccessControl(suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "access control: option 'default_policy' must be one of 'bypass', 'one_factor', 'two_factor', 'deny' but it is configured as 'invalid'") @@ -82,7 +82,7 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidNetworkGroupNetwork() { ValidateAccessControl(suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "access control: networks: network group 'internal' is invalid: the network 'abc.def.ghi.jkl' is not a valid IP or CIDR notation") @@ -93,7 +93,7 @@ func (suite *AccessControl) TestShouldRaiseErrorWithNoRulesDefined() { ValidateRules(suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "access control: 'default_policy' option 'deny' is invalid: when no rules are specified it must be 'two_factor' or 'one_factor'") @@ -106,7 +106,7 @@ func (suite *AccessControl) TestShouldRaiseWarningWithNoRulesDefined() { ValidateRules(suite.config, suite.validator) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Errors(), 0) suite.Require().Len(suite.validator.Warnings(), 1) suite.Assert().EqualError(suite.validator.Warnings()[0], "access control: no rules have been specified so the 'default_policy' of 'two_factor' is going to be applied to all requests") @@ -122,7 +122,7 @@ func (suite *AccessControl) TestShouldRaiseErrorsWithEmptyRules() { ValidateRules(suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 4) suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1: rule is invalid: must have the option 'domain' or 'domain_regex' configured") @@ -141,7 +141,7 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidPolicy() { ValidateRules(suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): rule 'policy' option 'invalid' is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'") @@ -158,7 +158,7 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidNetwork() { ValidateRules(suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): the network 'abc.def.ghi.jkl/32' is not a valid Group Name, IP, or CIDR notation") @@ -175,7 +175,7 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidMethod() { ValidateRules(suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): 'methods' option 'HOP' is invalid: must be one of 'GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'TRACE', 'CONNECT', 'OPTIONS', 'COPY', 'LOCK', 'MKCOL', 'MOVE', 'PROPFIND', 'PROPPATCH', 'UNLOCK'") diff --git a/internal/configuration/validator/authentication.go b/internal/configuration/validator/authentication.go index 2ac94d9cb2bb1..c6af945942434 100644 --- a/internal/configuration/validator/authentication.go +++ b/internal/configuration/validator/authentication.go @@ -1,7 +1,6 @@ package validator import ( - "errors" "fmt" "net/url" "strings" @@ -10,7 +9,7 @@ import ( "github.com/authelia/authelia/v4/internal/utils" ) -// ValidateAuthenticationBackend validates and updates the authentication backend config. +// ValidateAuthenticationBackend validates and updates the authentication backend configuration. func ValidateAuthenticationBackend(config *schema.AuthenticationBackendConfiguration, validator *schema.StructValidator) { if config.LDAP == nil && config.File == nil { validator.Push(fmt.Errorf(errFmtAuthBackendNotConfigured)) @@ -35,19 +34,17 @@ func ValidateAuthenticationBackend(config *schema.AuthenticationBackendConfigura } } - if config.EnableExternalResetPassword && !config.DisableResetPassword { - validator.Push(errors.New("You cannot enable both `internal reset password` and `external reset password` processes in `authentication_backend`")) - } - - if config.EnableExternalResetPassword { - err := utils.IsStringAbsURL(config.ExternalResetPasswordURL) - if err != nil { - validator.Push(fmt.Errorf("Provided reset url is invalid. Error from parser: %s", err)) + if config.PasswordReset.CustomURL.String() != "" { + switch config.PasswordReset.CustomURL.Scheme { + case schemeHTTP, schemeHTTPS: + config.DisableResetPassword = false + default: + validator.Push(fmt.Errorf(errFmtAuthBackendPasswordResetCustomURLScheme, config.PasswordReset.CustomURL.String(), config.PasswordReset.CustomURL.Scheme)) } } } -// validateFileAuthenticationBackend validates and updates the file authentication backend config. +// validateFileAuthenticationBackend validates and updates the file authentication backend configuration. func validateFileAuthenticationBackend(config *schema.FileAuthenticationBackendConfiguration, validator *schema.StructValidator) { if config.Path == "" { validator.Push(fmt.Errorf(errFmtFileAuthBackendPathNotConfigured)) diff --git a/internal/configuration/validator/authentication_test.go b/internal/configuration/validator/authentication_test.go index 6820c46bcd751..a575a231cb463 100644 --- a/internal/configuration/validator/authentication_test.go +++ b/internal/configuration/validator/authentication_test.go @@ -1,6 +1,7 @@ package validator import ( + "net/url" "testing" "time" @@ -58,8 +59,8 @@ func (suite *FileBasedAuthenticationBackend) SetupTest() { func (suite *FileBasedAuthenticationBackend) TestShouldValidateCompleteConfiguration() { ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) } func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenNoPathProvided() { @@ -67,7 +68,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenNoPathProvi ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: option 'path' is required") @@ -79,7 +80,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenMemoryNotMo ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'memory' must at least be parallelism multiplied by 8 when using algorithm 'argon2id' with parallelism 2 it should be at least 16 but it is configured as '8'") @@ -97,7 +98,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultConfigurationWh ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Assert().Len(suite.validator.Errors(), 0) suite.Assert().Equal(schema.DefaultPasswordConfiguration.KeyLength, suite.config.File.Password.KeyLength) @@ -115,7 +116,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultConfigurationWh ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Assert().Len(suite.validator.Errors(), 0) suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.KeyLength, suite.config.File.Password.KeyLength) @@ -130,7 +131,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenKeyLengthTo ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'key_length' must be 16 or more when using algorithm 'argon2id' but it is configured as '1'") @@ -141,7 +142,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenSaltLengthT ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'salt_length' must be 2 or more but it is configured a '-1'") @@ -152,7 +153,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenBadAlgorith ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'algorithm' must be either 'argon2id' or 'sha512' but it is configured as 'bogus'") @@ -163,7 +164,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenIterationsT ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'iterations' must be 1 or more but it is configured as '-1'") @@ -174,7 +175,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenParallelism ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'parallelism' must be 1 or more when using algorithm 'argon2id' but it is configured as '-1'") @@ -189,8 +190,8 @@ func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultValues() { ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) suite.Assert().Equal(schema.DefaultPasswordConfiguration.Algorithm, suite.config.File.Password.Algorithm) suite.Assert().Equal(schema.DefaultPasswordConfiguration.Iterations, suite.config.File.Password.Iterations) @@ -226,21 +227,47 @@ func (suite *LDAPAuthenticationBackendSuite) SetupTest() { func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateCompleteConfiguration() { ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) } func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenResetURLIsInvalid() { - suite.config.EnableExternalResetPassword = true + suite.config.PasswordReset.CustomURL = url.URL{Scheme: "ldap", Host: "google.com"} suite.config.DisableResetPassword = true - suite.config.ExternalResetPasswordURL = "example.com" //nolint:goconst + + suite.Assert().True(suite.config.DisableResetPassword) ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "Provided reset url is invalid. Error from parser: the url 'example.com' is not absolute because it doesn't start with a scheme like 'http://' or 'https://'") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: password_reset: option 'custom_url' is configured to 'ldap://google.com' which has the scheme 'ldap' but the scheme must be either 'http' or 'https'") + + suite.Assert().True(suite.config.DisableResetPassword) +} + +func (suite *FileBasedAuthenticationBackend) TestShouldNotRaiseErrorWhenResetURLIsValid() { + suite.config.PasswordReset.CustomURL = url.URL{Scheme: "https", Host: "google.com"} + + ValidateAuthenticationBackend(&suite.config, suite.validator) + + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) +} + +func (suite *FileBasedAuthenticationBackend) TestShouldConfigureDisableResetPasswordWhenCustomURL() { + suite.config.PasswordReset.CustomURL = url.URL{Scheme: "https", Host: "google.com"} + suite.config.DisableResetPassword = true + + suite.Assert().True(suite.config.DisableResetPassword) + + ValidateAuthenticationBackend(&suite.config, suite.validator) + + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) + + suite.Assert().False(suite.config.DisableResetPassword) } func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateDefaultImplementationAndUsernameAttribute() { @@ -251,8 +278,8 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateDefaultImplementa suite.Assert().Equal(schema.LDAPImplementationCustom, suite.config.LDAP.Implementation) suite.Assert().Equal(suite.config.LDAP.UsernameAttribute, schema.DefaultLDAPAuthenticationBackendConfiguration.UsernameAttribute) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) } func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenImplementationIsInvalidMSAD() { @@ -260,7 +287,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenImplementat ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'implementation' is configured as 'masd' but must be one of the following values: 'custom', 'activedirectory'") @@ -270,7 +297,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenURLNotProvi suite.config.LDAP.URL = "" ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'url' is required") @@ -281,7 +308,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenUserNotProv ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'user' is required") @@ -292,7 +319,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenPasswordNot ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'password' is required") @@ -303,7 +330,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenBaseDNNotPr ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Assert().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'base_dn' is required") @@ -314,7 +341,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseOnEmptyGroupsFilter( ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'groups_filter' is required") @@ -325,7 +352,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseOnEmptyUsersFilter() ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' is required") @@ -336,8 +363,8 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldNotRaiseOnEmptyUsernameAt ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) } func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseOnBadRefreshInterval() { @@ -345,7 +372,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseOnBadRefreshInterval ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: option 'refresh_interval' is configured to 'blah' but it must be either a duration notation or one of 'disable', or 'always': could not parse 'blah' as a duration") @@ -354,8 +381,8 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseOnBadRefreshInterval func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultImplementation() { ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) suite.Assert().Equal(schema.LDAPImplementationCustom, suite.config.LDAP.Implementation) } @@ -366,7 +393,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorOnBadFilterPlac ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Assert().True(suite.validator.HasErrors()) suite.Require().Len(suite.validator.Errors(), 4) @@ -379,8 +406,8 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorOnBadFilterPlac func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultGroupNameAttribute() { ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) suite.Assert().Equal("cn", suite.config.LDAP.GroupNameAttribute) } @@ -388,8 +415,8 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultGroupNameAttrib func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultMailAttribute() { ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) suite.Assert().Equal("mail", suite.config.LDAP.MailAttribute) } @@ -397,8 +424,8 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultMailAttribute() func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultDisplayNameAttribute() { ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) suite.Assert().Equal("displayName", suite.config.LDAP.DisplayNameAttribute) } @@ -406,8 +433,8 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultDisplayNameAttr func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultRefreshInterval() { ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) suite.Assert().Equal("5m", suite.config.RefreshInterval) } @@ -417,7 +444,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseWhenUsersFilterDoesN ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain enclosing parenthesis: '{username_attribute}={input}' should probably be '({username_attribute}={input})'") @@ -428,7 +455,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseWhenGroupsFilterDoes ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'groups_filter' must contain enclosing parenthesis: 'cn={input}' should probably be '(cn={input})'") @@ -438,7 +465,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseWhenUsersFilterDoesN suite.config.LDAP.UsersFilter = "(&({mail_attribute}={input})(objectClass=person))" ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{username_attribute}' but it is required") @@ -449,7 +476,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldHelpDetectNoInputPlacehol ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{input}' but it is required") @@ -460,8 +487,8 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultTLSMinimumVersi ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) suite.Assert().Equal(schema.DefaultLDAPAuthenticationBackendConfiguration.TLS.MinimumVersion, suite.config.LDAP.TLS.MinimumVersion) } @@ -473,7 +500,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldNotAllowInvalidTLSValue() ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: tls: option 'minimum_tls_version' is invalid: SSL2.0: supplied tls version isn't supported") @@ -504,8 +531,8 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) SetupTest() { func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldSetActiveDirectoryDefaults() { ValidateAuthenticationBackend(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) suite.Assert().Equal( schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout, diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go index 524750e92f942..75a79486d2959 100644 --- a/internal/configuration/validator/const.go +++ b/internal/configuration/validator/const.go @@ -69,6 +69,8 @@ const ( "backend is configured" errFmtAuthBackendRefreshInterval = "authentication_backend: option 'refresh_interval' is configured to '%s' but " + "it must be either a duration notation or one of 'disable', or 'always': %w" + errFmtAuthBackendPasswordResetCustomURLScheme = "authentication_backend: password_reset: option 'custom_url' is" + + " configured to '%s' which has the scheme '%s' but the scheme must be either 'http' or 'https'" errFmtFileAuthBackendPathNotConfigured = "authentication_backend: file: option 'path' is required" errFmtFileAuthBackendPasswordSaltLength = "authentication_backend: file: password: option 'salt_length' " + @@ -424,8 +426,7 @@ var ValidKeys = []string{ // Authentication Backend Keys. "authentication_backend.disable_reset_password", - "authentication_backend.enable_external_reset_password", - "authentication_backend.external_reset_password_url", + "authentication_backend.password_reset.custom_url", "authentication_backend.refresh_interval", // LDAP Authentication Backend Keys. diff --git a/internal/configuration/validator/notifier_test.go b/internal/configuration/validator/notifier_test.go index 7e7eb8d942896..3316e06330e23 100644 --- a/internal/configuration/validator/notifier_test.go +++ b/internal/configuration/validator/notifier_test.go @@ -34,14 +34,14 @@ func (suite *NotifierSuite) SetupTest() { func (suite *NotifierSuite) TestShouldEnsureAtLeastSMTPOrFilesystemIsProvided() { ValidateNotifier(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) suite.config.SMTP = nil ValidateNotifier(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().True(suite.validator.HasErrors()) suite.Assert().Len(suite.validator.Errors(), 1) @@ -52,7 +52,7 @@ func (suite *NotifierSuite) TestShouldEnsureAtLeastSMTPOrFilesystemIsProvided() func (suite *NotifierSuite) TestShouldEnsureEitherSMTPOrFilesystemIsProvided() { ValidateNotifier(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Errors(), 0) suite.config.FileSystem = &schema.FileSystemNotifierConfiguration{ Filename: "test", @@ -60,7 +60,7 @@ func (suite *NotifierSuite) TestShouldEnsureEitherSMTPOrFilesystemIsProvided() { ValidateNotifier(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().True(suite.validator.HasErrors()) suite.Assert().Len(suite.validator.Errors(), 1) @@ -74,8 +74,8 @@ func (suite *NotifierSuite) TestShouldEnsureEitherSMTPOrFilesystemIsProvided() { func (suite *NotifierSuite) TestSMTPShouldSetTLSDefaults() { ValidateNotifier(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) suite.Assert().Equal("example.com", suite.config.SMTP.TLS.ServerName) suite.Assert().Equal("TLS1.2", suite.config.SMTP.TLS.MinimumVersion) @@ -90,8 +90,8 @@ func (suite *NotifierSuite) TestSMTPShouldDefaultTLSServerNameToHost() { ValidateNotifier(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) suite.Assert().Equal("google.com", suite.config.SMTP.TLS.ServerName) suite.Assert().Equal("TLS1.1", suite.config.SMTP.TLS.MinimumVersion) @@ -102,15 +102,15 @@ func (suite *NotifierSuite) TestSMTPShouldEnsureHostAndPortAreProvided() { suite.config.FileSystem = nil ValidateNotifier(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) suite.config.SMTP.Host = "" suite.config.SMTP.Port = 0 ValidateNotifier(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Assert().True(suite.validator.HasErrors()) errors := suite.validator.Errors() @@ -126,7 +126,7 @@ func (suite *NotifierSuite) TestSMTPShouldEnsureSenderIsProvided() { ValidateNotifier(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().True(suite.validator.HasErrors()) suite.Assert().Len(suite.validator.Errors(), 1) @@ -144,14 +144,14 @@ func (suite *NotifierSuite) TestFileShouldEnsureFilenameIsProvided() { } ValidateNotifier(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) suite.config.FileSystem.Filename = "" ValidateNotifier(&suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().True(suite.validator.HasErrors()) suite.Assert().Len(suite.validator.Errors(), 1) diff --git a/internal/configuration/validator/theme_test.go b/internal/configuration/validator/theme_test.go index df279cb800861..1b29d0e38b94f 100644 --- a/internal/configuration/validator/theme_test.go +++ b/internal/configuration/validator/theme_test.go @@ -24,8 +24,8 @@ func (suite *Theme) SetupTest() { func (suite *Theme) TestShouldValidateCompleteConfiguration() { ValidateTheme(suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) } func (suite *Theme) TestShouldRaiseErrorWhenInvalidThemeProvided() { @@ -33,7 +33,7 @@ func (suite *Theme) TestShouldRaiseErrorWhenInvalidThemeProvided() { ValidateTheme(suite.config, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) suite.Assert().EqualError(suite.validator.Errors()[0], "option 'theme' must be one of 'light', 'dark', 'grey', 'auto' but it is configured as 'invalid'") diff --git a/internal/server/server.go b/internal/server/server.go index 96c8c8eff89a0..b52c87001a8cb 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -23,12 +23,8 @@ func registerRoutes(configuration schema.Configuration, providers middlewares.Pr autheliaMiddleware := middlewares.AutheliaMiddleware(configuration, providers) rememberMe := strconv.FormatBool(configuration.Session.RememberMeDuration != schema.RememberMeDisabled) resetPassword := strconv.FormatBool(!configuration.AuthenticationBackend.DisableResetPassword) - externalResetPassword := strconv.FormatBool(configuration.AuthenticationBackend.EnableExternalResetPassword) - externalResetURL := "" - if configuration.AuthenticationBackend.EnableExternalResetPassword { - externalResetURL = configuration.AuthenticationBackend.ExternalResetPasswordURL - } + resetPasswordCustomURL := configuration.AuthenticationBackend.PasswordReset.CustomURL.String() duoSelfEnrollment := f if configuration.DuoAPI != nil { @@ -40,9 +36,9 @@ func registerRoutes(configuration schema.Configuration, providers middlewares.Pr https := configuration.Server.TLS.Key != "" && configuration.Server.TLS.Certificate != "" - serveIndexHandler := ServeTemplatedFile(embeddedAssets, indexFile, configuration.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, externalResetPassword, externalResetURL, configuration.Session.Name, configuration.Theme, https) - serveSwaggerHandler := ServeTemplatedFile(swaggerAssets, indexFile, configuration.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, externalResetPassword, externalResetURL, configuration.Session.Name, configuration.Theme, https) - serveSwaggerAPIHandler := ServeTemplatedFile(swaggerAssets, apiFile, configuration.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, externalResetPassword, externalResetURL, configuration.Session.Name, configuration.Theme, https) + serveIndexHandler := ServeTemplatedFile(embeddedAssets, indexFile, configuration.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, configuration.Session.Name, configuration.Theme, https) + serveSwaggerHandler := ServeTemplatedFile(swaggerAssets, indexFile, configuration.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, configuration.Session.Name, configuration.Theme, https) + serveSwaggerAPIHandler := ServeTemplatedFile(swaggerAssets, apiFile, configuration.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, configuration.Session.Name, configuration.Theme, https) r := router.New() r.GET("/", autheliaMiddleware(serveIndexHandler)) @@ -83,7 +79,8 @@ func registerRoutes(configuration schema.Configuration, providers middlewares.Pr r.POST("/api/logout", autheliaMiddleware(handlers.LogoutPost)) // Only register endpoints if forgot password is not disabled. - if !configuration.AuthenticationBackend.DisableResetPassword { + if !configuration.AuthenticationBackend.DisableResetPassword && + configuration.AuthenticationBackend.PasswordReset.CustomURL.String() == "" { // Password reset related endpoints. r.POST("/api/reset-password/identity/start", autheliaMiddleware( handlers.ResetPasswordIdentityStart)) diff --git a/internal/server/template.go b/internal/server/template.go index 120ac58f18c57..b71041bc66254 100644 --- a/internal/server/template.go +++ b/internal/server/template.go @@ -16,7 +16,7 @@ import ( // ServeTemplatedFile serves a templated version of a specified file, // this is utilised to pass information between the backend and frontend // and generate a nonce to support a restrictive CSP while using material-ui. -func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberMe, resetPassword, externalResetPassword, externalResetURL, session, theme string, https bool) middlewares.RequestHandler { +func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, session, theme string, https bool) middlewares.RequestHandler { logger := logging.Logger() a, err := assets.Open(publicDir + file) @@ -81,7 +81,7 @@ func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberM ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf(cspDefaultTemplate, nonce)) } - err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ Base, BaseURL, CSPNonce, DuoSelfEnrollment, LogoOverride, RememberMe, ResetPassword, ExternalResetPassword, ExternalResetURL, Session, Theme string }{Base: base, BaseURL: baseURL, CSPNonce: nonce, DuoSelfEnrollment: duoSelfEnrollment, LogoOverride: logoOverride, RememberMe: rememberMe, ResetPassword: resetPassword, ExternalResetPassword: externalResetPassword, ExternalResetURL: externalResetURL, Session: session, Theme: theme}) + err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ Base, BaseURL, CSPNonce, DuoSelfEnrollment, LogoOverride, RememberMe, ResetPassword, ResetPasswordCustomURL, Session, Theme string }{Base: base, BaseURL: baseURL, CSPNonce: nonce, DuoSelfEnrollment: duoSelfEnrollment, LogoOverride: logoOverride, RememberMe: rememberMe, ResetPassword: resetPassword, ResetPasswordCustomURL: resetPasswordCustomURL, Session: session, Theme: theme}) if err != nil { ctx.RequestCtx.Error("an error occurred", 503) logger.Errorf("Unable to execute template: %v", err) diff --git a/web/.env.development b/web/.env.development index a7dc2646d601d..cc016d9680103 100644 --- a/web/.env.development +++ b/web/.env.development @@ -4,6 +4,5 @@ VITE_PUBLIC_URL="" VITE_DUO_SELF_ENROLLMENT=true VITE_REMEMBER_ME=true VITE_RESET_PASSWORD=true -VITE_EXTERNAL_RESET_PASSWORD=false -VITE_EXTERNAL_RESET_URL="" +VITE_RESET_PASSWORD_CUSTOM_URL="" VITE_THEME=light \ No newline at end of file diff --git a/web/.env.production b/web/.env.production index 33a4c27a6c663..b146b3147e46c 100644 --- a/web/.env.production +++ b/web/.env.production @@ -3,6 +3,5 @@ VITE_PUBLIC_URL={{.Base}} VITE_DUO_SELF_ENROLLMENT={{.DuoSelfEnrollment}} VITE_REMEMBER_ME={{.RememberMe}} VITE_RESET_PASSWORD={{.ResetPassword}} -VITE_EXTERNAL_RESET_PASSWORD={{.ExternalResetPassword}} -VITE_EXTERNAL_RESET_URL={{.ExternalResetURL}} +VITE_RESET_PASSWORD_CUSTOM_URL={{.ResetPasswordCustomURL}} VITE_THEME={{.Theme}} \ No newline at end of file diff --git a/web/index.html b/web/index.html index 43a6af9fc9937..70970870c2736 100644 --- a/web/index.html +++ b/web/index.html @@ -1,29 +1,28 @@ - - - - - - - - - - Login - Authelia - + + + + + + + + + + Login - Authelia + - - -
- - + + +
+ + diff --git a/web/src/App.tsx b/web/src/App.tsx index ac6189c8b9aaf..a1b181b8a6b1c 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -22,8 +22,7 @@ import { getDuoSelfEnrollment, getRememberMe, getResetPassword, - getExternalResetPassword, - getExternalResetUrl, + getResetPasswordCustomURL, getTheme, } from "@utils/Configuration"; import RegisterOneTimePassword from "@views/DeviceRegistration/RegisterOneTimePassword"; @@ -87,8 +86,7 @@ const App: React.FC = () => { duoSelfEnrollment={getDuoSelfEnrollment()} rememberMe={getRememberMe()} resetPassword={getResetPassword()} - externalResetPassword={getExternalResetPassword()} - externalResetUrl={getExternalResetUrl()} + resetPasswordCustomURL={getResetPasswordCustomURL()} /> } /> diff --git a/web/src/setupTests.js b/web/src/setupTests.js index 86a94b53ff5ea..1c5931ad7692d 100644 --- a/web/src/setupTests.js +++ b/web/src/setupTests.js @@ -4,6 +4,5 @@ document.body.setAttribute("data-basepath", ""); document.body.setAttribute("data-duoselfenrollment", "true"); document.body.setAttribute("data-rememberme", "true"); document.body.setAttribute("data-resetpassword", "true"); -document.body.setAttribute("data-externalresetpassword", "false"); -document.body.setAttribute("data-externalreseturl", ""); +document.body.setAttribute("data-resetpasswordcustomurl", ""); document.body.setAttribute("data-theme", "light"); diff --git a/web/src/utils/Configuration.ts b/web/src/utils/Configuration.ts index 0de236a4ac998..25ff419d37476 100644 --- a/web/src/utils/Configuration.ts +++ b/web/src/utils/Configuration.ts @@ -23,12 +23,8 @@ export function getResetPassword() { return getEmbeddedVariable("resetpassword") === "true"; } -export function getExternalResetPassword() { - return getEmbeddedVariable("externalresetpassword") === "true"; -} - -export function getExternalResetUrl() { - return getEmbeddedVariable("externalreseturl"); +export function getResetPasswordCustomURL() { + return getEmbeddedVariable("resetpasswordcustomurl"); } export function getTheme() { diff --git a/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx b/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx index 9beb1bf0faf71..329c830e2c2dd 100644 --- a/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx +++ b/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx @@ -16,9 +16,9 @@ import { postFirstFactor } from "@services/FirstFactor"; export interface Props { disabled: boolean; rememberMe: boolean; + resetPassword: boolean; - externalResetPassword: boolean; - externalResetUrl: string; + resetPasswordCustomURL: string; onAuthenticationStart: () => void; onAuthenticationFailure: () => void; @@ -79,9 +79,11 @@ const FirstFactorForm = function (props: Props) { const handleResetPasswordClick = () => { if (props.resetPassword) { - navigate(ResetPasswordStep1Route); - } else if (props.externalResetPassword) { - window.open(props.externalResetUrl); + if (props.resetPasswordCustomURL !== "") { + window.open(props.resetPasswordCustomURL); + } else { + navigate(ResetPasswordStep1Route); + } } }; @@ -198,17 +200,6 @@ const FirstFactorForm = function (props: Props) { {translate("Reset password?")} - ) : props.externalResetPassword ? ( - - - {translate("Reset password?")} - - ) : null} diff --git a/web/src/views/LoginPortal/LoginPortal.tsx b/web/src/views/LoginPortal/LoginPortal.tsx index bdaa92e251c5e..ffc512fff9372 100644 --- a/web/src/views/LoginPortal/LoginPortal.tsx +++ b/web/src/views/LoginPortal/LoginPortal.tsx @@ -28,9 +28,9 @@ import SecondFactorForm from "@views/LoginPortal/SecondFactor/SecondFactorForm"; export interface Props { duoSelfEnrollment: boolean; rememberMe: boolean; + resetPassword: boolean; - externalResetPassword: boolean; - externalResetUrl: string; + resetPasswordCustomURL: string; } const RedirectionErrorMessage = @@ -177,8 +177,7 @@ const LoginPortal = function (props: Props) { disabled={firstFactorDisabled} rememberMe={props.rememberMe} resetPassword={props.resetPassword} - externalResetPassword={props.externalResetPassword} - externalResetUrl={props.externalResetUrl} + resetPasswordCustomURL={props.resetPasswordCustomURL} onAuthenticationStart={() => setFirstFactorDisabled(true)} onAuthenticationFailure={() => setFirstFactorDisabled(false)} onAuthenticationSuccess={handleAuthSuccess}