feat: endpoint for deleting oAuth users & oAuth users returned data (#12912)

* feat: delete oAuth users

* check if access token matches userId in parameter

* driveby: return only user id and email in oauth users endpoints
This commit is contained in:
Lauris Skraucis 2023-12-22 13:53:42 +02:00 committed by GitHub
parent d4c946a0d6
commit 2df15d1d0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 78 additions and 19 deletions

View File

@ -2,7 +2,10 @@ import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
import { CreateUserResponse } from "@/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller";
import {
CreateUserResponse,
UserReturned,
} from "@/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller";
import { CreateUserInput } from "@/modules/users/inputs/create-user.input";
import { UpdateUserInput } from "@/modules/users/inputs/update-user.input";
import { UsersModule } from "@/modules/users/users.module";
@ -49,6 +52,9 @@ describe("OAuth Client Users Endpoints", () => {
it(`/PUT/:id`, () => {
return request(app.getHttpServer()).put("/api/v2/oauth-clients/100/users/200").expect(401);
});
it(`/DELETE/:id`, () => {
return request(app.getHttpServer()).delete("/api/v2/oauth-clients/100/users/200").expect(401);
});
});
afterAll(async () => {
@ -146,7 +152,7 @@ describe("OAuth Client Users Endpoints", () => {
.set("Authorization", `Bearer ${postResponseData.accessToken}`)
.expect(200);
const responseBody: ApiSuccessResponse<Omit<User, "password">> = response.body;
const responseBody: ApiSuccessResponse<UserReturned> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
@ -170,16 +176,16 @@ describe("OAuth Client Users Endpoints", () => {
expect(responseBody.data.email).toEqual(userUpdatedEmail);
});
it(`/DELETE/:id`, () => {
return request(app.getHttpServer())
.delete(`/api/v2/oauth-clients/${oAuthClient.id}/users/${postResponseData.user.id}`)
.set("Authorization", `Bearer ${postResponseData.accessToken}`)
.expect(200);
});
afterAll(async () => {
if (postResponseData?.user.id) {
await userRepositoryFixture.delete(postResponseData.user.id);
}
if (oAuthClient) {
await oauthClientRepositoryFixture.delete(oAuthClient.id);
}
if (organization) {
await teamRepositoryFixture.delete(organization.id);
}
await oauthClientRepositoryFixture.delete(oAuthClient.id);
await teamRepositoryFixture.delete(organization.id);
await app.close();
});

View File

@ -18,6 +18,7 @@ import {
Param,
Put,
BadRequestException,
Delete,
} from "@nestjs/common";
import { User } from "@prisma/client";
@ -61,7 +62,10 @@ export class OAuthClientUsersController {
return {
status: SUCCESS_STATUS,
data: {
user,
user: {
id: user.id,
email: user.email,
},
accessToken: accessToken,
refreshToken: refreshToken,
},
@ -74,9 +78,9 @@ export class OAuthClientUsersController {
async getUserById(
@GetUser("id") accessTokenUserId: number,
@Param("userId") userId: number
): Promise<ApiResponse<Partial<User>>> {
): Promise<ApiResponse<UserReturned>> {
if (accessTokenUserId !== userId) {
throw new BadRequestException("You can only access your own user data.");
throw new BadRequestException("userId parameter does not match access token");
}
const user = await this.userRepository.findById(userId);
@ -84,7 +88,13 @@ export class OAuthClientUsersController {
throw new NotFoundException(`User with ID ${userId} not found.`);
}
return { status: SUCCESS_STATUS, data: user };
return {
status: SUCCESS_STATUS,
data: {
id: user.id,
email: user.email,
},
};
}
@Put("/:userId")
@ -94,16 +104,59 @@ export class OAuthClientUsersController {
@GetUser("id") accessTokenUserId: number,
@Param("userId") userId: number,
@Body() body: UpdateUserInput
): Promise<ApiResponse<Partial<User>>> {
): Promise<ApiResponse<UserReturned>> {
if (accessTokenUserId !== userId) {
throw new BadRequestException("You can only update your own user data.");
throw new BadRequestException("userId parameter does not match access token");
}
this.logger.log(`Updating user with ID ${userId}: ${JSON.stringify(body, null, 2)}`);
const user = await this.userRepository.update(userId, body);
return { status: SUCCESS_STATUS, data: user };
return {
status: SUCCESS_STATUS,
data: {
id: user.id,
email: user.email,
},
};
}
@Delete("/:userId")
@HttpCode(HttpStatus.OK)
@UseGuards(AccessTokenGuard)
async deleteUser(
@GetUser("id") accessTokenUserId: number,
@Param("userId") userId: number
): Promise<ApiResponse<UserReturned>> {
if (accessTokenUserId !== userId) {
throw new BadRequestException("userId parameter does not match access token");
}
this.logger.log(`Deleting user with ID: ${userId}`);
const existingUser = await this.userRepository.findById(userId);
if (!existingUser) {
throw new NotFoundException(`User with ${userId} does not exist`);
}
if (existingUser.username) {
throw new BadRequestException("Cannot delete a non manually-managed user");
}
const user = await this.userRepository.delete(userId);
return {
status: SUCCESS_STATUS,
data: {
id: user.id,
email: user.email,
},
};
}
}
export type CreateUserResponse = { user: Partial<User>; accessToken: string; refreshToken: string };
export type UserReturned = Pick<User, "id" | "email">;
export type CreateUserResponse = { user: UserReturned; accessToken: string; refreshToken: string };