From a253b0c44379d72d83f8155b1221159461054e2b Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 8 Feb 2022 21:56:11 +0100 Subject: [PATCH] fix(user): hiding `isAdmin` and `password` property (#167) --- package-lock.json | 23 +++++++------ package.json | 1 + public/src/interfaces/User.ts | 2 +- public/src/pages/main/Userprofile.tsx | 3 +- src/users/api/UserDto.ts | 19 +++++++++-- src/users/users.controller.ts | 47 ++++++++++++++------------- src/users/users.service.ts | 11 +++++-- 7 files changed, 64 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index 739118ff..f68903ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,8 +53,7 @@ "dependencies": { "ansi-regex": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "resolved": "", "dev": true }, "ora": { @@ -1249,8 +1248,7 @@ }, "ansi-regex": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "resolved": "", "dev": true }, "ora": { @@ -2487,8 +2485,7 @@ "dependencies": { "ansi-regex": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "resolved": "", "dev": true }, "chalk": { @@ -2777,6 +2774,11 @@ "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", "dev": true }, + "class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -2832,8 +2834,7 @@ "dependencies": { "ansi-regex": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "resolved": "", "dev": true }, "is-fullwidth-code-point": { @@ -9791,8 +9792,7 @@ "dependencies": { "ansi-regex": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "resolved": "", "dev": true }, "is-fullwidth-code-point": { @@ -10005,8 +10005,7 @@ "dependencies": { "ansi-regex": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "resolved": "", "dev": true }, "is-fullwidth-code-point": { diff --git a/package.json b/package.json index 0b0bc79c..d675c8c7 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@nestjs/swagger": "^4.7.12", "axios": "^0.21.2", "bcrypt": "^5.0.0", + "class-transformer": "^0.5.1", "dot": "^1.1.3", "dotenv": "^8.2.0", "fastify-cookie": "^5.1.0", diff --git a/public/src/interfaces/User.ts b/public/src/interfaces/User.ts index cca77dc9..3e36e9c3 100644 --- a/public/src/interfaces/User.ts +++ b/public/src/interfaces/User.ts @@ -33,5 +33,5 @@ export interface UserData { email: string; lastName: string; firstName: string; - isAdmin: boolean; + isAdmin?: boolean; } diff --git a/public/src/pages/main/Userprofile.tsx b/public/src/pages/main/Userprofile.tsx index 477fcf38..8379c4f8 100644 --- a/public/src/pages/main/Userprofile.tsx +++ b/public/src/pages/main/Userprofile.tsx @@ -8,8 +8,7 @@ import AuthLayout from '../auth/AuthLayout'; const defaultUserData: UserData = { email: '', firstName: '', - lastName: '', - isAdmin: false + lastName: '' }; export const Userprofile = () => { diff --git a/src/users/api/UserDto.ts b/src/users/api/UserDto.ts index 8f288611..d0093ece 100644 --- a/src/users/api/UserDto.ts +++ b/src/users/api/UserDto.ts @@ -1,4 +1,5 @@ import { ApiHideProperty, ApiProperty } from '@nestjs/swagger'; +import { Exclude } from 'class-transformer'; export class UserDto { @ApiProperty() @@ -10,11 +11,25 @@ export class UserDto { @ApiProperty() lastName: string; + @Exclude() @ApiHideProperty() - isAdmin: boolean; + isAdmin?: boolean; + @Exclude() @ApiHideProperty() - password: string; + password?: string; + + @Exclude() + id: number; + + @Exclude() + photo: Buffer; + + @Exclude() + updatedAt: Date; + + @Exclude() + createdAt: Date; constructor( params: { diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 78095225..e68202e8 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -1,5 +1,6 @@ import { Body, + ClassSerializerInterceptor, Controller, ForbiddenException, Get, @@ -56,6 +57,7 @@ import { AdminGuard } from './users.guard'; import { PermissionDto } from './api/PermissionDto'; @Controller('/api/users') +@UseInterceptors(ClassSerializerInterceptor) @ApiTags('User controller') export class UsersController { private logger = new Logger(UsersController.name); @@ -81,17 +83,24 @@ export class UsersController { }) @ApiOkResponse({ type: UserDto, - description: 'Returns user object or empty object when user is not found', + description: 'Returns user object if it exists', + }) + @ApiNotFoundResponse({ + description: 'User not founded', + schema: { + type: 'object', + properties: { + statusCode: { type: 'number' }, + message: { type: 'string' }, + }, + }, }) async getUser(@Param('email') email: string): Promise { try { this.logger.debug(`Find a user by email: ${email}`); return new UserDto(await this.usersService.findByEmail(email)); } catch (err) { - throw new InternalServerErrorException({ - error: err.message, - location: __filename, - }); + throw new HttpException(err.message, err.status); } } @@ -207,13 +216,13 @@ export class UsersController { this.logger.debug(`Create a basic user: ${user}`); const userExists = await this.usersService.findByEmail(user.email); - if (userExists) { throw new HttpException('User already exists', 409); } - - return new UserDto(await this.usersService.createUser(user)); } catch (err) { + if (err.status === 404) { + return new UserDto(await this.usersService.createUser(user)); + } throw new HttpException( err.message ?? 'Something went wrong', err.status ?? 500, @@ -281,21 +290,16 @@ export class UsersController { @Body() newData: UserDto, @Param('email') email: string, @Req() req: FastifyRequest, - ) { + ): Promise { try { - let user = await this.usersService.findByEmail(email); + const user = await this.usersService.findByEmail(email); if (!user) { throw new NotFoundException('Could not find user'); } if (this.originEmail(req) !== email) { throw new ForbiddenException(); } - user = await this.usersService.updateUserInfo(user, newData); - return { - email: user.email, - firstName: user.firstName, - lastName: user.lastName, - }; + return new UserDto(await this.usersService.updateUserInfo(user, newData)); } catch (err) { throw new HttpException( err.message || 'Internal server error', @@ -325,7 +329,10 @@ export class UsersController { @ApiOkResponse({ description: 'Returns user info', }) - async getUserInfo(@Param('email') email: string, @Req() req: FastifyRequest) { + async getUserInfo( + @Param('email') email: string, + @Req() req: FastifyRequest, + ): Promise { try { const user = await this.usersService.findByEmail(email); @@ -335,11 +342,7 @@ export class UsersController { if (this.originEmail(req) !== email) { throw new ForbiddenException(); } - return { - email: user.email, - firstName: user.firstName, - lastName: user.lastName, - }; + return new UserDto(user); } catch (err) { throw new HttpException( err.message || 'Internal server error', diff --git a/src/users/users.service.ts b/src/users/users.service.ts index a23e6faa..91331bfb 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -1,6 +1,6 @@ import { EntityRepository, NotFoundError, wrap } from '@mikro-orm/core'; import { InjectRepository } from '@mikro-orm/nestjs'; -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { PermissionDto } from './api/PermissionDto'; import { hashPassword } from '../auth/credentials.utils'; import { User } from '../model/user.entity'; @@ -53,7 +53,7 @@ export class UsersService { return user; } - async updateUserInfo(oldUser: User, newData: UserDto): Promise { + async updateUserInfo(oldUser: User, newData: UserDto): Promise { this.log.debug(`updateUserInfo ${oldUser.email}`); const newUser = oldUser; wrap(newUser).assign({ @@ -65,7 +65,12 @@ export class UsersService { async findByEmail(email: string): Promise { this.log.debug(`Called findByEmail ${email}`); - return this.usersRepository.findOne({ email }); + const user = await this.usersRepository.findOne({ email }); + if (user) { + return user; + } else { + throw new NotFoundException('User not found'); + } } async getPermissions(email: string): Promise {