Skip to content

Commit a376e8d

Browse files
authored
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: ![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)
1 parent f8a49f4 commit a376e8d

File tree

7 files changed

+48
-23
lines changed

7 files changed

+48
-23
lines changed

coderd/httpapi/httpapi.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ func ResourceNotFound(rw http.ResponseWriter) {
154154
func Forbidden(rw http.ResponseWriter) {
155155
Write(context.Background(), rw, http.StatusForbidden, codersdk.Response{
156156
Message: "Forbidden.",
157+
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.",
157158
})
158159
}
159160

site/e2e/setup/addUsersAndLicense.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expect, test } from "@playwright/test";
22
import { API } from "api/api";
3-
import { Language } from "pages/CreateUserPage/CreateUserForm";
3+
import { Language } from "pages/CreateUserPage/Language";
44
import { coderPort, license, premiumTestsRequired, users } from "../constants";
55
import { expectUrl } from "../expectUrl";
66
import { createUser } from "../helpers";

site/src/api/errors.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,14 @@ export const getErrorDetail = (error: unknown): string | undefined => {
133133
return undefined;
134134
};
135135

136+
export const getErrorStatus = (error: unknown): number | undefined => {
137+
if (isApiError(error)) {
138+
return error.status;
139+
}
140+
141+
return undefined;
142+
};
143+
136144
export class DetailedError extends Error {
137145
constructor(
138146
message: string,
Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,44 @@
11
import AlertTitle from "@mui/material/AlertTitle";
2-
import { getErrorDetail, getErrorMessage } from "api/errors";
2+
import { getErrorDetail, getErrorMessage, getErrorStatus } from "api/errors";
33
import type { FC } from "react";
4+
import { Link } from "../Link/Link";
45
import { Alert, AlertDetail, type AlertProps } from "./Alert";
56

67
export const ErrorAlert: FC<
78
Omit<AlertProps, "severity" | "children"> & { error: unknown }
89
> = ({ error, ...alertProps }) => {
910
const message = getErrorMessage(error, "Something went wrong.");
1011
const detail = getErrorDetail(error);
12+
const status = getErrorStatus(error);
1113

1214
// For some reason, the message and detail can be the same on the BE, but does
1315
// not make sense in the FE to showing them duplicated
1416
const shouldDisplayDetail = message !== detail;
1517

1618
return (
1719
<Alert severity="error" {...alertProps}>
18-
{detail ? (
19-
<>
20-
<AlertTitle>{message}</AlertTitle>
21-
{shouldDisplayDetail && <AlertDetail>{detail}</AlertDetail>}
22-
</>
23-
) : (
24-
message
25-
)}
20+
{
21+
// When the error is a Forbidden response we include a link for the user to
22+
// go back to a known viewable page.
23+
status === 403 ? (
24+
<>
25+
<AlertTitle>{message}</AlertTitle>
26+
<AlertDetail>
27+
{detail}{" "}
28+
<Link href="/workspaces" className="w-fit">
29+
Go to workspaces
30+
</Link>
31+
</AlertDetail>
32+
</>
33+
) : detail ? (
34+
<>
35+
<AlertTitle>{message}</AlertTitle>
36+
{shouldDisplayDetail && <AlertDetail>{detail}</AlertDetail>}
37+
</>
38+
) : (
39+
message
40+
)
41+
}
2642
</Alert>
2743
);
2844
};

site/src/pages/CreateUserPage/CreateUserForm.tsx

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,7 @@ import {
1919
onChangeTrimmed,
2020
} from "utils/formUtils";
2121
import * as Yup from "yup";
22-
23-
export const Language = {
24-
emailLabel: "Email",
25-
passwordLabel: "Password",
26-
usernameLabel: "Username",
27-
nameLabel: "Full name",
28-
emailInvalid: "Please enter a valid email address.",
29-
emailRequired: "Please enter an email address.",
30-
passwordRequired: "Please enter a password.",
31-
createUser: "Create",
32-
cancel: "Cancel",
33-
};
22+
import { Language } from "./Language";
3423

3524
export const authMethodLanguage = {
3625
password: {

site/src/pages/CreateUserPage/CreateUserPage.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import {
44
renderWithAuth,
55
waitForLoaderToBeRemoved,
66
} from "testHelpers/renderHelpers";
7-
import { Language as FormLanguage } from "./CreateUserForm";
87
import { CreateUserPage } from "./CreateUserPage";
8+
import { Language as FormLanguage } from "./Language";
99

1010
const renderCreateUserPage = async () => {
1111
renderWithAuth(<CreateUserPage />, {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const Language = {
2+
emailLabel: "Email",
3+
passwordLabel: "Password",
4+
usernameLabel: "Username",
5+
nameLabel: "Full name",
6+
emailInvalid: "Please enter a valid email address.",
7+
emailRequired: "Please enter an email address.",
8+
passwordRequired: "Please enter a password.",
9+
createUser: "Create",
10+
cancel: "Cancel",
11+
};

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy