From a376e8dbfe8243617c1a02dade311efb5864bfa5 Mon Sep 17 00:00:00 2001 From: brettkolodny Date: Fri, 21 Feb 2025 16:26:07 -0500 Subject: [PATCH] fix: include a link and more useful error details for 403 response codes (#16644) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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: ![Screenshot 2025-02-20 at 11 06 06 AM](https://github.com/user-attachments/assets/cea5b86d-673b-482b-ac0b-f132eb518910) After: ![Screenshot 2025-02-20 at 11 06 19 AM](https://github.com/user-attachments/assets/6bf0e9fd-fc51-4d9a-afbc-fea9f0439aff) --- coderd/httpapi/httpapi.go | 1 + site/e2e/setup/addUsersAndLicense.spec.ts | 2 +- site/src/api/errors.ts | 8 +++++ site/src/components/Alert/ErrorAlert.tsx | 34 ++++++++++++++----- .../pages/CreateUserPage/CreateUserForm.tsx | 13 +------ .../CreateUserPage/CreateUserPage.test.tsx | 2 +- site/src/pages/CreateUserPage/Language.ts | 11 ++++++ 7 files changed, 48 insertions(+), 23 deletions(-) create mode 100644 site/src/pages/CreateUserPage/Language.ts diff --git a/coderd/httpapi/httpapi.go b/coderd/httpapi/httpapi.go index cd55a09d51..a9687d58a0 100644 --- a/coderd/httpapi/httpapi.go +++ b/coderd/httpapi/httpapi.go @@ -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.", }) } diff --git a/site/e2e/setup/addUsersAndLicense.spec.ts b/site/e2e/setup/addUsersAndLicense.spec.ts index f6817e0fd4..bcaa8c9281 100644 --- a/site/e2e/setup/addUsersAndLicense.spec.ts +++ b/site/e2e/setup/addUsersAndLicense.spec.ts @@ -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"; diff --git a/site/src/api/errors.ts b/site/src/api/errors.ts index f1e63d1e39..873163e11a 100644 --- a/site/src/api/errors.ts +++ b/site/src/api/errors.ts @@ -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, diff --git a/site/src/components/Alert/ErrorAlert.tsx b/site/src/components/Alert/ErrorAlert.tsx index 73d9c62480..0198ea4e99 100644 --- a/site/src/components/Alert/ErrorAlert.tsx +++ b/site/src/components/Alert/ErrorAlert.tsx @@ -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 ( - {detail ? ( - <> - {message} - {shouldDisplayDetail && {detail}} - - ) : ( - 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 ? ( + <> + {message} + + {detail}{" "} + + Go to workspaces + + + + ) : detail ? ( + <> + {message} + {shouldDisplayDetail && {detail}} + + ) : ( + message + ) + } ); }; diff --git a/site/src/pages/CreateUserPage/CreateUserForm.tsx b/site/src/pages/CreateUserPage/CreateUserForm.tsx index aebdd36e45..be8b4a1579 100644 --- a/site/src/pages/CreateUserPage/CreateUserForm.tsx +++ b/site/src/pages/CreateUserPage/CreateUserForm.tsx @@ -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: { diff --git a/site/src/pages/CreateUserPage/CreateUserPage.test.tsx b/site/src/pages/CreateUserPage/CreateUserPage.test.tsx index f8b256e2d0..ec75fc9a8e 100644 --- a/site/src/pages/CreateUserPage/CreateUserPage.test.tsx +++ b/site/src/pages/CreateUserPage/CreateUserPage.test.tsx @@ -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(, { diff --git a/site/src/pages/CreateUserPage/Language.ts b/site/src/pages/CreateUserPage/Language.ts new file mode 100644 index 0000000000..d449829aea --- /dev/null +++ b/site/src/pages/CreateUserPage/Language.ts @@ -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", +};