mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: include a link and more useful error details for 403 response codes (#16644)
Currently if a user gets to a page they don't have permission to view they're greeted with a vague error alert and no actionable items. This PR adds a link back to _/workspaces_ within the alert as well as more helpful error details. Before:  After: 
This commit is contained in:
@@ -154,6 +154,7 @@ func ResourceNotFound(rw http.ResponseWriter) {
|
||||
func Forbidden(rw http.ResponseWriter) {
|
||||
Write(context.Background(), rw, http.StatusForbidden, codersdk.Response{
|
||||
Message: "Forbidden.",
|
||||
Detail: "You don't have permission to view this content. If you believe this is a mistake, please contact your administrator or try signing in with different credentials.",
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { API } from "api/api";
|
||||
import { Language } from "pages/CreateUserPage/CreateUserForm";
|
||||
import { Language } from "pages/CreateUserPage/Language";
|
||||
import { coderPort, license, premiumTestsRequired, users } from "../constants";
|
||||
import { expectUrl } from "../expectUrl";
|
||||
import { createUser } from "../helpers";
|
||||
|
||||
@@ -133,6 +133,14 @@ export const getErrorDetail = (error: unknown): string | undefined => {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getErrorStatus = (error: unknown): number | undefined => {
|
||||
if (isApiError(error)) {
|
||||
return error.status;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export class DetailedError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import AlertTitle from "@mui/material/AlertTitle";
|
||||
import { getErrorDetail, getErrorMessage } from "api/errors";
|
||||
import { getErrorDetail, getErrorMessage, getErrorStatus } from "api/errors";
|
||||
import type { FC } from "react";
|
||||
import { Link } from "../Link/Link";
|
||||
import { Alert, AlertDetail, type AlertProps } from "./Alert";
|
||||
|
||||
export const ErrorAlert: FC<
|
||||
@@ -8,6 +9,7 @@ export const ErrorAlert: FC<
|
||||
> = ({ error, ...alertProps }) => {
|
||||
const message = getErrorMessage(error, "Something went wrong.");
|
||||
const detail = getErrorDetail(error);
|
||||
const status = getErrorStatus(error);
|
||||
|
||||
// For some reason, the message and detail can be the same on the BE, but does
|
||||
// not make sense in the FE to showing them duplicated
|
||||
@@ -15,14 +17,28 @@ export const ErrorAlert: FC<
|
||||
|
||||
return (
|
||||
<Alert severity="error" {...alertProps}>
|
||||
{detail ? (
|
||||
<>
|
||||
<AlertTitle>{message}</AlertTitle>
|
||||
{shouldDisplayDetail && <AlertDetail>{detail}</AlertDetail>}
|
||||
</>
|
||||
) : (
|
||||
message
|
||||
)}
|
||||
{
|
||||
// When the error is a Forbidden response we include a link for the user to
|
||||
// go back to a known viewable page.
|
||||
status === 403 ? (
|
||||
<>
|
||||
<AlertTitle>{message}</AlertTitle>
|
||||
<AlertDetail>
|
||||
{detail}{" "}
|
||||
<Link href="/workspaces" className="w-fit">
|
||||
Go to workspaces
|
||||
</Link>
|
||||
</AlertDetail>
|
||||
</>
|
||||
) : detail ? (
|
||||
<>
|
||||
<AlertTitle>{message}</AlertTitle>
|
||||
{shouldDisplayDetail && <AlertDetail>{detail}</AlertDetail>}
|
||||
</>
|
||||
) : (
|
||||
message
|
||||
)
|
||||
}
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,18 +19,7 @@ import {
|
||||
onChangeTrimmed,
|
||||
} from "utils/formUtils";
|
||||
import * as Yup from "yup";
|
||||
|
||||
export const Language = {
|
||||
emailLabel: "Email",
|
||||
passwordLabel: "Password",
|
||||
usernameLabel: "Username",
|
||||
nameLabel: "Full name",
|
||||
emailInvalid: "Please enter a valid email address.",
|
||||
emailRequired: "Please enter an email address.",
|
||||
passwordRequired: "Please enter a password.",
|
||||
createUser: "Create",
|
||||
cancel: "Cancel",
|
||||
};
|
||||
import { Language } from "./Language";
|
||||
|
||||
export const authMethodLanguage = {
|
||||
password: {
|
||||
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
renderWithAuth,
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "testHelpers/renderHelpers";
|
||||
import { Language as FormLanguage } from "./CreateUserForm";
|
||||
import { CreateUserPage } from "./CreateUserPage";
|
||||
import { Language as FormLanguage } from "./Language";
|
||||
|
||||
const renderCreateUserPage = async () => {
|
||||
renderWithAuth(<CreateUserPage />, {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export const Language = {
|
||||
emailLabel: "Email",
|
||||
passwordLabel: "Password",
|
||||
usernameLabel: "Username",
|
||||
nameLabel: "Full name",
|
||||
emailInvalid: "Please enter a valid email address.",
|
||||
emailRequired: "Please enter an email address.",
|
||||
passwordRequired: "Please enter a password.",
|
||||
createUser: "Create",
|
||||
cancel: "Cancel",
|
||||
};
|
||||
Reference in New Issue
Block a user