Skip to content

Commit b026a31

Browse files
authored
Merge pull request #131 from supabase/kevin/billing-1340-mitigate-timing-based-attacks-for-stripe-sync-engine-api-key
2 parents e5c70be + 42ad28f commit b026a31

File tree

2 files changed

+39
-1
lines changed

2 files changed

+39
-1
lines changed

src/utils/verifyApiKey.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { apiKeyMatches } from './verifyApiKey'
2+
3+
describe('verifyApiKey', () => {
4+
test.each([
5+
// false if apikey is undefined
6+
{ userAuth: 'some-pw', apiKey: '', expected: false },
7+
// false if user auth is undefined
8+
{ userAuth: '', apiKey: 'some-pw', expected: false },
9+
10+
// false if mismatch with different length
11+
{ userAuth: 'some-pw-2', apiKey: 'some-pw', expected: false },
12+
{ userAuth: 'short', apiKey: 'some-pw', expected: false },
13+
{ userAuth: 'looooooooooooong', apiKey: 'some-pw', expected: false },
14+
{ userAuth: 'sameeee', apiKey: 'some-pw', expected: false },
15+
16+
// true if actually matches
17+
{
18+
userAuth: 'ep5oWe3Aingi2chah9phai5eiKeisahviedei1geiNgaf4Neuv',
19+
apiKey: 'ep5oWe3Aingi2chah9phai5eiKeisahviedei1geiNgaf4Neuv',
20+
expected: true,
21+
},
22+
])('testing %s against %s, expected %s', ({ userAuth, apiKey, expected }) => {
23+
const result = apiKeyMatches(userAuth, apiKey)
24+
expect(result).toBe(expected)
25+
})
26+
})

src/utils/verifyApiKey.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from 'fastify'
22
import { getConfig } from './config'
3+
import { timingSafeEqual } from 'node:crypto'
34

45
const config = getConfig()
56

@@ -12,8 +13,19 @@ export const verifyApiKey = (
1213
return reply.code(401).send('Unauthorized')
1314
}
1415
const { authorization } = request.headers
15-
if (authorization !== config.API_KEY) {
16+
if (!apiKeyMatches(authorization, config.API_KEY)) {
1617
return reply.code(401).send('Unauthorized')
1718
}
1819
done()
1920
}
21+
22+
export function apiKeyMatches(authorization: string, apiKey: string | undefined): boolean {
23+
if (!apiKey) return false
24+
if (!authorization) return false
25+
if (authorization.length > apiKey.length) return false
26+
27+
// timingSafeEqual needs both buffers to be the same length
28+
const sameLengthAuth = authorization.padEnd(apiKey.length, ' ')
29+
30+
return timingSafeEqual(Buffer.from(sameLengthAuth), Buffer.from(apiKey))
31+
}

0 commit comments

Comments
 (0)