From 4d2bc861cfca53ef23f30f36dfd6c9f39f286dce Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Thu, 25 Apr 2024 09:39:30 -0400 Subject: [PATCH] fix: disable api key beta (#6021) --- .../src/views/Edit/Default/Auth/APIKey.tsx | 22 +++-- .../src/views/Edit/Default/Auth/index.tsx | 2 +- .../payload/src/auth/baseFields/apiKey.ts | 10 +- test/auth/e2e.spec.ts | 3 +- test/auth/int.spec.ts | 96 ++++++++++++++++++- 5 files changed, 118 insertions(+), 15 deletions(-) diff --git a/packages/next/src/views/Edit/Default/Auth/APIKey.tsx b/packages/next/src/views/Edit/Default/Auth/APIKey.tsx index cb7618a410..805c928fd9 100644 --- a/packages/next/src/views/Edit/Default/Auth/APIKey.tsx +++ b/packages/next/src/views/Edit/Default/Auth/APIKey.tsx @@ -16,8 +16,11 @@ const path = 'apiKey' const baseClass = 'api-key' const fieldBaseClass = 'field-type' -export const APIKey: React.FC<{ readOnly?: boolean }> = ({ readOnly }) => { - const [initialAPIKey, setInitialAPIKey] = useState(null) +export const APIKey: React.FC<{ enabled: boolean; readOnly?: boolean }> = ({ + enabled, + readOnly, +}) => { + const [initialAPIKey] = useState(uuidv4()) const [highlightedField, setHighlightedField] = useState(false) const { t } = useTranslation() const config = useConfig() @@ -70,14 +73,13 @@ export const APIKey: React.FC<{ readOnly?: boolean }> = ({ readOnly }) => { const { setValue, value } = fieldType useEffect(() => { - setInitialAPIKey(uuidv4()) - }, []) - - useEffect(() => { - if (!apiKeyValue) { + if (!apiKeyValue && enabled) { setValue(initialAPIKey) } - }, [apiKeyValue, setValue, initialAPIKey]) + if (!enabled) { + setValue(null) + } + }, [apiKeyValue, enabled, setValue, initialAPIKey]) useEffect(() => { if (highlightedField) { @@ -87,6 +89,10 @@ export const APIKey: React.FC<{ readOnly?: boolean }> = ({ readOnly }) => { } }, [highlightedField]) + if (!enabled) { + return null + } + return (
diff --git a/packages/next/src/views/Edit/Default/Auth/index.tsx b/packages/next/src/views/Edit/Default/Auth/index.tsx index a7a69edc8b..208b2638d2 100644 --- a/packages/next/src/views/Edit/Default/Auth/index.tsx +++ b/packages/next/src/views/Edit/Default/Auth/index.tsx @@ -151,7 +151,7 @@ export const Auth: React.FC = (props) => { name="enableAPIKey" readOnly={readOnly} /> - {enableAPIKey?.value && } +
)} {verify && ( diff --git a/packages/payload/src/auth/baseFields/apiKey.ts b/packages/payload/src/auth/baseFields/apiKey.ts index 0840a2c2eb..47782d84c7 100644 --- a/packages/payload/src/auth/baseFields/apiKey.ts +++ b/packages/payload/src/auth/baseFields/apiKey.ts @@ -17,7 +17,6 @@ export default [ Field: () => null, }, }, - defaultValue: false, label: ({ t }) => t('authentication:enableAPIKey'), }, { @@ -44,15 +43,18 @@ export default [ hooks: { beforeValidate: [ ({ data, req, value }) => { + if (data.apiKey === false || data.apiKey === null) { + return null + } + if (data.enableAPIKey === false || data.enableAPIKey === null) { + return null + } if (data.apiKey) { return crypto .createHmac('sha1', req.payload.secret) .update(data.apiKey as string) .digest('hex') } - if (data.enableAPIKey === false) { - return null - } return value }, ], diff --git a/test/auth/e2e.spec.ts b/test/auth/e2e.spec.ts index 8d006fd2d7..e78667d1ec 100644 --- a/test/auth/e2e.spec.ts +++ b/test/auth/e2e.spec.ts @@ -95,6 +95,7 @@ describe('auth', () => { user = await payload.create({ collection: apiKeysSlug, data: { + apiKey: uuid(), enableAPIKey: true, }, }) @@ -140,7 +141,7 @@ describe('auth', () => { const response = await fetch(`${apiURL}/${apiKeysSlug}/me`, { headers: { ...headers, - Authorization: `${slug} API-Key ${user.apiKey}`, + Authorization: `${apiKeysSlug} API-Key ${user.apiKey}`, }, }).then((res) => res.json()) diff --git a/test/auth/int.spec.ts b/test/auth/int.spec.ts index 0a3b0eb0e9..e71a3d49fe 100644 --- a/test/auth/int.spec.ts +++ b/test/auth/int.spec.ts @@ -2,13 +2,14 @@ import type { Payload } from 'payload' import type { User } from 'payload/auth' import { jwtDecode } from 'jwt-decode' +import { v4 as uuid } from 'uuid' import type { NextRESTClient } from '../helpers/NextRESTClient.js' import { devUser } from '../credentials.js' import { initPayloadInt } from '../helpers/initPayloadInt.js' import configPromise from './config.js' -import { namedSaveToJWTValue, saveToJWTKey, slug } from './shared.js' +import { apiKeysSlug, namedSaveToJWTValue, saveToJWTKey, slug } from './shared.js' let restClient: NextRESTClient let payload: Payload @@ -622,6 +623,99 @@ describe('Auth', () => { expect(fail.status).toStrictEqual(404) }) + + it('should not remove an API key from a user when updating other fields', async () => { + const apiKey = uuid() + const user = await payload.create({ + collection: 'api-keys', + data: { + apiKey, + enableAPIKey: true, + }, + }) + + const updatedUser = await payload.update({ + id: user.id, + collection: 'api-keys', + data: { + enableAPIKey: true, + }, + }) + + const userResult = await payload.find({ + collection: 'api-keys', + where: { + id: { + equals: user.id, + }, + }, + }) + + expect(updatedUser.apiKey).toStrictEqual(user.apiKey) + expect(userResult.docs[0].apiKey).toStrictEqual(user.apiKey) + }) + + it('should disable api key after updating apiKey: null', async () => { + const apiKey = uuid() + const user = await payload.create({ + collection: apiKeysSlug, + data: { + apiKey, + enableAPIKey: true, + }, + }) + + const updatedUser = await payload.update({ + id: user.id, + collection: apiKeysSlug, + data: { + apiKey: null, + }, + }) + + // use the api key in a fetch to assert that it is disabled + const response = await restClient + .GET(`/api-keys/me`, { + headers: { + Authorization: `${apiKeysSlug} API-Key ${apiKey}`, + }, + }) + .then((res) => res.json()) + + expect(updatedUser.apiKey).toBeNull() + expect(response.user).toBeNull() + }) + + it('should disable api key after updating with enableAPIKey:false', async () => { + const apiKey = uuid() + const user = await payload.create({ + collection: apiKeysSlug, + data: { + apiKey, + enableAPIKey: true, + }, + }) + + const updatedUser = await payload.update({ + id: user.id, + collection: apiKeysSlug, + data: { + enableAPIKey: false, + }, + }) + + // use the api key in a fetch to assert that it is disabled + const response = await restClient + .GET(`/api-keys/me`, { + headers: { + Authorization: `${apiKeysSlug} API-Key ${apiKey}`, + }, + }) + .then((res) => res.json()) + + expect(updatedUser.apiKey).toStrictEqual(apiKey) + expect(response.user).toBeNull() + }) }) describe('Local API', () => {