From f63e369dad8a736868f42aab5d6ab778195381bb Mon Sep 17 00:00:00 2001 From: Quildra Date: Sun, 26 Nov 2023 17:08:39 +0000 Subject: [PATCH] More upload safety checks and error reporting --- .../src/race-results/race-result.model.ts | 3 ++ .../src/race-results/race-results.service.ts | 13 ++++-- .../src/upload/upload.service.ts | 45 +++++++++++-------- .../app/interceptors/snackbar.interceptor.ts | 12 +++++ 4 files changed, 51 insertions(+), 22 deletions(-) diff --git a/packages/bridge-server/src/race-results/race-result.model.ts b/packages/bridge-server/src/race-results/race-result.model.ts index 11cdab8..0475690 100644 --- a/packages/bridge-server/src/race-results/race-result.model.ts +++ b/packages/bridge-server/src/race-results/race-result.model.ts @@ -23,4 +23,7 @@ export class RaceResult extends Model { @Column time: number; + + @Column + replayHash: string; } \ No newline at end of file diff --git a/packages/bridge-server/src/race-results/race-results.service.ts b/packages/bridge-server/src/race-results/race-results.service.ts index be6e465..ad0e81d 100644 --- a/packages/bridge-server/src/race-results/race-results.service.ts +++ b/packages/bridge-server/src/race-results/race-results.service.ts @@ -19,7 +19,7 @@ export class RaceResultsService { async findOne(id: number) { return this.raceResultModel.findOne({ where: {id: id} - }); + }); } async getFastestTimesForRace(raceId: number) { @@ -32,7 +32,13 @@ export class RaceResultsService { }); } - async create(raceId: number, racerId: number, time: number, replayPath: string) { + async findOneByReplayHash(hash: string) { + return this.raceResultModel.findOne({ + where: {replayHash: hash} + }); + } + + async create(raceId: number, racerId: number, time: number, replayPath: string, replayHash: string) { try { await this.sequelize.transaction( async t => { const transactionHost = { transaction: t }; @@ -40,7 +46,8 @@ export class RaceResultsService { raceId: raceId, racerId: racerId, replayPath: replayPath, - time: time + time: time, + replayHash: replayHash }, transactionHost ); diff --git a/packages/bridge-server/src/upload/upload.service.ts b/packages/bridge-server/src/upload/upload.service.ts index 0992bc4..b2d8b88 100644 --- a/packages/bridge-server/src/upload/upload.service.ts +++ b/packages/bridge-server/src/upload/upload.service.ts @@ -1,4 +1,4 @@ -import { HttpException, HttpStatus, Injectable, NotAcceptableException } from '@nestjs/common'; +import { Injectable, NotAcceptableException } from '@nestjs/common'; import { RacersService } from 'src/racers/racers.service'; import { RaceResultsService } from 'src/race-results/race-results.service'; import { RacesService } from 'src/races/races.service'; @@ -8,7 +8,9 @@ const AdvancableBuffer = require('../utilities/advanceable-buffer'); const gbxHeader = require('../trackmania-replays/gbx-header'); const gbxReplay = require('../trackmania-replays/gbx-replay'); -const fs = require('fs'); + +import { createHash } from 'crypto'; +import { readFileSync, unlinkSync } from 'fs'; @Injectable() @@ -23,52 +25,57 @@ export class UploadService { {} async replayUploaded(file: Express.Multer.File, body: any) { - console.log(file.path) + const hash = createHash('sha256'); + + let buffer = readFileSync(file.path); + hash.update(buffer); + let file_hash = hash.digest('hex'); + console.log(file_hash); + + let results = await this.raceResultsService.findOneByReplayHash(file_hash); + console.log(results); + + if(results != undefined) { + unlinkSync(file.path); + throw new NotAcceptableException('AlreadyUploaded', {cause: new Error(), description: 'This replay has already been submitted.'}); + } - let buffer = fs.readFileSync(file.path) - console.log(buffer) let buff = new AdvancableBuffer(buffer); let header = new gbxHeader(); + header.parse(buff); if (header.is_vaild == false) { - return; + unlinkSync(file.path); + throw new NotAcceptableException('GBXInvalid', {cause: new Error(), description: 'This is not a valid replay file'}); } - console.log(header); + let replay = new gbxReplay(); replay.parse(buff); let currentRacer = await this.racersService.findOrCreate(replay.gamerHandle); - console.log(currentRacer); let race = await this.racesService.findOneBySeasonAndMapUID(body.seasonId, replay.mapUID) - console.log(race); - if(race == undefined){ - console.log("No race for " + replay.mapUID); + if(race == undefined) { + throw new NotAcceptableException('RaceNotFound', {cause: new Error(), description: 'This replay is not for a valid race.'}) } if(race.endDate.getTime() < Date.now()) { throw new NotAcceptableException('RaceClosed', {cause: new Error(), description: 'Race closed for new entries.'}) - console.log("Too Late!") - return; } if(Date.now() < race.startDate.getTime()) { throw new NotAcceptableException('RaceNotOpen', {cause: new Error(), description: 'Race not yet open for entries.'}) - console.log("Too Soon!") - return; } - /* if(body.fileTime < race.startDate.getTime()) { throw new NotAcceptableException('EarlyReplay', {cause: new Error(), description: 'Replay generated before the race started.'}) console.log("Race Too Soon!") return; } - */ - let result = await this.raceResultsService.create(race.id, currentRacer.id, replay.bestTime, file.path); - console.log(result); + + await this.raceResultsService.create(race.id, currentRacer.id, replay.bestTime, file.path, file_hash); this.seasonStandingsService.updateStandings(body.seasonId); } diff --git a/packages/bridge-ui/src/app/interceptors/snackbar.interceptor.ts b/packages/bridge-ui/src/app/interceptors/snackbar.interceptor.ts index 5b0e457..fee5af7 100644 --- a/packages/bridge-ui/src/app/interceptors/snackbar.interceptor.ts +++ b/packages/bridge-ui/src/app/interceptors/snackbar.interceptor.ts @@ -35,10 +35,22 @@ export class SnackbarInterceptor implements HttpInterceptor { message = "Upload Failed: Race not open for uploads yet."; break; } + case "RaceNotFound": { + message = "Upload Failed: Replay reports to be for an race that doesn't exist"; + break; + } case "EarlyReplay": { message = "Upload Failed: Replay made before race open."; break; } + case 'AlreadyUploaded':{ + message = "Upload Rejected: Replay has already been submitted."; + break; + } + case 'GBXInvalid':{ + message = "Upload Rejected: Replay is not a valid GBX file."; + break; + } } this.snackBar.open(message, 'dismiss', { panelClass: ['errorSnack'], verticalPosition: 'top'}); return throwError(resp);