11// Logic to get and store the current list of public Fastly IPs from the Fastly API: https://www.fastly.com/documentation/reference/api/utils/public-ip-list/
22
3+ import ipaddr , { IPv4 , IPv6 } from 'ipaddr.js'
4+
5+ type IPRangeArr = [ IPv4 | IPv6 , number ] [ ]
6+
37// Default returned from ➜ curl "https://api.fastly.com/public-ip-list"
4- export const DEFAULT_FASTLY_IPS : string [ ] = [
8+ export const DEFAULT_FASTLY_IPS : IPRangeArr = [
59 '23.235.32.0/20' ,
610 '43.249.72.0/22' ,
711 '103.244.50.0/24' ,
@@ -21,22 +25,21 @@ export const DEFAULT_FASTLY_IPS: string[] = [
2125 '185.31.16.0/22' ,
2226 '199.27.72.0/21' ,
2327 '199.232.0.0/16' ,
24- ]
28+ ] . map ( ( cidr ) => ipaddr . parseCIDR ( cidr ) )
2529
26- let ipCache : string [ ] = [ ]
30+ let ipRangeCache : IPRangeArr = [ ]
2731
28- export async function getPublicFastlyIPs ( ) : Promise < string [ ] > {
32+ export async function getPublicFastlyIPs ( ) : Promise < IPRangeArr > {
2933 // Don't fetch the list in dev & testing, just use the defaults
3034 if ( process . env . NODE_ENV !== 'production' ) {
31- ipCache = DEFAULT_FASTLY_IPS
35+ ipRangeCache = DEFAULT_FASTLY_IPS
3236 }
3337
34- if ( ipCache . length ) {
35- return ipCache
38+ if ( ipRangeCache . length ) {
39+ return ipRangeCache
3640 }
3741
3842 const endpoint = 'https://api.fastly.com/public-ip-list'
39- let ips : string [ ] = [ ]
4043 let attempt = 0
4144
4245 while ( attempt < 3 ) {
@@ -47,8 +50,8 @@ export async function getPublicFastlyIPs(): Promise<string[]> {
4750 }
4851 const data = await response . json ( )
4952 if ( data && Array . isArray ( data . addresses ) ) {
50- ips = data . addresses
51- break
53+ ipRangeCache = data . addresses . map ( ( cidr : string ) => ipaddr . parseCIDR ( cidr ) )
54+ return ipRangeCache
5255 } else {
5356 throw new Error ( 'Invalid response structure' )
5457 }
@@ -57,25 +60,22 @@ export async function getPublicFastlyIPs(): Promise<string[]> {
5760 `Failed to fetch Fastly IPs: ${ error . message } . Retrying ${ 3 - attempt } more times` ,
5861 )
5962 attempt ++
60- if ( attempt >= 3 ) {
61- ips = DEFAULT_FASTLY_IPS
62- }
6363 }
6464 }
6565
66- ipCache = ips
67- return ips
66+ ipRangeCache = DEFAULT_FASTLY_IPS
67+ return ipRangeCache
6868}
6969
7070// The IPs we check in the rate-limiter are in the form `X.X.X.X`
7171// But the IPs returned from the Fastly API are in the form `X.X.X.X/Y`
7272// For an IP in the rate-limiter, we want `X.X.X.*` to match `X.X.X.X/Y`
7373export async function isFastlyIP ( ip : string ) : Promise < boolean > {
7474 // If IPs aren't initialized, fetch them
75- if ( ! ipCache . length ) {
75+ if ( ! ipRangeCache . length ) {
7676 await getPublicFastlyIPs ( )
7777 }
78- const parts = ip . split ( '.' )
79- const prefix = parts . slice ( 0 , 3 ) . join ( '.' )
80- return ipCache . some ( ( fastlyIP ) => fastlyIP . startsWith ( prefix ) )
78+ if ( ! ip ) return false // localhost
79+ const addr = ipaddr . parse ( ip )
80+ return ipRangeCache . some ( ( range ) => addr . match ( range ) )
8181}
0 commit comments