diff --git a/docs/admin/auth.rst b/docs/admin/auth.rst index 669b3358bb7f..d0551c14be8e 100644 --- a/docs/admin/auth.rst +++ b/docs/admin/auth.rst @@ -638,6 +638,9 @@ Once you have the package installed, you can hook it into the Django authenticat # Email is required for Weblate (used in VCS commits) "email": "mail", } + # Optional: route "Forgot your password?" to any service of choice + PASSWORD_RESET_URL = "https://id.example.net/password-reset/" + # Hide the registration form REGISTRATION_OPEN = False diff --git a/docs/admin/config.rst b/docs/admin/config.rst index 85acec43dc5c..77849718d2cf 100644 --- a/docs/admin/config.rst +++ b/docs/admin/config.rst @@ -836,6 +836,18 @@ Insert additional markup into the HTML header. Can be used for verification of s No sanitization is performed on the string. It is inserted as-is into the HTML header. +.. setting:: PASSWORD_RESET_URL + +PASSWORD_RESET_URL +------------------ + + URL for password reset when authentication is handled by an external identity provider + such as LDAP, SAML, or OAuth. + + When set, :guilabel:`Forgot your password?` on the sign-in page links to this URL + instead of Weblate's built-in password reset page. + + .. setting:: GET_HELP_URL GET_HELP_URL diff --git a/docs/admin/install/docker.rst b/docs/admin/install/docker.rst index 4df22ff335f0..95406131c78d 100644 --- a/docs/admin/install/docker.rst +++ b/docs/admin/install/docker.rst @@ -1865,6 +1865,10 @@ Site integration Configures :setting:`PRIVACY_URL`. +.. envvar:: WEBLATE_PASSWORD_RESET_URL + + Configures :setting:`PASSWORD_RESET_URL`. + Collecting error reports and monitoring performance +++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/weblate/accounts/tests/test_views.py b/weblate/accounts/tests/test_views.py index 45924cf57ce5..f8652f49cad2 100644 --- a/weblate/accounts/tests/test_views.py +++ b/weblate/accounts/tests/test_views.py @@ -289,6 +289,30 @@ def test_login_anonymous(self) -> None: response, "This username/password combination was not found." ) + @override_settings( + AUTHENTICATION_BACKENDS=( + "django.contrib.auth.backends.ModelBackend", + "weblate.accounts.auth.WeblateUserBackend", + ), + REGISTRATION_OPEN=False, + PASSWORD_RESET_URL="https://id.example.net/password-reset", + ) + def test_login_password_reset_url(self) -> None: + response = self.client.get(reverse("login")) + self.assertContains(response, 'href="https://id.example.net/password-reset"') + + @override_settings( + AUTHENTICATION_BACKENDS=( + "django.contrib.auth.backends.ModelBackend", + "weblate.accounts.auth.WeblateUserBackend", + ), + REGISTRATION_OPEN=False, + PASSWORD_RESET_URL=None, + ) + def test_login_without_configured_password_reset_url(self) -> None: + response = self.client.get(reverse("login")) + self.assertNotContains(response, reverse("password_reset")) + @override_settings(RATELIMIT_ATTEMPTS=20, AUTH_LOCK_ATTEMPTS=5) def test_login_ratelimit(self, login=False) -> None: if login: diff --git a/weblate/accounts/views.py b/weblate/accounts/views.py index e7bfee46954d..9fe1ec513677 100644 --- a/weblate/accounts/views.py +++ b/weblate/accounts/views.py @@ -898,8 +898,12 @@ def show_login_form(self) -> bool: def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) auth_backends = get_auth_keys() + reset_url = getattr(settings, "PASSWORD_RESET_URL", None) or getattr( + settings, "EXTERNAL_PASSWORD_RESET_URL", None + ) context["login_backends"] = [x for x in sorted(auth_backends) if x != "email"] - context["can_reset"] = self.has_email_auth + context["can_reset"] = self.has_email_auth or bool(reset_url) + context["reset_url"] = reset_url # Show login form for e-mail login or any third-party Django auth backend such as LDAP context["show_login_form"] = self.show_login_form context["title"] = gettext("Sign in") diff --git a/weblate/settings_docker.py b/weblate/settings_docker.py index 094dbdb1c664..58d05f0061d1 100644 --- a/weblate/settings_docker.py +++ b/weblate/settings_docker.py @@ -1475,7 +1475,9 @@ STATUS_URL = get_env_str("WEBLATE_STATUS_URL") LEGAL_URL = get_env_str("WEBLATE_LEGAL_URL") PRIVACY_URL = get_env_str("WEBLATE_PRIVACY_URL") - +PASSWORD_RESET_URL = get_env_str( + "WEBLATE_PASSWORD_RESET_URL", get_env_str("WEBLATE_EXTERNAL_PASSWORD_RESET_URL") +) # Third party services integration MATOMO_SITE_ID = get_env_str("WEBLATE_MATOMO_SITE_ID") MATOMO_URL = get_env_str("WEBLATE_MATOMO_URL") diff --git a/weblate/settings_example.py b/weblate/settings_example.py index 41dc84d50d6e..b3fc7d715f83 100644 --- a/weblate/settings_example.py +++ b/weblate/settings_example.py @@ -939,3 +939,4 @@ GOOGLE_ANALYTICS_ID = None SENTRY_DSN = None SENTRY_ENVIRONMENT = SITE_DOMAIN +PASSWORD_RESET_URL = None diff --git a/weblate/templates/accounts/login.html b/weblate/templates/accounts/login.html index fb6bb1299457..34f014737019 100644 --- a/weblate/templates/accounts/login.html +++ b/weblate/templates/accounts/login.html @@ -65,7 +65,9 @@