web/flows: rework error display, always use ak-stage-flow-error instead of shell
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
d6d72489a7
commit
3980eea7c6
|
@ -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,
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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(
|
||||
|
|
14
schema.yml
14
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
|
||||
|
|
|
@ -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: `<header class="pf-c-login__main-header">
|
||||
<h1 class="pf-c-title pf-m-3xl">
|
||||
${t`Whoops!`}
|
||||
</h1>
|
||||
</header>
|
||||
<div class="pf-c-login__main-body">
|
||||
<h3>${t`Something went wrong! Please try again later.`}</h3>
|
||||
<pre class="ak-exception">${body}</pre>
|
||||
</div>
|
||||
<footer class="pf-c-login__main-footer">
|
||||
<ul class="pf-c-login__main-footer-links">
|
||||
<li class="pf-c-login__main-footer-links-item">
|
||||
<a class="pf-c-button pf-m-primary pf-m-block" href="/">
|
||||
${t`Return`}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</footer>`,
|
||||
} as ChallengeTypes;
|
||||
const challenge: FlowErrorChallenge = {
|
||||
type: ChallengeChoices.Native,
|
||||
component: "ak-stage-flow-error",
|
||||
error: body,
|
||||
requestId: "",
|
||||
};
|
||||
this.challenge = challenge as ChallengeTypes;
|
||||
}
|
||||
|
||||
async renderChallengeNativeElement(): Promise<TemplateResult> {
|
||||
|
@ -395,6 +379,12 @@ export class FlowExecutor extends AKElement implements StageHost {
|
|||
.host=${this as StageHost}
|
||||
.challenge=${this.challenge}
|
||||
></ak-flow-provider-oauth2-code-finish>`;
|
||||
// Internal stages
|
||||
case "ak-stage-flow-error":
|
||||
return html`<ak-stage-flow-error
|
||||
.host=${this as StageHost}
|
||||
.challenge=${this.challenge}
|
||||
></ak-stage-flow-error>`;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -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<FlowErrorChallenge, FlowChallengeResponseRequest> {
|
||||
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<FlowErrorChallenge, FlowChallengeR
|
|||
</header>
|
||||
<div class="pf-c-login__main-body">
|
||||
<form class="pf-c-form">
|
||||
<ak-form-static
|
||||
class="pf-c-form__group"
|
||||
userAvatar="${this.challenge.pendingUserAvatar}"
|
||||
user=${this.challenge.pendingUser}
|
||||
>
|
||||
<div slot="link">
|
||||
<a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
|
||||
>${t`Not you?`}</a
|
||||
>
|
||||
</div>
|
||||
</ak-form-static>
|
||||
<div class="pf-c-form__group">
|
||||
<p>
|
||||
<h3 class="pf-c-title pf-m-3xl">
|
||||
${this.challenge?.error
|
||||
? this.challenge.error
|
||||
: t`Something went wrong! Please try again later.`}
|
||||
</p>
|
||||
</h3>
|
||||
${this.challenge?.traceback
|
||||
? html`<pre class="ak-exception">
|
||||
${this.challenge.error}${this.challenge.traceback}</pre
|
||||
>`
|
||||
? html`<div class="pf-c-form__group">
|
||||
<pre class="ak-exception">${this.challenge.traceback}</pre>
|
||||
</div>`
|
||||
: html``}
|
||||
${this.challenge?.requestId
|
||||
? html`<div class="pf-c-form__group">
|
||||
<p>${t`Request ID`}</p>
|
||||
<code>${this.challenge.requestId}</code>
|
||||
</div>`
|
||||
: html``}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<footer class="pf-c-login__main-footer">
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
ChallengeTypes,
|
||||
CurrentTenant,
|
||||
FlowChallengeResponseRequest,
|
||||
FlowErrorChallenge,
|
||||
FlowsApi,
|
||||
RedirectChallenge,
|
||||
ResponseError,
|
||||
|
@ -123,27 +124,13 @@ export class UserSettingsFlowExecutor extends AKElement implements StageHost {
|
|||
if (error instanceof Error) {
|
||||
body = error.message;
|
||||
}
|
||||
this.challenge = {
|
||||
type: ChallengeChoices.Shell,
|
||||
body: `<header class="pf-c-login__main-header">
|
||||
<h1 class="pf-c-title pf-m-3xl">
|
||||
${t`Whoops!`}
|
||||
</h1>
|
||||
</header>
|
||||
<div class="pf-c-login__main-body">
|
||||
<h3>${t`Something went wrong! Please try again later.`}</h3>
|
||||
<pre class="ak-exception">${body}</pre>
|
||||
</div>
|
||||
<footer class="pf-c-login__main-footer">
|
||||
<ul class="pf-c-login__main-footer-links">
|
||||
<li class="pf-c-login__main-footer-links-item">
|
||||
<a class="pf-c-button pf-m-primary pf-m-block" href="/">
|
||||
${t`Return`}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</footer>`,
|
||||
} as ChallengeTypes;
|
||||
const challenge: FlowErrorChallenge = {
|
||||
type: ChallengeChoices.Native,
|
||||
component: "ak-stage-flow-error",
|
||||
error: body,
|
||||
requestId: "",
|
||||
};
|
||||
this.challenge = challenge as ChallengeTypes;
|
||||
}
|
||||
|
||||
globalRefresh(): void {
|
||||
|
|
Reference in New Issue