Skip to content

Commit 49f0c39

Browse files
authored
disable api endpoint in-app rate-limiting (#54890)
1 parent f80f82d commit 49f0c39

3 files changed

Lines changed: 8 additions & 93 deletions

File tree

src/frame/middleware/api.ts

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,14 @@ import article from '@/article-api/middleware/article'
1010
import webhooks from '@/webhooks/middleware/webhooks.js'
1111
import { ExtendedRequest } from '@/types'
1212
import { noCacheControl } from './cache-control'
13-
import { createRateLimiter } from '@/shielding/middleware/rate-limit'
1413

1514
const router = express.Router()
1615

17-
// Please make sure to rate limit all routes in this file.
18-
const createAPIRateLimiter = (hitsPerMin: number) => createRateLimiter(hitsPerMin, true)
19-
20-
let eventsRouteRateLimit = 100
21-
let internalRoutesRateLimit = 25 // Used internally, higher rate limits
22-
let searchRoutesRateLimit = 15
23-
let publicRoutesRateLimit = 10 // Used publicly, lower rate limits
24-
if (process.env.NODE_ENV === 'test') {
25-
searchRoutesRateLimit = 2 // set to 2 so that api-ai-search.ts test will exceed rate limit after 3 requests
26-
}
27-
28-
router.use('/events', createAPIRateLimiter(eventsRouteRateLimit), events)
29-
router.use('/webhooks', createAPIRateLimiter(internalRoutesRateLimit), webhooks)
30-
router.use('/anchor-redirect', createAPIRateLimiter(internalRoutesRateLimit), anchorRedirect)
31-
router.use('/pagelist', createAPIRateLimiter(publicRoutesRateLimit), pageList)
32-
router.use('/article', createAPIRateLimiter(publicRoutesRateLimit), article)
16+
router.use('/events', events)
17+
router.use('/webhooks', webhooks)
18+
router.use('/anchor-redirect', anchorRedirect)
19+
router.use('/pagelist', pageList)
20+
router.use('/article', article)
3321

3422
// The purpose of this is for convenience to everyone who runs this code
3523
// base locally but don't have an Elasticsearch server locally.
@@ -38,14 +26,13 @@ router.use('/article', createAPIRateLimiter(publicRoutesRateLimit), article)
3826
// server or the known credentials to a remote Elasticsearch. Whenever
3927
// that's the case, they can just HTTP proxy to the production server.
4028
if (process.env.CSE_COPILOT_ENDPOINT || process.env.NODE_ENV === 'test') {
41-
router.use('/ai-search', createAPIRateLimiter(searchRoutesRateLimit), aiSearch)
29+
router.use('/ai-search', aiSearch)
4230
} else {
4331
console.log(
4432
'Proxying AI Search requests to docs.github.com. To use the cse-copilot endpoint, set the CSE_COPILOT_ENDPOINT environment variable.',
4533
)
4634
router.use(
4735
'/ai-search',
48-
createAPIRateLimiter(searchRoutesRateLimit),
4936
createProxyMiddleware({
5037
target: 'https://docs.github.com',
5138
changeOrigin: true,
@@ -56,11 +43,10 @@ if (process.env.CSE_COPILOT_ENDPOINT || process.env.NODE_ENV === 'test') {
5643
)
5744
}
5845
if (process.env.ELASTICSEARCH_URL) {
59-
router.use('/search', createAPIRateLimiter(searchRoutesRateLimit), search)
46+
router.use('/search', search)
6047
} else {
6148
router.use(
6249
'/search',
63-
createAPIRateLimiter(searchRoutesRateLimit),
6450
createProxyMiddleware({
6551
target: 'https://docs.github.com',
6652
changeOrigin: true,
@@ -74,7 +60,7 @@ if (process.env.ELASTICSEARCH_URL) {
7460
// We need access to specific httpOnly cookies set on github.com from the client
7561
// The only way to access these on the client is to fetch them from the server
7662
// Limit this endpoint to 1req/min because a client should only call this route once
77-
router.get('/cookies', createAPIRateLimiter(1), (req, res) => {
63+
router.get('/cookies', (req, res) => {
7864
noCacheControl(res)
7965
const cookies = {
8066
isStaff: Boolean(req.cookies?.staffonly?.startsWith('yes')) || false,

src/search/tests/api-ai-search.ts

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
// IMPORTANT: If you add more tests to this that make requests to
2-
// http://localhost:4000/api/ai-search/v1 make sure you increment the rate limit
3-
// value when NODE_ENV === 'test' in src/search/middleware/ai-search.ts
41
import { expect, test, describe, beforeAll, afterAll } from 'vitest'
52

63
import { post } from 'src/tests/helpers/e2etest.js'
@@ -148,50 +145,4 @@ describe('AI Search Routes', () => {
148145
},
149146
])
150147
})
151-
152-
test('should rate limit when total number of requests exceeds max amount', async () => {
153-
let apiBody = { query: 'How do I create a Repository?', language: 'en', version: 'dotcom' }
154-
155-
// First request isn't rate limited
156-
const response = await fetch('http://localhost:4000/api/ai-search/v1', {
157-
method: 'POST',
158-
headers: { 'Content-Type': 'application/json', 'fastly-client-ip': 'abc' },
159-
body: JSON.stringify(apiBody),
160-
})
161-
162-
expect(response.ok).toBe(true)
163-
expect(response.status).toBe(200)
164-
const limit = parseInt(response.headers.get('ratelimit-limit') || '0')
165-
const remaining = parseInt(response.headers.get('ratelimit-remaining') || '0')
166-
expect(limit).toEqual(2)
167-
expect(remaining).toBeLessThan(limit)
168-
169-
// Second request uses our last unused rate limit
170-
const response2 = await fetch('http://localhost:4000/api/ai-search/v1', {
171-
method: 'POST',
172-
headers: { 'Content-Type': 'application/json', 'fastly-client-ip': 'abc' },
173-
body: JSON.stringify(apiBody),
174-
})
175-
176-
expect(response2.ok).toBe(true)
177-
expect(response2.status).toBe(200)
178-
let newLimit = parseInt(response2.headers.get('ratelimit-limit') || '0')
179-
let newRemaining = parseInt(response2.headers.get('ratelimit-remaining') || '0')
180-
expect(newLimit).toBe(limit)
181-
expect(newRemaining).toBeLessThan(remaining)
182-
183-
// Our third request should be rate limited
184-
const response3 = await fetch('http://localhost:4000/api/ai-search/v1', {
185-
method: 'POST',
186-
headers: { 'Content-Type': 'application/json', 'fastly-client-ip': 'abc' },
187-
body: JSON.stringify(apiBody),
188-
})
189-
190-
expect(response3.ok).toBe(false)
191-
// temporary, its 418 now ... expect(response3.status).toBe(429)
192-
newLimit = parseInt(response3.headers.get('ratelimit-limit') || '0')
193-
newRemaining = parseInt(response3.headers.get('ratelimit-remaining') || '0')
194-
expect(newLimit).toBe(limit)
195-
expect(newRemaining).toBe(0)
196-
})
197148
})

src/shielding/tests/shielding.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -138,28 +138,6 @@ describe('rate limiting', () => {
138138
expect(res.headers['ratelimit-remaining']).toBeUndefined()
139139
})
140140

141-
test('/api/cookies only allows 1 request per minute', async () => {
142-
// Cookies only allows 1 request per minute
143-
const res1 = await get('/api/cookies', {
144-
headers: {
145-
'fastly-client-ip': 'abc123',
146-
},
147-
})
148-
expect(res1.statusCode).toBe(200)
149-
expect(res1.headers['ratelimit-limit']).toBe('1')
150-
expect(res1.headers['ratelimit-remaining']).toBe('0')
151-
152-
// A second request should be rate limited
153-
const res2 = await get('/api/cookies', {
154-
headers: {
155-
'fastly-client-ip': 'abc123',
156-
},
157-
})
158-
// temporary, its 418 now ... expect(res2.statusCode).toBe(429)
159-
expect(res2.headers['ratelimit-limit']).toBe('1')
160-
expect(res2.headers['ratelimit-remaining']).toBe('0')
161-
})
162-
163141
test('Fastly IPs are not rate limited', async () => {
164142
// Fastly IPs are in the form `X.X.X.X/Y`
165143
// Rate limited IPs are in the form `X.X.X.X`

0 commit comments

Comments
 (0)