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
This commit is contained in:
Eduard Gert
2024-04-17 15:27:21 +02:00
committed by GitHub
parent 2272a1d2a4
commit 5e13548b81
7 changed files with 78 additions and 24 deletions

View File

@@ -1,3 +1,4 @@
import { IconCircleX } from "@tabler/icons-react";
import type { ErrorResponse } from "@utils/api";
import { cn } from "@utils/helpers";
import classNames from "classnames";
@@ -88,7 +89,7 @@ export default function Notification<T>({
{loading ? (
<Loader2 size={14} className={"animate-spin"} />
) : error ? (
<XIcon size={14} />
<IconCircleX size={24} />
) : (
icon || <CheckIcon size={14} />
)}

View File

@@ -25,7 +25,7 @@ const RoutesContext = React.createContext(
);
export default function RoutesProvider({ children }: Props) {
const routeRequest = useApiCall<Route>("/routes");
const routeRequest = useApiCall<Route>("/routes", true);
const { mutate } = useSWRConfig();
const updateRoute = async (

View File

@@ -115,7 +115,7 @@ export function NameserverModalContent({
preset,
cell,
}: ModalProps) {
const nsRequest = useApiCall<NameserverGroup>("/dns/nameservers");
const nsRequest = useApiCall<NameserverGroup>("/dns/nameservers", true);
const { mutate } = useSWRConfig();
const isUpdate = useMemo(() => {
@@ -233,24 +233,31 @@ export function NameserverModalContent({
return domains.some((d) => d.name === "");
}, [domains]);
const nameLengthError = useMemo(() => {
if (name.length > 40) return "Name should be less than 40 characters";
return "";
}, [name]);
const hasAnyError = useMemo(() => {
return (
hasNSErrors ||
nsError ||
domainError ||
name == "" ||
nameservers.length == 0 ||
hasDomainErrors ||
groups.length == 0
groups.length == 0 ||
nameLengthError !== "" ||
name == ""
);
}, [
nsError,
domainError,
name,
nameservers,
groups,
hasNSErrors,
hasDomainErrors,
nameLengthError,
name,
]);
return (
@@ -427,6 +434,7 @@ export function NameserverModalContent({
<Input
autoFocus={true}
tabIndex={0}
error={nameLengthError}
placeholder={"e.g., Public DNS"}
value={name}
onChange={(e) => setName(e.target.value)}
@@ -516,7 +524,7 @@ function NameserverInput({
const validCIDR = cidr.isValidAddress(ip);
if (!validCIDR) {
onError && onError(true);
return "Please enter a valid CIDR, e.g., 192.168.1.0/24";
return "Please enter a valid IP, e.g., 192.168.1.0";
}
onError && onError(false);
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -532,7 +540,7 @@ function NameserverInput({
<div className={"w-full"}>
<Input
customPrefix={"IP"}
placeholder={"e.g., 172.16.0.0/16"}
placeholder={"e.g., 172.16.0.0"}
maxWidthClass={"w-full"}
value={ip}
className={"font-mono !text-[13px]"}

View File

@@ -183,17 +183,37 @@ export function RouteModalContent({
(cidrError && cidrError.length > 1) ||
(peerTab === "peer-group" && routingPeerGroups.length == 0) ||
(peerTab === "routing-peer" && !routingPeer) ||
groups.length == 0
groups.length == 0 ||
networkRange == ""
);
}, [cidrError, peerTab, routingPeerGroups.length, routingPeer, groups]);
}, [
cidrError,
peerTab,
routingPeerGroups.length,
routingPeer,
groups,
networkRange,
]);
const isNameEntered = useMemo(() => {
return !(networkIdentifier == "");
const networkIdentifierError = useMemo(() => {
return (networkIdentifier?.length || 0) > 40
? "Network Identifier must be less than 40 characters"
: "";
}, [networkIdentifier]);
const metricError = useMemo(() => {
return parseInt(metric) < 1 || parseInt(metric) > 9999
? "Metric must be between 1 and 9999"
: "";
}, [metric]);
const isNameEntered = useMemo(() => {
return networkIdentifier != "" && networkIdentifierError == "";
}, [networkIdentifier, networkIdentifierError]);
const canCreateOrSave = useMemo(() => {
return isNetworkEntered && isNameEntered;
}, [isNetworkEntered, isNameEntered]);
return isNetworkEntered && isNameEntered && metricError == "";
}, [isNetworkEntered, isNameEntered, metricError]);
return (
<ModalContent maxWidthClass={"max-w-xl"}>
@@ -250,7 +270,10 @@ export function RouteModalContent({
/>
Name & Description
</TabsTrigger>
<TabsTrigger value={"settings"} disabled={!canCreateOrSave}>
<TabsTrigger
value={"settings"}
disabled={!isNetworkEntered || !isNameEntered}
>
<Settings2
size={16}
className={
@@ -340,6 +363,7 @@ export function RouteModalContent({
Add a unique network identifier that is assigned to each device.
</HelpText>
<Input
error={networkIdentifierError}
autoFocus={true}
tabIndex={0}
ref={nameRef}
@@ -406,6 +430,8 @@ export function RouteModalContent({
max={9999}
maxWidthClass={"max-w-[200px]"}
value={metric}
error={metricError}
errorTooltip={true}
type={"number"}
onChange={(e) => setMetric(e.target.value)}
customPrefix={
@@ -469,7 +495,7 @@ export function RouteModalContent({
<Button
variant={"primary"}
onClick={() => setTab("settings")}
disabled={!canCreateOrSave}
disabled={!isNameEntered || !isNetworkEntered}
>
Continue
</Button>

View File

@@ -197,14 +197,21 @@ function RouteUpdateModalContent({ onSuccess, route, cell }: ModalProps) {
.filter((p) => p != undefined) as string[];
}, [groupedRoute]);
const metricError = useMemo(() => {
return parseInt(metric.toString()) < 1 || parseInt(metric.toString()) > 9999
? "Metric must be between 1 and 9999"
: "";
}, [metric]);
// Is button disabled
const isDisabled = useMemo(() => {
return (
(peerTab === "peer-group" && routingPeerGroups.length == 0) ||
(peerTab === "routing-peer" && !routingPeer) ||
groups.length == 0
groups.length == 0 ||
metricError !== ""
);
}, [peerTab, routingPeerGroups.length, routingPeer, groups]);
}, [peerTab, routingPeerGroups.length, routingPeer, groups, metricError]);
const [tab, setTab] = useState(
cell && cell == "metric" ? "settings" : "network",
@@ -352,6 +359,8 @@ function RouteUpdateModalContent({ onSuccess, route, cell }: ModalProps) {
max={9999}
maxWidthClass={"max-w-[200px]"}
value={metric}
error={metricError}
errorTooltip={true}
type={"number"}
onChange={(e) => setMetric(e.target.value)}
customPrefix={

View File

@@ -126,7 +126,7 @@ type ModalProps = {
};
export function SetupKeyModalContent({ onSuccess }: ModalProps) {
const setupKeyRequest = useApiCall<SetupKey>("/setup-keys");
const setupKeyRequest = useApiCall<SetupKey>("/setup-keys", true);
const { mutate } = useSWRConfig();
const [name, setName] = useState("");
@@ -143,10 +143,18 @@ export function SetupKeyModalContent({ onSuccess }: ModalProps) {
return reusable ? "Unlimited" : "1";
}, [reusable]);
const expiresInError = useMemo(() => {
const expires = parseInt(expiresIn);
if (expires < 1 || expires > 365) {
return "Days should be between 1 and 365";
}
return "";
}, [expiresIn]);
const isDisabled = useMemo(() => {
const trimmedName = trim(name);
return trimmedName.length === 0;
}, [name]);
return trimmedName.length === 0 || expiresInError.length > 0;
}, [name, expiresInError]);
const submit = () => {
if (!selectedGroups) return;
@@ -245,6 +253,8 @@ export function SetupKeyModalContent({ onSuccess }: ModalProps) {
min={1}
max={365}
value={expiresIn}
error={expiresInError}
errorTooltip={true}
type={"number"}
onChange={(e) => setExpiresIn(e.target.value)}
customPrefix={

View File

@@ -169,13 +169,13 @@ export function useApiErrorHandling(ignoreError = false) {
return login(currentPath);
}
if (err.code == 401 && err.message == "token invalid") {
return setError(err);
setError(err);
}
if (err.code == 500 && err.message == "internal server error") {
return setError(err);
setError(err);
}
if (err.code > 400 && err.code <= 500) {
return setError(err);
setError(err);
}
return Promise.reject(err);