-
Notifications
You must be signed in to change notification settings - Fork 18
Validate IPs and IP networks #2461
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
|
Ok, here's something interesting. I tried swapping in the export function validateIp(ip: string) {
- const isv4 = IPV4_REGEX.test(ip)
- const isv6 = !isv4 && IPV6_REGEX.test(ip)
+ let isv4 = false
+ try {
+ IPv4.fromString(ip)
+ isv4 = true
+ } catch (e) {}
+ let isv6 = false
+ if (!isv4) {
+ try {
+ IPv6.fromString(ip)
+ isv6 = true
+ } catch (e) {}
+ }
return { isv4, isv6, valid: isv4 || isv6 }
}
I didn't notice these differences before because my list of examples for IP networks was much less thorough than my kickass list for plain addresses. When I add the offending address to the list of IP nets,
Rust
So, after all that excitement, I am going to take |
2603284 to
1ca36d7
Compare
1ca36d7 to
72b506f
Compare
|
truly, we are jamming. 2024-09-24-validate-ips-and-nets.mp4 |
9e44d64 to
1cce7b4
Compare
| form.setValue('transitIps', [...transitIps, transitIp]) | ||
| transitIpsForm.reset() | ||
| } | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without wrapping this in handleSubmit, it doesn't run the validate function on submit. This way the inner function never gets called unless validation passes.
| // back to validating on submit instead of change | ||
| onTypeChange={() => | ||
| hostForm.reset({ type: hostForm.getValues('type'), value: '' }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of hate this, but it does pretty elegantly express what I want to happen here, namely that we are basically starting over from scratch whenever we change the type. Very weird combination of feeling both wrong and exactly right.
|
Still need to add another test or two and fix the clear button bug in the firewall rules inputs, but this is mostly ready to review. It's crazy long but most of the length is tests and fresh additions. |
david-crespo
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some explanations.
| name: TName | ||
| control: Control<TFieldValues> | ||
| onChange?: (value: string | null | undefined) => void | ||
| validate?: Validate<FieldPathValue<TFieldValues, TName>, TFieldValues> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding this lets us validate names in arbitrary-value comboboxes.
|
|
||
| test.each(invalid)('parseIp catches invalid IP: %s', (s) => { | ||
| expect(parseIp(s)).toStrictEqual({ type: 'error', message: 'Not a valid IP address' }) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From here up, the tests are basically unchanged, I just had to extract the lists of IPs so I could reuse them in the IP net tests.
| /^(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])(?:\.(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])){3}$/u | ||
|
|
||
| const IPV6_REGEX = | ||
| /^(?:(?:[\da-f]{1,4}:){7}[\da-f]{1,4}|(?:[\da-f]{1,4}:){1,7}:|(?:[\da-f]{1,4}:){1,6}:[\da-f]{1,4}|(?:[\da-f]{1,4}:){1,5}(?::[\da-f]{1,4}){1,2}|(?:[\da-f]{1,4}:){1,4}(?::[\da-f]{1,4}){1,3}|(?:[\da-f]{1,4}:){1,3}(?::[\da-f]{1,4}){1,4}|(?:[\da-f]{1,4}:){1,2}(?::[\da-f]{1,4}){1,5}|[\da-f]{1,4}:(?::[\da-f]{1,4}){1,6}|:(?:(?::[\da-f]{1,4}){1,7}|:)|fe80:(?::[\da-f]{0,4}){0,4}%[\da-z]+|::(?:f{4}(?::0{1,4})?:)?(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d)|(?:[\da-f]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d))$/iu |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
regexes are unchanged, the point was to distinguish between parse and validate and to add the IP net things
| validate={(value) => | ||
| // TODO: is required false correct here? should this function even have that argument? | ||
| validateName(value, `${capitalize(sectionType)} name`, false) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think required: false is correct because we don't want this to show a required error when it's left empty after a bad input was attempted.
| if (targets.value.some((t) => t.value === value && t.type === type)) return | ||
| targets.onChange([...targets.value, { type, value }]) | ||
| targetForm.reset() | ||
| targetForm.reset(targetAndHostDefaultValues) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed by useEffect below.
| /> | ||
| <MiniTable.ClearAndAddButtons | ||
| addButtonCopy="Add host filter" | ||
| disableClear={!!hostValue} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WHOOPS
| } else if (last.isv6) { | ||
| if (last.type === 'error') { | ||
| errors.last = { type: 'pattern', message: last.message } | ||
| } else if (last.type === 'v6') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing actually changes in this file, so I didn't change the tests.
| if (error) return error | ||
|
|
||
| if (transitIps.includes(value)) return 'Transit IP already in list' | ||
| }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doing this here instead of in the handleSubmit means we get a beautiful validation error inline on attempts to add a dupe.
| const targets = useController({ name: 'targets', control }).field | ||
| const targetType = targetForm.watch('type') | ||
| const targetValue = targetForm.watch('value') | ||
| const [targetType, targetValue] = targetForm.watch(['type', 'value']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡
| const { isSubmitSuccessful: targetSubmitSuccessful } = targetForm.formState | ||
| useEffect(() => { | ||
| if (targetSubmitSuccessful) targetForm.reset(targetAndHostDefaultValues) | ||
| }, [targetSubmitSuccessful, targetForm]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a clever workaround. A shame that we need to distinguish between the two flows, but premature validation errors are even worse. Nevertheless, this is clever.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking there is probably a way to wrap this up into some kind of subform helper hook.
charliepark
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great!
oxidecomputer/console@5561f28...073471c * [073471c4](oxidecomputer/console@073471c4) mock api: disks are created straight into detached state * [612ec90c](oxidecomputer/console@612ec90c) oxidecomputer/console#2485 * [614f1bb5](oxidecomputer/console@614f1bb5) oxidecomputer/console#2466 * [df2dc14c](oxidecomputer/console@df2dc14c) bump vite related deps for a weird vuln * [a030b9e0](oxidecomputer/console@a030b9e0) oxidecomputer/console#2464 * [dec48497](oxidecomputer/console@dec48497) oxidecomputer/console#2461 * [e46216aa](oxidecomputer/console@e46216aa) oxidecomputer/console#2482 * [0d897efe](oxidecomputer/console@0d897efe) oxidecomputer/console#2479 * [f88790db](oxidecomputer/console@f88790db) oxidecomputer/console#2478 * [d634c8f0](oxidecomputer/console@d634c8f0) oxidecomputer/console#2477 * [eeaa14c3](oxidecomputer/console@eeaa14c3) oxidecomputer/console#2475 * [5ece6e18](oxidecomputer/console@5ece6e18) oxidecomputer/console#2467 * [4b699e01](oxidecomputer/console@4b699e01) oxidecomputer/console#2448 * [9c9dc149](oxidecomputer/console@9c9dc149) oxidecomputer/console#2465 * [1aa0fc9b](oxidecomputer/console@1aa0fc9b) oxidecomputer/console#2463 * [57db4054](oxidecomputer/console@57db4054) oxidecomputer/console#2462 * [da7fe328](oxidecomputer/console@da7fe328) oxidecomputer/console#2460 * [e0d52efd](oxidecomputer/console@e0d52efd) oxidecomputer/console#2437 * [1625d02a](oxidecomputer/console@1625d02a) oxidecomputer/console#2458 * [fd82458e](oxidecomputer/console@fd82458e) oxidecomputer/console#2457 * [7daaa337](oxidecomputer/console@7daaa337) oxidecomputer/console#2453
oxidecomputer/console@5561f28...073471c * [073471c4](oxidecomputer/console@073471c4) mock api: disks are created straight into detached state * [612ec90c](oxidecomputer/console@612ec90c) oxidecomputer/console#2485 * [614f1bb5](oxidecomputer/console@614f1bb5) oxidecomputer/console#2466 * [df2dc14c](oxidecomputer/console@df2dc14c) bump vite related deps for a weird vuln * [a030b9e0](oxidecomputer/console@a030b9e0) oxidecomputer/console#2464 * [dec48497](oxidecomputer/console@dec48497) oxidecomputer/console#2461 * [e46216aa](oxidecomputer/console@e46216aa) oxidecomputer/console#2482 * [0d897efe](oxidecomputer/console@0d897efe) oxidecomputer/console#2479 * [f88790db](oxidecomputer/console@f88790db) oxidecomputer/console#2478 * [d634c8f0](oxidecomputer/console@d634c8f0) oxidecomputer/console#2477 * [eeaa14c3](oxidecomputer/console@eeaa14c3) oxidecomputer/console#2475 * [5ece6e18](oxidecomputer/console@5ece6e18) oxidecomputer/console#2467 * [4b699e01](oxidecomputer/console@4b699e01) oxidecomputer/console#2448 * [9c9dc149](oxidecomputer/console@9c9dc149) oxidecomputer/console#2465 * [1aa0fc9b](oxidecomputer/console@1aa0fc9b) oxidecomputer/console#2463 * [57db4054](oxidecomputer/console@57db4054) oxidecomputer/console#2462 * [da7fe328](oxidecomputer/console@da7fe328) oxidecomputer/console#2460 * [e0d52efd](oxidecomputer/console@e0d52efd) oxidecomputer/console#2437 * [1625d02a](oxidecomputer/console@1625d02a) oxidecomputer/console#2458 * [fd82458e](oxidecomputer/console@fd82458e) oxidecomputer/console#2457 * [7daaa337](oxidecomputer/console@7daaa337) oxidecomputer/console#2453



Followup work for #2437 (comment)
ip-numConfirm Rust code has the same validation results (https://github.com/oxidecomputer/test-ip-validation)ip-numbecause it does not have the same validation results