Skip to content

Commit

Permalink
fix(headless): WebAuthn signup vs login-on-verification
Browse files Browse the repository at this point in the history
  • Loading branch information
pennersr committed Dec 14, 2024
1 parent 0e52607 commit 0d44b98
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 19 deletions.
20 changes: 13 additions & 7 deletions allauth/account/internal/flows/email_verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,20 @@ def login_on_verification(request, verification) -> Optional[HttpResponse]:
LoginStageController,
)

if not app_settings.LOGIN_ON_EMAIL_CONFIRMATION:
return None
if request.user.is_authenticated:
return None
stage = LoginStageController.enter(request, EmailVerificationStage.key)
if not stage or not stage.login.user:
return None
if stage.login.user.pk != verification.email_address.user_id:
if (
(
# Logging in on email verification is disabled...
not app_settings.LOGIN_ON_EMAIL_CONFIRMATION
# (but, that is only relevant for verification-by-link)
and not app_settings.EMAIL_VERIFICATION_BY_CODE_ENABLED
)
or (request.user.is_authenticated)
or (not stage or not stage.login.user)
or (stage.login.user.pk != verification.email_address.user_id)
):
if stage:
stage.abort()
return None
return stage.exit()

Expand Down
5 changes: 1 addition & 4 deletions allauth/account/stages.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
from typing import Optional

from django.http import HttpResponseRedirect
from django.urls import reverse

from allauth.account import app_settings
from allauth.account.adapter import get_adapter
from allauth.account.app_settings import EmailVerificationMethod
Expand Down Expand Up @@ -40,7 +37,7 @@ def abort(self):
from allauth.account.internal.stagekit import clear_login

clear_login(self.request)
return HttpResponseRedirect(reverse("account_login"))
return headed_redirect_response("account_login")

def is_resumable(self, request):
return True
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,7 @@ def test_add_email(
assert resp.status_code == 400


@pytest.mark.parametrize(
"login_on_email_verification,status_code", [(False, 401), (True, 200)]
)
@pytest.mark.parametrize("login_on_email_verification", [False, True])
def test_signup_with_email_verification(
db,
client,
Expand All @@ -189,11 +187,11 @@ def test_signup_with_email_verification(
headless_client,
get_last_email_verification_code,
login_on_email_verification,
status_code,
mailoutbox,
):
settings.ACCOUNT_EMAIL_VERIFICATION = "mandatory"
settings.ACCOUNT_USERNAME_REQUIRED = False
# This setting should have no affect:
settings.ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = login_on_email_verification
settings.ACCOUNT_EMAIL_VERIFICATION_BY_CODE_ENABLED = True
email = email_factory()
Expand Down Expand Up @@ -233,6 +231,6 @@ def test_signup_with_email_verification(
addr = EmailAddress.objects.get(email=email)
assert addr.verified

assert resp.status_code == status_code
assert resp.status_code == 200
data = resp.json()
assert data["meta"]["is_authenticated"] is login_on_email_verification
assert data["meta"]["is_authenticated"]
50 changes: 48 additions & 2 deletions allauth/headless/mfa/tests/test_webauthn.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import pytest

from allauth.account import app_settings
from allauth.account.authentication import AUTHENTICATION_METHODS_SESSION_KEY
from allauth.headless.constants import Flow
from allauth.mfa.models import Authenticator
Expand Down Expand Up @@ -197,27 +198,72 @@ def test_2fa_login(
]


def test_passkey_signup(client, db, webauthn_registration_bypass, headless_reverse):
@pytest.mark.parametrize("login_on_email_verification", [False, True])
def test_passkey_signup(
client,
db,
webauthn_registration_bypass,
headless_reverse,
settings,
get_last_email_verification_code,
mailoutbox,
login_on_email_verification,
):
settings.ACCOUNT_EMAIL_VERIFICATION = app_settings.EmailVerificationMethod.MANDATORY
settings.ACCOUNT_EMAIL_VERIFICATION_BY_CODE_ENABLED = True
# This setting should have no influence when verifying by code:
settings.ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = login_on_email_verification

# Initiate passkey signup
resp = client.post(
headless_reverse("headless:mfa:signup_webauthn"),
data={"email": "[email protected]", "username": "passkey"},
content_type="application/json",
)

# Email verification kicks in.
assert resp.status_code == 401
flow = [flow for flow in resp.json()["data"]["flows"] if flow.get("is_pending")][0]
pending_flows = [
flow for flow in resp.json()["data"]["flows"] if flow.get("is_pending")
]
assert len(pending_flows) == 1
flow = pending_flows[0]
assert flow["id"] == Flow.VERIFY_EMAIL.value

# Verify email.
code = get_last_email_verification_code(client, mailoutbox)
resp = client.post(
headless_reverse("headless:account:verify_email"),
data={"key": code},
content_type="application/json",
)
assert resp.status_code == 401

# Now, the webauthn signup flow is pending.
pending_flows = [
flow for flow in resp.json()["data"]["flows"] if flow.get("is_pending")
]
assert len(pending_flows) == 1
flow = pending_flows[0]
assert flow["id"] == Flow.MFA_SIGNUP_WEBAUTHN.value

# Fetch flow creation options.
resp = client.get(headless_reverse("headless:mfa:signup_webauthn"))
data = resp.json()
assert "creation_options" in data["data"]

# Create a passkey.
user = get_user_model().objects.get(email="[email protected]")
with webauthn_registration_bypass(user, True) as credential:
resp = client.put(
headless_reverse("headless:mfa:signup_webauthn"),
data={"name": "Some key", "credential": credential},
content_type="application/json",
)

# Signed up successfully.
data = resp.json()
assert resp.status_code == 200
assert data["meta"]["is_authenticated"]
authenticator = Authenticator.objects.get(user=user)
assert authenticator.wrap().name == "Some key"
4 changes: 4 additions & 0 deletions docs/account/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ Available settings:
confirming the email address **immediately after signing up**, assuming users
didn't close their browser or used some sort of private browsing mode.

Note that this setting only affects email verification by link. It has no affect in
case you turn on code based verification
(``ACCOUNT_EMAIL_VERIFICATION_BY_CODE_ENABLED``).

``ACCOUNT_LOGIN_ON_PASSWORD_RESET`` (default: ``False``)
By changing this setting to ``True``, users will automatically be logged in
once they have reset their password. By default they are redirected to the
Expand Down

0 comments on commit 0d44b98

Please sign in to comment.