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}