Skip to content

Commit db6ad06

Browse files
authored
fix: handle multicast ips in add ip (#4129)
- add isPublicIp to validators - add unit tests to isPublicIP validator - refactor the validation logic in add_ip.vue
1 parent a3519a5 commit db6ad06

File tree

3 files changed

+74
-34
lines changed

3 files changed

+74
-34
lines changed

packages/playground/src/dashboard/components/add_ip.vue

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@
1717
></v-select>
1818
<input-validator
1919
:value="publicIP"
20-
:rules="[validators.required('IP is required.'), validators.isIPRange('Not a valid IP')]"
21-
:async-rules="[ipcheck]"
20+
:rules="[
21+
validators.required('IP is required.'),
22+
validators.isIPRange('Not a valid IP'),
23+
validators.isPublicIP(),
24+
]"
25+
:async-rules="[isExistingIp]"
2226
#="{ props }"
2327
>
2428
<input-tooltip tooltip="IP address in CIDR format xxx.xxx.xxx.xxx/xx">
@@ -36,8 +40,13 @@
3640
<input-validator
3741
v-if="type === IPType.range"
3842
:value="toPublicIP"
39-
:rules="[validators.required('IP is required.'), validators.isIPRange('Not a valid IP')]"
40-
:async-rules="[toIpCheck]"
43+
:rules="[
44+
validators.required('IP is required.'),
45+
validators.isIPRange('Not a valid IP'),
46+
validators.isPublicIP(),
47+
toIpCheck,
48+
]"
49+
:async-rules="[isExistingIp]"
4150
#="{ props }"
4251
>
4352
<input-tooltip tooltip="IP address in CIDR format xxx.xxx.xxx.xxx/xx">
@@ -134,7 +143,6 @@
134143
import { TFChainError } from "@threefold/tfchain_client";
135144
import { contains } from "cidr-tools";
136145
import { getIPRange } from "get-ip-range";
137-
import { default as PrivateIp } from "private-ip";
138146
import { ref, watch } from "vue";
139147
140148
import { gqlClient } from "@/clients";
@@ -181,22 +189,6 @@ export default {
181189
{ deep: true },
182190
);
183191
184-
async function ipcheck() {
185-
if (PrivateIp(publicIP.value.split("/")[0])) {
186-
return {
187-
message: "IP is not public",
188-
};
189-
}
190-
191-
if (await IpExistsCheck(publicIP.value)) {
192-
return {
193-
message: "IP exists.",
194-
};
195-
}
196-
197-
return undefined;
198-
}
199-
200192
watch(
201193
type,
202194
() => {
@@ -208,7 +200,15 @@ export default {
208200
const ips = await gqlClient.publicIps({ ip: true }, { where: { ip_eq: pubIp } });
209201
return ips.length > 0;
210202
}
211-
async function toIpCheck() {
203+
async function isExistingIp(ip: string) {
204+
if (await IpExistsCheck(ip)) {
205+
return {
206+
message: "IP exists.",
207+
};
208+
}
209+
return undefined;
210+
}
211+
function toIpCheck() {
212212
if (toPublicIP.value.split("/")[1] !== publicIP.value.split("/")[1]) {
213213
return {
214214
message: "Subnet is different.",
@@ -241,17 +241,6 @@ export default {
241241
message: "Range must not exceed 16.",
242242
};
243243
}
244-
if (PrivateIp(publicIP.value.split("/")[0])) {
245-
return {
246-
message: "IP is not public.",
247-
};
248-
}
249-
if (await IpExistsCheck(toPublicIP.value)) {
250-
return {
251-
message: "IP exists.",
252-
};
253-
}
254-
return undefined;
255244
}
256245
257246
function gatewayCheck() {
@@ -399,7 +388,7 @@ export default {
399388
showRange,
400389
addIPs,
401390
addFarmIp,
402-
ipcheck,
391+
isExistingIp,
403392
toIpCheck,
404393
gatewayCheck,
405394
};

packages/playground/src/utils/validators.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { GridClient } from "@threefold/grid_client";
2+
import { default as PrivateIp } from "private-ip";
23
import StellarSdk from "stellar-sdk";
34
import validator from "validator";
45
import type { Options } from "validator/lib/isBoolean";
@@ -406,6 +407,16 @@ export function isIPRange(msg: string, options?: validator.IPVersion) {
406407
};
407408
}
408409

410+
export function isPublicIP(msg = "IP is not public") {
411+
return (ip: string) => {
412+
const firstOctet = parseInt(ip.split(".")[0], 10);
413+
const isMulticast = firstOctet >= 224 && firstOctet <= 239;
414+
if (isMulticast || PrivateIp(ip.split("/")[0])) {
415+
return { message: msg };
416+
}
417+
};
418+
}
419+
409420
export function isISBN(msg: string, options?: validator.ISBNVersion) {
410421
return (value: string) => {
411422
if (!validator.isISBN(value, options)) {

packages/playground/tests/utils/ip.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, expect, it } from "vitest";
22

33
import { ipToLong, longToIp } from "../../src/utils/ip";
4+
import { isPublicIP } from "../../src/utils/validators";
45

56
describe("ipToLong", () => {
67
it('should convert IPv4 "0.0.0.0" to 0', () => {
@@ -57,3 +58,42 @@ describe("longToIp", () => {
5758
expect(longToIp(long)).toBe(ip);
5859
});
5960
});
61+
62+
describe("isPublicIP", () => {
63+
const validPublicIPs = ["8.8.8.8", "8.8.8.8/24", "172.32.0.1", "192.169.0.1", "11.12.13.14", "104.25.129.30"];
64+
65+
const invalidPublicIPs = [
66+
// Private IPs
67+
"10.0.0.1",
68+
"172.16.0.1",
69+
"192.168.0.1",
70+
// Loopback
71+
"127.0.0.1",
72+
// Link-local
73+
"169.254.0.1",
74+
// Multicast
75+
"224.0.0.1",
76+
"232.229.60.203",
77+
"239.255.255.255",
78+
// Reserved
79+
"240.0.0.1",
80+
"255.255.255.255",
81+
// Documentation and example ranges
82+
"192.0.2.1", // TEST-NET-1
83+
"198.51.100.1", // TEST-NET-2
84+
"203.0.113.1", // TEST-NET-3
85+
];
86+
87+
it.each(validPublicIPs)("should return undefined for valid public IP %s", ip => {
88+
expect(isPublicIP()(ip)).toBeUndefined();
89+
});
90+
91+
it.each(invalidPublicIPs)("should return an error message for invalid public IP %s", ip => {
92+
expect(isPublicIP()(ip)).toEqual({ message: "IP is not public" });
93+
});
94+
95+
it("should return a custom error message when provided", () => {
96+
const customMessage = "Custom error message";
97+
expect(isPublicIP(customMessage)("10.0.0.1")).toEqual({ message: customMessage });
98+
});
99+
});

0 commit comments

Comments
 (0)