Non-working breaking changes

This commit is contained in:
2023-04-19 10:23:03 +02:00
parent 1fb4ff26c4
commit 9284cce0ed
10 changed files with 124 additions and 208 deletions
+24 -31
View File
@@ -41,43 +41,36 @@ export async function CreateKillRecord(data: KillRecord) {
.execute() .execute()
} }
export async function FindServer({ name }: { name: string }) { // export async function FindServer({ name }: { name: string }) {
return await db // return await db
.selectFrom('server') // .selectFrom('server')
.select(['server.name', 'server.description']) // .select(['server.name', 'server.description'])
.where('server.name', '=', name) // .where('server.name', '=', name)
.executeTakeFirst() // .executeTakeFirst()
} // }
export async function CreateServer({ // export async function CreateServer({
name, // name,
description // description
}: { // }: {
name: string // name: string
description: string // description: string
}) { // }) {
return await db // return await db
.insertInto('server') // .insertInto('server')
.values({ name, description }) // .values({ name, description })
.returning(['id', 'token']) // .returning(['id', 'token'])
.executeTakeFirstOrThrow() // .executeTakeFirstOrThrow()
} // }
//tokens are stored in raw... maybe we should use something better in the future //tokens are stored in raw... maybe we should use something better in the future
//Using callback for express-basic-auth //Using callback for express-basic-auth
export function CheckServerToken( export function CheckServerToken(token: string) {
this: { params: any, body: any }, return db.selectFrom('hoster').select('id')
name: string, .where('token', '=', Buffer.from(token, 'base64').toString())
password: string,
cb: (error: Error | null, success: boolean) => void
) {
db.selectFrom('server')
.where('id', '=', Number(name))
.where('token', '=', password)
.executeTakeFirst() .executeTakeFirst()
.then((result) => { .then((result) => {
if (name != this.params.serverId) return cb(null, false) return result
return cb(null, !!result)
}) })
} }
/* /*
@@ -17,9 +17,12 @@ export async function up(db: Kysely<any>): Promise<void> {
await pgClient.query('ALTER TABLE server RENAME TO hoster') await pgClient.query('ALTER TABLE server RENAME TO hoster')
await pgClient.query('ALTER TABLE hoster DROP COLUMN description') 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<any>): Promise<void> { export async function down(db: Kysely<any>): Promise<void> {
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 add description character varying NULL')
await pgClient.query('ALTER TABLE hoster RENAME TO server') await pgClient.query('ALTER TABLE hoster RENAME TO server')
+4 -4
View File
@@ -2,7 +2,8 @@ import { ColumnType, Generated } from 'kysely'
export interface KillTable { export interface KillTable {
id: Generated<number> id: Generated<number>
server: number servername: string
host: number
killstat_version: string killstat_version: string
match_id: string match_id: string
game_mode: string game_mode: string
@@ -56,10 +57,9 @@ interface MapTable {
description: string description: string
image: string image: string
} }
interface ServerTable { interface HosterTable {
id: Generated<number> id: Generated<number>
name: string name: string
description: string
token: Generated<string> token: Generated<string>
} }
interface Database { interface Database {
@@ -67,7 +67,7 @@ interface Database {
player: PlayerTable player: PlayerTable
weapon: WeaponTable weapon: WeaponTable
maps: MapTable maps: MapTable
server: ServerTable hoster: HosterTable
} }
export default Database export default Database
+18 -16
View File
@@ -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<any>[] = [] const promises: Promise<any>[] = []
if (!await client.json.type('kills')) await client.json.set('kills', '', { data: {}, weapons: {}, servers: {}, players: {} }) 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.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', 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', `servers.${host}`)) await client.json.set('kills', `servers.${host}`, {})
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', `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.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', 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.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.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.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.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 }))) 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.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 })) promises.push(updatePath(`data`, { distance }))
//servers.1 //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 //servers.1.players.123456789
updatePath(`servers.${server}.players.${attacker_id}`, { distance }).then(async () => { updatePath(`servers.${host}.${servername}.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) 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.${server}.players.${victim_id}.deaths`, 1) await client.json.numIncrBy('kills', `servers.${host}.${servername}.players.${victim_id}.deaths`, 1)
//servers.1.players.123456789.weapons.epg //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 //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 //servers.1.weapons.epg.players.123456789
await updatePath(`servers.${server}.weapons.${cause_of_death}.players.${attacker_id}`, { distance }) await updatePath(`servers.${host}.${servername}.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) 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.${server}.weapons.${cause_of_death}.players.${victim_id}.deaths`, 1) await client.json.numIncrBy('kills', `servers.${host}.${servername}.weapons.${cause_of_death}.players.${victim_id}.deaths`, 1)
} }
)]) )])
+33 -26
View File
@@ -3,19 +3,19 @@ const { count, max, sum } = db.fn
import client from '../cache/redis' import client from '../cache/redis'
export const genPrefix = { export const genPrefix = {
global: ({ server }: { server?: number }) => { global: ({ server }: { server?: string }) => {
return (server ? `servers.${server}.` : '') + 'data' 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}` 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}` 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}` 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}` return (server ? `servers.${server}.` : '') + `weapons.${cause_of_death}.players.${attacker_id}`
}, },
} }
@@ -112,12 +112,14 @@ async function processServerStats() {
let promises: Promise<any>[] = [] let promises: Promise<any>[] = []
//Server global kills //Server global kills
await db.selectFrom('kill') await db.selectFrom('kill')
.select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance'), 'server']) .select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance'), 'servername', 'host'])
.groupBy(['server']).execute().then(async (data) => { .groupBy(['servername', 'host']).execute().then(async (data) => {
let transaction = client.multi() let transaction = client.multi()
const p = data.map(async ({ kills, max_distance, total_distance, server }) => { const p = data.map(async ({ kills, max_distance, total_distance, servername, host }) => {
if (!await client.json.type('kills', `servers.${server}`)) await client.json.set('kills', `servers.${server}`, { data: {}, weapons: {}, players: {} }) servername = servername.replace(/[^a-z0-9]/gi, '')
await processData(genPrefix.global({ server }), { total_distance, max_distance, kills }, transaction) 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) await Promise.all(p)
return transaction.exec() return transaction.exec()
@@ -125,53 +127,58 @@ async function processServerStats() {
//Server weapon kills //Server weapon kills
await db.selectFrom('kill') 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() let transaction = client.multi()
await Promise.all(data.map(async ({ kills, max_distance, total_distance, cause_of_death, server }) => { await Promise.all(data.map(async ({ kills, max_distance, total_distance, cause_of_death, servername, host }) => {
const prefix = genPrefix.weapon({ cause_of_death, server }) 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: {} }) if (!await client.json.type('kills', prefix)) await client.json.set('kills', prefix, { players: {} })
await processData(prefix, { kills, max_distance, total_distance }, transaction) await processData(prefix, { kills, max_distance, total_distance }, transaction)
})) }))
return transaction.exec() return transaction.exec()
}) })
//server player kill //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) => { .then(async (data) => {
let transaction = client.multi() let transaction = client.multi()
await Promise.all(data.map(async ({ kills, max_distance, total_distance, attacker_id, server }) => { await Promise.all(data.map(async ({ kills, max_distance, total_distance, attacker_id, servername, host }) => {
const prefix = genPrefix.player({ attacker_id, server }) 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: {} }) if (!await client.json.type('kills', prefix)) await client.json.set('kills', prefix, { weapons: {} })
await processData(prefix, { kills, max_distance, total_distance }, transaction) await processData(prefix, { kills, max_distance, total_distance }, transaction)
})) }))
return transaction.exec() return transaction.exec()
}) })
//server player deaths //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) => { .then(async (data) => {
let transaction = client.multi() let transaction = client.multi()
await Promise.all(data.map(async ({ kills, victim_id, server }) => { await Promise.all(data.map(async ({ kills, victim_id, servername, host }) => {
const prefix = genPrefix.player({ attacker_id: victim_id, server }) 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)) transaction.json.set('kills', prefix + ".deaths", Number(kills))
})) }))
return transaction.exec() return transaction.exec()
}) })
//player weapon kills //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) => { .then(async (data) => {
let transaction = client.multi() let transaction = client.multi()
await Promise.all(data.map(async ({ kills, max_distance, total_distance, attacker_id, cause_of_death, server }) => { await Promise.all(data.map(async ({ kills, max_distance, total_distance, attacker_id, cause_of_death, servername, host }) => {
await processData(genPrefix.playerWeapons({ attacker_id, cause_of_death, server }), { kills, max_distance, total_distance }, transaction) servername = servername.replace(/[^a-z0-9]/gi, '')
await processData(genPrefix.weaponPlayers({ attacker_id, cause_of_death, server }), { kills, max_distance, total_distance }, transaction) 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() return transaction.exec()
}) })
//player weapon deaths //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) => { .then(async (data) => {
let transaction = client.multi() let transaction = client.multi()
await Promise.all(data.map(async ({ kills, victim_id, cause_of_death, server }) => { await Promise.all(data.map(async ({ kills, victim_id, cause_of_death, servername, host }) => {
transaction.json.set('kills', genPrefix.weaponPlayers({ attacker_id: victim_id, cause_of_death, server }) + ".deaths", Number(kills)) 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() return transaction.exec()
}) })
-90
View File
@@ -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<any>
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
+27 -30
View File
@@ -1,37 +1,32 @@
import { NextFunction, Router } from 'express' import { NextFunction, Router } from 'express'
import expressBasicAuth from 'express-basic-auth' import expressBasicAuth from 'express-basic-auth'
import { body, header, param } from 'express-validator' import { body, header, param } from 'express-validator'
import register from './register'
import { CreateKillRecord, CheckServerToken } from '../db/db' import { CreateKillRecord, CheckServerToken } from '../db/db'
import { validateErrors } from '../common' import { validateErrors } from '../common'
const router = Router() const router = Router()
router.use('/', register)
//auth middleware //auth middleware
router.post( router.post(
'/:serverId*', '/*',
header('authorization') header('authorization')
.exists({ checkFalsy: true }) .exists({ checkFalsy: true })
.withMessage('Missing Authorization Header') .withMessage('Missing Authorization Header')
.bail() .bail()
.contains('Basic') .contains('Bearer')
.withMessage('Authorization Token is not Basic'), .withMessage('Authorization Token is not Bearer'),
validateErrors, validateErrors,
//Huge mess to retrieve server id from expressBasicAuth. We probably should fix it. async (req, res, next) => {
(req, res, next) => { if (!req) return res.sendStatus(500)
if (!req) res.sendStatus(500) if (!req.headers.authorization) return res.sendStatus(403)
return expressBasicAuth({ const query = await CheckServerToken(req.headers.authorization.split(' ')[1])
authorizeAsync: true, if (!query || !query.id) return res.sendStatus(403)
authorizer: CheckServerToken.bind(req), next()
unauthorizedResponse: { error: 'invalid credentials' }
})(req as any, res, next)
} }
) )
//Route to check auth //Route to check auth
router.post('/:serverId', (req, res) => { router.post('/', (req, res) => {
res.sendStatus(200) res.sendStatus(200)
}) })
@@ -39,8 +34,8 @@ const serversCount: { [id: string]: number } = {}
const serversTimeout: { [id: string]: NodeJS.Timeout } = {} const serversTimeout: { [id: string]: NodeJS.Timeout } = {}
//same rate limiting code as register. max 10 kills per server every 1 sec. should be enough. //same rate limiting code as register. max 10 kills per server every 1 sec. should be enough.
router.post('/:serverId/kill', (req, res, next) => { /*router.post('/kill', (req, res, next) => {
let serverId = Number(req.query.serverId) let host = Number(req.query.serverId)
if (serversCount[serverId] > 2) { if (serversCount[serverId] > 2) {
return res.status(429).json({ return res.status(429).json({
error: 'too many requests. Are players really making that much kills ?' 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 serversCount[serverId] = 0
}, 1000) }, 1000)
next() next()
}) })*/
router.post( router.post(
'/:serverId/kill', '/kill',
param('serverId').exists().toInt().isInt(),
body([ body([
'attacker_current_weapon_mods', 'attacker_current_weapon_mods',
'attacker_weapon_1_mods', 'attacker_weapon_1_mods',
@@ -104,6 +98,7 @@ router.post(
.withMessage('must be a valid float'), .withMessage('must be a valid float'),
body( body(
[ [
'servername',
'attacker_id', 'attacker_id',
'victim_id', 'victim_id',
'killstat_version', 'killstat_version',
@@ -131,9 +126,15 @@ router.post(
min: 0 min: 0
}), }),
body(['cause_of_death', 'victim_id'], 'mandatory').exists().notEmpty(), body(['cause_of_death', 'victim_id'], 'mandatory').exists().notEmpty(),
body('servername').customSanitizer(e => e.replace(/[^a-z0-9]/gi, '')),
validateErrors, 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 { const {
servername,
killstat_version, killstat_version,
match_id, match_id,
game_mode, game_mode,
@@ -167,13 +168,10 @@ router.post(
cause_of_death, cause_of_death,
distance distance
} = req.body } = req.body
if (!req.params.serverId) {
res.status(500).send('serverId cannot be undefined')
return
}
CreateKillRecord({ CreateKillRecord({
killstat_version, killstat_version,
server: Number(req.params.serverId), servername,
host,
match_id, match_id,
game_mode, game_mode,
map, map,
@@ -209,16 +207,15 @@ router.post(
.then((e) => { .then((e) => {
res.sendStatus(201) res.sendStatus(201)
console.log( console.log(
`[${Date.now().toLocaleString()}] Kill submitted for server ${ `[${Date.now().toLocaleString()}] Kill submitted for server ${servername}, ${attacker_name} killed ${victim_name}`
req.params.serverId
}, ${attacker_name} killed ${victim_name}`
) )
}) })
.catch((e) => { .catch((e) => {
res.sendStatus(500) res.sendStatus(500)
console.log({ console.log({
killstat_version, killstat_version,
server: Number(req.params.serverId), servername,
host,
match_id, match_id,
game_mode, game_mode,
map, map,
+3 -3
View File
@@ -16,9 +16,9 @@ describe('client', () => {
const request = await fetch("http://127.0.0.1:3000/servers") const request = await fetch("http://127.0.0.1:3000/servers")
const data = await request.json() const data = await request.json()
const first = Object.entries(data)[0] const first = Object.entries(data)[0]
expect(first).toHaveProperty('name') expect(first[1]).toHaveProperty('name')
expect(first).toHaveProperty('id') expect(first[1]).toHaveProperty('id')
expect(first).toHaveProperty('description') expect(first[1]).toHaveProperty('description')
}) })
test('player list', async () => { test('player list', async () => {
+3 -2
View File
@@ -14,6 +14,7 @@ let pgClient
const data = { const data = {
attacker_weapon_1_mods: 0, attacker_weapon_1_mods: 0,
victim_id: '0', victim_id: '0',
servername: 'testServer',
victim_name: 'TestVictim', victim_name: 'TestVictim',
victim_offhand_weapon_2: 0, victim_offhand_weapon_2: 0,
attacker_offhand_weapon_3: '0', attacker_offhand_weapon_3: '0',
@@ -67,7 +68,7 @@ beforeAll(async () => {
credentials: "same-origin", // include, *same-origin, omit credentials: "same-origin", // include, *same-origin, omit
headers: { headers: {
"Content-Type": "application/json", "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 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 credentials: "same-origin", // include, *same-origin, omit
headers: { headers: {
"Content-Type": "application/json", "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 body: JSON.stringify(data), // body data type must match "Content-Type" header
}); });
+9 -6
View File
@@ -12,12 +12,12 @@ beforeAll(async () => {
describe('server', () => { describe('server', () => {
test('server auth prefetch', async () => { 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. method: "POST", // *GET, POST, PUT, DELETE, etc.
credentials: "same-origin", // include, *same-origin, omit credentials: "same-origin", // include, *same-origin, omit
headers: { headers: {
"Content-Type": "application/json", "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) expect(response.status).toBe(200)
@@ -25,6 +25,7 @@ describe('server', () => {
test('register a kill', async () => { test('register a kill', async () => {
const data = { const data = {
servername: 'testserver',
attacker_weapon_1_mods: 0, attacker_weapon_1_mods: 0,
victim_id: '0', victim_id: '0',
victim_name: 'TestVictim', victim_name: 'TestVictim',
@@ -60,21 +61,23 @@ describe('server', () => {
victim_weapon_3: 'defender', victim_weapon_3: 'defender',
attacker_name: 'TestAttacker' 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. method: "POST", // *GET, POST, PUT, DELETE, etc.
credentials: "same-origin", // include, *same-origin, omit credentials: "same-origin", // include, *same-origin, omit
headers: { headers: {
"Content-Type": "application/json", "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 body: JSON.stringify(data), // body data type must match "Content-Type" header
}); });
console.log(await response.text())
expect(response.status).toBe(201) expect(response.status).toBe(201)
}) })
test('register a kill with missing data', async () => { test('register a kill with missing data', async () => {
const data = { const data = {
servername: 'testserver',
attacker_weapon_1_mods: NaN, attacker_weapon_1_mods: NaN,
victim_id: '0', victim_id: '0',
victim_name: 'TestVictim', victim_name: 'TestVictim',
@@ -110,12 +113,12 @@ describe('server', () => {
victim_weapon_3: 'arc_launcher', victim_weapon_3: 'arc_launcher',
attacker_name: 'TestAttacker' 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. method: "POST", // *GET, POST, PUT, DELETE, etc.
credentials: "same-origin", // include, *same-origin, omit credentials: "same-origin", // include, *same-origin, omit
headers: { headers: {
"Content-Type": "application/json", "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 body: JSON.stringify(data), // body data type must match "Content-Type" header
}); });