Skip to content

Commit 9c45321

Browse files
authored
fix: payload auth api-key algorithm compatibility (#13076)
When saving api-keys in prior versions you can have sha1 generated lookup keys. This ensures compatibility with newer sha256 lookups.
1 parent 96c24a2 commit 9c45321

File tree

3 files changed

+57
-8
lines changed

3 files changed

+57
-8
lines changed

packages/payload/src/auth/baseFields/apiKey.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export const apiKeyFields = [
5050
}
5151
if (data?.apiKey) {
5252
return crypto
53-
.createHmac('sha1', req.payload.secret)
53+
.createHmac('sha256', req.payload.secret)
5454
.update(data.apiKey as string)
5555
.digest('hex')
5656
}

packages/payload/src/auth/strategies/apiKey.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,34 @@ export const APIKeyAuthentication =
1212

1313
if (authHeader?.startsWith(`${collectionConfig.slug} API-Key `)) {
1414
const apiKey = authHeader.replace(`${collectionConfig.slug} API-Key `, '')
15-
const apiKeyIndex = crypto.createHmac('sha1', payload.secret).update(apiKey).digest('hex')
15+
16+
// TODO: V4 remove extra algorithm check
17+
// api keys saved prior to v3.46.0 will have sha1
18+
const sha1APIKeyIndex = crypto.createHmac('sha1', payload.secret).update(apiKey).digest('hex')
19+
const sha256APIKeyIndex = crypto
20+
.createHmac('sha256', payload.secret)
21+
.update(apiKey)
22+
.digest('hex')
23+
24+
const apiKeyConstraints = [
25+
{
26+
apiKeyIndex: {
27+
equals: sha1APIKeyIndex,
28+
},
29+
},
30+
{
31+
apiKeyIndex: {
32+
equals: sha256APIKeyIndex,
33+
},
34+
},
35+
]
1636

1737
try {
1838
const where: Where = {}
1939
if (collectionConfig.auth?.verify) {
2040
where.and = [
2141
{
22-
apiKeyIndex: {
23-
equals: apiKeyIndex,
24-
},
42+
or: apiKeyConstraints,
2543
},
2644
{
2745
_verified: {
@@ -30,9 +48,7 @@ export const APIKeyAuthentication =
3048
},
3149
]
3250
} else {
33-
where.apiKeyIndex = {
34-
equals: apiKeyIndex,
35-
}
51+
where.or = apiKeyConstraints
3652
}
3753

3854
const userQuery = await payload.find({

test/auth/int.spec.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ import type {
88
User,
99
} from 'payload'
1010

11+
import crypto from 'crypto'
1112
import { jwtDecode } from 'jwt-decode'
1213
import path from 'path'
1314
import { email as emailValidation } from 'payload/shared'
1415
import { fileURLToPath } from 'url'
1516
import { v4 as uuid } from 'uuid'
1617

1718
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
19+
import type { ApiKey } from './payload-types.js'
1820

1921
import { devUser } from '../credentials.js'
2022
import { initPayloadInt } from '../helpers/initPayloadInt.js'
@@ -829,6 +831,37 @@ describe('Auth', () => {
829831
expect(fail.status).toStrictEqual(404)
830832
})
831833

834+
it('should allow authentication with an API key saved with sha1', async () => {
835+
const usersQuery = await payload.find({
836+
collection: apiKeysSlug,
837+
})
838+
839+
const [user] = usersQuery.docs as [ApiKey]
840+
841+
const sha1Index = crypto
842+
.createHmac('sha256', payload.secret)
843+
.update(user.apiKey as string)
844+
.digest('hex')
845+
846+
await payload.db.updateOne({
847+
collection: apiKeysSlug,
848+
data: {
849+
apiKeyIndex: sha1Index,
850+
},
851+
id: user.id,
852+
})
853+
854+
const response = await restClient
855+
.GET(`/${apiKeysSlug}/${user?.id}`, {
856+
headers: {
857+
Authorization: `${apiKeysSlug} API-Key ${user?.apiKey}`,
858+
},
859+
})
860+
.then((res) => res.json())
861+
862+
expect(response.id).toStrictEqual(user.id)
863+
})
864+
832865
it('should not remove an API key from a user when updating other fields', async () => {
833866
const apiKey = uuid()
834867
const user = await payload.create({

0 commit comments

Comments
 (0)