import { useParams } from 'common' import { toast } from 'sonner' import { Button, Form, Input, Modal, Tooltip, TooltipContent, TooltipTrigger } from 'ui' import InformationBox from 'components/ui/InformationBox' import { useNetworkRestrictionsQuery } from 'data/network-restrictions/network-restrictions-query' import { useNetworkRestrictionsApplyMutation } from 'data/network-restrictions/network-retrictions-apply-mutation' import { DOCS_URL } from 'lib/constants' import { HelpCircle } from 'lucide-react' import { checkIfPrivate, getAddressEndRange, isValidAddress, normalize, } from './NetworkRestrictions.utils' const IPV4_MAX_CIDR_BLOCK_SIZE = 32 const IPV6_MAX_CIDR_BLOCK_SIZE = 128 interface AddRestrictionModalProps { type?: 'IPv4' | 'IPv6' hasOverachingRestriction: boolean onClose: () => void } const AddRestrictionModal = ({ type, hasOverachingRestriction, onClose, }: AddRestrictionModalProps) => { const formId = 'add-restriction-form' const { ref } = useParams() const { data } = useNetworkRestrictionsQuery({ projectRef: ref }, { enabled: type !== undefined }) const ipv4Restrictions = data?.config?.dbAllowedCidrs ?? [] // @ts-ignore [Joshen] API typing issue const ipv6Restrictions = data?.config?.dbAllowedCidrsV6 ?? [] const restrictedIps = ipv4Restrictions.concat(ipv6Restrictions) const { mutate: applyNetworkRestrictions, isLoading: isApplying } = useNetworkRestrictionsApplyMutation({ onSuccess: () => { toast.success('Successfully added restriction') onClose() }, }) const validate = (values: any) => { const errors: any = {} if (type === undefined) return errors const { ipAddress, cidrBlockSize } = values // Validate CIDR block size const isOutOfCidrSizeRange = type === 'IPv4' ? cidrBlockSize < 0 || cidrBlockSize > IPV4_MAX_CIDR_BLOCK_SIZE : cidrBlockSize < 0 || cidrBlockSize > IPV6_MAX_CIDR_BLOCK_SIZE if (cidrBlockSize.length === 0 || isOutOfCidrSizeRange) { errors.cidrBlockSize = `Size has to be between 0 to ${ type === 'IPv4' ? IPV4_MAX_CIDR_BLOCK_SIZE : IPV6_MAX_CIDR_BLOCK_SIZE }` } // Validate IP address const isValid = isValidAddress(ipAddress) if (!isValid) { errors.ipAddress = 'Please enter a valid IP address' return errors } try { const isPrivate = checkIfPrivate(type, ipAddress) if (isPrivate) errors.ipAddress = 'Private IP addresses are not supported' } catch (error: any) { errors.ipAddress = error.message } return errors } const onSubmit = async (values: any) => { if (!ref) return console.error('Project ref is required') const address = `${values.ipAddress}/${values.cidrBlockSize}` const normalizedAddress = normalize(address) const alreadyExists = restrictedIps.includes(address) || restrictedIps.includes(normalizedAddress) if (alreadyExists) { return toast(`The address ${address} is already restricted`) } // Need to replace over arching restriction (allow all / disallow all) if (hasOverachingRestriction) { const dbAllowedCidrs = type === 'IPv4' ? [normalizedAddress] : [] const dbAllowedCidrsV6 = type === 'IPv6' ? [normalizedAddress] : [] applyNetworkRestrictions({ projectRef: ref, dbAllowedCidrs, dbAllowedCidrsV6 }) } else { const dbAllowedCidrs = type === 'IPv4' ? [...ipv4Restrictions, normalizedAddress] : ipv4Restrictions const dbAllowedCidrsV6 = type === 'IPv6' ? [...ipv6Restrictions, normalizedAddress] : ipv6Restrictions applyNetworkRestrictions({ projectRef: ref, dbAllowedCidrs, dbAllowedCidrsV6 }) } } return (
{({ values }: any) => { const isPrivate = type !== undefined && isValidAddress(values.ipAddress) ? checkIfPrivate(type, values.ipAddress) : false const isValidBlockSize = values.cidrBlockSize !== '' && ((type === 'IPv4' && values.cidrBlockSize >= 0 && values.cidrBlockSize <= IPV4_MAX_CIDR_BLOCK_SIZE) || (type === 'IPv6' && values.cidrBlockSize >= 0 && values.cidrBlockSize <= IPV6_MAX_CIDR_BLOCK_SIZE)) const availableAddresses = type === 'IPv4' ? Math.pow(2, IPV4_MAX_CIDR_BLOCK_SIZE - (values?.cidrBlockSize ?? 0)) : Math.pow(2, IPV6_MAX_CIDR_BLOCK_SIZE - (values?.cidrBlockSize ?? 0)) const addressRange = type !== undefined ? getAddressEndRange(type, `${values.ipAddress}/${values.cidrBlockSize}`) : undefined const isValidCIDR = isValidBlockSize && !isPrivate && addressRange !== undefined const normalizedAddress = isValidCIDR ? normalize(`${values.ipAddress}/${values.cidrBlockSize}`) : `${values.ipAddress}/${values.cidrBlockSize}` return ( <>

This will add an IP address range to a list of allowed ranges that can access your database.

CIDR Block Size

Classless inter-domain routing (CIDR) notation is the notation used to identify networks and hosts in the networks. The block size tells us how many bits we need to take for the network prefix, and is a value between 0 to{' '} {type === 'IPv4' ? IPV4_MAX_CIDR_BLOCK_SIZE : IPV6_MAX_CIDR_BLOCK_SIZE} .
} id="cidrBlockSize" name="cidrBlockSize" type="number" placeholder={ type === 'IPv4' ? IPV4_MAX_CIDR_BLOCK_SIZE.toString() : IPV6_MAX_CIDR_BLOCK_SIZE.toString() } min={0} max={type === 'IPv4' ? IPV4_MAX_CIDR_BLOCK_SIZE : IPV6_MAX_CIDR_BLOCK_SIZE} />
{isValidCIDR ? (

The address range {normalizedAddress} will be restricted

Selected address space: {addressRange.start} to{' '} {addressRange.end}{' '}

Number of addresses: {availableAddresses}

) : (

A summary of your restriction will be shown here after entering a valid IP address and CIDR block size. IP addresses will also be normalized.

)} ) }}
) } export default AddRestrictionModal