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.
This commit is contained in:
Dan Ribbens
2025-07-07 21:23:02 -04:00
committed by GitHub
parent 96c24a22da
commit 9c453210f8
3 changed files with 57 additions and 8 deletions

View File

@@ -50,7 +50,7 @@ export const apiKeyFields = [
}
if (data?.apiKey) {
return crypto
.createHmac('sha1', req.payload.secret)
.createHmac('sha256', req.payload.secret)
.update(data.apiKey as string)
.digest('hex')
}

View File

@@ -12,16 +12,34 @@ export const APIKeyAuthentication =
if (authHeader?.startsWith(`${collectionConfig.slug} API-Key `)) {
const apiKey = authHeader.replace(`${collectionConfig.slug} API-Key `, '')
const apiKeyIndex = crypto.createHmac('sha1', payload.secret).update(apiKey).digest('hex')
// TODO: V4 remove extra algorithm check
// api keys saved prior to v3.46.0 will have sha1
const sha1APIKeyIndex = crypto.createHmac('sha1', payload.secret).update(apiKey).digest('hex')
const sha256APIKeyIndex = crypto
.createHmac('sha256', payload.secret)
.update(apiKey)
.digest('hex')
const apiKeyConstraints = [
{
apiKeyIndex: {
equals: sha1APIKeyIndex,
},
},
{
apiKeyIndex: {
equals: sha256APIKeyIndex,
},
},
]
try {
const where: Where = {}
if (collectionConfig.auth?.verify) {
where.and = [
{
apiKeyIndex: {
equals: apiKeyIndex,
},
or: apiKeyConstraints,
},
{
_verified: {
@@ -30,9 +48,7 @@ export const APIKeyAuthentication =
},
]
} else {
where.apiKeyIndex = {
equals: apiKeyIndex,
}
where.or = apiKeyConstraints
}
const userQuery = await payload.find({

View File

@@ -8,6 +8,7 @@ import type {
User,
} from 'payload'
import crypto from 'crypto'
import { jwtDecode } from 'jwt-decode'
import path from 'path'
import { email as emailValidation } from 'payload/shared'
@@ -15,6 +16,7 @@ import { fileURLToPath } from 'url'
import { v4 as uuid } from 'uuid'
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
import type { ApiKey } from './payload-types.js'
import { devUser } from '../credentials.js'
import { initPayloadInt } from '../helpers/initPayloadInt.js'
@@ -829,6 +831,37 @@ describe('Auth', () => {
expect(fail.status).toStrictEqual(404)
})
it('should allow authentication with an API key saved with sha1', async () => {
const usersQuery = await payload.find({
collection: apiKeysSlug,
})
const [user] = usersQuery.docs as [ApiKey]
const sha1Index = crypto
.createHmac('sha256', payload.secret)
.update(user.apiKey as string)
.digest('hex')
await payload.db.updateOne({
collection: apiKeysSlug,
data: {
apiKeyIndex: sha1Index,
},
id: user.id,
})
const response = await restClient
.GET(`/${apiKeysSlug}/${user?.id}`, {
headers: {
Authorization: `${apiKeysSlug} API-Key ${user?.apiKey}`,
},
})
.then((res) => res.json())
expect(response.id).toStrictEqual(user.id)
})
it('should not remove an API key from a user when updating other fields', async () => {
const apiKey = uuid()
const user = await payload.create({