Send password-reset emails
Python · Django · beginner · modification
Support has been resetting passwords by hand; this sends the reset email ourselves. The view looks the address up within the current workspace and always shows the same "check your inbox" page, so the form does not reveal which emails have accounts. The link is built from `default_token_generator` and the existing `password_reset_confirm` route, so the existing confirm view validates it. Tested with the locmem email backend: the email renders with the link, and its token passes `default_token_generator.check_token`.
Multi-tenant HR app on Django 5.1. Each workspace (`Tenant`) has exactly one `domain`: either `<slug>.acmepeople.app` or a custom domain such as `people.northwind.com`, and both are served by this app over HTTPS. `TenantMiddleware` (existing) sets `request.tenant` from the request's host and answers 404 for any host that is not a workspace domain. `accounts.User` has a `tenant` foreign key, and `(tenant, email)` is unique, case-insensitively. Sign-in and the reset confirm view look users up within `request.tenant`. Every account has a usable password (there is no SSO). `PASSWORD_RESET_TIMEOUT` is left at Django's default (3 days), and rate limiting of the form is handled at the proxy. `settings.APP_URL` is `https://app.acmepeople.com`, the "find your workspace" page of the marketing site, which is a separate site not served by this app and has no `/reset/` route. The email backend hands messages to a queue and does not fail at request time.
Requirements
- The "Forgot password" form (existing view, `PasswordResetRequestView`) now emails a reset link to the active user with that email address in the current workspace. The response is the same whether or not an account exists.
- The link must open the user's own workspace: `https://` + the workspace's domain + the path of the existing `password_reset_confirm` view (a `PasswordResetConfirmView` subclass, already in `accounts/urls.py`, whose `get_user` only finds users of `request.tenant`), with the usual `uidb64` and token from `default_token_generator`.
- The email is plain text from a template, sent through the configured email backend.
Files touched
- accounts/views.py
- accounts/emails.py
- accounts/templates/accounts/emails/password_reset.txt
--- accounts/views.py
from django.views.generic import FormView
+from .emails import send_password_reset
from .forms import PasswordResetRequestForm
+from .models import User
def form_valid(self, form):
- # Support resets passwords by hand until reset emails ship.
+ user = User.objects.filter(
+ tenant=self.request.tenant,
+ email__iexact=form.cleaned_data["email"],
+ is_active=True,
+ ).first()
+ if user is not None:
+ send_password_reset(user)
+ # Same response whether or not the address has an account.
return super().form_valid(form)
--- accounts/emails.py
+from django.conf import settings
+from django.contrib.auth.tokens import default_token_generator
+from django.core.mail import send_mail
+from django.template.loader import render_to_string
+from django.urls import reverse
+from django.utils.encoding import force_bytes
+from django.utils.http import urlsafe_base64_encode
+