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
-
7
3
// Default returned from ➜ curl "https://api.fastly.com/public-ip-list"
8
- export const DEFAULT_FASTLY_IPS : IPRangeArr = [
4
+ export const DEFAULT_FASTLY_IPS : string [ ] = [
9
5
'23.235.32.0/20' ,
10
6
'43.249.72.0/22' ,
11
7
'103.244.50.0/24' ,
@@ -25,21 +21,22 @@ export const DEFAULT_FASTLY_IPS: IPRangeArr = [
25
21
'185.31.16.0/22' ,
26
22
'199.27.72.0/21' ,
27
23
'199.232.0.0/16' ,
28
- ] . map ( ( cidr ) => ipaddr . parseCIDR ( cidr ) )
24
+ ]
29
25
30
- let ipRangeCache : IPRangeArr = [ ]
26
+ let ipCache : string [ ] = [ ]
31
27
32
- export async function getPublicFastlyIPs ( ) : Promise < IPRangeArr > {
28
+ export async function getPublicFastlyIPs ( ) : Promise < string [ ] > {
33
29
// Don't fetch the list in dev & testing, just use the defaults
34
30
if ( process . env . NODE_ENV !== 'production' ) {
35
- ipRangeCache = DEFAULT_FASTLY_IPS
31
+ ipCache = DEFAULT_FASTLY_IPS
36
32
}
37
33
38
- if ( ipRangeCache . length ) {
39
- return ipRangeCache
34
+ if ( ipCache . length ) {
35
+ return ipCache
40
36
}
41
37
42
38
const endpoint = 'https://api.fastly.com/public-ip-list'
39
+ let ips : string [ ] = [ ]
43
40
let attempt = 0
44
41
45
42
while ( attempt < 3 ) {
@@ -50,8 +47,8 @@ export async function getPublicFastlyIPs(): Promise<IPRangeArr> {
50
47
}
51
48
const data = await response . json ( )
52
49
if ( data && Array . isArray ( data . addresses ) ) {
53
- ipRangeCache = data . addresses . map ( ( cidr : string ) => ipaddr . parseCIDR ( cidr ) )
54
- return ipRangeCache
50
+ ips = data . addresses
51
+ break
55
52
} else {
56
53
throw new Error ( 'Invalid response structure' )
57
54
}
@@ -60,22 +57,25 @@ export async function getPublicFastlyIPs(): Promise<IPRangeArr> {
60
57
`Failed to fetch Fastly IPs: ${ error . message } . Retrying ${ 3 - attempt } more times` ,
61
58
)
62
59
attempt ++
60
+ if ( attempt >= 3 ) {
61
+ ips = DEFAULT_FASTLY_IPS
62
+ }
63
63
}
64
64
}
65
65
66
- ipRangeCache = DEFAULT_FASTLY_IPS
67
- return ipRangeCache
66
+ ipCache = ips
67
+ return ips
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 ( ! ipRangeCache . length ) {
75
+ if ( ! ipCache . length ) {
76
76
await getPublicFastlyIPs ( )
77
77
}
78
- if ( ! ip ) return false // localhost
79
- const addr = ipaddr . parse ( ip )
80
- return ipRangeCache . some ( ( range ) => addr . match ( range ) )
78
+ const parts = ip . split ( '.' )
79
+ const prefix = parts . slice ( 0 , 3 ) . join ( '.' )
80
+ return ipCache . some ( ( fastlyIP ) => fastlyIP . startsWith ( prefix ) )
81
81
}
0 commit comments