Skip to content

Commit

Permalink
Fixes #7 -- add tests for recovery token flow
Browse files Browse the repository at this point in the history
  • Loading branch information
sergei-maertens committed Feb 1, 2024
1 parent f7ffb30 commit c58254c
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 3 deletions.
3 changes: 0 additions & 3 deletions maykin_2fa/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
from django.contrib import admin
from django.shortcuts import redirect, resolve_url
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.views.decorators.cache import never_cache
from django.views.decorators.debug import sensitive_post_parameters

from two_factor.forms import TOTPDeviceForm
from two_factor.utils import default_device
Expand Down
14 changes: 14 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
from django_otp.plugins.otp_static.models import StaticToken
from django_otp.util import random_hex


Expand Down Expand Up @@ -34,3 +35,16 @@ def totp_device(user):
# See https://github.com/jazzband/django-two-factor-auth/blob/
# 3c4888c79e37dc4c137bbccafb5680c1e4b74eaa/tests/test_views_login.py#L225
return user.totpdevice_set.create(name="default", key=random_hex())


@pytest.fixture
def recovery_codes(user) -> list[str]:
"""
Ensure (backup) recovery codes are generated for the user fixture.
"""
device = user.staticdevice_set.create(name="backup")
tokens = [
StaticToken(device=device, token=StaticToken.random_token()) for _ in range(5)
]
StaticToken.objects.bulk_create(tokens)
return [token.token for token in tokens]
61 changes: 61 additions & 0 deletions tests/test_recovery_token_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import random

from django.urls import reverse

import pytest
from pytest_django.asserts import assertContains, assertRedirects, assertTemplateUsed


@pytest.mark.django_db
def test_non_authenticated_user_get(client):
recovery_url = reverse("maykin_2fa:recovery")
login_url = reverse("admin:login")

response = client.get(recovery_url)

# we don't care about the ?next parameter, this is not a meaningful flow
assertRedirects(response, login_url)


@pytest.mark.django_db
def test_non_authenticated_user_post(client):
recovery_url = reverse("maykin_2fa:recovery")
login_url = reverse("admin:login")

response = client.post(recovery_url)

# we don't care about the ?next parameter, this is not a meaningful flow
assertRedirects(response, login_url)


def test_recovery_token_authenticated_user(
client, user, totp_device, recovery_codes: list[str]
):
recovery_url = reverse("maykin_2fa:recovery")

# start at the login wizard
login_response = client.post(
reverse("admin:login"),
data={
"admin_login_view-current_step": "auth",
"auth-username": "johny",
"auth-password": "password",
},
)
assert login_response.status_code == 200

# check that the URL initializes on the recovery step
recovery_page = client.get(recovery_url)

assertTemplateUsed(recovery_page, "maykin_2fa/recovery_token.html")
assertContains(recovery_page, "Verify")

# enter a valid recovery token
response = client.post(
recovery_url,
data={
"admin_login_view-current_step": "backup",
"backup-otp_token": random.choice(recovery_codes),
},
)
assertRedirects(response, reverse("admin:index"))

0 comments on commit c58254c

Please sign in to comment.