Ratelimit endpoints (#20)
This commit is contained in:
parent
1ad019523c
commit
0654d159e1
|
@ -1,16 +1,19 @@
|
|||
const path = require( "path" )
|
||||
const accounts = require( path.join( __dirname, "../shared/accounts.js" ) )
|
||||
const accounts = require( path.join( __dirname, "../shared/accounts.js" ) )
|
||||
|
||||
module.exports = ( fastify, opts, done ) => {
|
||||
const { getRatelimit } = require("../shared/ratelimit.js")
|
||||
|
||||
module.exports = ( fastify, opts, done ) => {
|
||||
fastify.register( require( "fastify-multipart" ) )
|
||||
|
||||
// exported routes
|
||||
|
||||
|
||||
// POST /accounts/write_persistence
|
||||
// attempts to write persistent data for a player
|
||||
// note: this is entirely insecure atm, at the very least, we should prevent it from being called on servers that the account being written to isn't currently connected to
|
||||
fastify.post( '/accounts/write_persistence',
|
||||
fastify.post( '/accounts/write_persistence',
|
||||
{
|
||||
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__ACCOUNT_WRITEPERSISTENCE") }, // ratelimit
|
||||
schema: {
|
||||
querystring: {
|
||||
"id": { type: "string" },
|
||||
|
@ -19,14 +22,14 @@ module.exports = ( fastify, opts, done ) => {
|
|||
},
|
||||
},
|
||||
async ( request, response ) => {
|
||||
|
||||
|
||||
let clientIp = request.ip
|
||||
|
||||
|
||||
// pull the client ip address from a custom header if one is specified
|
||||
if (process.env.CLIENT_IP_HEADER && request.headers[process.env.CLIENT_IP_HEADER])
|
||||
clientIp = request.headers[process.env.CLIENT_IP_HEADER]
|
||||
|
||||
// check if account exists
|
||||
|
||||
// check if account exists
|
||||
let account = await accounts.AsyncGetPlayerByID( request.query.id )
|
||||
if ( !account )
|
||||
return null
|
||||
|
@ -42,15 +45,15 @@ module.exports = ( fastify, opts, done ) => {
|
|||
if ( !server || clientIp != server.ip || account.currentServerId != request.query.serverId )
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
// mostly temp
|
||||
let buf = await ( await request.file() ).toBuffer()
|
||||
|
||||
let buf = await ( await request.file() ).toBuffer()
|
||||
|
||||
if ( buf.length == account.persistentDataBaseline.length )
|
||||
await accounts.AsyncWritePlayerPersistenceBaseline( request.query.id, buf )
|
||||
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
|
||||
done()
|
||||
}
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
const path = require( "path" )
|
||||
const crypto = require( "crypto" )
|
||||
const { GameServer, GetGameServers } = require( path.join( __dirname, "../shared/gameserver.js" ) )
|
||||
const accounts = require( path.join( __dirname, "../shared/accounts.js" ) )
|
||||
const asyncHttp = require( path.join( __dirname, "../shared/asynchttp.js" ) )
|
||||
const accounts = require( path.join( __dirname, "../shared/accounts.js" ) )
|
||||
const asyncHttp = require( path.join( __dirname, "../shared/asynchttp.js" ) )
|
||||
|
||||
let shouldRequireSessionToken = process.env.REQUIRE_SESSION_TOKEN = true
|
||||
|
||||
const { getRatelimit } = require("../shared/ratelimit.js")
|
||||
|
||||
module.exports = ( fastify, opts, done ) => {
|
||||
// exported routes
|
||||
|
||||
|
||||
// POST /client/origin_auth
|
||||
// used to authenticate a user on northstar, so we know the person using their uid is really them
|
||||
// returns the user's northstar session token
|
||||
fastify.get( '/client/origin_auth',
|
||||
fastify.get( '/client/origin_auth',
|
||||
{
|
||||
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__CLIENT_ORIGINAUTH") }, // ratelimit
|
||||
schema: {
|
||||
querystring: {
|
||||
id: { type: "string" }, // the authing player's id
|
||||
|
@ -35,20 +38,20 @@ module.exports = ( fastify, opts, done ) => {
|
|||
port: 443,
|
||||
path: `/nucleus-oauth.php?qt=origin-requesttoken&type=server_token&code=${ request.query.token }&forceTrial=0&proto=0&json=1&&env=production&userId=${ parseInt( request.query.id ).toString(16).toUpperCase() }`
|
||||
} )
|
||||
|
||||
|
||||
let authJson
|
||||
try {
|
||||
authJson = JSON.parse( authResponse.toString() )
|
||||
} catch (error) {
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
|
||||
// check origin auth was fine
|
||||
// unsure if we can check the exact value of storeUri? doing an includes check just in case
|
||||
if ( !authResponse.length || authJson.hasOnlineAccess != "1" /* this is actually a string of either "1" or "0" */ || !authJson.storeUri.includes( "titanfall-2" ) )
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
|
||||
let account = await accounts.AsyncGetPlayerByID( request.query.id )
|
||||
if ( !account ) // create account for user
|
||||
{
|
||||
|
@ -60,7 +63,7 @@ module.exports = ( fastify, opts, done ) => {
|
|||
accounts.AsyncUpdateCurrentPlayerAuthToken( account.id, authToken )
|
||||
|
||||
let clientIp = request.ip
|
||||
|
||||
|
||||
// pull the client ip address from a custom header if one is specified
|
||||
if (process.env.CLIENT_IP_HEADER && request.headers[process.env.CLIENT_IP_HEADER])
|
||||
clientIp = request.headers[process.env.CLIENT_IP_HEADER]
|
||||
|
@ -76,8 +79,9 @@ module.exports = ( fastify, opts, done ) => {
|
|||
// POST /client/auth_with_server
|
||||
// attempts to authenticate a client with a gameserver, so they can connect
|
||||
// authentication includes giving them a 1-time token to join the gameserver, as well as sending their persistent data to the gameserver
|
||||
fastify.post( '/client/auth_with_server',
|
||||
fastify.post( '/client/auth_with_server',
|
||||
{
|
||||
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__CLIENT_AUTHWITHSERVER") }, // ratelimit
|
||||
schema: {
|
||||
querystring: {
|
||||
id: { type: "string" }, // id of the player trying to auth
|
||||
|
@ -89,14 +93,14 @@ module.exports = ( fastify, opts, done ) => {
|
|||
},
|
||||
async ( request, reply ) => {
|
||||
let server = GetGameServers()[ request.query.server ]
|
||||
|
||||
|
||||
if ( !server || ( server.hasPassword && request.query.password != server.password ) )
|
||||
return { success: false }
|
||||
|
||||
|
||||
let account = await accounts.AsyncGetPlayerByID( request.query.id )
|
||||
if ( !account )
|
||||
return { success: false }
|
||||
|
||||
|
||||
if ( shouldRequireSessionToken )
|
||||
{
|
||||
// check token
|
||||
|
@ -110,41 +114,42 @@ module.exports = ( fastify, opts, done ) => {
|
|||
|
||||
// fix this: game doesnt seem to set serverFilter right if it's >31 chars long, so restrict it to 31
|
||||
let authToken = crypto.randomBytes( 16 ).toString( "hex" ).substr( 0, 31 )
|
||||
|
||||
|
||||
// todo: build persistent data here, rather than sending baseline only
|
||||
let pdata = await accounts.AsyncGetPlayerPersistenceBufferForMods( request.query.id, server.modInfo.Mods.filter( m => !!m.pdiff ).map( m => m.pdiff ) )
|
||||
|
||||
let authResponse = await asyncHttp.request( {
|
||||
method: "POST",
|
||||
host: server.ip,
|
||||
port: server.authPort,
|
||||
let authResponse = await asyncHttp.request( {
|
||||
method: "POST",
|
||||
host: server.ip,
|
||||
port: server.authPort,
|
||||
path: `/authenticate_incoming_player?id=${request.query.id}&authToken=${authToken}&serverAuthToken=${server.serverAuthToken}`
|
||||
}, pdata )
|
||||
|
||||
|
||||
if ( !authResponse )
|
||||
return { success: false }
|
||||
|
||||
|
||||
let jsonResponse = JSON.parse( authResponse.toString() )
|
||||
if ( !jsonResponse.success )
|
||||
return { success: false }
|
||||
|
||||
|
||||
// update the current server for the player account
|
||||
accounts.AsyncUpdatePlayerCurrentServer( account.id, server.id )
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
|
||||
ip: server.ip,
|
||||
port: server.port,
|
||||
authToken: authToken
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// POST /client/auth_with_self
|
||||
// attempts to authenticate a client with their own server, before the server is created
|
||||
// note: atm, this just sends pdata to clients and doesn't do any kind of auth stuff, potentially rewrite later
|
||||
fastify.post( '/client/auth_with_self',
|
||||
{
|
||||
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__CLIENT_AUTHWITHSELF") }, // ratelimit
|
||||
schema: {
|
||||
querystring: {
|
||||
id: { type: "string" }, // id of the player trying to auth
|
||||
|
@ -156,7 +161,7 @@ module.exports = ( fastify, opts, done ) => {
|
|||
let account = await accounts.AsyncGetPlayerByID( request.query.id )
|
||||
if ( !account )
|
||||
return { success: false }
|
||||
|
||||
|
||||
if ( shouldRequireSessionToken )
|
||||
{
|
||||
// check token
|
||||
|
@ -171,16 +176,16 @@ module.exports = ( fastify, opts, done ) => {
|
|||
// fix this: game doesnt seem to set serverFilter right if it's >31 chars long, so restrict it to 31
|
||||
let authToken = crypto.randomBytes( 16 ).toString("hex").substr( 0, 31 )
|
||||
accounts.AsyncUpdatePlayerCurrentServer( account.id, "self" ) // bit of a hack: use the "self" id for local servers
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
|
||||
id: account.id,
|
||||
authToken: authToken,
|
||||
// this fucking sucks, but i couldn't get game to behave if i sent it as an ascii string, so using this for now
|
||||
persistentData: Array.from( new Uint8Array( account.persistentDataBaseline ) )
|
||||
persistentData: Array.from( new Uint8Array( account.persistentDataBaseline ) )
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
done()
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
const path = require( "path" )
|
||||
const fs = require( "fs" )
|
||||
|
||||
const { getRatelimit } = require("../shared/ratelimit.js")
|
||||
|
||||
let promodataPath = path.join( __dirname, "mainmenupromodata.json" )
|
||||
|
||||
// watch the mainmenupromodata file so we can update it without a masterserver restart
|
||||
fs.watch( promodataPath, ( curr, prev ) => {
|
||||
try
|
||||
try
|
||||
{
|
||||
mainMenuPromoData = JSON.parse( fs.readFileSync( promodataPath ).toString() )
|
||||
console.log( "updated main menu promo data successfully!" )
|
||||
|
@ -24,11 +25,13 @@ if ( fs.existsSync( promodataPath ))
|
|||
|
||||
module.exports = ( fastify, opts, done ) => {
|
||||
// exported routes
|
||||
|
||||
|
||||
// GET /client/mainmenupromos
|
||||
// returns main menu promo info
|
||||
fastify.get( '/client/mainmenupromos',
|
||||
{},
|
||||
fastify.get( '/client/mainmenupromos',
|
||||
{
|
||||
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__CLIENT_MAINMENUPROMOS") }, // ratelimit
|
||||
},
|
||||
async ( request, reply ) => {
|
||||
return mainMenuPromoData
|
||||
})
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
const { getRatelimit } = require("../shared/ratelimit.js")
|
||||
|
||||
module.exports = ( fastify, opts, done ) => {
|
||||
// exported routes
|
||||
|
||||
|
||||
// GET /
|
||||
// redirect anyone going to northstar.tf in a browser to the github
|
||||
fastify.get( '/',
|
||||
{
|
||||
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__REDIRECT") }, // ratelimit
|
||||
},
|
||||
async ( request, reply ) => {
|
||||
reply.redirect( "https://github.com/R2Northstar" )
|
||||
})
|
||||
|
@ -11,6 +16,9 @@ module.exports = ( fastify, opts, done ) => {
|
|||
// GET /discord
|
||||
// redirect anyone going to northstar.tf/discord to the discord
|
||||
fastify.get( '/discord',
|
||||
{
|
||||
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__REDIRECT") }, // ratelimit
|
||||
},
|
||||
async ( request, reply ) => {
|
||||
reply.redirect( "https://discord.gg/GYVRKC9pJh" )
|
||||
})
|
||||
|
@ -18,6 +26,9 @@ module.exports = ( fastify, opts, done ) => {
|
|||
// GET /wiki
|
||||
// redirect anyone going to northstar.tf/wiki to the wiki
|
||||
fastify.get( '/wiki',
|
||||
{
|
||||
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__REDIRECT") }, // ratelimit
|
||||
},
|
||||
async ( request, reply ) => {
|
||||
reply.redirect( "https://r2northstar.gitbook.io/" )
|
||||
})
|
||||
|
|
|
@ -1,32 +1,38 @@
|
|||
const path = require( "path" )
|
||||
const { GameServer, GetGameServers, RemoveGameServer } = require( path.join( __dirname, "../shared/gameserver.js" ) )
|
||||
|
||||
const { getRatelimit } = require("../shared/ratelimit.js")
|
||||
|
||||
module.exports = ( fastify, opts, done ) => {
|
||||
fastify.register(require( "fastify-cors" ))
|
||||
|
||||
// exported routes
|
||||
|
||||
// GET /client/servers
|
||||
|
||||
// GET /client/servers
|
||||
// returns a list of available servers
|
||||
fastify.get( '/client/servers', async ( request, response ) => {
|
||||
fastify.get( '/client/servers',
|
||||
{
|
||||
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__CLIENT_SERVERS") }, // ratelimit
|
||||
},
|
||||
async ( request, response ) => {
|
||||
let displayServerArray = []
|
||||
let expiredServers = [] // might be better to move this to another function at some point, but easiest to do here atm
|
||||
|
||||
|
||||
let servers = Object.values( GetGameServers() )
|
||||
|
||||
|
||||
for ( let i = 0; i < servers.length; i++ )
|
||||
{
|
||||
{
|
||||
// prune servers if they've had 30 seconds since last heartbeat
|
||||
if ( Date.now() - servers[ i ].lastHeartbeat > 30000 )
|
||||
{
|
||||
expiredServers.push( servers[ i ] )
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
// don't show non-private_match servers on lobby since they'll pollute server list
|
||||
if ( servers[ i ].map == "mp_lobby" && servers[ i ].playlist != "private_match" )
|
||||
continue
|
||||
|
||||
|
||||
// create a copy of the gameserver obj for clients so we can hide sensitive info
|
||||
let copy = new GameServer( servers[ i ] )
|
||||
delete copy.ip
|
||||
|
@ -34,16 +40,16 @@ module.exports = ( fastify, opts, done ) => {
|
|||
delete copy.authPort
|
||||
delete copy.password
|
||||
delete copy.serverAuthToken
|
||||
|
||||
|
||||
displayServerArray.push( copy )
|
||||
}
|
||||
|
||||
|
||||
// delete servers that we've marked for deletion
|
||||
for ( let server of expiredServers )
|
||||
RemoveGameServer( server )
|
||||
|
||||
|
||||
return displayServerArray
|
||||
})
|
||||
|
||||
|
||||
done()
|
||||
}
|
17
dev.env
17
dev.env
|
@ -12,4 +12,19 @@ CLIENT_IP_HEADER=
|
|||
# not used for dev
|
||||
SSL_KEY_PATH=
|
||||
SSL_CERT_PATH=
|
||||
TRUST_PROXY=
|
||||
TRUST_PROXY=
|
||||
|
||||
# ratelimit
|
||||
USE_RATELIMIT=1
|
||||
REQ_PER_MINUTE__GLOBAL=100
|
||||
REQ_PER_MINUTE__REDIRECT=25
|
||||
REQ_PER_MINUTE__CLIENT_ORIGINAUTH=5
|
||||
REQ_PER_MINUTE__CLIENT_AUTHWITHSERVER=10
|
||||
REQ_PER_MINUTE__CLIENT_AUTHWITHSELF=25
|
||||
REQ_PER_MINUTE__CLIENT_MAINMENUPROMOS=20
|
||||
REQ_PER_MINUTE__CLIENT_SERVERS=100
|
||||
REQ_PER_MINUTE__SERVER_ADDSERVER=5
|
||||
REQ_PER_MINUTE__SERVER_HEARTBEAT=60
|
||||
REQ_PER_MINUTE__SERVER_UPDATEVALUES=20
|
||||
REQ_PER_MINUTE__SERVER_REMOVESERVER=5
|
||||
REQ_PER_MINUTE__ACCOUNT_WRITEPERSISTENCE=50
|
17
index.js
17
index.js
|
@ -29,6 +29,23 @@ else
|
|||
|
||||
const ROUTE_PATHS = [ "client", "server", "account" ]
|
||||
|
||||
if(!!(process.env.USE_RATELIMIT)) {
|
||||
fastify.register(require('fastify-rate-limit'),
|
||||
{
|
||||
global: false,
|
||||
errorResponseBuilder: function(req, context) {
|
||||
return {
|
||||
code: 429,
|
||||
error: 'Too Many Requests',
|
||||
message: `Request limit reached. You can send ${context.max} requests every ${context.after}. Timeout expiry: ${context.ttl}ms.`,
|
||||
date: Date.now(),
|
||||
expiresIn: context.ttl // milliseconds
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
for ( let routePath of ROUTE_PATHS )
|
||||
{
|
||||
for ( let file of fs.readdirSync( routePath ) )
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"fastify": "^3.24.0",
|
||||
"fastify-cors": "^6.0.2",
|
||||
"fastify-multipart": "^5.1.0",
|
||||
"fastify-rate-limit": "^5.7.0",
|
||||
"sqlite3": "^5.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1254,6 +1255,16 @@
|
|||
"resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.0.tgz",
|
||||
"integrity": "sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w=="
|
||||
},
|
||||
"node_modules/fastify-rate-limit": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/fastify-rate-limit/-/fastify-rate-limit-5.7.0.tgz",
|
||||
"integrity": "sha512-9pfnVpz6rUy7VGqBVN9blIT7LgmETiK7hRA0Pu8eHJFq8qq8FBPvrQYXxGXb2nt63laD6ZuvT1O7TxucZI1HIA==",
|
||||
"dependencies": {
|
||||
"fastify-plugin": "^3.0.0",
|
||||
"ms": "^2.1.1",
|
||||
"tiny-lru": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fastify-warning": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz",
|
||||
|
@ -4521,6 +4532,16 @@
|
|||
"resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.0.tgz",
|
||||
"integrity": "sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w=="
|
||||
},
|
||||
"fastify-rate-limit": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/fastify-rate-limit/-/fastify-rate-limit-5.7.0.tgz",
|
||||
"integrity": "sha512-9pfnVpz6rUy7VGqBVN9blIT7LgmETiK7hRA0Pu8eHJFq8qq8FBPvrQYXxGXb2nt63laD6ZuvT1O7TxucZI1HIA==",
|
||||
"requires": {
|
||||
"fastify-plugin": "^3.0.0",
|
||||
"ms": "^2.1.1",
|
||||
"tiny-lru": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"fastify-warning": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz",
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"fastify": "^3.24.0",
|
||||
"fastify-cors": "^6.0.2",
|
||||
"fastify-multipart": "^5.1.0",
|
||||
"fastify-rate-limit": "^5.7.0",
|
||||
"sqlite3": "^5.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -27,4 +28,4 @@
|
|||
"husky": "^7.0.0",
|
||||
"nodemon": "^2.0.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,29 @@
|
|||
const path = require( "path" )
|
||||
const crypto = require( "crypto" )
|
||||
const { GameServer, GetGameServers, AddGameServer, RemoveGameServer } = require( path.join( __dirname, "../shared/gameserver.js" ) )
|
||||
const asyncHttp = require( path.join( __dirname, "../shared/asynchttp.js" ) )
|
||||
const asyncHttp = require( path.join( __dirname, "../shared/asynchttp.js" ) )
|
||||
const pjson = require( path.join( __dirname, "../shared/pjson.js" ) )
|
||||
const Filter = require('bad-words')
|
||||
let filter = new Filter();
|
||||
|
||||
const VERIFY_STRING = "I am a northstar server!"
|
||||
|
||||
const { getRatelimit } = require("../shared/ratelimit.js")
|
||||
|
||||
async function SharedTryAddServer( request )
|
||||
{
|
||||
// check server's verify endpoint on their auth server, make sure it's fine
|
||||
// in the future we could probably check the server's connect port too, with a c2s_connect packet or smth, but atm this is good enough
|
||||
|
||||
let clientIp = request.ip
|
||||
|
||||
|
||||
// pull the client ip address from a custom header if one is specified
|
||||
if (process.env.CLIENT_IP_HEADER && request.headers[process.env.CLIENT_IP_HEADER])
|
||||
clientIp = request.headers[process.env.CLIENT_IP_HEADER]
|
||||
|
||||
|
||||
let hasValidModInfo = true
|
||||
let modInfo
|
||||
|
||||
|
||||
if ( request.isMultipart() )
|
||||
{
|
||||
try
|
||||
|
@ -38,10 +40,10 @@ async function SharedTryAddServer( request )
|
|||
port: request.query.authPort,
|
||||
path: "/verify"
|
||||
})
|
||||
|
||||
|
||||
if ( !authServerResponse || authServerResponse.toString() != VERIFY_STRING )
|
||||
return { success: false }
|
||||
|
||||
|
||||
// pdiff stuff
|
||||
if ( modInfo && modInfo.Mods )
|
||||
{
|
||||
|
@ -55,7 +57,7 @@ async function SharedTryAddServer( request )
|
|||
mod.pdiff = pjson.ParseDefinitionDiffs( mod.pdiff )
|
||||
mod.pdiff.hash = pdiffHash
|
||||
}
|
||||
catch ( ex )
|
||||
catch ( ex )
|
||||
{
|
||||
mod.pdiff = null
|
||||
}
|
||||
|
@ -74,7 +76,7 @@ async function SharedTryAddServer( request )
|
|||
request.query.port = parseInt( request.query.port )
|
||||
|
||||
if ( typeof request.query.authPort == 'string' )
|
||||
request.query.authPort = parseInt( request.query.authPort )
|
||||
request.query.authPort = parseInt( request.query.authPort )
|
||||
|
||||
let name = filter.clean( request.query.name )
|
||||
let description = request.query.description == "" ? "" : filter.clean( request.query.description )
|
||||
|
@ -92,11 +94,12 @@ module.exports = ( fastify, opts, done ) => {
|
|||
fastify.register( require( "fastify-multipart" ) )
|
||||
|
||||
// exported routes
|
||||
|
||||
|
||||
// POST /server/add_server
|
||||
// adds a gameserver to the server list
|
||||
fastify.post( '/server/add_server',
|
||||
fastify.post( '/server/add_server',
|
||||
{
|
||||
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__SERVER_ADDSERVER") }, // ratelimit
|
||||
schema: {
|
||||
querystring: {
|
||||
port: { type: "integer" }, // the port the gameserver is being hosted on ( for connect )
|
||||
|
@ -113,11 +116,12 @@ module.exports = ( fastify, opts, done ) => {
|
|||
async ( request, reply ) => {
|
||||
return SharedTryAddServer( request )
|
||||
})
|
||||
|
||||
|
||||
// POST /server/heartbeat
|
||||
// refreshes a gameserver's last heartbeat time, gameservers are removed after 30 seconds without a heartbeat
|
||||
fastify.post( '/server/heartbeat',
|
||||
{
|
||||
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__SERVER_HEARTBEAT") }, // ratelimit
|
||||
schema: {
|
||||
querystring: {
|
||||
id: { type: "string" }, // the id of the server sending this message
|
||||
|
@ -126,20 +130,20 @@ module.exports = ( fastify, opts, done ) => {
|
|||
}
|
||||
},
|
||||
async ( request, reply ) => {
|
||||
|
||||
|
||||
let clientIp = request.ip
|
||||
|
||||
|
||||
// pull the client ip address from a custom header if one is specified
|
||||
if (process.env.CLIENT_IP_HEADER && request.headers[process.env.CLIENT_IP_HEADER])
|
||||
clientIp = request.headers[process.env.CLIENT_IP_HEADER]
|
||||
|
||||
|
||||
let server = GetGameServers()[ request.query.id ]
|
||||
// dont update if the server doesnt exist, or the server isnt the one sending the heartbeat
|
||||
if ( !server || clientIp != server.ip || !request.query.id )// remove !request.playerCount as if playercount==0 it will trigger skip heartbeat update
|
||||
{
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
else // Added else so update heartbeat will trigger,Have to add the brackets for me to work for some reason
|
||||
{
|
||||
server.lastHeartbeat = Date.now()
|
||||
|
@ -147,21 +151,24 @@ module.exports = ( fastify, opts, done ) => {
|
|||
return null
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// POST /server/update_values
|
||||
// updates values shown on the server list, such as map, playlist, or player count
|
||||
// no schema for this one, since it's fully dynamic and fastify doesnt do optional params
|
||||
fastify.post( '/server/update_values', async ( request, reply ) => {
|
||||
|
||||
fastify.post( '/server/update_values',
|
||||
{
|
||||
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__SERVER_UPDATEVALUES") }, // ratelimit
|
||||
},
|
||||
async ( request, reply ) => {
|
||||
let clientIp = request.ip
|
||||
|
||||
|
||||
// pull the client ip address from a custom header if one is specified
|
||||
if (process.env.CLIENT_IP_HEADER && request.headers[process.env.CLIENT_IP_HEADER])
|
||||
clientIp = request.headers[process.env.CLIENT_IP_HEADER]
|
||||
|
||||
|
||||
if ( !( "id" in request.query ) )
|
||||
return null
|
||||
|
||||
|
||||
let server = GetGameServers()[ request.query.id ]
|
||||
|
||||
// if server doesn't exist, try adding it
|
||||
|
@ -174,12 +181,12 @@ module.exports = ( fastify, opts, done ) => {
|
|||
|
||||
// update heartbeat
|
||||
server.lastHeartbeat = Date.now()
|
||||
|
||||
|
||||
for ( let key of Object.keys( request.query ) )
|
||||
{
|
||||
if ( key == "id" || key == "port" || key == "authport" || !( key in server ) || request.query[ key ].length >= 512 )
|
||||
continue
|
||||
|
||||
|
||||
if ( key == "playerCount" || key == "maxPlayers" )
|
||||
{
|
||||
server[ key ] = parseInt( request.query[ key ] )
|
||||
|
@ -189,14 +196,15 @@ module.exports = ( fastify, opts, done ) => {
|
|||
server[ key ] = request.query[ key ]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
// DELETE /server/remove_server
|
||||
|
||||
// DELETE /server/remove_server
|
||||
// removes a gameserver from the server list
|
||||
fastify.delete( '/server/remove_server',
|
||||
{
|
||||
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__SERVER_REMOVESERVER") }, // ratelimit
|
||||
schema: {
|
||||
querystring: {
|
||||
id: { type: "string" }
|
||||
|
@ -208,10 +216,10 @@ module.exports = ( fastify, opts, done ) => {
|
|||
// dont remove if the server doesnt exist, or the server isnt the one sending the heartbeat
|
||||
if ( !server || clientIp != server.ip )
|
||||
return null
|
||||
|
||||
|
||||
RemoveGameServer( server )
|
||||
return null
|
||||
})
|
||||
|
||||
|
||||
done()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
function getRatelimit(envVar) {
|
||||
return { max: Number(process.env[envVar]) || (Number(process.env.REQ_PER_MINUTE__GLOBAL) || 9999) }
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getRatelimit
|
||||
}
|
Loading…
Reference in New Issue