We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Wagtail doesn't gracefully support session invalidation on password change
According to [Django's documentation](https://docs.djangoproject.com/en/1.7/topics/auth/default/#session-invalidation-on-password-change), SessionAuthenticationMiddleware is new in Django 1.7, enabled by default, and will be mandatory in Django 2.0.

Currently, when the middleware is loaded and the user changes their password, they are immediately kicked out to the sign in screen. The user's session is most likely invalidated. This is very obtrusive and the user is not informed if their password was successfully updated. I believe the offending code is in
[account.py](https://github.com/torchbox/wagtail/blob/master/wagtail/wagtailadmin/views/account.py#L26) and attempted to modify the code from the example to make it work, but the outcome was the same:

``` python
# ...
from django.contrib.auth import update_session_auth_hash # new code
# ...
def change_password(request):
    can_change_password = request.user.has_usable_password()

    if can_change_password:
        if request.POST:
            form = SetPasswordForm(request.user, request.POST)

            if form.is_valid():
                form.save()
                update_session_auth_hash(request, form.user) # new code

                messages.success(request, _("Your password has been changed successfully!"))
                return redirect('wagtailadmin_account')
        else:
            form = SetPasswordForm(request.user)
    else:
        form = None

    return render(request, 'wagtailadmin/account/change_password.html', {
        'form': form,
        'can_change_password': can_change_password,
    })
```

I am, currently, a Django novice, so that's as far as I was able to get. Hope this is an easy fix!


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `wagtail/wagtailadmin/views/account.py`
Content:
```
1 from django.conf import settings
2 from django.shortcuts import render, redirect
3 from django.contrib import messages
4 from django.contrib.auth.forms import SetPasswordForm
5 from django.contrib.auth.views import logout as auth_logout, login as auth_login
6 from django.utils.translation import ugettext as _ 
7 from django.views.decorators.debug import sensitive_post_parameters
8 from django.views.decorators.cache import never_cache
9 
10 from wagtail.wagtailadmin import forms
11 from wagtail.wagtailusers.forms import NotificationPreferencesForm
12 from wagtail.wagtailusers.models import UserProfile
13 from wagtail.wagtailcore.models import UserPagePermissionsProxy
14 
15 
16 def account(request):
17     user_perms = UserPagePermissionsProxy(request.user)
18     show_notification_preferences = user_perms.can_edit_pages() or user_perms.can_publish_pages()
19 
20     return render(request, 'wagtailadmin/account/account.html', {
21         'show_change_password': getattr(settings, 'WAGTAIL_PASSWORD_MANAGEMENT_ENABLED', True) and request.user.has_usable_password(),
22         'show_notification_preferences': show_notification_preferences
23     })
24 
25 
26 def change_password(request):
27     can_change_password = request.user.has_usable_password()
28 
29     if can_change_password:
30         if request.POST:
31             form = SetPasswordForm(request.user, request.POST)
32 
33             if form.is_valid():
34                 form.save()
35 
36                 messages.success(request, _("Your password has been changed successfully!"))
37                 return redirect('wagtailadmin_account')
38         else:
39             form = SetPasswordForm(request.user)
40     else:
41         form = None
42 
43     return render(request, 'wagtailadmin/account/change_password.html', {
44         'form': form,
45         'can_change_password': can_change_password,
46     })
47 
48 
49 def notification_preferences(request):
50 
51     if request.POST:
52         form = NotificationPreferencesForm(request.POST, instance=UserProfile.get_for_user(request.user))
53 
54         if form.is_valid():
55             form.save()
56             messages.success(request, _("Your preferences have been updated successfully!"))
57             return redirect('wagtailadmin_account')
58     else:
59         form = NotificationPreferencesForm(instance=UserProfile.get_for_user(request.user))
60 
61     # quick-and-dirty catch-all in case the form has been rendered with no
62     # fields, as the user has no customisable permissions
63     if not form.fields:
64         return redirect('wagtailadmin_account')
65 
66     return render(request, 'wagtailadmin/account/notification_preferences.html', {
67         'form': form,
68     })
69 
70 
71 @sensitive_post_parameters()
72 @never_cache
73 def login(request):
74     if request.user.is_authenticated() and request.user.has_perm('wagtailadmin.access_admin'):
75         return redirect('wagtailadmin_home')
76     else:
77         from django.contrib.auth import get_user_model
78         return auth_login(request,
79             template_name='wagtailadmin/login.html',
80             authentication_form=forms.LoginForm,
81             extra_context={
82                 'show_password_reset': getattr(settings, 'WAGTAIL_PASSWORD_MANAGEMENT_ENABLED', True),
83                 'username_field': get_user_model().USERNAME_FIELD,
84             },
85         )
86 
87 
88 def logout(request):
89     response = auth_logout(request, next_page='wagtailadmin_login')
90 
91     # By default, logging out will generate a fresh sessionid cookie. We want to use the
92     # absence of sessionid as an indication that front-end pages are being viewed by a
93     # non-logged-in user and are therefore cacheable, so we forcibly delete the cookie here.
94     response.delete_cookie(settings.SESSION_COOKIE_NAME,
95         domain=settings.SESSION_COOKIE_DOMAIN,
96         path=settings.SESSION_COOKIE_PATH)
97 
98     # HACK: pretend that the session hasn't been modified, so that SessionMiddleware
99     # won't override the above and write a new cookie.
100     request.session.modified = False
101 
102     return response
103
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/wagtail/wagtailadmin/views/account.py b/wagtail/wagtailadmin/views/account.py
--- a/wagtail/wagtailadmin/views/account.py
+++ b/wagtail/wagtailadmin/views/account.py
@@ -3,6 +3,7 @@
 from django.contrib import messages
 from django.contrib.auth.forms import SetPasswordForm
 from django.contrib.auth.views import logout as auth_logout, login as auth_login
+from django.contrib.auth import update_session_auth_hash
 from django.utils.translation import ugettext as _ 
 from django.views.decorators.debug import sensitive_post_parameters
 from django.views.decorators.cache import never_cache
@@ -32,6 +33,7 @@
 
             if form.is_valid():
                 form.save()
+                update_session_auth_hash(request, form.user)
 
                 messages.success(request, _("Your password has been changed successfully!"))
                 return redirect('wagtailadmin_account')

{"golden_diff": "diff --git a/wagtail/wagtailadmin/views/account.py b/wagtail/wagtailadmin/views/account.py\n--- a/wagtail/wagtailadmin/views/account.py\n+++ b/wagtail/wagtailadmin/views/account.py\n@@ -3,6 +3,7 @@\n from django.contrib import messages\n from django.contrib.auth.forms import SetPasswordForm\n from django.contrib.auth.views import logout as auth_logout, login as auth_login\n+from django.contrib.auth import update_session_auth_hash\n from django.utils.translation import ugettext as _ \n from django.views.decorators.debug import sensitive_post_parameters\n from django.views.decorators.cache import never_cache\n@@ -32,6 +33,7 @@\n \n             if form.is_valid():\n                 form.save()\n+                update_session_auth_hash(request, form.user)\n \n                 messages.success(request, _(\"Your password has been changed successfully!\"))\n                 return redirect('wagtailadmin_account')\n", "issue": "Wagtail doesn't gracefully support session invalidation on password change\nAccording to [Django's documentation](https://docs.djangoproject.com/en/1.7/topics/auth/default/#session-invalidation-on-password-change), SessionAuthenticationMiddleware is new in Django 1.7, enabled by default, and will be mandatory in Django 2.0.\n\nCurrently, when the middleware is loaded and the user changes their password, they are immediately kicked out to the sign in screen. The user's session is most likely invalidated. This is very obtrusive and the user is not informed if their password was successfully updated. I believe the offending code is in\n[account.py](https://github.com/torchbox/wagtail/blob/master/wagtail/wagtailadmin/views/account.py#L26) and attempted to modify the code from the example to make it work, but the outcome was the same:\n\n``` python\n# ...\nfrom django.contrib.auth import update_session_auth_hash # new code\n# ...\ndef change_password(request):\n    can_change_password = request.user.has_usable_password()\n\n    if can_change_password:\n        if request.POST:\n            form = SetPasswordForm(request.user, request.POST)\n\n            if form.is_valid():\n                form.save()\n                update_session_auth_hash(request, form.user) # new code\n\n                messages.success(request, _(\"Your password has been changed successfully!\"))\n                return redirect('wagtailadmin_account')\n        else:\n            form = SetPasswordForm(request.user)\n    else:\n        form = None\n\n    return render(request, 'wagtailadmin/account/change_password.html', {\n        'form': form,\n        'can_change_password': can_change_password,\n    })\n```\n\nI am, currently, a Django novice, so that's as far as I was able to get. Hope this is an easy fix!\n\n", "before_files": [{"content": "from django.conf import settings\nfrom django.shortcuts import render, redirect\nfrom django.contrib import messages\nfrom django.contrib.auth.forms import SetPasswordForm\nfrom django.contrib.auth.views import logout as auth_logout, login as auth_login\nfrom django.utils.translation import ugettext as _ \nfrom django.views.decorators.debug import sensitive_post_parameters\nfrom django.views.decorators.cache import never_cache\n\nfrom wagtail.wagtailadmin import forms\nfrom wagtail.wagtailusers.forms import NotificationPreferencesForm\nfrom wagtail.wagtailusers.models import UserProfile\nfrom wagtail.wagtailcore.models import UserPagePermissionsProxy\n\n\ndef account(request):\n    user_perms = UserPagePermissionsProxy(request.user)\n    show_notification_preferences = user_perms.can_edit_pages() or user_perms.can_publish_pages()\n\n    return render(request, 'wagtailadmin/account/account.html', {\n        'show_change_password': getattr(settings, 'WAGTAIL_PASSWORD_MANAGEMENT_ENABLED', True) and request.user.has_usable_password(),\n        'show_notification_preferences': show_notification_preferences\n    })\n\n\ndef change_password(request):\n    can_change_password = request.user.has_usable_password()\n\n    if can_change_password:\n        if request.POST:\n            form = SetPasswordForm(request.user, request.POST)\n\n            if form.is_valid():\n                form.save()\n\n                messages.success(request, _(\"Your password has been changed successfully!\"))\n                return redirect('wagtailadmin_account')\n        else:\n            form = SetPasswordForm(request.user)\n    else:\n        form = None\n\n    return render(request, 'wagtailadmin/account/change_password.html', {\n        'form': form,\n        'can_change_password': can_change_password,\n    })\n\n\ndef notification_preferences(request):\n\n    if request.POST:\n        form = NotificationPreferencesForm(request.POST, instance=UserProfile.get_for_user(request.user))\n\n        if form.is_valid():\n            form.save()\n            messages.success(request, _(\"Your preferences have been updated successfully!\"))\n            return redirect('wagtailadmin_account')\n    else:\n        form = NotificationPreferencesForm(instance=UserProfile.get_for_user(request.user))\n\n    # quick-and-dirty catch-all in case the form has been rendered with no\n    # fields, as the user has no customisable permissions\n    if not form.fields:\n        return redirect('wagtailadmin_account')\n\n    return render(request, 'wagtailadmin/account/notification_preferences.html', {\n        'form': form,\n    })\n\n\n@sensitive_post_parameters()\n@never_cache\ndef login(request):\n    if request.user.is_authenticated() and request.user.has_perm('wagtailadmin.access_admin'):\n        return redirect('wagtailadmin_home')\n    else:\n        from django.contrib.auth import get_user_model\n        return auth_login(request,\n            template_name='wagtailadmin/login.html',\n            authentication_form=forms.LoginForm,\n            extra_context={\n                'show_password_reset': getattr(settings, 'WAGTAIL_PASSWORD_MANAGEMENT_ENABLED', True),\n                'username_field': get_user_model().USERNAME_FIELD,\n            },\n        )\n\n\ndef logout(request):\n    response = auth_logout(request, next_page='wagtailadmin_login')\n\n    # By default, logging out will generate a fresh sessionid cookie. We want to use the\n    # absence of sessionid as an indication that front-end pages are being viewed by a\n    # non-logged-in user and are therefore cacheable, so we forcibly delete the cookie here.\n    response.delete_cookie(settings.SESSION_COOKIE_NAME,\n        domain=settings.SESSION_COOKIE_DOMAIN,\n        path=settings.SESSION_COOKIE_PATH)\n\n    # HACK: pretend that the session hasn't been modified, so that SessionMiddleware\n    # won't override the above and write a new cookie.\n    request.session.modified = False\n\n    return response\n", "path": "wagtail/wagtailadmin/views/account.py"}], "after_files": [{"content": "from django.conf import settings\nfrom django.shortcuts import render, redirect\nfrom django.contrib import messages\nfrom django.contrib.auth.forms import SetPasswordForm\nfrom django.contrib.auth.views import logout as auth_logout, login as auth_login\nfrom django.contrib.auth import update_session_auth_hash\nfrom django.utils.translation import ugettext as _ \nfrom django.views.decorators.debug import sensitive_post_parameters\nfrom django.views.decorators.cache import never_cache\n\nfrom wagtail.wagtailadmin import forms\nfrom wagtail.wagtailusers.forms import NotificationPreferencesForm\nfrom wagtail.wagtailusers.models import UserProfile\nfrom wagtail.wagtailcore.models import UserPagePermissionsProxy\n\n\ndef account(request):\n    user_perms = UserPagePermissionsProxy(request.user)\n    show_notification_preferences = user_perms.can_edit_pages() or user_perms.can_publish_pages()\n\n    return render(request, 'wagtailadmin/account/account.html', {\n        'show_change_password': getattr(settings, 'WAGTAIL_PASSWORD_MANAGEMENT_ENABLED', True) and request.user.has_usable_password(),\n        'show_notification_preferences': show_notification_preferences\n    })\n\n\ndef change_password(request):\n    can_change_password = request.user.has_usable_password()\n\n    if can_change_password:\n        if request.POST:\n            form = SetPasswordForm(request.user, request.POST)\n\n            if form.is_valid():\n                form.save()\n                update_session_auth_hash(request, form.user)\n\n                messages.success(request, _(\"Your password has been changed successfully!\"))\n                return redirect('wagtailadmin_account')\n        else:\n            form = SetPasswordForm(request.user)\n    else:\n        form = None\n\n    return render(request, 'wagtailadmin/account/change_password.html', {\n        'form': form,\n        'can_change_password': can_change_password,\n    })\n\n\ndef notification_preferences(request):\n\n    if request.POST:\n        form = NotificationPreferencesForm(request.POST, instance=UserProfile.get_for_user(request.user))\n\n        if form.is_valid():\n            form.save()\n            messages.success(request, _(\"Your preferences have been updated successfully!\"))\n            return redirect('wagtailadmin_account')\n    else:\n        form = NotificationPreferencesForm(instance=UserProfile.get_for_user(request.user))\n\n    # quick-and-dirty catch-all in case the form has been rendered with no\n    # fields, as the user has no customisable permissions\n    if not form.fields:\n        return redirect('wagtailadmin_account')\n\n    return render(request, 'wagtailadmin/account/notification_preferences.html', {\n        'form': form,\n    })\n\n\n@sensitive_post_parameters()\n@never_cache\ndef login(request):\n    if request.user.is_authenticated() and request.user.has_perm('wagtailadmin.access_admin'):\n        return redirect('wagtailadmin_home')\n    else:\n        from django.contrib.auth import get_user_model\n        return auth_login(request,\n            template_name='wagtailadmin/login.html',\n            authentication_form=forms.LoginForm,\n            extra_context={\n                'show_password_reset': getattr(settings, 'WAGTAIL_PASSWORD_MANAGEMENT_ENABLED', True),\n                'username_field': get_user_model().USERNAME_FIELD,\n            },\n        )\n\n\ndef logout(request):\n    response = auth_logout(request, next_page='wagtailadmin_login')\n\n    # By default, logging out will generate a fresh sessionid cookie. We want to use the\n    # absence of sessionid as an indication that front-end pages are being viewed by a\n    # non-logged-in user and are therefore cacheable, so we forcibly delete the cookie here.\n    response.delete_cookie(settings.SESSION_COOKIE_NAME,\n        domain=settings.SESSION_COOKIE_DOMAIN,\n        path=settings.SESSION_COOKIE_PATH)\n\n    # HACK: pretend that the session hasn't been modified, so that SessionMiddleware\n    # won't override the above and write a new cookie.\n    request.session.modified = False\n\n    return response\n", "path": "wagtail/wagtailadmin/views/account.py"}]}