Skip to content

Commit 8492459

Browse files
authored
[Bugfix]: Propagate api request handlers to throw errors on failed requests (#10)
1 parent bc9f54b commit 8492459

File tree

9 files changed

+122
-82
lines changed

9 files changed

+122
-82
lines changed

{{cookiecutter.project_slug}}/frontend/app/components/moderation/CreateUser.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,22 @@ export default function CreateUser() {
2525
password: generateUUID(),
2626
fullName: values.fullName ? values.fullName : "",
2727
}
28-
const res = await apiAuth.createUserProfile(accessToken, data)
29-
if (!res.id) {
28+
try {
29+
const res = await apiAuth.createUserProfile(accessToken, data)
30+
if (!res.id) throw "Error"
3031
dispatch(
3132
addNotice({
32-
title: "Update error",
33-
content: "Invalid request.",
34-
icon: "error",
33+
title: "User created",
34+
content:
35+
"An email has been sent to the user with their new login details.",
3536
}),
3637
)
37-
} else {
38+
} catch {
3839
dispatch(
3940
addNotice({
40-
title: "User created",
41-
content:
42-
"An email has been sent to the user with their new login details.",
41+
title: "Update error",
42+
content: "Invalid request.",
43+
icon: "error",
4344
}),
4445
)
4546
}

{{cookiecutter.project_slug}}/frontend/app/components/moderation/ToggleActive.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,21 @@ export default function ToggleActive(props: ToggleActiveProps) {
2626
email: props.email,
2727
is_active: !props.check,
2828
}
29-
const res = await apiAuth.toggleUserState(accessToken, data)
30-
if (!res || !res.msg) {
29+
try {
30+
const res = await apiAuth.toggleUserState(accessToken, data)
31+
if (!res.msg) throw res
32+
} catch (results) {
3133
dispatch(
3234
addNotice({
3335
title: "Update error",
34-
content: res ? res.msg : "Invalid request.",
36+
//@ts-ignore
37+
content: results?.msg ?? "Invalid request.",
3538
icon: "error",
3639
}),
3740
)
3841
setEnabled(props.check)
3942
}
4043
}
4144

42-
return <CheckToggle check={props.check} onClick={submit} />
45+
return <CheckToggle check={enabled} onClick={submit} />
4346
}

{{cookiecutter.project_slug}}/frontend/app/components/moderation/ToggleMod.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@ export default function ToggleMod(props: ToggleModProps) {
2626
email: props.email,
2727
is_superuser: !props.check,
2828
}
29-
const res = await apiAuth.toggleUserState(accessToken, data)
30-
if (!res || !res.msg) {
29+
try {
30+
const res = await apiAuth.toggleUserState(accessToken, data)
31+
if (!res.msg) throw res
32+
} catch (results) {
3133
dispatch(
3234
addNotice({
3335
title: "Update error",
34-
content: res ? res.msg : "Invalid request.",
36+
//@ts-ignore
37+
content: results?.msg ?? "Invalid request.",
3538
icon: "error",
3639
}),
3740
)

{{cookiecutter.project_slug}}/frontend/app/components/moderation/UserTable.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useAppDispatch, useAppSelector } from "../../lib/hooks"
99
import { RootState } from "../../lib/store"
1010
import { useEffect, useState } from "react"
1111
import { refreshTokens, token } from "../../lib/slices/tokensSlice"
12+
import { addNotice } from "../../lib/slices/toastsSlice"
1213

1314
const renderUserProfiles = (userProfiles: IUserProfile[]) => {
1415
return userProfiles.map((profile) => (
@@ -48,8 +49,18 @@ export default function UserTable() {
4849

4950
async function getAllUsers() {
5051
await dispatch(refreshTokens())
51-
const res = await apiAuth.getAllUsers(accessToken)
52-
if (res && res.length) setUserProfiles(res)
52+
try {
53+
const res = await apiAuth.getAllUsers(accessToken)
54+
if (res && res.length) setUserProfiles(res)
55+
} catch {
56+
dispatch(
57+
addNotice({
58+
title: "User Fetch Issue",
59+
content: "Failed to fetch all users, please check logged in permissions",
60+
icon: "error",
61+
})
62+
)
63+
}
5364
}
5465

5566
useEffect(() => {

{{cookiecutter.project_slug}}/frontend/app/components/settings/Security.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ import {
1515
updateUserProfile,
1616
} from "../../lib/slices/authSlice"
1717
import { refreshTokens, token } from "../../lib/slices/tokensSlice"
18+
import { addNotice } from "../../lib/slices/toastsSlice"
1819
import { QRCodeSVG } from 'qrcode.react'
1920

21+
2022
const title = "Security"
2123
const redirectTOTP = "/settings"
2224
const qrSize = 200
@@ -111,13 +113,22 @@ export default function Security() {
111113
}
112114
if (totpEnabled !== currentProfile.totp && totpEnabled) {
113115
await dispatch(refreshTokens())
114-
const res = await apiAuth.requestNewTOTP(accessToken)
115-
if (res) {
116-
totpNew.key = res.key
117-
totpNew.uri = res.uri
118-
totpClaim.uri = res.uri
119-
totpClaim.password = values.original
120-
changeTotpModal(true)
116+
try {
117+
const res = await apiAuth.requestNewTOTP(accessToken)
118+
if (res) {
119+
totpNew.key = res.key
120+
totpNew.uri = res.uri
121+
totpClaim.uri = res.uri
122+
totpClaim.password = values.original
123+
changeTotpModal(true)
124+
}
125+
} catch (error) {
126+
dispatch(
127+
addNotice({
128+
title: "Two-Factor Setup error",
129+
content: "Failed to fetch a Two-Factor enablement code, please try again!"
130+
})
131+
)
121132
}
122133
}
123134
if (totpEnabled !== currentProfile.totp && !totpEnabled) {

{{cookiecutter.project_slug}}/frontend/app/lib/api/auth.ts

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,30 @@ import {
1111
} from "../interfaces"
1212
import { apiCore } from "./core"
1313

14+
const jsonify = async (response: Response) => {
15+
if(response.ok) {
16+
return await response.json()
17+
}
18+
throw `Request failed with ${response.status}: ${response.statusText}`
19+
}
20+
1421
export const apiAuth = {
15-
// TEST
16-
async getTestText() {
17-
const res = await fetch(`${apiCore.url}/users/tester`)
18-
return (await res.json()) as IMsg
19-
},
2022
// LOGIN WITH MAGIC LINK OR OAUTH2 (USERNAME/PASSWORD)
21-
async loginWithMagicLink(email: string) {
23+
async loginWithMagicLink(email: string): Promise<IWebToken> {
2224
const res = await fetch(`${apiCore.url}/login/magic/${email}`, {
2325
method: "POST",
2426
})
25-
return (await res.json()) as IWebToken
27+
return (await jsonify(res)) as IWebToken
2628
},
27-
async validateMagicLink(token: string, data: IWebToken) {
29+
async validateMagicLink(token: string, data: IWebToken): Promise<ITokenResponse> {
2830
const res = await fetch(`${apiCore.url}/login/claim`, {
2931
method: "POST",
3032
body: JSON.stringify(data),
3133
headers: apiCore.headers(token),
3234
})
33-
return (await res.json()) as ITokenResponse
35+
return (await jsonify(res)) as ITokenResponse
3436
},
35-
async loginWithOauth(username: string, password: string) {
37+
async loginWithOauth(username: string, password: string): Promise<ITokenResponse> {
3638
// Version of this: https://github.com/unjs/ofetch/issues/37#issuecomment-1262226065
3739
// useFetch is borked, so you'll need to ignore errors https://github.com/unjs/ofetch/issues/37
3840
const params = new URLSearchParams()
@@ -44,85 +46,85 @@ export const apiAuth = {
4446
// @ts-ignore
4547
headers: { "Content-Disposition": params },
4648
})
47-
return (await res.json()) as ITokenResponse
49+
return (await jsonify(res)) as ITokenResponse
4850
},
4951
// TOTP SETUP AND AUTHENTICATION
50-
async loginWithTOTP(token: string, data: IWebToken) {
52+
async loginWithTOTP(token: string, data: IWebToken): Promise<ITokenResponse> {
5153
const res = await fetch(`${apiCore.url}/login/totp`, {
5254
method: "POST",
5355
body: JSON.stringify(data),
5456
headers: apiCore.headers(token),
5557
})
56-
return (await res.json()) as ITokenResponse
58+
return (await jsonify(res)) as ITokenResponse
5759
},
58-
async requestNewTOTP(token: string) {
60+
async requestNewTOTP(token: string): Promise<INewTOTP> {
5961
const res = await fetch(`${apiCore.url}/users/new-totp`, {
6062
method: "POST",
6163
headers: apiCore.headers(token),
6264
})
63-
return (await res.json()) as INewTOTP
65+
return (await jsonify(res)) as INewTOTP
6466
},
65-
async enableTOTPAuthentication(token: string, data: IEnableTOTP) {
67+
async enableTOTPAuthentication(token: string, data: IEnableTOTP): Promise<IMsg> {
6668
const res = await fetch(`${apiCore.url}/login/totp`, {
6769
method: "PUT",
6870
body: JSON.stringify(data),
6971
headers: apiCore.headers(token),
7072
})
71-
return (await res.json()) as IMsg
73+
return (await jsonify(res)) as IMsg
7274
},
73-
async disableTOTPAuthentication(token: string, data: IUserProfileUpdate) {
75+
async disableTOTPAuthentication(token: string, data: IUserProfileUpdate): Promise<IMsg> {
7476
const res = await fetch(`${apiCore.url}/login/totp`, {
7577
method: "DELETE",
7678
body: JSON.stringify(data),
7779
headers: apiCore.headers(token),
7880
})
79-
return (await res.json()) as IMsg
81+
return (await jsonify(res)) as IMsg
8082
},
8183
// MANAGE JWT TOKENS (REFRESH / REVOKE)
82-
async getRefreshedToken(token: string) {
84+
async getRefreshedToken(token: string): Promise<ITokenResponse> {
8385
const res = await fetch(`${apiCore.url}/login/refresh`, {
8486
method: "POST",
8587
headers: apiCore.headers(token),
8688
})
87-
return (await res.json()) as ITokenResponse
89+
return (await jsonify(res)) as ITokenResponse
8890
},
89-
async revokeRefreshedToken(token: string) {
91+
async revokeRefreshedToken(token: string): Promise<IMsg> {
9092
const res = await fetch(`${apiCore.url}/login/revoke`, {
9193
method: "POST",
9294
headers: apiCore.headers(token),
9395
})
94-
return (await res.json()) as IMsg
96+
return (await jsonify(res)) as IMsg
9597
},
9698
// USER PROFILE MANAGEMENT
97-
async createProfile(data: IUserOpenProfileCreate) {
99+
async createProfile(data: IUserOpenProfileCreate): Promise<IUserProfile> {
98100
const res = await fetch(`${apiCore.url}/users/`, {
99101
method: "POST",
100102
body: JSON.stringify(data),
101103
})
102-
return (await res.json()) as IUserProfile
104+
return (await jsonify(res)) as IUserProfile
103105
},
104-
async getProfile(token: string) {
106+
async getProfile(token: string): Promise<IUserProfile> {
105107
const res = await fetch(`${apiCore.url}/users/`, {
106108
headers: apiCore.headers(token),
107109
})
108-
return (await res.json()) as IUserProfile
110+
return (await jsonify(res)) as IUserProfile
109111
},
110-
async updateProfile(token: string, data: IUserProfileUpdate) {
112+
async updateProfile(token: string, data: IUserProfileUpdate): Promise<IUserProfile> {
111113
const res = await fetch(`${apiCore.url}/users/`, {
112114
method: "PUT",
113115
body: JSON.stringify(data),
114116
headers: apiCore.headers(token),
115117
})
116-
return (await res.json()) as IUserProfile
118+
return (await jsonify(res)) as IUserProfile
117119
},
118120
// ACCOUNT RECOVERY
119-
async recoverPassword(email: string) {
121+
async recoverPassword(email: string): Promise<IMsg | IWebToken> {
120122
const res = await fetch(`${apiCore.url}/login/recover/${email}`, {
121123
method: "POST",
122124
})
123-
return (await res.json()) as IMsg | IWebToken
125+
return (await jsonify(res)) as IMsg | IWebToken
124126
},
125-
async resetPassword(password: string, claim: string, token: string) {
127+
async resetPassword(password: string, claim: string, token: string): Promise<IMsg> {
126128
const res = await fetch(`${apiCore.url}/login/reset`, {
127129
method: "POST",
128130
body: JSON.stringify({
@@ -131,44 +133,44 @@ export const apiAuth = {
131133
}),
132134
headers: apiCore.headers(token),
133135
})
134-
return (await res.json()) as IMsg
136+
return (await jsonify(res)) as IMsg
135137
},
136-
async requestValidationEmail(token: string) {
138+
async requestValidationEmail(token: string): Promise<IMsg> {
137139
const res = await fetch(`${apiCore.url}/users/send-validation-email`, {
138140
method: "POST",
139141
headers: apiCore.headers(token),
140142
})
141-
return (await res.json()) as IMsg
143+
return (await jsonify(res)) as IMsg
142144
},
143-
async validateEmail(token: string, validation: string) {
145+
async validateEmail(token: string, validation: string): Promise<IMsg> {
144146
const res = await fetch(`${apiCore.url}/users/validate-email`, {
145147
method: "POST",
146148
body: JSON.stringify({ validation }),
147149
headers: apiCore.headers(token),
148150
})
149-
return (await res.json()) as IMsg
151+
return (await jsonify(res)) as IMsg
150152
},
151153
// ADMIN USER MANAGEMENT
152-
async getAllUsers(token: string) {
154+
async getAllUsers(token: string): Promise<IUserProfile[]> {
153155
const res = await fetch(`${apiCore.url}/users/all`, {
154156
headers: apiCore.headers(token),
155157
})
156-
return (await res.json()) as IUserProfile[]
158+
return (await jsonify(res)) as IUserProfile[]
157159
},
158-
async toggleUserState(token: string, data: IUserProfileUpdate) {
160+
async toggleUserState(token: string, data: IUserProfileUpdate): Promise<IMsg> {
159161
const res = await fetch(`${apiCore.url}/users/toggle-state`, {
160162
method: "POST",
161163
body: JSON.stringify(data),
162164
headers: apiCore.headers(token),
163165
})
164-
return (await res.json()) as IMsg
166+
return (await jsonify(res)) as IMsg
165167
},
166-
async createUserProfile(token: string, data: IUserProfileCreate) {
168+
async createUserProfile(token: string, data: IUserProfileCreate): Promise<IUserProfile> {
167169
const res = await fetch(`${apiCore.url}/users/create`, {
168170
method: "POST",
169171
body: JSON.stringify(data),
170172
headers: apiCore.headers(token),
171173
})
172-
return (await res.json()) as IUserProfile
174+
return (await jsonify(res)) as IUserProfile
173175
},
174176
}

0 commit comments

Comments
 (0)