From 9284cce0ed90586150503ebb7df645cf20125afd Mon Sep 17 00:00:00 2001 From: legonzaur Date: Wed, 19 Apr 2023 10:23:03 +0200 Subject: [PATCH] Non-working breaking changes --- src/db/db.ts | 55 +++++------- .../2023-04-09-02 server auth patch.ts | 3 + src/db/model.ts | 8 +- src/process/onKill.ts | 34 +++---- src/process/process.ts | 59 ++++++------ src/server/register.ts | 90 ------------------- src/server/server.ts | 57 ++++++------ tests/client.test.ts | 6 +- tests/realtime.test.ts | 5 +- tests/server.test.ts | 15 ++-- 10 files changed, 124 insertions(+), 208 deletions(-) delete mode 100644 src/server/register.ts diff --git a/src/db/db.ts b/src/db/db.ts index c669b8f..5bee2f5 100644 --- a/src/db/db.ts +++ b/src/db/db.ts @@ -41,43 +41,36 @@ export async function CreateKillRecord(data: KillRecord) { .execute() } -export async function FindServer({ name }: { name: string }) { - return await db - .selectFrom('server') - .select(['server.name', 'server.description']) - .where('server.name', '=', name) - .executeTakeFirst() -} +// export async function FindServer({ name }: { name: string }) { +// return await db +// .selectFrom('server') +// .select(['server.name', 'server.description']) +// .where('server.name', '=', name) +// .executeTakeFirst() +// } -export async function CreateServer({ - name, - description -}: { - name: string - description: string -}) { - return await db - .insertInto('server') - .values({ name, description }) - .returning(['id', 'token']) - .executeTakeFirstOrThrow() -} +// export async function CreateServer({ +// name, +// description +// }: { +// name: string +// description: string +// }) { +// return await db +// .insertInto('server') +// .values({ name, description }) +// .returning(['id', 'token']) +// .executeTakeFirstOrThrow() +// } //tokens are stored in raw... maybe we should use something better in the future //Using callback for express-basic-auth -export function CheckServerToken( - this: { params: any, body: any }, - name: string, - password: string, - cb: (error: Error | null, success: boolean) => void -) { - db.selectFrom('server') - .where('id', '=', Number(name)) - .where('token', '=', password) +export function CheckServerToken(token: string) { + return db.selectFrom('hoster').select('id') + .where('token', '=', Buffer.from(token, 'base64').toString()) .executeTakeFirst() .then((result) => { - if (name != this.params.serverId) return cb(null, false) - return cb(null, !!result) + return result }) } /* diff --git a/src/db/migrations/2023-04-09-02 server auth patch.ts b/src/db/migrations/2023-04-09-02 server auth patch.ts index c62ddf1..41038cd 100644 --- a/src/db/migrations/2023-04-09-02 server auth patch.ts +++ b/src/db/migrations/2023-04-09-02 server auth patch.ts @@ -17,9 +17,12 @@ export async function up(db: Kysely): Promise { await pgClient.query('ALTER TABLE server RENAME TO hoster') await pgClient.query('ALTER TABLE hoster DROP COLUMN description') + await pgClient.query('ALTER TABLE kill ADD COLUMN host integer NULL') + //Update the hosts column once the hoster table is manually updated } export async function down(db: Kysely): Promise { + await pgClient.query('ALTER TABLE kill DROP COLUMN host') await pgClient.query('ALTER TABLE hoster add description character varying NULL') await pgClient.query('ALTER TABLE hoster RENAME TO server') diff --git a/src/db/model.ts b/src/db/model.ts index d9d1fba..47a74c0 100644 --- a/src/db/model.ts +++ b/src/db/model.ts @@ -2,7 +2,8 @@ import { ColumnType, Generated } from 'kysely' export interface KillTable { id: Generated - server: number + servername: string + host: number killstat_version: string match_id: string game_mode: string @@ -56,10 +57,9 @@ interface MapTable { description: string image: string } -interface ServerTable { +interface HosterTable { id: Generated name: string - description: string token: Generated } interface Database { @@ -67,7 +67,7 @@ interface Database { player: PlayerTable weapon: WeaponTable maps: MapTable - server: ServerTable + hoster: HosterTable } export default Database diff --git a/src/process/onKill.ts b/src/process/onKill.ts index b6e7dea..8d51722 100644 --- a/src/process/onKill.ts +++ b/src/process/onKill.ts @@ -22,44 +22,46 @@ export default async function listenKills() { } -async function updateGlobal({ cause_of_death, distance, attacker_id, victim_id, server }: { cause_of_death: string, distance: number, attacker_id: string, victim_id: string, server: number }) { +async function updateGlobal({ cause_of_death, distance, attacker_id, victim_id, servername, host }: { cause_of_death: string, distance: number, attacker_id: string, victim_id: string, servername: string, host: number }) { + servername = servername.replace(/[^a-z0-9]/gi, '') const promises: Promise[] = [] if (!await client.json.type('kills')) await client.json.set('kills', '', { data: {}, weapons: {}, servers: {}, players: {} }) if (!await client.json.type('kills', genPrefix.weapon({ cause_of_death }))) await client.json.set('kills', genPrefix.weapon({ cause_of_death }), { players: {}, max_distance: 0, kills: 0, total_distance: 0 }) if (!await client.json.type('kills', genPrefix.player({ attacker_id }))) await client.json.set('kills', genPrefix.player({ attacker_id }), { weapons: {}, max_distance: 0, kills: 0, deaths: 0, total_distance: 0 }) - if (!await client.json.type('kills', `servers.${server}`)) await client.json.set('kills', `servers.${server}`, { data: {}, weapons: {}, players: {}, max_distance: 0, kills: 0, total_distance: 0 }) - if (!await client.json.type('kills', genPrefix.weapon({ cause_of_death, server }))) await client.json.set('kills', genPrefix.weapon({ cause_of_death, server }), { players: {}, max_distance: 0, kills: 0, total_distance: 0 }) - if (!await client.json.type('kills', genPrefix.player({ attacker_id, server }))) await client.json.set('kills', genPrefix.player({ attacker_id, server }), { weapons: {}, max_distance: 0, kills: 0, deaths: 0, total_distance: 0 }) + if (!await client.json.type('kills', `servers.${host}`)) await client.json.set('kills', `servers.${host}`, {}) + if (!await client.json.type('kills', `servers.${host}.${servername}`)) await client.json.set('kills', `servers.${host}.${servername}`, { data: {}, weapons: {}, players: {}, max_distance: 0, kills: 0, total_distance: 0 }) + if (!await client.json.type('kills', genPrefix.weapon({ cause_of_death, server: `${host}.${servername}` }))) await client.json.set('kills', genPrefix.weapon({ cause_of_death, server: `${host}.${servername}` }), { players: {}, max_distance: 0, kills: 0, total_distance: 0 }) + if (!await client.json.type('kills', genPrefix.player({ attacker_id, server: `${host}.${servername}` }))) await client.json.set('kills', genPrefix.player({ attacker_id, server: `${host}.${servername}` }), { weapons: {}, max_distance: 0, kills: 0, deaths: 0, total_distance: 0 }) - if (!await client.json.type('kills', genPrefix.player({ attacker_id: victim_id, server }))) await client.json.set('kills', genPrefix.player({ attacker_id: victim_id, server }), { weapons: {}, max_distance: 0, kills: 0, deaths: 0, total_distance: 0 }) + if (!await client.json.type('kills', genPrefix.player({ attacker_id: victim_id, server: `${host}.${servername}` }))) await client.json.set('kills', genPrefix.player({ attacker_id: victim_id, server: `${host}.${servername}` }), { weapons: {}, max_distance: 0, kills: 0, deaths: 0, total_distance: 0 }) if (!await client.json.type('kills', genPrefix.player({ attacker_id: victim_id }))) await client.json.set('kills', genPrefix.player({ attacker_id: victim_id }), { weapons: {}, max_distance: 0, kills: 0, deaths: 0, total_distance: 0 }) if (!await client.json.type('kills', genPrefix.weaponPlayers({ attacker_id: victim_id, cause_of_death }))) await client.json.set('kills', genPrefix.weaponPlayers({ attacker_id: victim_id, cause_of_death }), { max_distance: 0, kills: 0, deaths: 0, total_distance: 0 }) if (!await client.json.type('kills', genPrefix.playerWeapons({ attacker_id: victim_id, cause_of_death }))) await client.json.set('kills', genPrefix.playerWeapons({ attacker_id: victim_id, cause_of_death }), { max_distance: 0, kills: 0, total_distance: 0 }) - if (!await client.json.type('kills', genPrefix.weaponPlayers({ attacker_id: victim_id, cause_of_death, server }))) await client.json.set('kills', genPrefix.weaponPlayers({ attacker_id: victim_id, cause_of_death, server }), { max_distance: 0, kills: 0, deaths: 0, total_distance: 0 }) - if (!await client.json.type('kills', genPrefix.playerWeapons({ attacker_id: victim_id, cause_of_death, server }))) await client.json.set('kills', genPrefix.playerWeapons({ attacker_id: victim_id, cause_of_death, server }), { max_distance: 0, kills: 0, total_distance: 0 }) + if (!await client.json.type('kills', genPrefix.weaponPlayers({ attacker_id: victim_id, cause_of_death, server: `${host}.${servername}` }))) await client.json.set('kills', genPrefix.weaponPlayers({ attacker_id: victim_id, cause_of_death, server: `${host}.${servername}` }), { max_distance: 0, kills: 0, deaths: 0, total_distance: 0 }) + if (!await client.json.type('kills', genPrefix.playerWeapons({ attacker_id: victim_id, cause_of_death, server: `${host}.${servername}` }))) await client.json.set('kills', genPrefix.playerWeapons({ attacker_id: victim_id, cause_of_death, server: `${host}.${servername}` }), { max_distance: 0, kills: 0, total_distance: 0 }) promises.push(updatePath(`data`, { distance })) //servers.1 - promises.push(updatePath(`servers.${server}`, { distance }).then(() => Promise.all([ + promises.push(updatePath(`servers.${host}.${servername}`, { distance }).then(() => Promise.all([ //servers.1.players.123456789 - updatePath(`servers.${server}.players.${attacker_id}`, { distance }).then(async () => { - if (!await client.json.type('kills', `servers.${server}.players.${victim_id}.deaths`)) await client.json.set('kills', `servers.${server}.players.${victim_id}.deaths`, 0) - await client.json.numIncrBy('kills', `servers.${server}.players.${victim_id}.deaths`, 1) + updatePath(`servers.${host}.${servername}.players.${attacker_id}`, { distance }).then(async () => { + if (!await client.json.type('kills', `servers.${host}.${servername}.players.${victim_id}.deaths`)) await client.json.set('kills', `servers.${host}.${servername}.players.${victim_id}.deaths`, 0) + await client.json.numIncrBy('kills', `servers.${host}.${servername}.players.${victim_id}.deaths`, 1) //servers.1.players.123456789.weapons.epg - await updatePath(`servers.${server}.players.${attacker_id}.weapons.${cause_of_death}`, { distance }) + await updatePath(`servers.${host}.${servername}.players.${attacker_id}.weapons.${cause_of_death}`, { distance }) } ), //servers.1.weapons.epg - updatePath(`servers.${server}.weapons.${cause_of_death}`, { distance }).then(async () => { + updatePath(`servers.${host}.${servername}.weapons.${cause_of_death}`, { distance }).then(async () => { //servers.1.weapons.epg.players.123456789 - await updatePath(`servers.${server}.weapons.${cause_of_death}.players.${attacker_id}`, { distance }) - if (!await client.json.type('kills', `servers.${server}.players.${victim_id}.deaths`)) await client.json.set('kills', `servers.${server}.weapons.${cause_of_death}.players.${victim_id}`, 0) - await client.json.numIncrBy('kills', `servers.${server}.weapons.${cause_of_death}.players.${victim_id}.deaths`, 1) + await updatePath(`servers.${host}.${servername}.weapons.${cause_of_death}.players.${attacker_id}`, { distance }) + if (!await client.json.type('kills', `servers.${host}.${servername}.players.${victim_id}.deaths`)) await client.json.set('kills', `servers.${host}.${servername}.weapons.${cause_of_death}.players.${victim_id}`, 0) + await client.json.numIncrBy('kills', `servers.${host}.${servername}.weapons.${cause_of_death}.players.${victim_id}.deaths`, 1) } )]) diff --git a/src/process/process.ts b/src/process/process.ts index 35c3ed3..4b4440c 100644 --- a/src/process/process.ts +++ b/src/process/process.ts @@ -3,19 +3,19 @@ const { count, max, sum } = db.fn import client from '../cache/redis' export const genPrefix = { - global: ({ server }: { server?: number }) => { + global: ({ server }: { server?: string }) => { return (server ? `servers.${server}.` : '') + 'data' }, - weapon: ({ cause_of_death, server }: { cause_of_death: string, server?: number }) => { + weapon: ({ cause_of_death, server }: { cause_of_death: string, server?: string }) => { return (server ? `servers.${server}.` : '') + `weapons.${cause_of_death}` }, - player: ({ attacker_id, server }: { attacker_id: string, server?: number }) => { + player: ({ attacker_id, server }: { attacker_id: string, server?: string }) => { return (server ? `servers.${server}.` : '') + `players.${attacker_id}` }, - playerWeapons: ({ cause_of_death, attacker_id, server }: { attacker_id: string, cause_of_death: string, server?: number }) => { + playerWeapons: ({ cause_of_death, attacker_id, server }: { attacker_id: string, cause_of_death: string, server?: string }) => { return (server ? `servers.${server}.` : '') + `players.${attacker_id}.weapons.${cause_of_death}` }, - weaponPlayers: ({ cause_of_death, attacker_id, server }: { attacker_id: string, cause_of_death: string, server?: number }) => { + weaponPlayers: ({ cause_of_death, attacker_id, server }: { attacker_id: string, cause_of_death: string, server?: string }) => { return (server ? `servers.${server}.` : '') + `weapons.${cause_of_death}.players.${attacker_id}` }, } @@ -112,12 +112,14 @@ async function processServerStats() { let promises: Promise[] = [] //Server global kills await db.selectFrom('kill') - .select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance'), 'server']) - .groupBy(['server']).execute().then(async (data) => { + .select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance'), 'servername', 'host']) + .groupBy(['servername', 'host']).execute().then(async (data) => { let transaction = client.multi() - const p = data.map(async ({ kills, max_distance, total_distance, server }) => { - if (!await client.json.type('kills', `servers.${server}`)) await client.json.set('kills', `servers.${server}`, { data: {}, weapons: {}, players: {} }) - await processData(genPrefix.global({ server }), { total_distance, max_distance, kills }, transaction) + const p = data.map(async ({ kills, max_distance, total_distance, servername, host }) => { + servername = servername.replace(/[^a-z0-9]/gi, '') + if (!await client.json.type('kills', `servers.${host}`)) await client.json.set('kills', `servers.${host}`, {}) + if (!await client.json.type('kills', `servers.${host}.${servername}`)) await client.json.set('kills', `servers.${host}.${servername}`, { data: {}, weapons: {}, players: {} }) + await processData(genPrefix.global({ server: `${host}.${servername}` }), { total_distance, max_distance, kills }, transaction) }) await Promise.all(p) return transaction.exec() @@ -125,53 +127,58 @@ async function processServerStats() { //Server weapon kills await db.selectFrom('kill') - .select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance'), 'cause_of_death', 'server']).groupBy(['cause_of_death', 'server']).execute().then(async (data) => { + .select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance'), 'cause_of_death', 'servername', 'host']).groupBy(['cause_of_death', 'servername', 'host']).execute().then(async (data) => { let transaction = client.multi() - await Promise.all(data.map(async ({ kills, max_distance, total_distance, cause_of_death, server }) => { - const prefix = genPrefix.weapon({ cause_of_death, server }) + await Promise.all(data.map(async ({ kills, max_distance, total_distance, cause_of_death, servername, host }) => { + servername = servername.replace(/[^a-z0-9]/gi, '') + const prefix = genPrefix.weapon({ cause_of_death, server: `${host}.${servername}` }) if (!await client.json.type('kills', prefix)) await client.json.set('kills', prefix, { players: {} }) await processData(prefix, { kills, max_distance, total_distance }, transaction) })) return transaction.exec() }) //server player kill - await db.selectFrom('kill').select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance'), 'attacker_id', 'server']).groupBy(['attacker_id', 'server']).whereRef("attacker_id", '!=', 'victim_id').execute() + await db.selectFrom('kill').select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance'), 'attacker_id', 'servername', 'host']).groupBy(['attacker_id', 'servername', 'host']).whereRef("attacker_id", '!=', 'victim_id').execute() .then(async (data) => { let transaction = client.multi() - await Promise.all(data.map(async ({ kills, max_distance, total_distance, attacker_id, server }) => { - const prefix = genPrefix.player({ attacker_id, server }) + await Promise.all(data.map(async ({ kills, max_distance, total_distance, attacker_id, servername, host }) => { + servername = servername.replace(/[^a-z0-9]/gi, '') + const prefix = genPrefix.player({ attacker_id, server: `${host}.${servername}` }) if (!await client.json.type('kills', prefix)) await client.json.set('kills', prefix, { weapons: {} }) await processData(prefix, { kills, max_distance, total_distance }, transaction) })) return transaction.exec() }) //server player deaths - await db.selectFrom('kill').select([count('id').as('kills'), 'victim_id', 'server']).groupBy(['victim_id', 'server']).execute() + await db.selectFrom('kill').select([count('id').as('kills'), 'victim_id', 'servername', 'host']).groupBy(['victim_id', 'servername', 'host']).execute() .then(async (data) => { let transaction = client.multi() - await Promise.all(data.map(async ({ kills, victim_id, server }) => { - const prefix = genPrefix.player({ attacker_id: victim_id, server }) + await Promise.all(data.map(async ({ kills, victim_id, servername, host }) => { + servername = servername.replace(/[^a-z0-9]/gi, '') + const prefix = genPrefix.player({ attacker_id: victim_id, server: `${host}.${servername}` }) transaction.json.set('kills', prefix + ".deaths", Number(kills)) })) return transaction.exec() }) //player weapon kills - await db.selectFrom('kill').select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance'), 'attacker_id', 'cause_of_death', 'server']).groupBy(['attacker_id', 'cause_of_death', 'server']).whereRef("attacker_id", '!=', 'victim_id').execute() + await db.selectFrom('kill').select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance'), 'attacker_id', 'cause_of_death', 'servername', 'host']).groupBy(['attacker_id', 'cause_of_death', 'servername', 'host']).whereRef("attacker_id", '!=', 'victim_id').execute() .then(async (data) => { let transaction = client.multi() - await Promise.all(data.map(async ({ kills, max_distance, total_distance, attacker_id, cause_of_death, server }) => { - await processData(genPrefix.playerWeapons({ attacker_id, cause_of_death, server }), { kills, max_distance, total_distance }, transaction) - await processData(genPrefix.weaponPlayers({ attacker_id, cause_of_death, server }), { kills, max_distance, total_distance }, transaction) + await Promise.all(data.map(async ({ kills, max_distance, total_distance, attacker_id, cause_of_death, servername, host }) => { + servername = servername.replace(/[^a-z0-9]/gi, '') + await processData(genPrefix.playerWeapons({ attacker_id, cause_of_death, server: `${host}.${servername}` }), { kills, max_distance, total_distance }, transaction) + await processData(genPrefix.weaponPlayers({ attacker_id, cause_of_death, server: `${host}.${servername}` }), { kills, max_distance, total_distance }, transaction) })) return transaction.exec() }) //player weapon deaths - await db.selectFrom('kill').select([count('id').as('kills'), 'victim_id', 'cause_of_death', 'server']).groupBy(['victim_id', 'cause_of_death', 'server']).execute() + await db.selectFrom('kill').select([count('id').as('kills'), 'victim_id', 'cause_of_death', 'servername', 'host']).groupBy(['victim_id', 'cause_of_death', 'servername', 'host']).execute() .then(async (data) => { let transaction = client.multi() - await Promise.all(data.map(async ({ kills, victim_id, cause_of_death, server }) => { - transaction.json.set('kills', genPrefix.weaponPlayers({ attacker_id: victim_id, cause_of_death, server }) + ".deaths", Number(kills)) + await Promise.all(data.map(async ({ kills, victim_id, cause_of_death, servername, host }) => { + servername = servername.replace(/[^a-z0-9]/gi, '') + transaction.json.set('kills', genPrefix.weaponPlayers({ attacker_id: victim_id, cause_of_death, server: `${host}.${servername}` }) + ".deaths", Number(kills)) })) return transaction.exec() }) diff --git a/src/server/register.ts b/src/server/register.ts deleted file mode 100644 index d57f994..0000000 --- a/src/server/register.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Router } from 'express' -import { GetRequest, validateErrors } from '../common' -import { FindServer, CreateServer } from '../db/db' -import { body, validationResult } from 'express-validator' - -const verificationString = 'I am a northstar server!' -const masterServerURL = 'https://northstar.tf' - -const router = Router() - -const hostsCount: { [id: string]: number } = {} -const hostsTimeout: { [id: string]: NodeJS.Timeout } = {} - -//Very simple rate limiting. max 2 registers per IP every 5 mins. Maybe 2 is a bit few ? -router.post('/register', (req, res, next) => { - let ip = - req.header('x-forwarded-for') || req.socket.remoteAddress || 'undefined' - if (hostsCount[ip] > 2) { - return res.status(429).json({ - error: - 'too many requests. Please wait 5 minutes before requesting a register again.' - }) - } - clearTimeout(hostsTimeout[ip]) - hostsCount[ip] = hostsCount[ip] ?? (hostsCount[ip] + 1) | 1 - hostsTimeout[ip] = setTimeout(() => { - hostsCount[ip] = 0 - }, 300000) - - next() -}) - -router.post( - '/register', - body(['name', 'description']).isString().withMessage('must be strings'), - body('auth_port') - .toInt() - .isInt({ min: 1, max: 65535 }) - .withMessage('must be between 1 and 65535'), - validateErrors, - async (req, res) => { - try { - //Check if server name isn't already in database - if (!!(await FindServer({ name: req.body.name }))) { - return res - .status(403) - .json({ error: 'Server already exists in the database' }) - } - - //Check if server is in masterserver's list - const masterServerList = JSON.parse( - await GetRequest(masterServerURL + '/client/servers') - ) as Array - if (!masterServerList.find((e) => e.name == req.body.name)) { - return res - .status(403) - .json({ error: 'Server not listed in masterserver' }) - } - - //Send request to verify server. Not very useful for now, but maybe a future method for auth ? - //Maybe should set a blacklist here for local domain ? - const endpoint = - 'http://' + - (req.header('x-forwarded-for') || req.socket.remoteAddress) + - ':' + - req.body.auth_port + - '/verify' - if ((await GetRequest(endpoint)) != verificationString) { - return res.status(400).json({ - error: "Couldn't reach gameserver at " + endpoint - }) - } - - //send token here - res.status(201).json( - await CreateServer({ - name: req.body.name, - description: req.body.description - }) - ) - } catch (e) { - console.log(e) - return res.status(400).json({ - error: "Server encountered an error, Couldn't register gameserver." - }) - } - } -) - -export default router diff --git a/src/server/server.ts b/src/server/server.ts index 1e5787e..afe4886 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -1,37 +1,32 @@ import { NextFunction, Router } from 'express' import expressBasicAuth from 'express-basic-auth' import { body, header, param } from 'express-validator' -import register from './register' import { CreateKillRecord, CheckServerToken } from '../db/db' import { validateErrors } from '../common' const router = Router() -router.use('/', register) - //auth middleware router.post( - '/:serverId*', + '/*', header('authorization') .exists({ checkFalsy: true }) .withMessage('Missing Authorization Header') .bail() - .contains('Basic') - .withMessage('Authorization Token is not Basic'), + .contains('Bearer') + .withMessage('Authorization Token is not Bearer'), validateErrors, - //Huge mess to retrieve server id from expressBasicAuth. We probably should fix it. - (req, res, next) => { - if (!req) res.sendStatus(500) - return expressBasicAuth({ - authorizeAsync: true, - authorizer: CheckServerToken.bind(req), - unauthorizedResponse: { error: 'invalid credentials' } - })(req as any, res, next) + async (req, res, next) => { + if (!req) return res.sendStatus(500) + if (!req.headers.authorization) return res.sendStatus(403) + const query = await CheckServerToken(req.headers.authorization.split(' ')[1]) + if (!query || !query.id) return res.sendStatus(403) + next() } ) //Route to check auth -router.post('/:serverId', (req, res) => { +router.post('/', (req, res) => { res.sendStatus(200) }) @@ -39,8 +34,8 @@ const serversCount: { [id: string]: number } = {} const serversTimeout: { [id: string]: NodeJS.Timeout } = {} //same rate limiting code as register. max 10 kills per server every 1 sec. should be enough. -router.post('/:serverId/kill', (req, res, next) => { - let serverId = Number(req.query.serverId) +/*router.post('/kill', (req, res, next) => { + let host = Number(req.query.serverId) if (serversCount[serverId] > 2) { return res.status(429).json({ error: 'too many requests. Are players really making that much kills ?' @@ -53,11 +48,10 @@ router.post('/:serverId/kill', (req, res, next) => { serversCount[serverId] = 0 }, 1000) next() -}) +})*/ router.post( - '/:serverId/kill', - param('serverId').exists().toInt().isInt(), + '/kill', body([ 'attacker_current_weapon_mods', 'attacker_weapon_1_mods', @@ -104,6 +98,7 @@ router.post( .withMessage('must be a valid float'), body( [ + 'servername', 'attacker_id', 'victim_id', 'killstat_version', @@ -131,9 +126,15 @@ router.post( min: 0 }), body(['cause_of_death', 'victim_id'], 'mandatory').exists().notEmpty(), + body('servername').customSanitizer(e => e.replace(/[^a-z0-9]/gi, '')), validateErrors, - (req, res) => { + async (req, res) => { + if (!req.headers.authorization) return res.sendStatus(403) + const query = (await CheckServerToken(req.headers.authorization.split(' ')[1])) + if (!query) return + const host = query.id const { + servername, killstat_version, match_id, game_mode, @@ -167,13 +168,10 @@ router.post( cause_of_death, distance } = req.body - if (!req.params.serverId) { - res.status(500).send('serverId cannot be undefined') - return - } CreateKillRecord({ killstat_version, - server: Number(req.params.serverId), + servername, + host, match_id, game_mode, map, @@ -209,16 +207,15 @@ router.post( .then((e) => { res.sendStatus(201) console.log( - `[${Date.now().toLocaleString()}] Kill submitted for server ${ - req.params.serverId - }, ${attacker_name} killed ${victim_name}` + `[${Date.now().toLocaleString()}] Kill submitted for server ${servername}, ${attacker_name} killed ${victim_name}` ) }) .catch((e) => { res.sendStatus(500) console.log({ killstat_version, - server: Number(req.params.serverId), + servername, + host, match_id, game_mode, map, diff --git a/tests/client.test.ts b/tests/client.test.ts index 51f6a27..6f04e57 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -16,9 +16,9 @@ describe('client', () => { const request = await fetch("http://127.0.0.1:3000/servers") const data = await request.json() const first = Object.entries(data)[0] - expect(first).toHaveProperty('name') - expect(first).toHaveProperty('id') - expect(first).toHaveProperty('description') + expect(first[1]).toHaveProperty('name') + expect(first[1]).toHaveProperty('id') + expect(first[1]).toHaveProperty('description') }) test('player list', async () => { diff --git a/tests/realtime.test.ts b/tests/realtime.test.ts index 8d71513..cc8e98b 100644 --- a/tests/realtime.test.ts +++ b/tests/realtime.test.ts @@ -14,6 +14,7 @@ let pgClient const data = { attacker_weapon_1_mods: 0, victim_id: '0', + servername: 'testServer', victim_name: 'TestVictim', victim_offhand_weapon_2: 0, attacker_offhand_weapon_3: '0', @@ -67,7 +68,7 @@ beforeAll(async () => { credentials: "same-origin", // include, *same-origin, omit headers: { "Content-Type": "application/json", - 'Authorization': `Basic ${Buffer.from(process.env.SERVERAUTH_ID + ':' + process.env.SERVERAUTH_TOKEN).toString('base64')}` + 'Authorization': `Bearer ${Buffer.from('' + process.env.SERVERAUTH_TOKEN).toString('base64')}` }, body: JSON.stringify(data), // body data type must match "Content-Type" header }); @@ -108,7 +109,7 @@ describe('realtime', () => { credentials: "same-origin", // include, *same-origin, omit headers: { "Content-Type": "application/json", - 'Authorization': `Basic ${Buffer.from(process.env.SERVERAUTH_ID + ':' + process.env.SERVERAUTH_TOKEN).toString('base64')}` + 'Authorization': `Bearer ${Buffer.from('' + process.env.SERVERAUTH_TOKEN).toString('base64')}` }, body: JSON.stringify(data), // body data type must match "Content-Type" header }); diff --git a/tests/server.test.ts b/tests/server.test.ts index e71e44e..7c534f6 100644 --- a/tests/server.test.ts +++ b/tests/server.test.ts @@ -12,12 +12,12 @@ beforeAll(async () => { describe('server', () => { test('server auth prefetch', async () => { - const response = await fetch(`http://127.0.0.1:3001/${process.env.SERVERAUTH_ID}`, { + const response = await fetch(`http://127.0.0.1:3001/`, { method: "POST", // *GET, POST, PUT, DELETE, etc. credentials: "same-origin", // include, *same-origin, omit headers: { "Content-Type": "application/json", - 'Authorization': `Basic ${Buffer.from(process.env.SERVERAUTH_ID + ':' + process.env.SERVERAUTH_TOKEN).toString('base64')}` + 'Authorization': `Bearer ${Buffer.from('' + process.env.SERVERAUTH_TOKEN).toString('base64')}` } }); expect(response.status).toBe(200) @@ -25,6 +25,7 @@ describe('server', () => { test('register a kill', async () => { const data = { + servername: 'testserver', attacker_weapon_1_mods: 0, victim_id: '0', victim_name: 'TestVictim', @@ -60,21 +61,23 @@ describe('server', () => { victim_weapon_3: 'defender', attacker_name: 'TestAttacker' } - const response = await fetch(`http://127.0.0.1:3001/${process.env.SERVERAUTH_ID}/kill`, { + const response = await fetch(`http://127.0.0.1:3001/kill`, { method: "POST", // *GET, POST, PUT, DELETE, etc. credentials: "same-origin", // include, *same-origin, omit headers: { "Content-Type": "application/json", - 'Authorization': `Basic ${Buffer.from(process.env.SERVERAUTH_ID + ':' + process.env.SERVERAUTH_TOKEN).toString('base64')}` + 'Authorization': `Bearer ${Buffer.from(process.env.SERVERAUTH_TOKEN + '').toString('base64')}` }, body: JSON.stringify(data), // body data type must match "Content-Type" header }); + console.log(await response.text()) expect(response.status).toBe(201) }) test('register a kill with missing data', async () => { const data = { + servername: 'testserver', attacker_weapon_1_mods: NaN, victim_id: '0', victim_name: 'TestVictim', @@ -110,12 +113,12 @@ describe('server', () => { victim_weapon_3: 'arc_launcher', attacker_name: 'TestAttacker' } - const response = await fetch(`http://127.0.0.1:3001/${process.env.SERVERAUTH_ID}/kill`, { + const response = await fetch(`http://127.0.0.1:3001/kill`, { method: "POST", // *GET, POST, PUT, DELETE, etc. credentials: "same-origin", // include, *same-origin, omit headers: { "Content-Type": "application/json", - 'Authorization': `Basic ${Buffer.from(process.env.SERVERAUTH_ID + ':' + process.env.SERVERAUTH_TOKEN).toString('base64')}` + 'Authorization': `Bearer ${Buffer.from(process.env.SERVERAUTH_TOKEN + '').toString('base64')}` }, body: JSON.stringify(data), // body data type must match "Content-Type" header });