Rotate and delete API access, not just revoke
Rotating hands out a fresh token, voids the old one and lifts a revocation. Deleting removes the row for good, taking its idempotency keys with it. Both are admin only and land in the audit log.
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { findActiveClientByTokenHash, listClients } from '~/data/repositories/clients'
|
||||
import { performCreateClient, performRevokeClient, tokenPrefix } from '~/lib/admin-clients'
|
||||
import {
|
||||
performCreateClient,
|
||||
performDeleteClient,
|
||||
performRevokeClient,
|
||||
performRotateClient,
|
||||
tokenPrefix,
|
||||
} from '~/lib/admin-clients'
|
||||
import { hashToken } from '~/lib/api-auth'
|
||||
import {
|
||||
adminViewer,
|
||||
@@ -157,3 +163,76 @@ describe('performRevokeClient', () => {
|
||||
expect((await listClients())[0]?.revokedAt).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
async function makeClient(name = 'Claude'): Promise<{ id: string, token: string }> {
|
||||
const state = await performCreateClient(
|
||||
adminViewer(),
|
||||
form({ name, mode: 'write', scope: 'internal' }),
|
||||
)
|
||||
|
||||
if (state.status !== 'ok' || !state.id || !state.token) {
|
||||
throw new Error('Zugang nicht angelegt')
|
||||
}
|
||||
|
||||
return { id: state.id, token: state.token }
|
||||
}
|
||||
|
||||
function withId(id: string): FormData {
|
||||
return form({ id })
|
||||
}
|
||||
|
||||
describe('Token erneuern', () => {
|
||||
it('macht das alte Token ungültig und gibt ein neues zurück', async () => {
|
||||
const { id, token } = await makeClient()
|
||||
const state = await performRotateClient(adminViewer(), withId(id))
|
||||
|
||||
expect(state.status).toBe('ok')
|
||||
expect(state.token).toBeDefined()
|
||||
expect(state.token).not.toBe(token)
|
||||
|
||||
expect(await findActiveClientByTokenHash(hashToken(token))).toBeUndefined()
|
||||
expect(await findActiveClientByTokenHash(hashToken(state.token!))).toMatchObject({ id })
|
||||
})
|
||||
|
||||
it('weckt einen widerrufenen Zugang wieder auf', async () => {
|
||||
const { id } = await makeClient()
|
||||
|
||||
await performRevokeClient(adminViewer(), withId(id))
|
||||
|
||||
const state = await performRotateClient(adminViewer(), withId(id))
|
||||
|
||||
expect(await findActiveClientByTokenHash(hashToken(state.token!))).toMatchObject({ id, revokedAt: null })
|
||||
})
|
||||
|
||||
it('bleibt Moderatoren verwehrt', async () => {
|
||||
const { id } = await makeClient()
|
||||
const state = await performRotateClient(moderatorViewer(), withId(id))
|
||||
|
||||
expect(state).toMatchObject({ status: 'error', message: 'forbidden' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('Zugang löschen', () => {
|
||||
it('entfernt ihn wirklich, nicht nur als widerrufen', async () => {
|
||||
const { id, token } = await makeClient()
|
||||
const state = await performDeleteClient(adminViewer(), withId(id))
|
||||
|
||||
expect(state).toMatchObject({ status: 'ok', message: 'clientDeleted' })
|
||||
expect(await listClients()).toHaveLength(0)
|
||||
expect(await findActiveClientByTokenHash(hashToken(token))).toBeUndefined()
|
||||
})
|
||||
|
||||
it('meldet einen unbekannten Zugang', async () => {
|
||||
const state = await performDeleteClient(adminViewer(), withId('11111111-1111-4111-8111-111111111111'))
|
||||
|
||||
expect(state).toMatchObject({ status: 'error', message: 'clientUnknown' })
|
||||
})
|
||||
|
||||
it('bleibt Moderatoren verwehrt', async () => {
|
||||
const { id } = await makeClient()
|
||||
const state = await performDeleteClient(moderatorViewer(), withId(id))
|
||||
|
||||
expect(state).toMatchObject({ status: 'error', message: 'forbidden' })
|
||||
expect(await listClients()).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@ async function counts() {
|
||||
}
|
||||
}
|
||||
|
||||
describe('seed', () => {
|
||||
describe('seed', { timeout: 30_000 }, () => {
|
||||
it('legt Marken, Projekte und Beiträge an', async () => {
|
||||
await seed()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user