Skip to content

Configure login password-reset link via PASSWORD_RESET_URL#18564

Open
baltenaxis wants to merge 3 commits intoWeblateOrg:mainfrom
baltenaxis:main
Open

Configure login password-reset link via PASSWORD_RESET_URL#18564
baltenaxis wants to merge 3 commits intoWeblateOrg:mainfrom
baltenaxis:main

Conversation

@baltenaxis
Copy link

Motivation

Fix issue #10101 by allowing LDAP/external-auth deployments to send users to the correct password reset page.

What this PR changes

  • Adds a configurable PASSWORD_RESET_URL for the login page reset link.
  • Keeps compatibility with existing EXTERNAL_PASSWORD_RESET_URL.
  • Updates login logic/template so “Forgot your password?” uses the configured URL, or falls back to internal password_reset when not set.
  • Adds tests for configured vs. non-configured reset URL behavior.
  • Updates docs for settings, Docker env var (WEBLATE_PASSWORD_RESET_URL), and LDAP example.

Testing

  • Added unit tests in weblate.accounts.tests.test_views for reset-link behavior.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new configurable setting to control where the “Forgot your password?” link on the login page points, primarily to support external identity providers (e.g., LDAP/SAML/OAuth) in deployments where Weblate’s internal password reset is not applicable.

Changes:

  • Introduces PASSWORD_RESET_URL (with backward-compat fallback to EXTERNAL_PASSWORD_RESET_URL) and wires it into the login view/template.
  • Adds Docker env var support via WEBLATE_PASSWORD_RESET_URL (falling back to WEBLATE_EXTERNAL_PASSWORD_RESET_URL).
  • Documents the setting/env var and adds tests for the externally-configured reset link.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
weblate/templates/accounts/login.html Uses reset_url when set; otherwise falls back to the internal password_reset route.
weblate/settings_example.py Adds PASSWORD_RESET_URL = None to the example settings.
weblate/settings_docker.py Reads PASSWORD_RESET_URL from WEBLATE_PASSWORD_RESET_URL with legacy env var fallback.
weblate/accounts/views.py Adds reset_url/can_reset context logic to support external reset links.
weblate/accounts/tests/test_views.py Adds tests around presence/absence of the reset link based on settings.
docs/admin/install/docker.rst Documents WEBLATE_PASSWORD_RESET_URL.
docs/admin/config.rst Documents the new PASSWORD_RESET_URL setting.
docs/admin/auth.rst Updates LDAP example to show PASSWORD_RESET_URL usage.

Comment on lines +292 to +311
@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,
)
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests override AUTHENTICATION_BACKENDS using override_settings, but social_core caches loaded backends globally; other tests in the repo use social_core_override_settings to clear this cache when changing AUTHENTICATION_BACKENDS. These new tests can become order-dependent/flaky unless they also clear social_core.backends.utils.BACKENDSCACHE (e.g., switch to social_core_override_settings or reset the cache in setup/teardown).

Copilot uses AI. Check for mistakes.
Comment on lines +300 to +315
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"))

Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current tests cover (a) external PASSWORD_RESET_URL displayed when e-mail auth is disabled and (b) no reset link when neither internal nor external reset is available. The PR description also promises a fallback to the internal password_reset when PASSWORD_RESET_URL is not set, but there is no test asserting that behavior when social_core.backends.email.EmailAuth is enabled. Please add a test that verifies the login page includes the internal reset URL when e-mail auth is available and PASSWORD_RESET_URL is unset.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +839 to +849
.. 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.

Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new PASSWORD_RESET_URL setting documentation is missing the usual .. versionadded:: directive used for other settings in this file. Also, docs/admin/config.rst appears to keep settings sections roughly ordered; consider placing this entry near the other PASSWORD_* settings to keep the reference easy to navigate.

Copilot uses AI. Check for mistakes.
Comment on lines +844 to +849
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.

Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces user-visible behavior/a new setting; the repo’s changelog (docs/changes.rst) typically gets an entry for such changes. Please add a short note under the appropriate rubric so administrators notice PASSWORD_RESET_URL in release notes.

Copilot uses AI. Check for mistakes.
Copy link
Member

@nijel nijel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for EXTERNAL_PASSWORD_RESET_URL? It seems just a duplicate for the same setting. Also please see the copilot feedback, I think all of that is valid.

auth_backends = get_auth_keys()
reset_url = getattr(settings, "PASSWORD_RESET_URL", None) or getattr(
settings, "EXTERNAL_PASSWORD_RESET_URL", None
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please include PASSWORD_RESET_URL in WeblateAccountsConf, so that it can be accessed directly from the settings.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be better to pass using the existing context processor in weblate/trans/context_processors.py.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants