Skip to content

Commit

Permalink
Kick player (#32)
Browse files Browse the repository at this point in the history
Co-authored-by: Karol Wąsowski <wasowski02@protonmail.com>
Co-authored-by: sormys <szymonp1806@gmail.com>
  • Loading branch information
3 people authored Apr 29, 2023
1 parent e4be279 commit ae27bf9
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"scripts": {
"start": "tsc && node dist/index.js",
"build": "tsc",
"test": "jest src/tests --coverage --config package.json",
"test": "jest src/tests --coverage --config package.json ",
"lint": "eslint --ext '.js,.ts,.tsx' src/",
"pretty": "yarn prettier --write .",
"pretty-check": "yarn prettier --check src/",
Expand Down
3 changes: 3 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import express from 'express'
import { isCelebrateError } from 'celebrate'

import kickPlayer from './routes/kickPlayer'
import joinGame from './routes/joinGame'
import createGame from './routes/createGame'
import leaveGame from './routes/leaveGame'
Expand Down Expand Up @@ -47,6 +48,8 @@ app.use(joinGame)

app.use(createGame)

app.use(kickPlayer)

app.use(leaveGame)

app.use(errorHandling)
35 changes: 35 additions & 0 deletions src/firebase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import admin from 'firebase-admin'
import { readFileSync } from 'fs'

const serviceAccount = JSON.parse(
readFileSync('./src/serviceAccount.json', 'utf-8')
)

admin.initializeApp({
credential: admin.credential.cert({
privateKey: serviceAccount.private_key,
clientEmail: serviceAccount.client_email,
projectId: serviceAccount.project_id,
}),
})

export const verifyFCMToken = async (fcmToken) => {
if (process.env.JEST_WORKER_ID !== undefined) {
// We don't want to verify tokens when testing
return true
} else {
let sentSuccessfully = true
await admin
.messaging()
.send(
{
token: fcmToken,
},
true
)
.catch(() => {
sentSuccessfully = false
})
return sentSuccessfully
}
}
95 changes: 95 additions & 0 deletions src/routes/kickPlayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { getClient } from '../utils/databaseConnection'
import { rateLimiter } from '../utils/rateLimiter'
import { celebrate, Joi, Segments } from 'celebrate'
import { sendFirebaseMessage, verifyFCMToken } from '../utils/firebase'
import sha256 from 'crypto-js/sha256'

import express, { type Router } from 'express'
const router: Router = express.Router()

router.get(
'/kickPlayer',
rateLimiter,
celebrate({
[Segments.QUERY]: Joi.object().keys({
creatorToken: Joi.string()
.required()
.min(1)
.max(250)
.label('creatorToken'),
playerToken: Joi.string().required().min(1).max(250).label('playerToken'),
}),
}),
async (req, res) => {
if (
req.query.creatorToken === req.query.playerToken ||
!(await verifyFCMToken(req.query.creatorToken))
) {
return res.sendStatus(400)
}

const client = getClient()
client
.connect()
.then(async () => {
// Define queries
const getGameQuery = 'SELECT game_id FROM Games WHERE game_master=$1'
const getPlayersQuery = 'SELECT token FROM Players WHERE game_id=$1'
const deletePlayerQuery = 'DELETE FROM Players WHERE token=$1'

// Check if game exists
const getGameResult = await client.query(getGameQuery, [
req.query.creatorToken,
])

if (getGameResult.rowCount === 0) {
return res.sendStatus(400)
}
const gameId = getGameResult.rows[0].game_id

// Verify player is in game and kick
const getPlayersResult = await client.query(getPlayersQuery, [gameId])
let playerInGame = false
let kickedPlayerToken = ''

getPlayersResult.rows.forEach((row) => {
if (sha256(row.token).toString() === req.query.playerToken) {
playerInGame = true
kickedPlayerToken = row.token
}
})

if (!playerInGame) {
return res.sendStatus(400)
}

await client.query(deletePlayerQuery, [kickedPlayerToken])

// Notify players about the changes
const message = {
data: {
type: 'playerKicked',
playerHash: req.query.playerToken,
},
token: '',
}
getPlayersResult.rows.forEach(async (row) => {
message.token = row.token
await sendFirebaseMessage(message)
})

// TODO: Fix game state

return res.sendStatus(200)
})
.catch(async (err) => {
console.log(err.stack)
return res.sendStatus(500)
})
.finally(async () => {
await client.end()
})
}
)

export default router
88 changes: 88 additions & 0 deletions src/tests/kickPlayer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { app } from '../app'
import request from 'supertest'
import { getClient } from '../utils/databaseConnection'
import sha256 from 'crypto-js/sha256'
import { type newGameInfo } from '../app'

test('Kick player, wrong args', (done) => {
request(app).get('/kickPlayer').expect(400).end(done)
request(app).get('/kickPlayer?creatorToken=2137').expect(400).end(done)
request(app).get('/kickPlayer?playerToken=1337').expect(400).end(done)

request(app)
.get('/kickPlayer?playerToken=1337'.concat('&creatorToken=1337'))
.expect(400)
.end(done)
})

test('Kick player, correct arguments', async () => {
const gameMasterToken = 'TESTKICK'
const playerToken = 'TESTKICK2'
const gameMasterNick = 'NICKKICK'
const playerNick = 'NICKKICK2'

const res = await request(app)
.get(
'/createGame/?creatorToken='
.concat(gameMasterToken)
.concat('&nickname=')
.concat(gameMasterNick)
)
.expect(200)
const gameId = (res.body as newGameInfo).gameKey

// Creator exists, but the player does not
await request(app)
.get(
'/kickPlayer?creatorToken='
.concat(gameMasterToken)
.concat('&playerToken=2137')
)
.expect(400)

const verifyNoPlayerQuery = 'SELECT token FROM Players WHERE token=$1'

const client = getClient()
await client
.connect()
.then(async () => {
await request(app)
.get(
'/joinGame/?playerToken='
.concat(playerToken)
.concat('&nickname=')
.concat(playerNick)
.concat('&gameId=')
.concat(gameId.toString())
)
.expect(200)

await request(app)
.get(
'/kickPlayer?'.concat(
'creatorToken='
.concat(gameMasterToken)
.concat('&playerToken='.concat(sha256(playerToken).toString()))
)
)
.expect(200)

await client.query(verifyNoPlayerQuery, [playerToken]).then((res) => {
expect(res.rowCount).toEqual(0)
})
})
.finally(async () => {
const deleteGameQuery = 'DELETE FROM Games WHERE game_master = $1'
await client.query(deleteGameQuery, [gameMasterToken]).catch((err) => {
console.log(err.stack)
})
const deletePlayerQuery =
'DELETE FROM Players WHERE token = $1 or token = $2'
await client
.query(deletePlayerQuery, [playerToken, gameMasterToken])
.catch((err) => {
console.log(err.stack)
})
await client.end()
})
}, 20000)

0 comments on commit ae27bf9

Please sign in to comment.