|
1 | 1 | import datetime |
2 | | -import hashlib |
3 | 2 | import time |
4 | 3 | from typing import Any |
5 | 4 | from urllib.parse import urlencode |
|
17 | 16 | from django.contrib.auth.views import PasswordResetView as DjPasswordResetView |
18 | 17 | from django.contrib.messages.views import SuccessMessageMixin |
19 | 18 | from django.contrib.sites.shortcuts import get_current_site |
20 | | -from django.core.cache import cache |
21 | 19 | from django.core.exceptions import PermissionDenied |
22 | 20 | from django.core.signing import TimestampSigner |
23 | 21 | from django.db import IntegrityError |
|
88 | 86 | # never accepted by the other. |
89 | 87 | EMAIL_CHANGE_REQUEST_SALT = "hypha.users.email_change.request" |
90 | 88 | EMAIL_CHANGE_CONFIRM_SALT = "hypha.users.email_change.confirm" |
| 89 | +# Session key holding the last email change request that mails were sent for. |
| 90 | +EMAIL_CHANGE_SENT_SESSION_KEY = "email_change_sent" |
91 | 91 |
|
92 | 92 |
|
93 | 93 | @method_decorator( |
@@ -229,24 +229,17 @@ def account_email_change(request): |
229 | 229 | if updated_email == current_email: |
230 | 230 | return redirect("users:account") |
231 | 231 |
|
232 | | - # The value can only be used once, so reloading the page cannot be used to |
233 | | - # send the same mails over and over. cache.add() is an atomic check-and-set, |
234 | | - # so concurrent requests with the same value cannot both get through. |
235 | | - used_cache_key = ( |
236 | | - f"email-change-request:{hashlib.sha256(signed_value.encode()).hexdigest()}" |
237 | | - ) |
238 | | - if not cache.add(used_cache_key, True, timeout=settings.PASSWORD_RESET_TIMEOUT): |
239 | | - # Already handled, e.g. a reload or the back button: the mails are on |
240 | | - # their way, so show the same page as the first request did. |
| 232 | + # Reloading, or going back through the elevate page, must not send the same |
| 233 | + # mails again. The session is kept per user and, unlike the cache, is not |
| 234 | + # culled, so it reliably remembers the request handled last. |
| 235 | + if request.session.get(EMAIL_CHANGE_SENT_SESSION_KEY) == signed_value: |
| 236 | + # The mails are on their way, so show the same page as the first |
| 237 | + # request did. |
241 | 238 | return redirect("users:confirm_link_sent") |
242 | 239 |
|
243 | | - try: |
244 | | - _send_email_change_mails(request, current_email, updated_email) |
245 | | - except Exception: |
246 | | - # Release the value so the user can retry instead of being left with a |
247 | | - # used link and no mail. |
248 | | - cache.delete(used_cache_key) |
249 | | - raise |
| 240 | + _send_email_change_mails(request, current_email, updated_email) |
| 241 | + # Marked only once sent, so a request that failed can be retried. |
| 242 | + request.session[EMAIL_CHANGE_SENT_SESSION_KEY] = signed_value |
250 | 243 | return redirect("users:confirm_link_sent") |
251 | 244 |
|
252 | 245 |
|
|
0 commit comments