diff --git a/packages/core/src/routes/admin-user/personal-access-token.openapi.json b/packages/core/src/routes/admin-user/personal-access-token.openapi.json index a31c8f13197..c229ce9fcc7 100644 --- a/packages/core/src/routes/admin-user/personal-access-token.openapi.json +++ b/packages/core/src/routes/admin-user/personal-access-token.openapi.json @@ -43,6 +43,53 @@ } } } + }, + "/api/users/{userId}/personal-access-tokens/{name}": { + "delete": { + "summary": "Delete personal access token", + "description": "Delete a token for the user by name.", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "The name of the token." + } + ], + "responses": { + "204": { + "description": "The token was deleted successfully." + } + } + }, + "patch": { + "summary": "Update personal access token", + "description": "Update a token for the user by name.", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "The name of the token." + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "name": { + "description": "The token name to update. Must be unique within the user." + } + } + } + } + } + }, + "responses": { + "204": { + "description": "The token was updated successfully." + } + } + } } } } diff --git a/packages/core/src/routes/admin-user/personal-access-token.ts b/packages/core/src/routes/admin-user/personal-access-token.ts index 0b8c22f8492..e154fa7d3dd 100644 --- a/packages/core/src/routes/admin-user/personal-access-token.ts +++ b/packages/core/src/routes/admin-user/personal-access-token.ts @@ -59,4 +59,45 @@ export default function adminUserPersonalAccessTokenRoutes { + const { + params: { userId, name }, + } = ctx.guard; + + await queries.personalAccessTokens.deleteByName(userId, name); + ctx.status = 204; + + return next(); + } + ); + + router.patch( + '/users/:userId/personal-access-tokens/:name', + koaGuard({ + params: z.object({ userId: z.string(), name: z.string() }), + body: PersonalAccessTokens.updateGuard.pick({ name: true }).required(), + response: PersonalAccessTokens.guard, + status: [200, 400, 404], + }), + async (ctx, next) => { + const { + params: { userId, name }, + body, + } = ctx.guard; + + ctx.body = await queries.personalAccessTokens.update({ + where: { userId, name }, + set: body, + jsonbMode: 'replace', + }); + return next(); + } + ); } diff --git a/packages/integration-tests/src/api/admin-user.ts b/packages/integration-tests/src/api/admin-user.ts index a09074bb211..0453ef956e8 100644 --- a/packages/integration-tests/src/api/admin-user.ts +++ b/packages/integration-tests/src/api/admin-user.ts @@ -140,3 +140,17 @@ export const createPersonalAccessToken = async ({ export const getUserPersonalAccessTokens = async (userId: string) => authedAdminApi.get(`users/${userId}/personal-access-tokens`).json(); + +export const deletePersonalAccessToken = async (userId: string, name: string) => + authedAdminApi.delete(`users/${userId}/personal-access-tokens/${name}`); + +export const updatePersonalAccessToken = async ( + userId: string, + name: string, + body: Record +) => + authedAdminApi + .patch(`users/${userId}/personal-access-tokens/${name}`, { + json: body, + }) + .json(); diff --git a/packages/integration-tests/src/tests/api/admin-user.personal-access-tokens.test.ts b/packages/integration-tests/src/tests/api/admin-user.personal-access-tokens.test.ts index 8c7983d8f0f..8ffbe6af6db 100644 --- a/packages/integration-tests/src/tests/api/admin-user.personal-access-tokens.test.ts +++ b/packages/integration-tests/src/tests/api/admin-user.personal-access-tokens.test.ts @@ -2,8 +2,10 @@ import { HTTPError } from 'ky'; import { createPersonalAccessToken, + deletePersonalAccessToken, deleteUser, getUserPersonalAccessTokens, + updatePersonalAccessToken, } from '#src/api/admin-user.js'; import { createUserByAdmin } from '#src/helpers/index.js'; import { devFeatureTest, randomString } from '#src/utils.js'; @@ -67,7 +69,7 @@ devFeatureTest.describe('personal access tokens', () => { await deleteUser(user.id); }); - it('should be able to create, get multiple PATs', async () => { + it('should be able to create, get, and delete multiple PATs', async () => { const user = await createUserByAdmin(); const name1 = randomString(); const name2 = randomString(); @@ -85,6 +87,29 @@ devFeatureTest.describe('personal access tokens', () => { expect.arrayContaining([pat1, pat2]) ); + await Promise.all([ + deletePersonalAccessToken(user.id, name1), + deletePersonalAccessToken(user.id, name2), + ]); + expect(await getUserPersonalAccessTokens(user.id)).toEqual([]); + + await deleteUser(user.id); + }); + + it('should be able to update PAT', async () => { + const user = await createUserByAdmin(); + const name = randomString(); + await createPersonalAccessToken({ + userId: user.id, + name, + }); + + const newName = randomString(); + const updatedPat = await updatePersonalAccessToken(user.id, name, { + name: newName, + }); + expect(updatedPat).toEqual(expect.objectContaining({ userId: user.id, name: newName })); + await deleteUser(user.id); }); });