fix: disable api key beta (#6021)

This commit is contained in:
Dan Ribbens
2024-04-25 09:39:30 -04:00
committed by GitHub
parent 98722dc0fd
commit 4d2bc861cf
5 changed files with 118 additions and 15 deletions

View File

@@ -16,8 +16,11 @@ const path = 'apiKey'
const baseClass = 'api-key' const baseClass = 'api-key'
const fieldBaseClass = 'field-type' const fieldBaseClass = 'field-type'
export const APIKey: React.FC<{ readOnly?: boolean }> = ({ readOnly }) => { export const APIKey: React.FC<{ enabled: boolean; readOnly?: boolean }> = ({
const [initialAPIKey, setInitialAPIKey] = useState(null) enabled,
readOnly,
}) => {
const [initialAPIKey] = useState(uuidv4())
const [highlightedField, setHighlightedField] = useState(false) const [highlightedField, setHighlightedField] = useState(false)
const { t } = useTranslation() const { t } = useTranslation()
const config = useConfig() const config = useConfig()
@@ -70,14 +73,13 @@ export const APIKey: React.FC<{ readOnly?: boolean }> = ({ readOnly }) => {
const { setValue, value } = fieldType const { setValue, value } = fieldType
useEffect(() => { useEffect(() => {
setInitialAPIKey(uuidv4()) if (!apiKeyValue && enabled) {
}, [])
useEffect(() => {
if (!apiKeyValue) {
setValue(initialAPIKey) setValue(initialAPIKey)
} }
}, [apiKeyValue, setValue, initialAPIKey]) if (!enabled) {
setValue(null)
}
}, [apiKeyValue, enabled, setValue, initialAPIKey])
useEffect(() => { useEffect(() => {
if (highlightedField) { if (highlightedField) {
@@ -87,6 +89,10 @@ export const APIKey: React.FC<{ readOnly?: boolean }> = ({ readOnly }) => {
} }
}, [highlightedField]) }, [highlightedField])
if (!enabled) {
return null
}
return ( return (
<React.Fragment> <React.Fragment>
<div className={[fieldBaseClass, 'api-key', 'read-only'].filter(Boolean).join(' ')}> <div className={[fieldBaseClass, 'api-key', 'read-only'].filter(Boolean).join(' ')}>

View File

@@ -151,7 +151,7 @@ export const Auth: React.FC<Props> = (props) => {
name="enableAPIKey" name="enableAPIKey"
readOnly={readOnly} readOnly={readOnly}
/> />
{enableAPIKey?.value && <APIKey readOnly={readOnly} />} <APIKey enabled={!!enableAPIKey?.value} readOnly={readOnly} />
</div> </div>
)} )}
{verify && ( {verify && (

View File

@@ -17,7 +17,6 @@ export default [
Field: () => null, Field: () => null,
}, },
}, },
defaultValue: false,
label: ({ t }) => t('authentication:enableAPIKey'), label: ({ t }) => t('authentication:enableAPIKey'),
}, },
{ {
@@ -44,15 +43,18 @@ export default [
hooks: { hooks: {
beforeValidate: [ beforeValidate: [
({ 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) { if (data.apiKey) {
return crypto return crypto
.createHmac('sha1', req.payload.secret) .createHmac('sha1', req.payload.secret)
.update(data.apiKey as string) .update(data.apiKey as string)
.digest('hex') .digest('hex')
} }
if (data.enableAPIKey === false) {
return null
}
return value return value
}, },
], ],

View File

@@ -95,6 +95,7 @@ describe('auth', () => {
user = await payload.create({ user = await payload.create({
collection: apiKeysSlug, collection: apiKeysSlug,
data: { data: {
apiKey: uuid(),
enableAPIKey: true, enableAPIKey: true,
}, },
}) })
@@ -140,7 +141,7 @@ describe('auth', () => {
const response = await fetch(`${apiURL}/${apiKeysSlug}/me`, { const response = await fetch(`${apiURL}/${apiKeysSlug}/me`, {
headers: { headers: {
...headers, ...headers,
Authorization: `${slug} API-Key ${user.apiKey}`, Authorization: `${apiKeysSlug} API-Key ${user.apiKey}`,
}, },
}).then((res) => res.json()) }).then((res) => res.json())

View File

@@ -2,13 +2,14 @@ import type { Payload } from 'payload'
import type { User } from 'payload/auth' import type { User } from 'payload/auth'
import { jwtDecode } from 'jwt-decode' import { jwtDecode } from 'jwt-decode'
import { v4 as uuid } from 'uuid'
import type { NextRESTClient } from '../helpers/NextRESTClient.js' import type { NextRESTClient } from '../helpers/NextRESTClient.js'
import { devUser } from '../credentials.js' import { devUser } from '../credentials.js'
import { initPayloadInt } from '../helpers/initPayloadInt.js' import { initPayloadInt } from '../helpers/initPayloadInt.js'
import configPromise from './config.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 restClient: NextRESTClient
let payload: Payload let payload: Payload
@@ -622,6 +623,99 @@ describe('Auth', () => {
expect(fail.status).toStrictEqual(404) 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', () => { describe('Local API', () => {