web/admin: improve error handling (#11212)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L.
2024-09-05 01:31:06 +02:00
committed by GitHub
parent 8886532ed6
commit a8b33b25e8
5 changed files with 33 additions and 34 deletions

View File

@ -1,5 +1,6 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { parseAPIError } from "@goauthentik/common/errors";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
@ -24,7 +25,6 @@ import {
type TransactionApplicationRequest,
type TransactionApplicationResponse,
ValidationError,
ValidationErrorFromJSON,
} from "@goauthentik/api";
import BasePanel from "../BasePanel";
@ -133,9 +133,7 @@ export class ApplicationWizardCommitApplication extends BasePanel {
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.catch(async (resolution: any) => {
const errors = (this.errors = ValidationErrorFromJSON(
await resolution.response.json(),
));
const errors = await parseAPIError(resolution);
this.dispatchWizardUpdate({
update: {
...this.wizard,

View File

@ -1,4 +1,5 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { parseAPIError } from "@goauthentik/common/errors";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
@ -22,7 +23,7 @@ import {
PromptTypeEnum,
ResponseError,
StagesApi,
ValidationErrorFromJSON,
ValidationError,
} from "@goauthentik/api";
class PreviewStageHost implements StageHost {
@ -83,10 +84,8 @@ export class PromptForm extends ModelForm<Prompt, string> {
});
this.previewError = undefined;
} catch (exc) {
const errorMessage = ValidationErrorFromJSON(
await (exc as ResponseError).response.json(),
);
this.previewError = errorMessage.nonFieldErrors;
const errorMessage = parseAPIError(exc as ResponseError);
this.previewError = (errorMessage as ValidationError).nonFieldErrors;
}
}

View File

@ -12,11 +12,17 @@ export class RequestError extends Error {}
export type APIErrorTypes = ValidationError | GenericError;
export const HTTP_BAD_REQUEST = 400;
export const HTTP_INTERNAL_SERVICE_ERROR = 500;
export async function parseAPIError(error: Error): Promise<APIErrorTypes> {
if (!(error instanceof ResponseError)) {
return error;
}
if (error.response.status < 400 || error.response.status > 499) {
if (
error.response.status < HTTP_BAD_REQUEST ||
error.response.status >= HTTP_INTERNAL_SERVICE_ERROR
) {
return error;
}
const body = await error.response.json();

View File

@ -1,4 +1,5 @@
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { parseAPIError } from "@goauthentik/common/errors";
import { MessageLevel } from "@goauthentik/common/messages";
import { camelToSnake, convertToSlug, dateToUTC } from "@goauthentik/common/utils";
import { AKElement } from "@goauthentik/elements/Base";
@ -6,6 +7,7 @@ import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFor
import { PreventFormSubmit } from "@goauthentik/elements/forms/helpers";
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit";
import { property, state } from "lit/decorators.js";
@ -18,7 +20,7 @@ import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-gro
import PFSwitch from "@patternfly/patternfly/components/Switch/switch.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { ResponseError, ValidationError, ValidationErrorFromJSON } from "@goauthentik/api";
import { ResponseError, ValidationError, instanceOfValidationError } from "@goauthentik/api";
export class APIError extends Error {
constructor(public response: ValidationError) {
@ -124,9 +126,6 @@ export function serializeForm<T extends KeyUnknown>(
return json as unknown as T;
}
const HTTP_BAD_REQUEST = 400;
const HTTP_INTERNAL_SERVICE_ERROR = 500;
/**
* Form
*
@ -307,18 +306,9 @@ export abstract class Form<T> extends AKElement {
return response;
} catch (ex) {
if (ex instanceof ResponseError) {
let msg = ex.response.statusText;
if (
ex.response.status >= HTTP_BAD_REQUEST &&
ex.response.status < HTTP_INTERNAL_SERVICE_ERROR
) {
const errorMessage = ValidationErrorFromJSON(await ex.response.json());
if (!errorMessage) {
return errorMessage;
}
if (errorMessage instanceof Error) {
throw errorMessage;
}
let errorMessage = ex.response.statusText;
const error = await parseAPIError(ex);
if (instanceOfValidationError(error)) {
// assign all input-related errors to their elements
const elements =
this.shadowRoot?.querySelectorAll<HorizontalFormElement>(
@ -330,26 +320,28 @@ export abstract class Form<T> extends AKElement {
if (!elementName) {
return;
}
if (camelToSnake(elementName) in errorMessage) {
element.errorMessages = errorMessage[camelToSnake(elementName)];
if (camelToSnake(elementName) in error) {
element.errorMessages = (error as ValidationError)[
camelToSnake(elementName)
];
element.invalid = true;
} else {
element.errorMessages = [];
element.invalid = false;
}
});
if (errorMessage.nonFieldErrors) {
this.nonFieldErrors = errorMessage.nonFieldErrors;
if ((error as ValidationError).nonFieldErrors) {
this.nonFieldErrors = (error as ValidationError).nonFieldErrors;
}
errorMessage = msg("Invalid update request.");
// Only change the message when we have `detail`.
// Everything else is handled in the form.
if ("detail" in errorMessage) {
msg = errorMessage.detail;
if ("detail" in (error as ValidationError)) {
errorMessage = (error as ValidationError).detail;
}
}
// error is local or not from rest_framework
showMessage({
message: msg,
message: errorMessage,
level: MessageLevel.error,
});
}

View File

@ -9,6 +9,7 @@ import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/messages/Message";
import { APIMessage } from "@goauthentik/elements/messages/Message";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, property } from "lit/decorators.js";
@ -20,6 +21,9 @@ export function showMessage(message: APIMessage, unique = false): void {
if (!container) {
throw new SentryIgnoredError("failed to find message container");
}
if (message.message.trim() === "") {
message.message = msg("Error");
}
container.addMessage(message, unique);
container.requestUpdate();
}