diff --git a/src/game/game.controller.ts b/src/game/game.controller.ts index 775d4945cdd697b6dcd8847a96011b0f26392b47..6c44c7e068f3fef30dbe27c7a495fc6567899789 100644 --- a/src/game/game.controller.ts +++ b/src/game/game.controller.ts @@ -1,43 +1,72 @@ -import { Controller, Post, UseGuards, Body, Get, Param, UsePipes, Put } from '@nestjs/common'; +import { + Controller, + Post, + UseGuards, + Body, + Get, + Param, + UsePipes, + Put, +} from '@nestjs/common'; import { GameService } from './game.service'; import { AuthGuard } from '../shared/auth.guard'; import { User } from '../user/user.decorator'; import { GameDTO } from './game.dto'; import { ValidationPipe } from '../shared/validation.pipe'; +import { Game_PersonEntity } from './game.entity'; +import { Roles } from '../shared/roles.decorator'; @Controller('game') export class GameController { - constructor(private gameservice: GameService) { } + constructor(private gameservice: GameService) {} - @Post('new') - @UseGuards(new AuthGuard()) - @UsePipes(new ValidationPipe()) - async newGame(@User('id') person, @Body() body: GameDTO ) { - return this.gameservice.createNewGame(person, body); - } + @Post('new') + @UseGuards(new AuthGuard()) + //@UsePipes(new ValidationPipe()) + async newGame(@User('id') person, @Body() body: GameDTO) { + return this.gameservice.createNewGame(person, body); + } - @Put(':id') - @UseGuards(new AuthGuard()) - @UsePipes(new ValidationPipe()) - async editGame(@Param('id') id: string, @Body() body: GameDTO) { - return this.gameservice.editGame(body); - } + @Put(':id') + @Roles('admin') + @UseGuards(new AuthGuard()) + @UsePipes(new ValidationPipe()) + async editGame(@Param('id') id: string, @Body() body: GameDTO) { + return this.gameservice.editGame(id, 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); - } + // @UseGuards(new AuthGuard()) + // @UsePipes(new ValidationPipe()) + // @Post(':id') + // async joinGame(@User('id') person, @Body() data: Game_PersonEntity) { + // try { + // return this.gameservice.joinGame(person, data, json); + // } catch (error) {} + // } - @Get('listgames') - async listGames() { - return this.gameservice.listGames(); + @Put('joinfaction') + @UseGuards(new AuthGuard()) + joinFaction( + @User('id') person, + @Param('id') gameId, + @Param('faction') faction: string, + @Body() password: string, + ) { + try { + // return this.gameservice.joinFaction(person, gameId, faction, password); + } catch (error) { + return error; } + } - @Get(':id') - async returnGameInfo(@Param('id') id: string) { - return this.gameservice.returnGameInfo(id); - } + @Get('listgames') + async listGames() { + return this.gameservice.listGames(); + } + + @Get(':id') + async returnGameInfo(@Param('id') id: string) { + return this.gameservice.returnGameInfo(id); + } } diff --git a/src/game/game.dto.ts b/src/game/game.dto.ts index 6a21db70c718a578c632700f69da5b010554f1df..9c5b06df85c69343e66322d0735a4f58bb6e793f 100644 --- a/src/game/game.dto.ts +++ b/src/game/game.dto.ts @@ -30,15 +30,9 @@ 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: string[]; - factions: FactionDTO[]; + factions?: FactionDTO[]; } export class FactionDTO { @@ -48,4 +42,5 @@ export class FactionDTO { name: string; id: string; game: GameDTO; + password: string; } diff --git a/src/game/game.entity.ts b/src/game/game.entity.ts index 0dc0cdf36d3b973a424a4788e3608a6571a8e80f..293b420dd90d2a3bfaad9a0afa97d3c5f0681522 100644 --- a/src/game/game.entity.ts +++ b/src/game/game.entity.ts @@ -8,6 +8,8 @@ import { } from 'typeorm'; import { PersonEntity } from '../user/user.entity'; +import * as jwt from 'jsonwebtoken'; + // table that stores all created games @Entity('Game') export class GameEntity { @@ -15,16 +17,14 @@ export class GameEntity { @Column('text') name: string; @Column('text') desc: string; @Column('json') center: JSON; - @Column('json') map: JSON; + @Column({ type: 'json', nullable: true }) map: JSON; @Column('timestamp') startdate: Timestamp; @Column('timestamp') enddate: Timestamp; - @Column("text", {array: true}) passwords: string[]; @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; return { id, name }; @@ -36,28 +36,53 @@ export class GameEntity { export class FactionEntity { @PrimaryGeneratedColumn('uuid') id: string; @Column('text') name: string; + @Column('text') password: string; @ManyToOne(type => GameEntity, game => game.factions) game: GameEntity; - @OneToMany(type => Game_PersonEntity, game_persons => game_persons.faction, - {onUpdate: 'CASCADE'}) + @OneToMany(type => Game_PersonEntity, game_persons => game_persons.faction, { + onUpdate: 'CASCADE', + }) 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; - /* + @PrimaryGeneratedColumn('uuid') gamepersonId: string; + @Column({ type: 'text', nullable: true }) role: 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[]; */ -} + async checkPlayerRole(userId: PersonEntity, gameId: GameEntity) { + try { + return userId; + } catch (error) { + return error.message; + } + } + + public tokenObject() { + const { gametoken } = this; + return gametoken; + } + private get gametoken() { + const { gamepersonId, role } = this; + return jwt.sign( + { + gamepersonId, + role, + }, + process.env.SECRET, + { expiresIn: '7d' }, + ); + } +} diff --git a/src/game/game.service.ts b/src/game/game.service.ts index 6caa2c2a1d3061657c49e9e8a4ef927c2442f7fe..fa31ebf3f2f1c99cbf5642c70eec6a69cb920cc1 100644 --- a/src/game/game.service.ts +++ b/src/game/game.service.ts @@ -20,39 +20,53 @@ export class GameService { ) {} // create a new game - 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); + async createNewGame(personId: PersonEntity, gameData: GameDTO) { + try { + // 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: gameData.factions, + }); + await this.gameRepository.insert(game); + const gamePerson = await this.game_PersonRepository.create({ + faction: null, + game: game, + person: personId, + }); + gamePerson['role'] = 'admin'; + await this.game_PersonRepository.insert(gamePerson); + return { + message: 'New game added', + gametoken: gamePerson.tokenObject(), + }; + } catch (error) { + return error.message; } - // else add the game to the database - const game = await this.gameRepository.create({ - ...gameData, - factions: gameData.factions, - }); - await this.gameRepository.insert(game); - return { - "message": "New game added" - }; } // edit already created game - async editGame(id: string, gameData: Partial<GameDTO>) { + async editGame(id: string, gameData: GameDTO) { try { - // update game entry in db const updatedGame = await this.gameRepository.create({ ...gameData, - factions: gameData.factions - }) - updatedGame["id"] = id + factions: gameData.factions, + }); + updatedGame['id'] = id; const gameId = await this.gameRepository.save(updatedGame); - + // get all the factions that are associated with the game to deny duplicate entries - const factions = await this.factionRepository.find({select: ["name"], where: {game: gameId}}) - const factionNames = factions.map(({ name }) => name) - + const factions = await this.factionRepository.find({ + select: ['name'], + where: { game: gameId }, + }); + const factionNames = factions.map(({ name }) => name); + // add the factions to db if (gameData.factions) { gameData.factions.map(async faction => { @@ -69,10 +83,11 @@ export class GameService { return { message: 'Game updated', }; - } catch (error) {console.log(error)} + } catch (error) { + console.log(error); + } } - // checks the password, creates an entry in GamePerson table with associated role&faction async joinGame(person, gameId, json) { const user = await this.personRepository.findOne({ @@ -80,10 +95,8 @@ export class GameService { }); const game = await this.gameRepository.findOne({ where: { id: gameId } }); - const index = game.passwords.indexOf(json.password); - // create game_Person entry -/* const gamePerson = await this.game_PersonRepository.create({ + /* const gamePerson = await this.game_PersonRepository.create({ faction, gameId, person, diff --git a/src/mapmarkers/mapmarker.entity.ts b/src/mapmarkers/mapmarker.entity.ts index da7ed5e8935d3b8a8aade0beb99578bf4e62e426..dd2caae1a766c60d4627e969eac2a37518b13dca 100644 --- a/src/mapmarkers/mapmarker.entity.ts +++ b/src/mapmarkers/mapmarker.entity.ts @@ -17,5 +17,5 @@ export class MapMarkerEntity { @Column({type: 'json', nullable: true}) features: JSON; @ManyToOne(type => PersonEntity, player => player.markers) player?: PersonEntity; - @ManyToOne(type => FactionEntity) + //@ManyToOne(type => FactionEntity) } \ No newline at end of file diff --git a/src/mapmarkers/mapmarker.service.ts b/src/mapmarkers/mapmarker.service.ts index 0a7c20f2ddbb93a0e2d88ac572a57c007b8dd42d..1a948694810e6ba42b961bde7bd73de02ba24fb6 100644 --- a/src/mapmarkers/mapmarker.service.ts +++ b/src/mapmarkers/mapmarker.service.ts @@ -1,6 +1,6 @@ import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; -import { Repository, In } from "typeorm"; -import { InjectRepository } from "@nestjs/typeorm"; +import { Repository, In } from 'typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; import { MapMarkerEntity } from './mapmarker.entity'; import { MapMarkerDTO } from './mapmarker.dto'; @@ -9,52 +9,50 @@ import { userInfo } from 'os'; @Injectable() export class MapMarkerService { - constructor( - //create references to tables as repositories - @InjectRepository(MapMarkerEntity) private mapmarkerRepository: Repository<MapMarkerEntity>, - @InjectRepository(PersonEntity) private personRepository: Repository<PersonEntity> - ) { } + constructor( + //create references to tables as repositories + @InjectRepository(MapMarkerEntity) + private mapmarkerRepository: Repository<MapMarkerEntity>, + @InjectRepository(PersonEntity) + private personRepository: Repository<PersonEntity>, + ) {} - // insert markers - async insertLocation(personId: string, data: MapMarkerDTO) { - try { - - //get functions runtime as timestamp - data.timestamp = new Date(Date.now()).toLocaleString(); - //check from database for the user who uploads the data - const user = await this.personRepository.findOne({ where: { id: personId } }) - //create© entity properties - const location = await this.mapmarkerRepository.create({ ...data, player: user }); - // insert created entity NOTE: insert method doesn't check for duplicates. - await this.mapmarkerRepository.insert(location); - // return data and player id&name - return { ...data, player: location.player.nameObject() }; - } catch (error) { - return error; - } + // insert marker + async insertLocation(personId: string, data: MapMarkerDTO) { + try { + //get functions runtime as timestamp + data.timestamp = new Date(Date.now()).toLocaleString(); + //check from database for the user who uploads the data + const user = await this.personRepository.findOne({ + where: { id: personId }, + }); + //create© entity properties + const location = await this.mapmarkerRepository.create({ + ...data, + player: user, + }); + // insert created entity NOTE: insert method doesn't check for duplicates. + await this.mapmarkerRepository.insert(location); + // return data and player id&name + return { ...data, player: location.player.nameObject() }; + } catch (error) { + return error; } + } - // get all markers - async getAllMarkers() { - try { - // find all markers with specified player - const markers = await this.mapmarkerRepository.find({ relations: ['player'] }); - // return markers from database with said playerdata - return markers.map(marker => { return { ...marker, player: marker.player.nameObject() } }); - } catch (error) { - return error.message; - } + // get all markers + async getAllMarkers() { + try { + // find all markers with specified player + const markers = await this.mapmarkerRepository.find({ + relations: ['player'], + }); + // return markers from database with said playerdata + return markers.map(marker => { + return { ...marker, player: marker.player.nameObject() }; + }); + } catch (error) { + return error.message; } - - async test(): Promise<string>{ - try { - return 'hello'; - } catch (error) { - throw error; - } - } - - async getFactionMarkers(){ - - } -} \ No newline at end of file + } +} diff --git a/src/mapmarkers/mapmarkers.controller.ts b/src/mapmarkers/mapmarkers.controller.ts index 26956bda2a8e000cf3bb6ed73e0bd2de7c4e7aa9..84ecba56781da97ff86195cc1940ae4617edee66 100644 --- a/src/mapmarkers/mapmarkers.controller.ts +++ b/src/mapmarkers/mapmarkers.controller.ts @@ -29,21 +29,4 @@ export class MapMarkersController { return error.message; } } - - @Get('test') - async test(){ - try { - return this.mapmarkerservice.test(); - } catch (error) { - return error.message; - } - } - @Get('testroles') - async testroles(){ - try { - return this.mapmarkerservice.test(); - } catch (error) { - return error.message; - } - } } diff --git a/src/shared/auth.guard.ts b/src/shared/auth.guard.ts index f1eed74268cbfe02109590d219e799acaf3413e1..ff2bdf406d5badffc467cdf22d0f61935aae0af8 100644 --- a/src/shared/auth.guard.ts +++ b/src/shared/auth.guard.ts @@ -1,55 +1,43 @@ -import { Injectable, ExecutionContext, CanActivate, HttpException, HttpStatus } from '@nestjs/common'; +import { + Injectable, + ExecutionContext, + CanActivate, + HttpException, + HttpStatus, +} from '@nestjs/common'; import * as jwt from 'jsonwebtoken'; @Injectable() export class AuthGuard implements CanActivate { - - // check for logged in user - async canActivate(context: ExecutionContext): Promise<boolean> { - // get request - const request = context.switchToHttp().getRequest(); - // check for authorization header - if (!request.headers.authorization) { - return false; - } - - // validate token - request.user = await this.validateToken(request.headers.authorization); - - return true; + // check for logged in user + async canActivate(context: ExecutionContext): Promise<boolean> { + // get request + const request = context.switchToHttp().getRequest(); + // check for authorization header + if (!request.headers.authorization) { + return false; } // validate token - async validateToken(auth: string) { - // check if header contains Bearer - if (auth.split(" ")[0] !== 'Bearer') { - throw new HttpException('Invalid token', HttpStatus.FORBIDDEN); - } - // get the token - const token = auth.split(" ")[1]; - try { - - // return token. - return await jwt.verify(token, process.env.SECRET); - } catch (err) { - const message = `Token error: ${err.message || err.name}` - throw new HttpException(message, HttpStatus.FORBIDDEN); - } - } + request.user = await this.validateToken(request.headers.authorization); + + return true; + } - async checkRole(auth: string){ - // check if header contains Bearer - if (auth.split(" ")[0] !== 'Bearer') { - throw new HttpException('Invalid token', HttpStatus.FORBIDDEN); - } - // get the token - const token = auth.split(" ")[1]; - try { - // return token. - return await jwt.verify(token, process.env.SECRET); - } catch (err) { - const message = `Token error: ${err.message || err.name}` - throw new HttpException(message, HttpStatus.FORBIDDEN); - } + // validate token + async validateToken(auth: string) { + // check if header contains Bearer + if (auth.split(' ')[0] !== 'Bearer') { + throw new HttpException('Invalid token', HttpStatus.FORBIDDEN); + } + // get the token + const token = auth.split(' ')[1]; + try { + // return token. + return await jwt.verify(token, process.env.SECRET); + } catch (err) { + const message = `Token error: ${err.message || err.name}`; + throw new HttpException(message, HttpStatus.FORBIDDEN); } + } } diff --git a/src/shared/roles.guard.ts b/src/shared/roles.guard.ts index ab7b3f1ecd230239ff6364f28cc3cb4fda8762b8..03e9eccc39d8b1383a0550d53453e59f10803733 100644 --- a/src/shared/roles.guard.ts +++ b/src/shared/roles.guard.ts @@ -1,21 +1,47 @@ -import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { + Injectable, + CanActivate, + ExecutionContext, + HttpException, + HttpStatus, +} from '@nestjs/common'; import { Observable } from 'rxjs'; import { Reflector } from '@nestjs/core'; +import * as jwt from 'jsonwebtoken'; + @Injectable() export class RolesGuard implements CanActivate { - constructor(private readonly reflector: Reflector){} + constructor(private readonly reflector: Reflector) {} canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { const roles = this.reflector.get<string[]>('roles', context.getHandler()); - if(!roles){ - return true; + if (!roles) { + return true; } const request = context.switchToHttp().getRequest(); const user = request.user; - const hasRole = () => user.roles.some((role) => roles.includes(role)); - return user && user.roles && hasRole(); + const role = this.checkRole(request.headers.authorization); + const hasRole = () => user.roles.some(role => roles.includes(role)); + return user && role && hasRole(); + } + + async checkRole(auth: string) { + // check if header contains Bearer + if (auth.split(' ')[0] !== 'Bearer') { + throw new HttpException('Invalid token', HttpStatus.FORBIDDEN); + } + // get the token + const token = auth.split(' ')[1]; + try { + const decoded = await jwt.decode(token); + console.log(decoded); + return decoded; + } catch (err) { + const message = `Token error: ${err.message || err.name}`; + throw new HttpException(message, HttpStatus.FORBIDDEN); + } } -} \ No newline at end of file +} diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 2910ed9c445715f94c698935428bf2c85ef6d717..4d4158d3f037850c8c5230975ef5f46ae5a75ede 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -1,4 +1,11 @@ -import { Controller, Post, Body, UsePipes, Get, UseGuards } from '@nestjs/common'; +import { + Controller, + Post, + Body, + UsePipes, + Get, + UseGuards, +} from '@nestjs/common'; import { UserService } from './user.service'; import { UserDTO } from './user.dto'; @@ -7,24 +14,24 @@ import { ValidationPipe } from '../shared/validation.pipe'; @Controller('user') export class UserController { - constructor(private userService: UserService) { } + constructor(private userService: UserService) {} - @Post('register') - @UsePipes(new ValidationPipe()) - createUser(@Body() data: UserDTO) { - return this.userService.register(data); - } + @Post('register') + @UsePipes(new ValidationPipe()) + createUser(@Body() data: UserDTO) { + return this.userService.register(data); + } - @Post('login') - @UsePipes(new ValidationPipe()) - loginUser(@Body() data: UserDTO) { - return this.userService.login(data); - } + @Post('login') + @UsePipes(new ValidationPipe()) + loginUser(@Body() data: UserDTO) { + return this.userService.login(data); + } - // verifies the token - @Get('verify') - @UseGuards(new AuthGuard()) - showMap() { - return true; - } + // verifies the token + @Get('verify') + @UseGuards(new AuthGuard()) + showMap() { + return true; + } } diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index a71b569dc1434482557dc9701d2a644749081895..0d003830f3330cf005632b78b52b94f55559d7dc 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -1,4 +1,10 @@ -import { Entity, Column, PrimaryGeneratedColumn, BeforeInsert, OneToMany } from 'typeorm'; +import { + Entity, + Column, + PrimaryGeneratedColumn, + BeforeInsert, + OneToMany, +} from 'typeorm'; import * as bcrypt from 'bcryptjs'; import * as jwt from 'jsonwebtoken'; @@ -7,44 +13,45 @@ import { Game_PersonEntity } from '../game/game.entity'; @Entity('Person') export class PersonEntity { - @PrimaryGeneratedColumn('uuid') id: string; - @Column({type: 'text', unique: true}) name: string; - @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}; - } - - // returns username and the id - nameObject() { - const {id, name} = this; - return {id, name}; - } - - async comparePassword(attempt: string) { - return await bcrypt.compareSync(attempt, this.password); - } - - private get token() { - const {id, name} = this; - return jwt.sign({ - id, name - }, - process.env.SECRET, - { expiresIn: '7d'}, - ); - } -} \ No newline at end of file + @PrimaryGeneratedColumn('uuid') id: string; + @Column({ type: 'text', unique: true }) name: string; + @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 }; + } + + // returns username and the id + nameObject() { + const { id, name } = this; + return { id, name }; + } + + async comparePassword(attempt: string) { + return await bcrypt.compareSync(attempt, this.password); + } + + private get token() { + const { id, name } = this; + return jwt.sign( + { + id, + name, + }, + process.env.SECRET, + { expiresIn: '7d' }, + ); + } +} diff --git a/src/user/user.service.ts b/src/user/user.service.ts index fb1b637e00c2f7774db66f0a89eb8dfd89dd8bee..8d0a64d8ea0430d1633e67a17ee3fb782d21ce72 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -55,7 +55,7 @@ export class UserService { } // liitytään factionii - async joinFaction(): Promise<boolean>{ + async joinFaction(game: GameDTO, data: UserDTO): Promise<boolean>{ try { // tarkistetaan factionin salasana return true;