register match working

This commit is contained in:
2023-08-09 23:32:36 +02:00
parent fecbb80975
commit 338a283a07
10 changed files with 758 additions and 406 deletions
+1 -1
Submodule src/db updated: 491213147e...c985feb726
-291
View File
@@ -1,291 +0,0 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Router } from 'express'
import { body, header } from 'express-validator'
import { /* CreateKillRecord, */ CheckServerToken } from '../db/db'
import { validateErrors } from '../common'
const router = Router()
// auth middleware
router.post(
'/*',
header('authorization')
.exists({ checkFalsy: true })
.withMessage('Missing Authorization Header')
.bail()
.custom((e) => e.split(' ')[0].toLowerCase() === 'bearer')
.withMessage('Authorization Token is not Bearer'),
validateErrors,
(req, res, next) => {
void (async () => {
if (!req) return res.sendStatus(500)
if (!req.headers.authorization) {
console.error('no authorization header')
return res.sendStatus(403)
}
const query = await CheckServerToken(
req.headers.authorization.split(' ')[1]
)
if (!query?.host_id) {
console.error(
`incorrect token : ${
req.headers.authorization.split(' ')[1]
} with IP ${
req.headers['x-forwarded-for']?.toString() ??
req.socket.remoteAddress?.toString() ??
''
}`
)
return res.sendStatus(403)
}
next()
})()
}
)
// Route to check auth
router.post('/', (req, res) => {
res.sendStatus(200)
})
// const serversCount: Record<string, number> = {}
// const serversTimeout: Record<string, NodeJS.Timeout> = {}
// same rate limiting code as register. max 10 kills per server every 1 sec. should be enough.
/* 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 ?'
})
}
clearTimeout(serversTimeout[serverId])
serversCount[serverId] =
serversCount[serverId] ?? (serversCount[serverId] + 1) | 1
serversTimeout[serverId] = setTimeout(() => {
serversCount[serverId] = 0
}, 1000)
next()
}) */
router.post(
'/kill',
body([
'attacker_current_weapon_mods',
'attacker_weapon_1_mods',
'attacker_weapon_2_mods',
'attacker_weapon_3_mods',
'attacker_offhand_weapon_1_mods',
'attacker_offhand_weapon_2_mods',
'victim_current_weapon_mods',
'victim_weapon_1_mods',
'victim_weapon_2_mods',
'victim_weapon_3_mods',
'victim_offhand_weapon_1_mods',
'victim_offhand_weapon_2_mods'
]).customSanitizer((value) => {
if (isNaN(value) || !value) {
value = 0
}
return value
}),
body([
'attacker_current_weapon_mods',
'attacker_weapon_1_mods',
'attacker_weapon_2_mods',
'attacker_weapon_3_mods',
'attacker_offhand_weapon_1_mods',
'attacker_offhand_weapon_2_mods',
'victim_current_weapon_mods',
'victim_weapon_1_mods',
'victim_weapon_2_mods',
'victim_weapon_3_mods',
'victim_offhand_weapon_1_mods',
'victim_offhand_weapon_2_mods'
])
.toInt()
.isInt()
.withMessage('must be a valid int'),
body(['distance', 'player_count'])
.toInt()
.isInt()
.withMessage('must be a valid int'),
body(['distance', 'game_time'])
.toFloat()
.isFloat()
.withMessage('must be a valid float'),
body(
[
'attacker_id',
'victim_id',
'killstat_version',
'match_id',
'game_mode',
'map',
'attacker_name',
'attacker_current_weapon',
'attacker_weapon_1',
'attacker_weapon_2',
'attacker_weapon_3',
'attacker_offhand_weapon_1',
'attacker_offhand_weapon_2',
'victim_name',
'victim_current_weapon',
'victim_weapon_1',
'victim_weapon_2',
'victim_weapon_3',
'victim_offhand_weapon_1',
'victim_offhand_weapon_2',
'cause_of_death',
'victim_titan',
'attacker_titan'
],
'must be composed of a maximum of 50 valid ascii characters'
)
.isString()
.isLength({ max: 50 })
.isAscii(),
body('servername').isString().isLength({ max: 100 }).isAscii(),
body(['distance', 'game_time'], 'must be postitive floats').isFloat({
min: 0
}),
body(['cause_of_death', 'victim_id'], 'mandatory').exists().notEmpty(),
// do we need this ?
// body('servername').customSanitizer(e => e.replace(/[^a-z0-9]/gi, '')),
validateErrors,
(req, res) => {
void (async () => {
// Do we check the same thing twice ?????
if (!req.headers.authorization) return res.sendStatus(403)
const headers = req.headers.authorization.split(' ')
if (headers[0].toLowerCase() !== 'bearer') { return res.status(403).send('authorization must be token bearer') }
const query = await CheckServerToken(headers[1])
if (!query) return res.sendStatus(403)
// const host = query.id
// const {
// servername,
// match_id,
// game_mode,
// map,
// game_time,
// player_count,
// attacker_name,
// attacker_id,
// attacker_current_weapon,
// attacker_current_weapon_mods,
// attacker_weapon_1,
// attacker_weapon_1_mods,
// attacker_weapon_2,
// attacker_weapon_2_mods,
// attacker_weapon_3,
// attacker_weapon_3_mods,
// attacker_offhand_weapon_1,
// attacker_offhand_weapon_1_mods,
// attacker_offhand_weapon_2,
// attacker_offhand_weapon_2_mods,
// victim_name,
// victim_id,
// victim_current_weapon,
// victim_current_weapon_mods,
// victim_weapon_1,
// victim_weapon_1_mods,
// victim_weapon_2,
// victim_weapon_2_mods,
// victim_weapon_3,
// victim_weapon_3_mods,
// victim_offhand_weapon_1,
// victim_offhand_weapon_1_mods,
// victim_offhand_weapon_2,
// victim_offhand_weapon_2_mods,
// cause_of_death,
// distance
// } = req.body
// CreateKillRecord({
// servername,
// host,
// match_id,
// game_mode,
// map,
// game_time,
// player_count,
// attacker_name,
// attacker_id,
// attacker_current_weapon,
// attacker_current_weapon_mods,
// attacker_weapon_1,
// attacker_weapon_1_mods,
// attacker_weapon_2,
// attacker_weapon_2_mods,
// attacker_weapon_3,
// attacker_weapon_3_mods,
// attacker_offhand_weapon_1,
// attacker_offhand_weapon_1_mods,
// attacker_offhand_weapon_2,
// attacker_offhand_weapon_2_mods,
// victim_name,
// victim_id,
// victim_current_weapon,
// victim_current_weapon_mods,
// victim_weapon_1,
// victim_weapon_1_mods,
// victim_weapon_2,
// victim_weapon_2_mods,
// victim_weapon_3,
// victim_weapon_3_mods,
// victim_offhand_weapon_1,
// victim_offhand_weapon_1_mods,
// victim_offhand_weapon_2,
// victim_offhand_weapon_2_mods,
// cause_of_death,
// distance
// })
// .then((e) => {
// res.sendStatus(201)
// console.log(
// `[${new Date().toLocaleString()}] Kill submitted for server ${servername}, ${attacker_name} killed ${victim_name}`
// )
// })
// .catch((e) => {
// res.sendStatus(500)
// console.error({
// killstat_version,
// servername,
// host,
// match_id,
// game_mode,
// map,
// game_time,
// player_count,
// attacker_name,
// attacker_id,
// attacker_current_weapon,
// attacker_current_weapon_mods,
// attacker_weapon_1,
// attacker_weapon_1_mods,
// attacker_weapon_2,
// attacker_weapon_2_mods,
// attacker_weapon_3,
// attacker_weapon_3_mods,
// attacker_offhand_weapon_1,
// attacker_offhand_weapon_2,
// victim_name,
// victim_id,
// victim_current_weapon,
// victim_current_weapon_mods,
// victim_weapon_1,
// victim_weapon_1_mods,
// victim_weapon_2,
// victim_weapon_2_mods,
// victim_weapon_3,
// victim_weapon_3_mods,
// victim_offhand_weapon_1,
// victim_offhand_weapon_2,
// cause_of_death,
// distance
// })
// console.error(e)
// })
})()
}
)
export default router
+54
View File
@@ -0,0 +1,54 @@
import { RequestHandler, Request } from "express";
import typia from "typia";
type WeaponKillData = {
name: string;
mods: number;
};
type PlayerKillData = {
velocity: number;
name: string;
loadout: {
ordnance: WeaponKillData;
secondary: WeaponKillData;
primary: WeaponKillData;
tactical: WeaponKillData;
anti_titan: WeaponKillData;
};
current_weapon: WeaponKillData;
state: string;
titan: string | null;
id: number | bigint;
cloaked: boolean;
};
export type MatchData = {
server_name: string;
game_map: string;
gamemode: string;
air_accel: boolean;
};
export type KillData = {
game_time: number;
player_count: number;
match_id: number;
victim: PlayerKillData;
attacker: PlayerKillData;
distance: number;
cause_of_death: string;
};
type RequestBody<T> = Request<{}, {}, T>;
export const validateBody =
<T>(checker: (input: T) => typia.IValidation<T>): RequestHandler =>
(req, res, next) => {
const result: typia.IValidation<T> = checker(req.body);
if (!result.success) {
res.status(400).send({ errors: result.errors });
} else {
next();
}
};
+5 -1
View File
@@ -1,8 +1,12 @@
// Typed routes from https://urosstok.com/blog/typed-routes-in-express
import * as dotenv from 'dotenv'
import express from 'express'
import cors from 'cors'
import { dbReady } from './db/db'
import server from './server/server'
import server from './generated/server'
dotenv.config()
const app = express()
+142
View File
@@ -0,0 +1,142 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Router, Request, Response } from "express";
import { header } from "express-validator";
import db, { /* CreateKillRecord, */ CheckServerToken } from "../db/db";
import { validateErrors } from "../common";
import { KillData, MatchData, validateBody } from "../server/types";
import typia from "typia";
const router = Router();
// auth middleware
router.post(
"/*",
header("authorization")
.exists({ checkFalsy: true })
.withMessage("Missing Authorization Header")
.bail()
.custom((e) => e.split(" ")[0].toLowerCase() === "bearer")
.withMessage("Authorization Token is not Bearer"),
validateErrors,
(req, res, next) => {
void (async () => {
if (!req.headers.authorization) {
return res.sendStatus(401);
}
const query = await CheckServerToken(
req.headers.authorization.split(" ")[1]
);
if (!query?.host_id) {
console.error(
`incorrect token : ${
req.headers.authorization.split(" ")[1]
} with IP ${
req.headers["x-forwarded-for"]?.toString() ??
req.socket.remoteAddress?.toString() ??
""
}`
);
return res.status(401).send({
errors: [
{
msg: "Incorrect Token",
param: "authorization",
location: "headers",
},
],
});
}
res.locals.host_id = query.host_id;
next();
})();
}
);
// Route to check auth
router.post("/", (req, res) => {
res.sendStatus(200);
});
// const serversCount: Record<string, number> = {}
// const serversTimeout: Record<string, NodeJS.Timeout> = {}
// same rate limiting code as register. max 10 kills per server every 1 sec. should be enough.
/* 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 ?'
})
}
clearTimeout(serversTimeout[serverId])
serversCount[serverId] =
serversCount[serverId] ?? (serversCount[serverId] + 1) | 1
serversTimeout[serverId] = setTimeout(() => {
serversCount[serverId] = 0
}, 1000)
next()
}) */
type RequestBody<T> = Request<{}, {}, T>;
router.post(
"/kill",
validateBody(typia.createValidate<KillData>()),
(req: RequestBody<KillData>, res: Response) => {
void (async () => {
const host_id = res.locals.host_id;
const { match_id } = req.body;
const match = await db
.selectFrom("ToneAPI_v3.match")
.select(["ToneAPI_v3.match.host_id", "ToneAPI_v3.match.match_id"])
.where("ToneAPI_v3.match.host_id", "=", host_id)
.where("ToneAPI_v3.match.match_id", "=", match_id)
.executeTakeFirst();
if (!match) {
res
.status(403)
.send({
errors: [
{
msg: "Match does not exists",
param: "match_id",
location: "body",
},
],
});
return;
}
req.body;
})();
}
);
router.post(
"/match",
validateBody(typia.createValidate<MatchData>()),
(req: RequestBody<MatchData>, res: Response) => {
void (async () => {
const host_id = res.locals.host_id;
const { air_accel, server_name, game_map, gamemode } = req.body;
const server = await db
.selectFrom("ToneAPI_v3.server")
.selectAll()
.where("ToneAPI_v3.server.host_id", "=", host_id)
.where("ToneAPI_v3.server.server_name", "=", server_name)
.executeTakeFirst();
if (!server) {
await db
.insertInto("ToneAPI_v3.server")
.values({ host_id, server_name })
.execute();
}
const match = await db
.insertInto("ToneAPI_v3.match")
.values({ air_accel, server_name, game_map, gamemode, host_id })
.executeTakeFirst();
res.status(201).send({ match: match.insertId });
})();
}
);
export default router;