From 334573dbf0b799ca827aaea7b761d20541a6ed79 Mon Sep 17 00:00:00 2001 From: L4168 <L4168@student.jamk.fi> Date: Fri, 7 Jun 2019 12:26:23 +0300 Subject: [PATCH] added input validation & comments --- src/app.module.ts | 3 +- src/game/game.controller.ts | 21 ++++++++++---- src/game/game.dto.ts | 17 +++++++++++- src/game/game.entity.ts | 29 ++++++++++++++++++-- src/game/game.service.ts | 50 +++++++++++++++++++++++----------- src/shared/array-validation.ts | 17 ++++++++++++ src/user/user.decorator.ts | 1 + src/user/user.entity.ts | 6 ++++ 8 files changed, 119 insertions(+), 25 deletions(-) create mode 100644 src/shared/array-validation.ts diff --git a/src/app.module.ts b/src/app.module.ts index 13f94c7..13fd352 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,8 +1,9 @@ import { Module } from '@nestjs/common'; import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core'; import { TypeOrmModule } from "@nestjs/typeorm"; -import { AppService } from './app.service'; import { Connection } from "typeorm"; + +import { AppService } from './app.service'; import { UserModule } from './user/user.module'; import { HttpErrorFilter } from './shared/http-error.filter'; import { LoggingInterceptor } from './shared/logging.interceptor'; diff --git a/src/game/game.controller.ts b/src/game/game.controller.ts index 4ec3580..ed3bef3 100644 --- a/src/game/game.controller.ts +++ b/src/game/game.controller.ts @@ -1,8 +1,11 @@ -import { Controller, Post, UseGuards, Body, Get, Param } from '@nestjs/common'; +import { Controller, Post, UseGuards, Body, Get, Param, UsePipes } from '@nestjs/common'; + import { GameService } from './game.service'; import { AuthGuard } from '../shared/auth.guard'; -import { User } from 'src/user/user.decorator'; -import { GameDTO, FactionDTO } from './game.dto'; +import { User } from '../user/user.decorator'; +import { GameDTO } from './game.dto'; +import { ValidationPipe } from '../shared/validation.pipe'; +import { async } from 'rxjs/internal/scheduler/async'; @Controller('game') export class GameController { @@ -10,8 +13,16 @@ export class GameController { @Post('new') @UseGuards(new AuthGuard()) - async newGame(@User('id') person, @Body() body) { - return this.gameservice.createNewGame(person, body, body.factions); + @UsePipes(new ValidationPipe()) + async newGame(@User('id') person, @Body() body: GameDTO ) { + return this.gameservice.createNewGame(person, body); + } + + @UseGuards(new AuthGuard()) + @UsePipes(new ValidationPipe()) + @Post(':id') + async joinGame(@User('id') person, @Param('id') id: string, @Body() password: string) { + return this.gameservice.joinGame(person, id, password); } @Get('listgames') diff --git a/src/game/game.dto.ts b/src/game/game.dto.ts index 3f7bed0..5a41f38 100644 --- a/src/game/game.dto.ts +++ b/src/game/game.dto.ts @@ -4,9 +4,14 @@ import { IsJSON, IsNotEmpty, Length, + IsArray, + Validate, } from 'class-validator'; +import { ArrayLength } from 'src/shared/array-validation'; export class GameDTO { + // uses class-validator built in validations + // see https://github.com/typestack/class-validator @IsString() @IsNotEmpty() @Length(2, 31) @@ -15,7 +20,8 @@ export class GameDTO { @IsNotEmpty() @Length(1, 255) desc: string; - @IsJSON() + //@IsJSON() + // doesn't accept with IsJSON, WIP to get validation for map map: JSON; @IsDateString() @IsNotEmpty() @@ -23,6 +29,15 @@ export class GameDTO { @IsDateString() @IsNotEmpty() enddate: string; + @IsArray() + @IsNotEmpty() + @Length(5, 15, { + each: true, + }) + // custom validation for array length (arr>min, arr<max) + @Validate(ArrayLength, [4, 8]) + passwords: []; + factions: FactionDTO[]; } export class FactionDTO { diff --git a/src/game/game.entity.ts b/src/game/game.entity.ts index 0a35559..c4008f7 100644 --- a/src/game/game.entity.ts +++ b/src/game/game.entity.ts @@ -6,7 +6,7 @@ import { OneToMany, Timestamp, } from 'typeorm'; - +import { PersonEntity } from 'src/user/user.entity'; // table that stores all created games @Entity('Game') @@ -17,8 +17,12 @@ export class GameEntity { @Column('json') map: JSON; @Column('timestamp') startdate: Timestamp; @Column('timestamp') enddate: Timestamp; + @Column("text", {array: true}) passwords: []; @OneToMany(type => FactionEntity, faction => faction.game) factions: FactionEntity[]; + @OneToMany(type => Game_PersonEntity, game_persons => game_persons.game) + game_persons: Game_PersonEntity[]; + gameObject() { const { id, name } = this; @@ -33,4 +37,25 @@ export class FactionEntity { @Column('text') name: string; @ManyToOne(type => GameEntity, game => game.factions) game: GameEntity; -} \ No newline at end of file + @OneToMany(type => Game_PersonEntity, game_persons => game_persons.faction) + game_persons: Game_PersonEntity[]; + +} + +// table that stores players associated with particular game +@Entity('Game_Person') +export class Game_PersonEntity { + @PrimaryGeneratedColumn('uuid') gameId: string; + @ManyToOne(type => FactionEntity, faction => faction.game_persons) + faction: FactionEntity; + @ManyToOne(type => GameEntity, game => game.game_persons) + game: GameEntity; + @ManyToOne(type => PersonEntity, person => person.game_persons) + person: PersonEntity; + /* + @ManyToOne(type => PersonRoleEntity, person_role => person_role.game_persons) + person_role: PersonRoleEntity; + @ManyToMany(type => CoordinateEntity, game_person_coordinates => game_person_coordinates.game_persons) + game_person_coordinates: CoordinateEntity[]; */ +} + diff --git a/src/game/game.service.ts b/src/game/game.service.ts index a14ae18..5c5fca0 100644 --- a/src/game/game.service.ts +++ b/src/game/game.service.ts @@ -1,8 +1,10 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, HttpException, HttpStatus } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { Repository, In } from 'typeorm'; + import { GameEntity, FactionEntity } from './game.entity'; -import { FactionDTO, GameDTO } from './game.dto'; +import { GameDTO } from './game.dto'; +import { PersonEntity } from 'src/user/user.entity'; @Injectable() export class GameService { @@ -11,43 +13,59 @@ export class GameService { private gameRepository: Repository<GameEntity>, @InjectRepository(FactionEntity) private factionRepository: Repository<FactionEntity>, - ) { } + @InjectRepository(PersonEntity) + private personRepository: Repository<PersonEntity>, + ) {} // create a new game - async createNewGame( - personId: string, - gameData: GameDTO, - factions: FactionDTO[], - ) { + async createNewGame(personId: string, gameData: GameDTO) { + // checks if a game with the same name exists already + const { name } = gameData; + if (await this.gameRepository.findOne({ where: { name } })) { + throw new HttpException('Game already exists', HttpStatus.BAD_REQUEST); + } + // else add the game to the database const game = await this.gameRepository.create({ ...gameData, - factions: factions, + factions: gameData.factions, }); await this.gameRepository.insert(game); // get the id of the game created to pass it to factions table - const gameid = await this.gameRepository.findOne({ where: { name: gameData.name } }) + const gameid = await this.gameRepository.findOne({ + where: { name: gameData.name }, + }); - factions.map(async faction => { + gameData.factions.map(async faction => { let name = await this.factionRepository.create({ ...faction, - game: gameid + game: gameid, }); await this.factionRepository.insert(name); }); return 'success'; } + // checks the password, creates an entry in GamePerson table with associated role&faction + async joinGame(person, gameId, password) { + const user = await this.personRepository.findOne({ where: { id: person.id } }); + const game = await this.gameRepository.findOne({ where: { id: gameId } }); + return 'WIP'; + } + // returns name and id of each game async listGames() { const games = await this.gameRepository.find({ relations: ['factions'] }); return games.map(game => { - return { game }; + return game.gameObject(); }); } // returns information about a game identified by id async returnGameInfo(id: string) { - const game = await this.gameRepository.findOne({ where: { id: id }, relations: ['factions'] }); + const game = await this.gameRepository.findOne({ + where: { id: id }, + relations: ['factions'], + }); return game; } -} \ No newline at end of file +} diff --git a/src/shared/array-validation.ts b/src/shared/array-validation.ts new file mode 100644 index 0000000..494ab42 --- /dev/null +++ b/src/shared/array-validation.ts @@ -0,0 +1,17 @@ +import {ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments} from "class-validator"; +import { Logger } from "@nestjs/common"; + +// validates array length +@ValidatorConstraint({ name: "arrayLength", async: true }) +export class ArrayLength implements ValidatorConstraintInterface { + + validate(array: string[], args: ValidationArguments) { + Logger.log(array.length) + return array.length > args.constraints[0] && array.length < args.constraints[1]; // for async validations you must return a Promise<boolean> here + } + + defaultMessage(args: ValidationArguments) { // here you can provide default error message if validation failed + return "Please input all passwords"; + } + +} \ No newline at end of file diff --git a/src/user/user.decorator.ts b/src/user/user.decorator.ts index 29f93b7..2c499d9 100644 --- a/src/user/user.decorator.ts +++ b/src/user/user.decorator.ts @@ -1,5 +1,6 @@ import { createParamDecorator } from "@nestjs/common"; +// used to pass user information to controllers export const User = createParamDecorator((data, req) => { return data ? req.user[data] : req.user; }) \ No newline at end of file diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index e718ce4..6c00a00 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -2,6 +2,7 @@ import { Entity, Column, PrimaryGeneratedColumn, BeforeInsert, OneToMany } from import * as bcrypt from 'bcryptjs'; import * as jwt from 'jsonwebtoken'; import { MapMarkerEntity } from 'src/mapmarkers/mapmarker.entity'; +import { Game_PersonEntity } from 'src/game/game.entity'; @Entity('Person') export class PersonEntity { @@ -10,12 +11,17 @@ export class PersonEntity { @Column('text') password: string; @OneToMany(type => MapMarkerEntity, marker => marker.player) markers: MapMarkerEntity[]; + @OneToMany(type => Game_PersonEntity, game_persons => game_persons.person) + game_persons: Game_PersonEntity[]; + + // hashes the password before inserting it to database @BeforeInsert() async hashPassword() { this.password = await bcrypt.hash(this.password, 10); } + // returns username and associated token tokenObject() { const {name, token} = this; return {name, token}; -- GitLab