Skip to content

Commit

Permalink
fix: disable api key checkbox does not remove api key (#6017)
Browse files Browse the repository at this point in the history
  • Loading branch information
DanRibbens committed Apr 25, 2024
1 parent 881119b commit 0ffdcc6
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 17 deletions.
Expand Up @@ -7,14 +7,14 @@ import CopyToClipboard from '../../../../elements/CopyToClipboard'
import GenerateConfirmation from '../../../../elements/GenerateConfirmation'
import { useFormFields } from '../../../../forms/Form/context'
import Label from '../../../../forms/Label'
import useField from '../../../../forms/useField'
import { fieldBaseClass } from '../../../../forms/field-types/shared'
import useField from '../../../../forms/useField'

const path = 'apiKey'
const baseClass = 'api-key'

const APIKey: React.FC<{ readOnly?: boolean }> = ({ readOnly }) => {
const [initialAPIKey, setInitialAPIKey] = useState(null)
const APIKey: React.FC<{ enabled: boolean; readOnly?: boolean }> = ({ enabled, readOnly }) => {
const [initialAPIKey] = useState(uuidv4())
const [highlightedField, setHighlightedField] = useState(false)
const { t } = useTranslation()

Expand Down Expand Up @@ -51,14 +51,13 @@ 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) {
Expand All @@ -68,6 +67,10 @@ const APIKey: React.FC<{ readOnly?: boolean }> = ({ readOnly }) => {
}
}, [highlightedField])

if (!enabled) {
return null
}

return (
<React.Fragment>
<div className={[fieldBaseClass, 'api-key', 'read-only'].filter(Boolean).join(' ')}>
Expand Down
Expand Up @@ -145,7 +145,7 @@ const Auth: React.FC<Props> = (props) => {
{useAPIKey && (
<div className={`${baseClass}__api-key`}>
<Checkbox admin={{ readOnly }} label={t('enableAPIKey')} name="enableAPIKey" />
{enableAPIKey?.value && <APIKey readOnly={readOnly} />}
<APIKey enabled={!!enableAPIKey?.value} readOnly={readOnly} />
</div>
)}
{verify && <Checkbox admin={{ readOnly }} label={t('verified')} name="_verified" />}
Expand Down
12 changes: 7 additions & 5 deletions packages/payload/src/auth/baseFields/apiKey.ts
Expand Up @@ -20,7 +20,6 @@ export default [
Field: () => null,
},
},
defaultValue: false,
label: labels['authentication:enableAPIKey'],
},
{
Expand All @@ -46,16 +45,19 @@ export default [
hidden: true,
hooks: {
beforeValidate: [
async ({ data, req, value }) => {
({ 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
},
],
Expand Down
4 changes: 3 additions & 1 deletion test/auth/e2e.spec.ts
@@ -1,6 +1,7 @@
import type { Page } from '@playwright/test'

import { expect, test } from '@playwright/test'
import { v4 as uuid } from 'uuid'

import payload from '../../packages/payload/src'
import { initPageConsoleErrorCatch, login, saveDocAndAssert } from '../helpers'
Expand Down Expand Up @@ -82,6 +83,7 @@ describe('auth', () => {
user = await payload.create({
collection: apiKeysSlug,
data: {
apiKey: uuid(),
enableAPIKey: true,
},
})
Expand Down Expand Up @@ -117,7 +119,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())

Expand Down
94 changes: 93 additions & 1 deletion test/auth/int.spec.ts
@@ -1,13 +1,14 @@
import { GraphQLClient } from 'graphql-request'
import jwtDecode from 'jwt-decode'
import { v4 as uuid } from 'uuid'

import type { User } from '../../packages/payload/src/auth'

import payload from '../../packages/payload/src'
import configPromise from '../collections-graphql/config'
import { devUser } from '../credentials'
import { initPayloadTest } from '../helpers/configHelpers'
import { namedSaveToJWTValue, saveToJWTKey, slug } from './shared'
import { apiKeysSlug, namedSaveToJWTValue, saveToJWTKey, slug } from './shared'

require('isomorphic-fetch')

Expand Down Expand Up @@ -680,5 +681,96 @@ 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 fetch(`${apiUrl}/${apiKeysSlug}/me`, {
headers: {
...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 fetch(`${apiUrl}/${apiKeysSlug}/me`, {
headers: {
...headers,
Authorization: `${apiKeysSlug} API-Key ${apiKey}`,
},
}).then((res) => res.json())

expect(updatedUser.apiKey).toStrictEqual(apiKey)
expect(response.user).toBeNull()
})
})
})

0 comments on commit 0ffdcc6

Please sign in to comment.