diff --git a/authentik/flows/stage.py b/authentik/flows/stage.py index 18ffc15e8aae..e110c63f2216 100644 --- a/authentik/flows/stage.py +++ b/authentik/flows/stage.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING +from django.conf import settings from django.contrib.auth.models import AnonymousUser from django.http import HttpRequest from django.http.request import QueryDict @@ -224,6 +225,14 @@ def challenge_invalid(self, response: ChallengeResponse) -> HttpResponse: full_errors[field].append(field_error) challenge_response.initial_data["response_errors"] = full_errors if not challenge_response.is_valid(): + if settings.TEST: + raise StageInvalidException( + ( + f"Invalid challenge response: \n\t{challenge_response.errors}" + f"\n\nValidated data:\n\t {challenge_response.data}" + f"\n\nInitial data:\n\t {challenge_response.initial_data}" + ), + ) self.logger.error( "f(ch): invalid challenge response", errors=challenge_response.errors, diff --git a/authentik/stages/authenticator_validate/stage.py b/authentik/stages/authenticator_validate/stage.py index bde76d37db40..01d69b4386d1 100644 --- a/authentik/stages/authenticator_validate/stage.py +++ b/authentik/stages/authenticator_validate/stage.py @@ -332,7 +332,7 @@ def get_challenge(self) -> AuthenticatorValidationChallenge: serializer = SelectableStageSerializer( data={ "pk": stage.pk, - "name": getattr(stage, "friendly_name", stage.name), + "name": getattr(stage, "friendly_name", stage.name) or stage.name, "verbose_name": str(stage._meta.verbose_name) .replace("Setup Stage", "") .strip(), diff --git a/authentik/stages/authenticator_validate/tests/test_stage.py b/authentik/stages/authenticator_validate/tests/test_stage.py index 82a2f5132251..438149a92aac 100644 --- a/authentik/stages/authenticator_validate/tests/test_stage.py +++ b/authentik/stages/authenticator_validate/tests/test_stage.py @@ -4,6 +4,7 @@ from django.test.client import RequestFactory from django.urls.base import reverse +from django.utils.timezone import now from authentik.core.tests.utils import create_test_admin_user, create_test_flow from authentik.flows.models import FlowDesignation, FlowStageBinding, NotConfiguredAction @@ -13,6 +14,7 @@ from authentik.lib.generators import generate_id, generate_key from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice from authentik.stages.authenticator_static.models import AuthenticatorStaticStage +from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage, TOTPDigits from authentik.stages.authenticator_validate.api import AuthenticatorValidateStageSerializer from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses from authentik.stages.authenticator_validate.stage import PLAN_CONTEXT_DEVICE_CHALLENGES @@ -76,8 +78,8 @@ def test_not_configured_action_multiple(self): conf_stage = AuthenticatorStaticStage.objects.create( name=generate_id(), ) - conf_stage2 = AuthenticatorStaticStage.objects.create( - name=generate_id(), + conf_stage2 = AuthenticatorTOTPStage.objects.create( + name=generate_id(), digits=TOTPDigits.SIX ) stage = AuthenticatorValidateStage.objects.create( name=generate_id(), @@ -153,10 +155,14 @@ def test_validate_selected_challenge(self): { "device_class": "static", "device_uid": "1", + "challenge": {}, + "last_used": now(), }, { "device_class": "totp", "device_uid": "2", + "challenge": {}, + "last_used": now(), }, ] session[SESSION_KEY_PLAN] = plan diff --git a/authentik/stages/identification/stage.py b/authentik/stages/identification/stage.py index a588fa0d576a..e8841bcd58e0 100644 --- a/authentik/stages/identification/stage.py +++ b/authentik/stages/identification/stage.py @@ -26,6 +26,7 @@ from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, ChallengeStageView from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_GET +from authentik.lib.avatars import DEFAULT_AVATAR from authentik.lib.utils.reflection import all_subclasses from authentik.lib.utils.urls import reverse_with_qs from authentik.root.middleware import ClientIPMiddleware @@ -224,6 +225,8 @@ def get_challenge(self) -> Challenge: "js_url": current_stage.captcha_stage.js_url, "site_key": current_stage.captcha_stage.public_key, "interactive": current_stage.captcha_stage.interactive, + "pending_user": "", + "pending_user_avatar": DEFAULT_AVATAR, } if current_stage.captcha_stage else None