diff --git a/backend/src/interceptors/remove-users-fields/remove-users-fields.interceptor.ts b/backend/src/interceptors/remove-users-fields/remove-users-fields.interceptor.ts new file mode 100644 index 00000000..770addb9 --- /dev/null +++ b/backend/src/interceptors/remove-users-fields/remove-users-fields.interceptor.ts @@ -0,0 +1,26 @@ +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; +import { User } from '@prisma/client'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +@Injectable() +export class RemoveUsersFieldsInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler): Observable { + return next.handle().pipe( + map((user) => { + if (!user) { + return user; + } + delete user.refreshToken; + delete user.mfaSecret; + delete user.mfaEnabled; + return user; + }), + ); + } +} diff --git a/backend/src/main.ts b/backend/src/main.ts index d4b6e12c..ddeccd86 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -8,7 +8,11 @@ async function bootstrap() { // Set up cookie parser app.use(cookieParser()); - app.useGlobalPipes(new ValidationPipe()); + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + }), + ); app.enableCors({ origin: process.env.FRONTEND_URL, credentials: true, diff --git a/backend/src/users/dto/patchUser.dto.ts b/backend/src/users/dto/patchUser.dto.ts new file mode 100644 index 00000000..7bf333be --- /dev/null +++ b/backend/src/users/dto/patchUser.dto.ts @@ -0,0 +1,15 @@ +import { IsBoolean, IsOptional, IsString } from 'class-validator'; + +export class PatchUserDto { + @IsString() + @IsOptional() + displayName?: string; + + @IsString() + @IsOptional() + avatar?: string; + + @IsBoolean() + @IsOptional() + mfaEnabled?: boolean; +} diff --git a/backend/src/users/users.controller.ts b/backend/src/users/users.controller.ts index 9ac10dab..9bcd9498 100644 --- a/backend/src/users/users.controller.ts +++ b/backend/src/users/users.controller.ts @@ -7,70 +7,43 @@ import { Post, UseGuards, Req, + UseInterceptors, + UnauthorizedException, } from '@nestjs/common'; import { UsersService } from './users.service'; -import { UpdateUserDto } from './dto/updateUser.dto'; +import { PatchUserDto } from './dto/patchUser.dto'; import { CreateUserDto } from './dto/createUser.dto'; import { AccessTokenGuard } from 'src/auth/jwt/jwt.guard'; import { User } from '@prisma/client'; import { Request } from 'express'; +import { RemoveUsersFieldsInterceptor } from 'src/interceptors/remove-users-fields/remove-users-fields.interceptor'; @Controller('users') +@UseGuards(AccessTokenGuard) +@UseInterceptors(RemoveUsersFieldsInterceptor) export class UsersController { constructor(private service: UsersService) {} - @UseGuards(AccessTokenGuard) @Get('me') - findMe(@Req() req: Request) { - try { - const user = req.user as User; - return this.service.findOne(user.login, { - login: true, - displayName: true, - email: true, - avatar: true, - status: true, - victory: true, - mfaEnabled: true, - createdAt: true, - updatedAt: true, - }); - } catch (error) { - return error; - } + findMe(@Req() request: Request & { user: User }) { + const { login } = request.user; + return this.service.findOne(login); } @Get() findAll() { - return this.service.findAll({ - select: { - login: true, - displayName: true, - email: true, - avatar: true, - status: true, - victory: true, - mfaEnabled: true, - createdAt: true, - updatedAt: true, - }, - }); + return this.service.findAll(); } @Get(':login') - findOne(@Param('login') login: string) { - return this.service.findOne(login, { - id: true, - login: true, - displayName: true, - email: true, - avatar: true, - status: true, - victory: true, - mfaEnabled: true, - createdAt: true, - updatedAt: true, - }); + async findOne(@Param('login') login: string) { + const user = await this.service.findOne(login); + + if (!user) { + throw new UnauthorizedException('User not found'); + } + + return user; } @Post() @@ -79,7 +52,19 @@ export class UsersController { } @Patch(':login') - update(@Param('login') login: string, @Body() updateUserDto: UpdateUserDto) { + update( + @Req() request: Request & { user: User }, + @Param('login') login: string, + @Body() updateUserDto: PatchUserDto, + ) { + const { user } = request; + + if (user.login !== login) { + throw new UnauthorizedException( + 'You are not authorized to update this user', + ); + } + return this.service.update(login, updateUserDto); } } diff --git a/backend/src/users/users.service.ts b/backend/src/users/users.service.ts index 2aa62b3d..70112490 100644 --- a/backend/src/users/users.service.ts +++ b/backend/src/users/users.service.ts @@ -8,27 +8,27 @@ import { User } from '@prisma/client'; export class UsersService { constructor(private prisma: PrismaService) {} - async findAll(args: any = {}) { - return await this.prisma.user.findMany(args); + async findAll() { + return await this.prisma.user.findMany({ + select: { + login: true, + displayName: true, + email: true, + avatar: true, + status: true, + victory: true, + mfaEnabled: true, + createdAt: true, + updatedAt: true, + }, + }); } - async findOne(login: string, select: any = null): Promise { - let args = {} as any; - if (select != null) { - args = { - where: { - login: login, - }, - select: select, - }; - } else { - args = { - where: { - login: login, - }, - }; - } - const user: User = await this.prisma.user.findUnique(args); + async findOne(login: string): Promise { + const user = await this.prisma.user.findUnique({ + where: { login: login }, + }); + return user; }