diff --git a/authentik/core/expression/evaluator.py b/authentik/core/expression/evaluator.py index 71557144a..85586c840 100644 --- a/authentik/core/expression/evaluator.py +++ b/authentik/core/expression/evaluator.py @@ -8,6 +8,7 @@ from django.http import HttpRequest from authentik.core.models import User from authentik.events.models import Event, EventAction from authentik.lib.expression.evaluator import BaseEvaluator +from authentik.lib.utils.errors import exception_to_string from authentik.policies.types import PolicyRequest @@ -38,7 +39,7 @@ class PropertyMappingEvaluator(BaseEvaluator): def handle_error(self, exc: Exception, expression_source: str): """Exception Handler""" - error_string = "\n".join(format_tb(exc.__traceback__) + [str(exc)]) + error_string = exception_to_string(exc) event = Event.new( EventAction.PROPERTY_MAPPING_EXCEPTION, expression=expression_source, diff --git a/authentik/flows/challenge.py b/authentik/flows/challenge.py index 15cea8b92..78645bb75 100644 --- a/authentik/flows/challenge.py +++ b/authentik/flows/challenge.py @@ -1,9 +1,9 @@ """Challenge helpers""" from dataclasses import asdict, is_dataclass from enum import Enum -from traceback import format_tb from typing import TYPE_CHECKING, Optional, TypedDict from uuid import UUID +from rest_framework.request import Request from django.core.serializers.json import DjangoJSONEncoder from django.db import models @@ -11,6 +11,7 @@ from django.http import JsonResponse from rest_framework.fields import CharField, ChoiceField, DictField from authentik.core.api.utils import PassiveSerializer +from authentik.lib.utils.errors import exception_to_string if TYPE_CHECKING: from authentik.flows.stage import StageView @@ -90,32 +91,31 @@ class WithUserInfoChallenge(Challenge): pending_user_avatar = CharField() -class FlowErrorChallenge(WithUserInfoChallenge): +class FlowErrorChallenge(Challenge): """Challenge class when an unhandled error occurs during a stage. Normal users are shown an error message, superusers are shown a full stacktrace.""" - component = CharField(default="xak-flow-error") + type = CharField(default=ChallengeTypes.NATIVE.value) + component = CharField(default="ak-stage-flow-error") request_id = CharField() error = CharField(required=False) traceback = CharField(required=False) - def __init__(self, *args, **kwargs): - request = kwargs.pop("request", None) - error = kwargs.pop("error", None) - super().__init__(*args, **kwargs) + def __init__(self, request: Optional[Request] = None, error: Optional[Exception] = None): + super().__init__(data={}) if not request or not error: return - self.request_id = request.request_id + self.initial_data["request_id"] = request.request_id from authentik.core.models import USER_ATTRIBUTE_DEBUG if request.user and request.user.is_authenticated: if request.user.is_superuser or request.user.group_attributes(request).get( USER_ATTRIBUTE_DEBUG, False ): - self.error = error - self.traceback = "".join(format_tb(self.error.__traceback__)) + self.initial_data["error"] = str(error) + self.initial_data["traceback"] = exception_to_string(error) class AccessDeniedChallenge(WithUserInfoChallenge): diff --git a/authentik/flows/views/executor.py b/authentik/flows/views/executor.py index cce3fa6d0..4fc5b5e52 100644 --- a/authentik/flows/views/executor.py +++ b/authentik/flows/views/executor.py @@ -255,7 +255,7 @@ class FlowExecutorView(APIView): message=exception_to_string(exc), ).from_http(self.request) challenge = FlowErrorChallenge(self.request, exc) - challenge.is_valid() + challenge.is_valid(raise_exception=True) return to_stage_response(self.request, HttpChallengeResponse(challenge)) @extend_schema( diff --git a/schema.yml b/schema.yml index ac68062cf..b21de6262 100644 --- a/schema.yml +++ b/schema.yml @@ -26611,7 +26611,7 @@ components: ak-stage-consent: '#/components/schemas/ConsentChallenge' ak-stage-dummy: '#/components/schemas/DummyChallenge' ak-stage-email: '#/components/schemas/EmailChallenge' - xak-flow-error: '#/components/schemas/FlowErrorChallenge' + ak-stage-flow-error: '#/components/schemas/FlowErrorChallenge' ak-stage-identification: '#/components/schemas/IdentificationChallenge' ak-provider-oauth2-device-code: '#/components/schemas/OAuthDeviceCodeChallenge' ak-provider-oauth2-device-code-finish: '#/components/schemas/OAuthDeviceCodeFinishChallenge' @@ -27876,22 +27876,19 @@ components: are shown an error message, superusers are shown a full stacktrace. properties: type: - $ref: '#/components/schemas/ChallengeChoices' + type: string + default: native flow_info: $ref: '#/components/schemas/ContextualFlowInfo' component: type: string - default: xak-flow-error + default: ak-stage-flow-error response_errors: type: object additionalProperties: type: array items: $ref: '#/components/schemas/ErrorDetail' - pending_user: - type: string - pending_user_avatar: - type: string request_id: type: string error: @@ -27899,10 +27896,7 @@ components: traceback: type: string required: - - pending_user - - pending_user_avatar - request_id - - type FlowImportResult: type: object description: Logs of an attempted flow import diff --git a/web/src/flow/FlowExecutor.ts b/web/src/flow/FlowExecutor.ts index 8e648479c..7e8f3dd8a 100644 --- a/web/src/flow/FlowExecutor.ts +++ b/web/src/flow/FlowExecutor.ts @@ -10,6 +10,7 @@ import { first } from "@goauthentik/common/utils"; import { WebsocketClient } from "@goauthentik/common/ws"; import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/LoadingOverlay"; +import "@goauthentik/flow/stages/FlowErrorStage"; import "@goauthentik/flow/stages/RedirectStage"; import "@goauthentik/flow/stages/access_denied/AccessDeniedStage"; // Import webauthn-related stages to prevent issues on safari @@ -45,6 +46,7 @@ import { ChallengeTypes, CurrentTenant, FlowChallengeResponseRequest, + FlowErrorChallenge, FlowsApi, LayoutEnum, RedirectChallenge, @@ -107,10 +109,6 @@ export class FlowExecutor extends AKElement implements StageHost { :host { position: relative; } - .ak-exception { - font-family: monospace; - overflow-x: scroll; - } .pf-c-drawer__content { background-color: transparent; } @@ -254,27 +252,13 @@ export class FlowExecutor extends AKElement implements StageHost { } else if (error instanceof Error) { body = error.message; } - this.challenge = { - type: ChallengeChoices.Shell, - body: `
-

- ${t`Whoops!`} -

-
-
-

${t`Something went wrong! Please try again later.`}

-
${body}
-
- `, - } as ChallengeTypes; + const challenge: FlowErrorChallenge = { + type: ChallengeChoices.Native, + component: "ak-stage-flow-error", + error: body, + requestId: "", + }; + this.challenge = challenge as ChallengeTypes; } async renderChallengeNativeElement(): Promise { @@ -395,6 +379,12 @@ export class FlowExecutor extends AKElement implements StageHost { .host=${this as StageHost} .challenge=${this.challenge} >`; + // Internal stages + case "ak-stage-flow-error": + return html``; default: break; } diff --git a/web/src/flow/stages/FlowErrorStage.ts b/web/src/flow/stages/FlowErrorStage.ts index ccd75766b..48a534667 100644 --- a/web/src/flow/stages/FlowErrorStage.ts +++ b/web/src/flow/stages/FlowErrorStage.ts @@ -4,9 +4,8 @@ import { BaseStage } from "@goauthentik/flow/stages/base"; import { t } from "@lingui/macro"; -import { CSSResult, TemplateResult, html } from "lit"; +import { CSSResult, TemplateResult, css, html } from "lit"; import { customElement } from "lit/decorators.js"; -import { ifDefined } from "lit/directives/if-defined.js"; import AKGlobal from "@goauthentik/common/styles/authentik.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css"; @@ -20,7 +19,23 @@ import { FlowChallengeResponseRequest, FlowErrorChallenge } from "@goauthentik/a @customElement("ak-stage-flow-error") export class FlowErrorStage extends BaseStage { static get styles(): CSSResult[] { - return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, AKGlobal]; + return [ + PFBase, + PFLogin, + PFForm, + PFFormControl, + PFTitle, + AKGlobal, + css` + pre { + overflow-x: scroll; + max-width: calc( + 35rem - var(--pf-c-login__main-body--PaddingRight) - + var(--pf-c-login__main-body--PaddingRight) + ); + } + `, + ]; } render(): TemplateResult { @@ -32,29 +47,22 @@ export class FlowErrorStage extends BaseStage