Skip to content

Commit 5e13548

Browse files
authored
Add better input validation for setup-keys, nameserver and routes (#373)
* Return the correct promise for errors * Update icon * Add better validation for routes * Add better validation for DNS * Add better validation for setup keys * Merge exit nodes to input validation
1 parent 2272a1d commit 5e13548

File tree

7 files changed

+78
-24
lines changed

7 files changed

+78
-24
lines changed

src/components/Notification.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { IconCircleX } from "@tabler/icons-react";
12
import type { ErrorResponse } from "@utils/api";
23
import { cn } from "@utils/helpers";
34
import classNames from "classnames";
@@ -88,7 +89,7 @@ export default function Notification<T>({
8889
{loading ? (
8990
<Loader2 size={14} className={"animate-spin"} />
9091
) : error ? (
91-
<XIcon size={14} />
92+
<IconCircleX size={24} />
9293
) : (
9394
icon || <CheckIcon size={14} />
9495
)}

src/contexts/RoutesProvider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const RoutesContext = React.createContext(
2525
);
2626

2727
export default function RoutesProvider({ children }: Props) {
28-
const routeRequest = useApiCall<Route>("/routes");
28+
const routeRequest = useApiCall<Route>("/routes", true);
2929
const { mutate } = useSWRConfig();
3030

3131
const updateRoute = async (

src/modules/dns-nameservers/NameserverModal.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export function NameserverModalContent({
115115
preset,
116116
cell,
117117
}: ModalProps) {
118-
const nsRequest = useApiCall<NameserverGroup>("/dns/nameservers");
118+
const nsRequest = useApiCall<NameserverGroup>("/dns/nameservers", true);
119119
const { mutate } = useSWRConfig();
120120

121121
const isUpdate = useMemo(() => {
@@ -233,24 +233,31 @@ export function NameserverModalContent({
233233
return domains.some((d) => d.name === "");
234234
}, [domains]);
235235

236+
const nameLengthError = useMemo(() => {
237+
if (name.length > 40) return "Name should be less than 40 characters";
238+
return "";
239+
}, [name]);
240+
236241
const hasAnyError = useMemo(() => {
237242
return (
238243
hasNSErrors ||
239244
nsError ||
240245
domainError ||
241-
name == "" ||
242246
nameservers.length == 0 ||
243247
hasDomainErrors ||
244-
groups.length == 0
248+
groups.length == 0 ||
249+
nameLengthError !== "" ||
250+
name == ""
245251
);
246252
}, [
247253
nsError,
248254
domainError,
249-
name,
250255
nameservers,
251256
groups,
252257
hasNSErrors,
253258
hasDomainErrors,
259+
nameLengthError,
260+
name,
254261
]);
255262

256263
return (
@@ -427,6 +434,7 @@ export function NameserverModalContent({
427434
<Input
428435
autoFocus={true}
429436
tabIndex={0}
437+
error={nameLengthError}
430438
placeholder={"e.g., Public DNS"}
431439
value={name}
432440
onChange={(e) => setName(e.target.value)}
@@ -516,7 +524,7 @@ function NameserverInput({
516524
const validCIDR = cidr.isValidAddress(ip);
517525
if (!validCIDR) {
518526
onError && onError(true);
519-
return "Please enter a valid CIDR, e.g., 192.168.1.0/24";
527+
return "Please enter a valid IP, e.g., 192.168.1.0";
520528
}
521529
onError && onError(false);
522530
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -532,7 +540,7 @@ function NameserverInput({
532540
<div className={"w-full"}>
533541
<Input
534542
customPrefix={"IP"}
535-
placeholder={"e.g., 172.16.0.0/16"}
543+
placeholder={"e.g., 172.16.0.0"}
536544
maxWidthClass={"w-full"}
537545
value={ip}
538546
className={"font-mono !text-[13px]"}

src/modules/routes/RouteModal.tsx

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -183,17 +183,37 @@ export function RouteModalContent({
183183
(cidrError && cidrError.length > 1) ||
184184
(peerTab === "peer-group" && routingPeerGroups.length == 0) ||
185185
(peerTab === "routing-peer" && !routingPeer) ||
186-
groups.length == 0
186+
groups.length == 0 ||
187+
networkRange == ""
187188
);
188-
}, [cidrError, peerTab, routingPeerGroups.length, routingPeer, groups]);
189+
}, [
190+
cidrError,
191+
peerTab,
192+
routingPeerGroups.length,
193+
routingPeer,
194+
groups,
195+
networkRange,
196+
]);
189197

190-
const isNameEntered = useMemo(() => {
191-
return !(networkIdentifier == "");
198+
const networkIdentifierError = useMemo(() => {
199+
return (networkIdentifier?.length || 0) > 40
200+
? "Network Identifier must be less than 40 characters"
201+
: "";
192202
}, [networkIdentifier]);
193203

204+
const metricError = useMemo(() => {
205+
return parseInt(metric) < 1 || parseInt(metric) > 9999
206+
? "Metric must be between 1 and 9999"
207+
: "";
208+
}, [metric]);
209+
210+
const isNameEntered = useMemo(() => {
211+
return networkIdentifier != "" && networkIdentifierError == "";
212+
}, [networkIdentifier, networkIdentifierError]);
213+
194214
const canCreateOrSave = useMemo(() => {
195-
return isNetworkEntered && isNameEntered;
196-
}, [isNetworkEntered, isNameEntered]);
215+
return isNetworkEntered && isNameEntered && metricError == "";
216+
}, [isNetworkEntered, isNameEntered, metricError]);
197217

198218
return (
199219
<ModalContent maxWidthClass={"max-w-xl"}>
@@ -250,7 +270,10 @@ export function RouteModalContent({
250270
/>
251271
Name & Description
252272
</TabsTrigger>
253-
<TabsTrigger value={"settings"} disabled={!canCreateOrSave}>
273+
<TabsTrigger
274+
value={"settings"}
275+
disabled={!isNetworkEntered || !isNameEntered}
276+
>
254277
<Settings2
255278
size={16}
256279
className={
@@ -340,6 +363,7 @@ export function RouteModalContent({
340363
Add a unique network identifier that is assigned to each device.
341364
</HelpText>
342365
<Input
366+
error={networkIdentifierError}
343367
autoFocus={true}
344368
tabIndex={0}
345369
ref={nameRef}
@@ -406,6 +430,8 @@ export function RouteModalContent({
406430
max={9999}
407431
maxWidthClass={"max-w-[200px]"}
408432
value={metric}
433+
error={metricError}
434+
errorTooltip={true}
409435
type={"number"}
410436
onChange={(e) => setMetric(e.target.value)}
411437
customPrefix={
@@ -469,7 +495,7 @@ export function RouteModalContent({
469495
<Button
470496
variant={"primary"}
471497
onClick={() => setTab("settings")}
472-
disabled={!canCreateOrSave}
498+
disabled={!isNameEntered || !isNetworkEntered}
473499
>
474500
Continue
475501
</Button>

src/modules/routes/RouteUpdateModal.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,14 +197,21 @@ function RouteUpdateModalContent({ onSuccess, route, cell }: ModalProps) {
197197
.filter((p) => p != undefined) as string[];
198198
}, [groupedRoute]);
199199

200+
const metricError = useMemo(() => {
201+
return parseInt(metric.toString()) < 1 || parseInt(metric.toString()) > 9999
202+
? "Metric must be between 1 and 9999"
203+
: "";
204+
}, [metric]);
205+
200206
// Is button disabled
201207
const isDisabled = useMemo(() => {
202208
return (
203209
(peerTab === "peer-group" && routingPeerGroups.length == 0) ||
204210
(peerTab === "routing-peer" && !routingPeer) ||
205-
groups.length == 0
211+
groups.length == 0 ||
212+
metricError !== ""
206213
);
207-
}, [peerTab, routingPeerGroups.length, routingPeer, groups]);
214+
}, [peerTab, routingPeerGroups.length, routingPeer, groups, metricError]);
208215

209216
const [tab, setTab] = useState(
210217
cell && cell == "metric" ? "settings" : "network",
@@ -352,6 +359,8 @@ function RouteUpdateModalContent({ onSuccess, route, cell }: ModalProps) {
352359
max={9999}
353360
maxWidthClass={"max-w-[200px]"}
354361
value={metric}
362+
error={metricError}
363+
errorTooltip={true}
355364
type={"number"}
356365
onChange={(e) => setMetric(e.target.value)}
357366
customPrefix={

src/modules/setup-keys/SetupKeyModal.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ type ModalProps = {
126126
};
127127

128128
export function SetupKeyModalContent({ onSuccess }: ModalProps) {
129-
const setupKeyRequest = useApiCall<SetupKey>("/setup-keys");
129+
const setupKeyRequest = useApiCall<SetupKey>("/setup-keys", true);
130130
const { mutate } = useSWRConfig();
131131

132132
const [name, setName] = useState("");
@@ -143,10 +143,18 @@ export function SetupKeyModalContent({ onSuccess }: ModalProps) {
143143
return reusable ? "Unlimited" : "1";
144144
}, [reusable]);
145145

146+
const expiresInError = useMemo(() => {
147+
const expires = parseInt(expiresIn);
148+
if (expires < 1 || expires > 365) {
149+
return "Days should be between 1 and 365";
150+
}
151+
return "";
152+
}, [expiresIn]);
153+
146154
const isDisabled = useMemo(() => {
147155
const trimmedName = trim(name);
148-
return trimmedName.length === 0;
149-
}, [name]);
156+
return trimmedName.length === 0 || expiresInError.length > 0;
157+
}, [name, expiresInError]);
150158

151159
const submit = () => {
152160
if (!selectedGroups) return;
@@ -245,6 +253,8 @@ export function SetupKeyModalContent({ onSuccess }: ModalProps) {
245253
min={1}
246254
max={365}
247255
value={expiresIn}
256+
error={expiresInError}
257+
errorTooltip={true}
248258
type={"number"}
249259
onChange={(e) => setExpiresIn(e.target.value)}
250260
customPrefix={

src/utils/api.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,13 +169,13 @@ export function useApiErrorHandling(ignoreError = false) {
169169
return login(currentPath);
170170
}
171171
if (err.code == 401 && err.message == "token invalid") {
172-
return setError(err);
172+
setError(err);
173173
}
174174
if (err.code == 500 && err.message == "internal server error") {
175-
return setError(err);
175+
setError(err);
176176
}
177177
if (err.code > 400 && err.code <= 500) {
178-
return setError(err);
178+
setError(err);
179179
}
180180

181181
return Promise.reject(err);

0 commit comments

Comments
 (0)