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
-
+### 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}