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')
|
@Delete('revoke')
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
async deletePAT(@Query('id') id: string) {
|
async deletePAT(@GqlUser() user: AuthUser, @Query('id') id: string) {
|
||||||
const result = await this.accessTokenService.deletePAT(id);
|
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);
|
if (E.isLeft(result)) throwHTTPErr(result.left);
|
||||||
return result.right;
|
return result.right;
|
||||||
|
|
|
||||||
|
|
@ -112,11 +112,17 @@ describe('AccessTokenService', () => {
|
||||||
|
|
||||||
describe('deletePAT', () => {
|
describe('deletePAT', () => {
|
||||||
test('should throw ACCESS_TOKEN_NOT_FOUND if Access Token is not found', async () => {
|
test('should throw ACCESS_TOKEN_NOT_FOUND if Access Token is not found', async () => {
|
||||||
mockPrisma.personalAccessToken.delete.mockRejectedValueOnce(
|
mockPrisma.personalAccessToken.deleteMany.mockResolvedValueOnce({
|
||||||
'RecordNotFound',
|
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({
|
expect(result).toEqualLeft({
|
||||||
message: ACCESS_TOKEN_NOT_FOUND,
|
message: ACCESS_TOKEN_NOT_FOUND,
|
||||||
statusCode: HttpStatus.NOT_FOUND,
|
statusCode: HttpStatus.NOT_FOUND,
|
||||||
|
|
@ -124,13 +130,37 @@ describe('AccessTokenService', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should successfully delete a new Access Token', async () => {
|
test('should successfully delete a new Access Token', async () => {
|
||||||
mockPrisma.personalAccessToken.delete.mockResolvedValueOnce(
|
mockPrisma.personalAccessToken.deleteMany.mockResolvedValueOnce({
|
||||||
userAccessToken,
|
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);
|
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', () => {
|
describe('listAllUserPAT', () => {
|
||||||
|
|
|
||||||
|
|
@ -103,20 +103,22 @@ export class AccessTokenService {
|
||||||
* Delete a Personal Access Token
|
* Delete a Personal Access Token
|
||||||
*
|
*
|
||||||
* @param accessTokenID ID of the 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
|
* @returns Either of true or error message
|
||||||
*/
|
*/
|
||||||
async deletePAT(accessTokenID: string) {
|
async deletePAT(accessTokenID: string, userUid: string) {
|
||||||
try {
|
const { count } = await this.prisma.personalAccessToken.deleteMany({
|
||||||
await this.prisma.personalAccessToken.delete({
|
where: { id: accessTokenID, userUid },
|
||||||
where: { id: accessTokenID },
|
});
|
||||||
});
|
|
||||||
return E.right(true);
|
if (count === 0) {
|
||||||
} catch {
|
|
||||||
return E.left({
|
return E.left({
|
||||||
message: ACCESS_TOKEN_NOT_FOUND,
|
message: ACCESS_TOKEN_NOT_FOUND,
|
||||||
statusCode: HttpStatus.NOT_FOUND,
|
statusCode: HttpStatus.NOT_FOUND,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return E.right(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue