Browse Source

Fix redirect-after-login when using TOTP

If user has both WebAuthn and TOTP configured,
when logging in, they will be asked to choose between
"Use security keys" and "Use authenticator app".
The "Use authenticator app" is a link to a different
page (/accounts/login/two_factor/totp/). This commit makes
sure the ?next= query parameter is preserved when navigating
to that page.

For reference, the ?next= query parameter is the URL we should
redirect to after a successful login. Use case:
User is logged out. They click on a bookmarked "Check Details"
link. They get redirected to the login form. After
entering username & password and completing 2FA,
they get redirected to the "Check Details" page they
originally wanted to visit.
pull/551/head
Pēteris Caune 3 years ago
parent
commit
f85aec225d
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
3 changed files with 20 additions and 4 deletions
  1. +8
    -0
      hc/accounts/tests/test_login_webauthn.py
  2. +9
    -1
      hc/accounts/views.py
  3. +3
    -3
      templates/accounts/login_webauthn.html

+ 8
- 0
hc/accounts/tests/test_login_webauthn.py View File

@ -33,6 +33,14 @@ class LoginWebAuthnTestCase(BaseTestCase):
r = self.client.get(self.url) r = self.client.get(self.url)
self.assertContains(r, "Use authenticator app") self.assertContains(r, "Use authenticator app")
def test_it_preserves_next_parameter_in_totp_url(self):
self.profile.totp = "0" * 32
self.profile.save()
url = self.url + "?next=" + self.channels_url
r = self.client.get(url)
self.assertContains(r, "/login/two_factor/totp/?next=" + self.channels_url)
def test_it_requires_unauthenticated_user(self): def test_it_requires_unauthenticated_user(self):
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")


+ 9
- 1
hc/accounts/views.py View File

@ -827,9 +827,17 @@ def login_webauthn(request):
options, state = FIDO2_SERVER.authenticate_begin(credentials) options, state = FIDO2_SERVER.authenticate_begin(credentials)
request.session["state"] = state request.session["state"] = state
totp_url = None
if user.profile.totp:
totp_url = reverse("hc-login-totp")
redirect_url = request.GET.get("next")
if _allow_redirect(redirect_url):
totp_url += "?next=%s" % redirect_url
ctx = { ctx = {
"options": base64.b64encode(cbor.encode(options)).decode(), "options": base64.b64encode(cbor.encode(options)).decode(),
"offer_totp": True if user.profile.totp else False,
"totp_url": totp_url,
} }
return render(request, "accounts/login_webauthn.html", ctx) return render(request, "accounts/login_webauthn.html", ctx)


+ 3
- 3
templates/accounts/login_webauthn.html View File

@ -13,7 +13,7 @@
<h1>Two-factor Authentication</h1> <h1>Two-factor Authentication</h1>
<div id="pick-method"> <div id="pick-method">
{% if offer_totp %}
{% if totp_url %}
<p>Please select how you want to authenticate.</p> <p>Please select how you want to authenticate.</p>
{% else %} {% else %}
<p> <p>
@ -26,8 +26,8 @@
id="use-key-btn" id="use-key-btn"
type="button" type="button"
class="btn btn-primary">Use security key</button> class="btn btn-primary">Use security key</button>
{% if offer_totp %}
<a href="{% url 'hc-login-totp' %}" class="btn btn-default">
{% if totp_url %}
<a href="{{ totp_url }}" class="btn btn-default">
Use authenticator app Use authenticator app
</a> </a>
{% endif %} {% endif %}


Loading…
Cancel
Save