-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Configure login password-reset link via PASSWORD_RESET_URL #18564
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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. | ||
|
|
||
|
Comment on lines
+844
to
+849
|
||
|
|
||
| .. setting:: GET_HELP_URL | ||
|
|
||
| GET_HELP_URL | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
| ) | ||
|
Comment on lines
+292
to
+311
|
||
| def test_login_without_configured_password_reset_url(self) -> None: | ||
| response = self.client.get(reverse("login")) | ||
| self.assertNotContains(response, reverse("password_reset")) | ||
|
|
||
|
Comment on lines
+300
to
+315
|
||
| @override_settings(RATELIMIT_ATTEMPTS=20, AUTH_LOCK_ATTEMPTS=5) | ||
| def test_login_ratelimit(self, login=False) -> None: | ||
| if login: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -364,7 +364,7 @@ | |
|
|
||
| # Populate scopes from the database | ||
| for subscription in user.subscription_set.select_related("project", "component"): | ||
| key = ( | ||
| subscription.scope, | ||
| subscription.project_id or -1, | ||
| subscription.component_id or -1, | ||
|
|
@@ -480,8 +480,8 @@ | |
| "new_backends": new_backends, | ||
| "has_email_auth": "email" in all_backends, | ||
| "auditlog": user.auditlog_set.order()[:20], | ||
| "totp_keys": user.totpdevice_set.all(), | ||
| "webauthn_keys": user.webauthncredential_set.all(), | ||
| "recovery_keys_count": StaticToken.objects.filter( | ||
| device__user=user | ||
| ).count(), | ||
|
|
@@ -728,7 +728,7 @@ | |
|
|
||
| # Filter where project is active | ||
| user_translation_ids = set( | ||
| all_changes.content() | ||
| .filter(timestamp__gte=timezone.now() - timedelta(days=90)) | ||
| .values_list("translation", flat=True) | ||
| ) | ||
|
|
@@ -743,7 +743,7 @@ | |
|
|
||
| context["page_profile"] = user.profile | ||
| # Last user activity | ||
| context["last_changes"] = all_changes.recent() | ||
| context["last_changes_url"] = urlencode({"user": user.username}) | ||
| context["page_user_translations"] = translation_prefetch_tasks( | ||
| prefetch_stats(user_translations) | ||
|
|
@@ -898,8 +898,12 @@ | |
| 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 | ||
| ) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| # 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") | ||
|
|
@@ -1318,7 +1322,7 @@ | |
| else: | ||
| user = get_object_or_404(User, username=self.kwargs["user"]) | ||
| return ( | ||
| Suggestion.objects.filter_access(self.request.user) | ||
| .filter(user=user) | ||
| .order() | ||
| ) | ||
|
|
@@ -1362,7 +1366,7 @@ | |
| return redirect_profile("#account") | ||
|
|
||
| # Block removal of last verified email | ||
| verified = VerifiedEmail.objects.filter(social__user=request.user).exclude( | ||
| social__provider=backend, social_id=association_id | ||
| ) | ||
| if not verified.exists(): | ||
|
|
@@ -1651,7 +1655,7 @@ | |
| search = form.cleaned_data.get("q", "") | ||
| if search: | ||
| users = users.search( | ||
| search, parser=form.fields["q"].parser, user=self.request.user | ||
| ) | ||
| else: | ||
| users = users.order() | ||
|
|
@@ -1746,7 +1750,7 @@ | |
| AuditLog.objects.create( | ||
| request.user, request, "twofactor-remove", device=key_name | ||
| ) | ||
| messages.success(request, self.message_remove) | ||
| elif "name" in request.POST: | ||
| obj.name = request.POST["name"] | ||
| obj.save(update_fields=["name"]) | ||
|
|
@@ -1842,7 +1846,7 @@ | |
| device=get_key_name(device), | ||
| ) | ||
| if form.cleaned_data["remove_previous"]: | ||
| for old in user.totpdevice_set.exclude(pk=device.pk): | ||
| key_name = get_key_name(old) | ||
| old.delete() | ||
| AuditLog.objects.create( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -939,3 +939,4 @@ | |
| GOOGLE_ANALYTICS_ID = None | ||
| SENTRY_DSN = None | ||
| SENTRY_ENVIRONMENT = SITE_DOMAIN | ||
| PASSWORD_RESET_URL = None | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new
PASSWORD_RESET_URLsetting documentation is missing the usual.. versionadded::directive used for other settings in this file. Also,docs/admin/config.rstappears to keep settings sections roughly ordered; consider placing this entry near the otherPASSWORD_*settings to keep the reference easy to navigate.