fix(backend): enforce user ownership when deleting PAT (#5916)
Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
parent
d6ea86dcca
commit
1f4ae3dd88
3 changed files with 55 additions and 17 deletions
|
|
@ -51,8 +51,14 @@ export class AccessTokenController {
|
|||
|
||||
@Delete('revoke')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async deletePAT(@Query('id') id: string) {
|
||||
const result = await this.accessTokenService.deletePAT(id);
|
||||
async deletePAT(@GqlUser() user: AuthUser, @Query('id') id: string) {
|
||||
if (!id) {
|
||||
throw new BadRequestException(
|
||||
createCLIErrorResponse(ACCESS_TOKENS_INVALID_DATA_ID),
|
||||
);
|
||||
}
|
||||
|
||||
const result = await this.accessTokenService.deletePAT(id, user.uid);
|
||||
|
||||
if (E.isLeft(result)) throwHTTPErr(result.left);
|
||||
return result.right;
|
||||
|
|
|
|||
|
|
@ -112,11 +112,17 @@ describe('AccessTokenService', () => {
|
|||
|
||||
describe('deletePAT', () => {
|
||||
test('should throw ACCESS_TOKEN_NOT_FOUND if Access Token is not found', async () => {
|
||||
mockPrisma.personalAccessToken.delete.mockRejectedValueOnce(
|
||||
'RecordNotFound',
|
||||
);
|
||||
mockPrisma.personalAccessToken.deleteMany.mockResolvedValueOnce({
|
||||
count: 0,
|
||||
});
|
||||
|
||||
const result = await accessTokenService.deletePAT(userAccessToken.id);
|
||||
const result = await accessTokenService.deletePAT(
|
||||
userAccessToken.id,
|
||||
user.uid,
|
||||
);
|
||||
expect(mockPrisma.personalAccessToken.deleteMany).toHaveBeenCalledWith({
|
||||
where: { id: userAccessToken.id, userUid: user.uid },
|
||||
});
|
||||
expect(result).toEqualLeft({
|
||||
message: ACCESS_TOKEN_NOT_FOUND,
|
||||
statusCode: HttpStatus.NOT_FOUND,
|
||||
|
|
@ -124,13 +130,37 @@ describe('AccessTokenService', () => {
|
|||
});
|
||||
|
||||
test('should successfully delete a new Access Token', async () => {
|
||||
mockPrisma.personalAccessToken.delete.mockResolvedValueOnce(
|
||||
userAccessToken,
|
||||
);
|
||||
mockPrisma.personalAccessToken.deleteMany.mockResolvedValueOnce({
|
||||
count: 1,
|
||||
});
|
||||
|
||||
const result = await accessTokenService.deletePAT(userAccessToken.id);
|
||||
const result = await accessTokenService.deletePAT(
|
||||
userAccessToken.id,
|
||||
user.uid,
|
||||
);
|
||||
expect(mockPrisma.personalAccessToken.deleteMany).toHaveBeenCalledWith({
|
||||
where: { id: userAccessToken.id, userUid: user.uid },
|
||||
});
|
||||
expect(result).toEqualRight(true);
|
||||
});
|
||||
|
||||
test('should throw ACCESS_TOKEN_NOT_FOUND when token belongs to a different user', async () => {
|
||||
mockPrisma.personalAccessToken.deleteMany.mockResolvedValueOnce({
|
||||
count: 0,
|
||||
});
|
||||
|
||||
const result = await accessTokenService.deletePAT(
|
||||
userAccessToken.id,
|
||||
'different-user-uid',
|
||||
);
|
||||
expect(mockPrisma.personalAccessToken.deleteMany).toHaveBeenCalledWith({
|
||||
where: { id: userAccessToken.id, userUid: 'different-user-uid' },
|
||||
});
|
||||
expect(result).toEqualLeft({
|
||||
message: ACCESS_TOKEN_NOT_FOUND,
|
||||
statusCode: HttpStatus.NOT_FOUND,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('listAllUserPAT', () => {
|
||||
|
|
|
|||
|
|
@ -103,20 +103,22 @@ export class AccessTokenService {
|
|||
* Delete a Personal Access Token
|
||||
*
|
||||
* @param accessTokenID ID of the Personal Access Token
|
||||
* @param userUid UID of the user requesting the deletion
|
||||
* @returns Either of true or error message
|
||||
*/
|
||||
async deletePAT(accessTokenID: string) {
|
||||
try {
|
||||
await this.prisma.personalAccessToken.delete({
|
||||
where: { id: accessTokenID },
|
||||
});
|
||||
return E.right(true);
|
||||
} catch {
|
||||
async deletePAT(accessTokenID: string, userUid: string) {
|
||||
const { count } = await this.prisma.personalAccessToken.deleteMany({
|
||||
where: { id: accessTokenID, userUid },
|
||||
});
|
||||
|
||||
if (count === 0) {
|
||||
return E.left({
|
||||
message: ACCESS_TOKEN_NOT_FOUND,
|
||||
statusCode: HttpStatus.NOT_FOUND,
|
||||
});
|
||||
}
|
||||
|
||||
return E.right(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue