diff --git a/config.template.yml b/config.template.yml
index 8d6e1d12f..800356185 100644
--- a/config.template.yml
+++ b/config.template.yml
@@ -67,6 +67,9 @@ duo_api:
#
# There are two supported backends: 'ldap' and 'file'.
authentication_backend:
+ # Disable both the HTML element and the API for reset password functionality
+ disable_reset_password: false
+
# LDAP backend configuration.
#
# This backend allows Authelia to be scaled to more
diff --git a/docs/configuration/authentication/file.md b/docs/configuration/authentication/file.md
index fb812df6c..d89b87530 100644
--- a/docs/configuration/authentication/file.md
+++ b/docs/configuration/authentication/file.md
@@ -16,6 +16,7 @@ Configuring Authelia to use a file is done by specifying the path to the
file in the configuration file.
authentication_backend:
+ disable_reset_password: false
file:
path: /var/lib/authelia/users.yml
password_hashing:
diff --git a/docs/configuration/authentication/index.md b/docs/configuration/authentication/index.md
index 8b1fa6b32..37d6f6c91 100644
--- a/docs/configuration/authentication/index.md
+++ b/docs/configuration/authentication/index.md
@@ -12,4 +12,18 @@ There are two ways to store the users along with their password:
* LDAP: users are stored in remote servers like OpenLDAP, OpenAM or Microsoft Active Directory.
* File: users are stored in YAML file with a hashed version of their password.
-
\ No newline at end of file
+
+## Disabling Reset Password
+
+You can disable the reset password functionality for additional security as per this configuration:
+
+```yaml
+# The authentication backend to use for verifying user passwords
+# and retrieve information such as email address and groups
+# users belong to.
+#
+# There are two supported backends: 'ldap' and 'file'.
+authentication_backend:
+ # Disable both the HTML element and the API for reset password functionality
+ disable_reset_password: true
+```
\ No newline at end of file
diff --git a/docs/configuration/authentication/ldap.md b/docs/configuration/authentication/ldap.md
index 21bcc0b7b..56049f0a2 100644
--- a/docs/configuration/authentication/ldap.md
+++ b/docs/configuration/authentication/ldap.md
@@ -16,6 +16,7 @@ Configuration of the LDAP backend is done as follows
```yaml
authentication_backend:
+ disable_reset_password: false
ldap:
# The url to the ldap server. Scheme can be ldap:// or ldaps://
url: ldap://127.0.0.1
diff --git a/docs/security/measures.md b/docs/security/measures.md
index 48cd6bc16..8e88a5c3e 100644
--- a/docs/security/measures.md
+++ b/docs/security/measures.md
@@ -97,6 +97,12 @@ an attacker to intercept a link used to setup 2FA; which reduces security
## Additional security
+### Reset Password
+
+It's possible to disable the reset password functionality and is recommended for anyone
+wanting to increase security. See the [configuration](../configuration/authentication/index.md)
+for information.
+
### Session security
We have a few options to configure the security of a session. The main and most important
diff --git a/internal/configuration/schema/authentication.go b/internal/configuration/schema/authentication.go
index 9418c0309..42ebda589 100644
--- a/internal/configuration/schema/authentication.go
+++ b/internal/configuration/schema/authentication.go
@@ -60,6 +60,7 @@ var DefaultPasswordOptionsSHA512Configuration = PasswordHashingConfiguration{
// AuthenticationBackendConfiguration represents the configuration related to the authentication backend.
type AuthenticationBackendConfiguration struct {
- Ldap *LDAPAuthenticationBackendConfiguration `mapstructure:"ldap"`
- File *FileAuthenticationBackendConfiguration `mapstructure:"file"`
+ DisableResetPassword bool `mapstructure:"disable_reset_password"`
+ Ldap *LDAPAuthenticationBackendConfiguration `mapstructure:"ldap"`
+ File *FileAuthenticationBackendConfiguration `mapstructure:"file"`
}
diff --git a/internal/configuration/validator/authentication.go b/internal/configuration/validator/authentication.go
index d8ae7cb04..7eeba813e 100644
--- a/internal/configuration/validator/authentication.go
+++ b/internal/configuration/validator/authentication.go
@@ -108,10 +108,12 @@ func validateLdapAuthenticationBackend(configuration *schema.LDAPAuthenticationB
configuration.URL = validateLdapURL(configuration.URL, validator)
}
+ // TODO: see if it's possible to disable this check if disable_reset_password is set and when anonymous/user binding is supported (#101 and #387)
if configuration.User == "" {
validator.Push(errors.New("Please provide a user name to connect to the LDAP server"))
}
+ // TODO: see if it's possible to disable this check if disable_reset_password is set and when anonymous/user binding is supported (#101 and #387)
if configuration.Password == "" {
validator.Push(errors.New("Please provide a password to connect to the LDAP server"))
}
diff --git a/internal/handlers/handler_configuration.go b/internal/handlers/handler_configuration.go
index 3a2b02cd3..d4eb9aff0 100644
--- a/internal/handlers/handler_configuration.go
+++ b/internal/handlers/handler_configuration.go
@@ -4,13 +4,15 @@ import "github.com/authelia/authelia/internal/middlewares"
type ConfigurationBody struct {
GoogleAnalyticsTrackingID string `json:"ga_tracking_id,omitempty"`
- RememberMeEnabled bool `json:"remember_me_enabled"` // whether remember me is enabled or not
+ RememberMe bool `json:"remember_me"` // whether remember me is enabled or not
+ ResetPassword bool `json:"reset_password"`
}
func ConfigurationGet(ctx *middlewares.AutheliaCtx) {
body := ConfigurationBody{
GoogleAnalyticsTrackingID: ctx.Configuration.GoogleAnalyticsTrackingID,
- RememberMeEnabled: ctx.Providers.SessionProvider.RememberMe != 0,
+ RememberMe: ctx.Providers.SessionProvider.RememberMe != 0,
+ ResetPassword: !ctx.Configuration.AuthenticationBackend.DisableResetPassword,
}
ctx.SetJSONBody(body)
}
diff --git a/internal/handlers/handler_configuration_test.go b/internal/handlers/handler_configuration_test.go
index a5189241c..9c0a4cf45 100644
--- a/internal/handlers/handler_configuration_test.go
+++ b/internal/handlers/handler_configuration_test.go
@@ -29,7 +29,8 @@ func (s *ConfigurationSuite) TestShouldReturnConfiguredGATrackingID() {
expectedBody := ConfigurationBody{
GoogleAnalyticsTrackingID: GATrackingID,
- RememberMeEnabled: true,
+ RememberMe: true,
+ ResetPassword: true,
}
ConfigurationGet(s.mock.Ctx)
@@ -44,7 +45,22 @@ func (s *ConfigurationSuite) TestShouldDisableRememberMe() {
s.mock.Ctx.Configuration.Session)
expectedBody := ConfigurationBody{
GoogleAnalyticsTrackingID: GATrackingID,
- RememberMeEnabled: false,
+ RememberMe: false,
+ ResetPassword: true,
+ }
+
+ ConfigurationGet(s.mock.Ctx)
+ s.mock.Assert200OK(s.T(), expectedBody)
+}
+
+func (s *ConfigurationSuite) TestShouldDisableResetPassword() {
+ GATrackingID := "ABC"
+ s.mock.Ctx.Configuration.GoogleAnalyticsTrackingID = GATrackingID
+ s.mock.Ctx.Configuration.AuthenticationBackend.DisableResetPassword = true
+ expectedBody := ConfigurationBody{
+ GoogleAnalyticsTrackingID: GATrackingID,
+ RememberMe: true,
+ ResetPassword: false,
}
ConfigurationGet(s.mock.Ctx)
diff --git a/internal/server/server.go b/internal/server/server.go
index cec8dbb04..4206dbf54 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -41,13 +41,16 @@ func StartServer(configuration schema.Configuration, providers middlewares.Provi
router.POST("/api/firstfactor", autheliaMiddleware(handlers.FirstFactorPost))
router.POST("/api/logout", autheliaMiddleware(handlers.LogoutPost))
- // Password reset related endpoints.
- router.POST("/api/reset-password/identity/start", autheliaMiddleware(
- handlers.ResetPasswordIdentityStart))
- router.POST("/api/reset-password/identity/finish", autheliaMiddleware(
- handlers.ResetPasswordIdentityFinish))
- router.POST("/api/reset-password", autheliaMiddleware(
- handlers.ResetPasswordPost))
+ // only register endpoints if forgot password is not disabled
+ if !configuration.AuthenticationBackend.DisableResetPassword {
+ // Password reset related endpoints.
+ router.POST("/api/reset-password/identity/start", autheliaMiddleware(
+ handlers.ResetPasswordIdentityStart))
+ router.POST("/api/reset-password/identity/finish", autheliaMiddleware(
+ handlers.ResetPasswordIdentityFinish))
+ router.POST("/api/reset-password", autheliaMiddleware(
+ handlers.ResetPasswordPost))
+ }
// Information about the user
router.GET("/api/user/info", autheliaMiddleware(
diff --git a/web/src/App.tsx b/web/src/App.tsx
index 7d9135873..5c91401b1 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -56,7 +56,9 @@ const App: React.FC = () => {
-
+
diff --git a/web/src/models/Configuration.ts b/web/src/models/Configuration.ts
index c57dd9898..ab9bc123b 100644
--- a/web/src/models/Configuration.ts
+++ b/web/src/models/Configuration.ts
@@ -2,7 +2,8 @@ import { SecondFactorMethod } from "./Methods";
export interface Configuration {
ga_tracking_id: string;
- remember_me_enabled: boolean;
+ remember_me: boolean;
+ reset_password: boolean;
}
export interface ExtendedConfiguration {
diff --git a/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx b/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx
index a9f1636b0..12d95c2ec 100644
--- a/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx
+++ b/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx
@@ -12,6 +12,7 @@ import FixedTextField from "../../../components/FixedTextField";
export interface Props {
disabled: boolean;
rememberMe: boolean;
+ resetPassword: boolean;
onAuthenticationStart: () => void;
onAuthenticationFailure: () => void;
@@ -41,7 +42,7 @@ export default function (props: Props) {
const handleRememberMeChange = () => {
setRememberMe(!rememberMe);
- }
+ };
const handleSignIn = async () => {
if (username === "" || password === "") {
@@ -67,11 +68,11 @@ export default function (props: Props) {
setPassword("");
passwordRef.current.focus();
}
- }
+ };
const handleResetPasswordClick = () => {
history.push(ResetPasswordStep1Route);
- }
+ };
return (
-
- {props.rememberMe ?
-
- }
- className={style.rememberMe}
- label="Remember me"
- /> : null}
-
- Reset password?
-
-
+ {props.rememberMe || props.resetPassword ?
+
+ {props.rememberMe ?
+
+ }
+ className={style.rememberMe}
+ label="Remember me"
+ /> : null}
+ {props.resetPassword ?
+
+ Reset password?
+ : null}
+ : null}