### What? Prevents decrypted apiKey from being saved back to database on the auth refresh operation. ### Why? References issue #13063: refreshing a token for a logged-in user decrypted `apiKey` and wrote it back in plaintext, corrupting the user record. ### How? The user is now fetched with `db.findOne` instead of `findByID`, preserving the encryption of the key when saved back to the database using `db.updateOne`. The user record is then re-fetched using `findByID`, allowing for the decrypted key to be provided in the response. ### Tests * ✅ keeps apiKey encrypted in DB after refresh * ✅ returns user with decrypted apiKey after refresh Fixes #13063
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import url from 'url'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
import type { Collection } from '../../collections/config/types.js'
|
||||
import type { Document, PayloadRequest } from '../../types/index.js'
|
||||
@@ -74,11 +73,10 @@ export const refreshOperation = async (incomingArgs: Arguments): Promise<Result>
|
||||
const parsedURL = url.parse(args.req.url!)
|
||||
const isGraphQL = parsedURL.pathname === config.routes.graphQL
|
||||
|
||||
const user = await args.req.payload.findByID({
|
||||
id: args.req.user.id,
|
||||
collection: args.req.user.collection,
|
||||
depth: isGraphQL ? 0 : args.collection.config.auth.depth,
|
||||
req: args.req,
|
||||
let user = await req.payload.db.findOne<any>({
|
||||
collection: collectionConfig.slug,
|
||||
req,
|
||||
where: { id: { equals: args.req.user.id } },
|
||||
})
|
||||
|
||||
const sid = args.req.user._sid
|
||||
@@ -88,7 +86,7 @@ export const refreshOperation = async (incomingArgs: Arguments): Promise<Result>
|
||||
throw new Forbidden(args.req.t)
|
||||
}
|
||||
|
||||
const existingSession = user.sessions.find(({ id }) => id === sid)
|
||||
const existingSession = user.sessions.find(({ id }: { id: number }) => id === sid)
|
||||
|
||||
const now = new Date()
|
||||
const tokenExpInMs = collectionConfig.auth.tokenExpiration * 1000
|
||||
@@ -106,6 +104,13 @@ export const refreshOperation = async (incomingArgs: Arguments): Promise<Result>
|
||||
})
|
||||
}
|
||||
|
||||
user = await req.payload.findByID({
|
||||
id: user.id,
|
||||
collection: collectionConfig.slug,
|
||||
depth: isGraphQL ? 0 : args.collection.config.auth.depth,
|
||||
req: args.req,
|
||||
})
|
||||
|
||||
if (user) {
|
||||
user.collection = args.req.user.collection
|
||||
user._strategy = args.req.user._strategy
|
||||
|
||||
@@ -262,6 +262,42 @@ describe('Auth', () => {
|
||||
expect(data.user.custom).toBe('Goodbye, world!')
|
||||
})
|
||||
|
||||
it('keeps apiKey encrypted in DB after refresh operation', async () => {
|
||||
const apiKey = '987e6543-e21b-12d3-a456-426614174999'
|
||||
const user = await payload.create({
|
||||
collection: slug,
|
||||
data: { email: 'user@example.com', password: 'Password123', apiKey, enableAPIKey: true },
|
||||
})
|
||||
const { token } = await payload.login({
|
||||
collection: 'users',
|
||||
data: { email: 'user@example.com', password: 'Password123' },
|
||||
})
|
||||
await restClient.POST('/users/refresh-token', {
|
||||
headers: { Authorization: `JWT ${token}` },
|
||||
})
|
||||
const raw = await payload.db.findOne<any>({
|
||||
collection: 'users',
|
||||
req: { locale: 'en' } as any,
|
||||
where: { id: { equals: user.id } },
|
||||
})
|
||||
expect(raw?.apiKey).not.toContain('-') // still ciphertext
|
||||
})
|
||||
|
||||
it('returns a user with decrypted apiKey after refresh', async () => {
|
||||
const { token } = await payload.login({
|
||||
collection: 'users',
|
||||
data: { email: 'user@example.com', password: 'Password123' },
|
||||
})
|
||||
|
||||
const res = await restClient
|
||||
.POST('/users/refresh-token', {
|
||||
headers: { Authorization: `JWT ${token}` },
|
||||
})
|
||||
.then((r) => r.json())
|
||||
|
||||
expect(res.user.apiKey).toMatch(/[0-9a-f-]{36}/) // UUID string
|
||||
})
|
||||
|
||||
it('should allow a user to be created', async () => {
|
||||
const response = await restClient.POST(`/${slug}`, {
|
||||
body: JSON.stringify({
|
||||
|
||||
Reference in New Issue
Block a user