From fe054d5af5b5994b33fed59b0fba5038c5d4e859 Mon Sep 17 00:00:00 2001 From: wangsijie Date: Tue, 13 Aug 2024 11:37:58 +0800 Subject: [PATCH] feat(core): modify and delete PATs (#6390) --- .../src/queries/personal-access-tokens.ts | 10 +++- .../personal-access-token.openapi.json | 47 +++++++++++++++++++ .../admin-user/personal-access-token.ts | 37 +++++++++++++++ .../integration-tests/src/api/admin-user.ts | 14 ++++++ .../admin-user.personal-access-tokens.test.ts | 27 ++++++++++- 5 files changed, 133 insertions(+), 2 deletions(-) diff --git a/packages/core/src/queries/personal-access-tokens.ts b/packages/core/src/queries/personal-access-tokens.ts index 27cb40c65..2e7ff7a85 100644 --- a/packages/core/src/queries/personal-access-tokens.ts +++ b/packages/core/src/queries/personal-access-tokens.ts @@ -14,7 +14,7 @@ export class PersonalAccessTokensQueries { returning: true, }); - public readonly update = buildUpdateWhereWithPool(this.pool)(PersonalAccessTokens, true); + private readonly update = buildUpdateWhereWithPool(this.pool)(PersonalAccessTokens, true); constructor(public readonly pool: CommonQueryMethods) {} @@ -26,6 +26,14 @@ export class PersonalAccessTokensQueries { `); } + async updateName(userId: string, name: string, newName: string) { + return this.update({ + where: { userId, name }, + set: { name: newName }, + jsonbMode: 'replace', + }); + } + async getTokensByUserId(userId: string) { return this.pool.any(sql` select ${sql.join(Object.values(fields), sql`, `)} 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 a31c8f131..c229ce9fc 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 0b8c22f84..911a785e9 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,41 @@ 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.updateName(userId, name, body.name); + return next(); + } + ); } diff --git a/packages/integration-tests/src/api/admin-user.ts b/packages/integration-tests/src/api/admin-user.ts index 00932186f..93f760f7c 100644 --- a/packages/integration-tests/src/api/admin-user.ts +++ b/packages/integration-tests/src/api/admin-user.ts @@ -143,3 +143,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 8c7983d8f..8ffbe6af6 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); }); });