1
1
// 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/
2
2
3
+ import ipaddr , { IPv4 , IPv6 } from 'ipaddr.js'
4
+
5
+ type IPRangeArr = [ IPv4 | IPv6 , number ] [ ]
6
+
3
7
// 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 = [
5
9
'23.235.32.0/20' ,
6
10
'43.249.72.0/22' ,
7
11
'103.244.50.0/24' ,
@@ -21,22 +25,21 @@ export const DEFAULT_FASTLY_IPS: string[] = [
21
25
'185.31.16.0/22' ,
22
26
'199.27.72.0/21' ,
23
27
'199.232.0.0/16' ,
24
- ]
28
+ ] . map ( ( cidr ) => ipaddr . parseCIDR ( cidr ) )
25
29
26
- let ipCache : string [ ] = [ ]
30
+ let ipRangeCache : IPRangeArr = [ ]
27
31
28
- export async function getPublicFastlyIPs ( ) : Promise < string [ ] > {
32
+ export async function getPublicFastlyIPs ( ) : Promise < IPRangeArr > {
29
33
// Don't fetch the list in dev & testing, just use the defaults
30
34
if ( process . env . NODE_ENV !== 'production' ) {
31
- ipCache = DEFAULT_FASTLY_IPS
35
+ ipRangeCache = DEFAULT_FASTLY_IPS
32
36
}
33
37
34
- if ( ipCache . length ) {
35
- return ipCache
38
+ if ( ipRangeCache . length ) {
39
+ return ipRangeCache
36
40
}
37
41
38
42
const endpoint = 'https://api.fastly.com/public-ip-list'
39
- let ips : string [ ] = [ ]
40
43
let attempt = 0
41
44
42
45
while ( attempt < 3 ) {
@@ -47,8 +50,8 @@ export async function getPublicFastlyIPs(): Promise<string[]> {
47
50
}
48
51
const data = await response . json ( )
49
52
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
52
55
} else {
53
56
throw new Error ( 'Invalid response structure' )
54
57
}
@@ -57,25 +60,22 @@ export async function getPublicFastlyIPs(): Promise<string[]> {
57
60
`Failed to fetch Fastly IPs: ${ error . message } . Retrying ${ 3 - attempt } more times` ,
58
61
)
59
62
attempt ++
60
- if ( attempt >= 3 ) {
61
- ips = DEFAULT_FASTLY_IPS
62
- }
63
63
}
64
64
}
65
65
66
- ipCache = ips
67
- return ips
66
+ ipRangeCache = DEFAULT_FASTLY_IPS
67
+ return ipRangeCache
68
68
}
69
69
70
70
// The IPs we check in the rate-limiter are in the form `X.X.X.X`
71
71
// But the IPs returned from the Fastly API are in the form `X.X.X.X/Y`
72
72
// For an IP in the rate-limiter, we want `X.X.X.*` to match `X.X.X.X/Y`
73
73
export async function isFastlyIP ( ip : string ) : Promise < boolean > {
74
74
// If IPs aren't initialized, fetch them
75
- if ( ! ipCache . length ) {
75
+ if ( ! ipRangeCache . length ) {
76
76
await getPublicFastlyIPs ( )
77
77
}
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 ) )
81
81
}
0 commit comments