Skip to content

Commit

Permalink
feat: add proper vpn/proxy detection w/ proxycheck.io
Browse files Browse the repository at this point in the history
  • Loading branch information
hsanger committed Oct 17, 2024
1 parent e427351 commit 8d9b2cb
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 24 deletions.
21 changes: 21 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@damienvesper/bit-buffer": "^1.0.1",
"croner": "^8.1.1",
"dotenv": "^16.4.5",
"proxycheck-ts": "^0.0.11",
"ts-node": "^10.9.2",
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.48.0",
"ws": "^8.18.0"
Expand Down
11 changes: 5 additions & 6 deletions server/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,6 @@ export interface ConfigType {
*/
readonly refreshDuration: number

/**
* If `true`, a list of blocked IPs will be downloaded from the given URL on server startup. The IPs must be separated by newlines.
* The list is only reloaded on server startup.
*/
readonly ipBlocklistURL?: string

/**
* Limits the number of teams that can be created by any one IP address.
*/
Expand All @@ -231,6 +225,11 @@ export interface ConfigType {
* If a player's username matches one of the regexes in this array, it will be replaced with the default username.
*/
readonly usernameFilters?: RegExp[]

/**
* If specified, the proxycheck.io API will be used to detect and block VPNs and proxies.
*/
readonly proxyCheckAPIKey?: string
}

/**
Expand Down
43 changes: 25 additions & 18 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { CustomTeam, CustomTeamPlayer, type CustomTeamPlayerContainer } from "./
import { Logger } from "./utils/misc";
import { cors, createServer, forbidden, getIP, textDecoder } from "./utils/serverHelpers";
import { cleanUsername } from "./utils/misc";
import ProxyCheck from "proxycheck-ts";

export interface Punishment {
readonly id: string
Expand All @@ -30,7 +31,11 @@ export interface Punishment {

let punishments: Punishment[] = [];

let ipBlocklist: string[] | undefined;
const proxyCheck = Config.protection?.proxyCheckAPIKey
? new ProxyCheck({ api_key: Config.protection.proxyCheckAPIKey })
: undefined;

const knownVPNs = new Set<string>();

function removePunishment(ip: string): void {
punishments = punishments.filter(p => p.ip !== ip);
Expand Down Expand Up @@ -96,11 +101,25 @@ if (isMainThread) {
removePunishment(ip);
}
response = { success: false, message: punishment.punishmentType, reason: punishment.reason, reportID: punishment.reportId };

Check failure on line 103 in server/src/server.ts

View workflow job for this annotation

GitHub Actions / Lint

Block must not be padded by blank lines
} else if (ipBlocklist?.includes(ip)) {
response = { success: false, message: "perma" };

} else if (knownVPNs.has(ip)) {
response = { success: false, message: "perma", reason: "VPN/proxy detected. To play the game, please disable it." };

Check failure on line 106 in server/src/server.ts

View workflow job for this annotation

GitHub Actions / Lint

Block must not be padded by blank lines

} else {
const teamID = new URLSearchParams(req.getQuery()).get("teamID");
if (teamID) {
let teamID;
const result = await proxyCheck?.checkIP(ip, { vpn: 3 }, 5000);

if (
result?.status === "ok"
&& (result[ip].proxy === "yes" || result[ip].vpn === "yes")
) {
knownVPNs.add(ip);
response = { success: false, message: "perma", reason: "VPN/proxy detected. To play the game, please disable it." };

Check failure on line 117 in server/src/server.ts

View workflow job for this annotation

GitHub Actions / Lint

Block must not be padded by blank lines

} else if (
maxTeamSize !== TeamSize.Solo
&& (teamID = new URLSearchParams(req.getQuery()).get("teamID"))
) {
const team = customTeams.get(teamID);
if (team?.gameID !== undefined) {
response = games[team.gameID]
Expand All @@ -109,6 +128,7 @@ if (isMainThread) {
} else {
response = { success: false };
}

Check failure on line 130 in server/src/server.ts

View workflow job for this annotation

GitHub Actions / Lint

Block must not be padded by blank lines

} else {
response = findGame();
}
Expand Down Expand Up @@ -370,19 +390,6 @@ if (isMainThread) {

Logger.log("Reloaded punishment list");
}, protection.refreshDuration);

const ipBlocklistURL = protection.ipBlocklistURL;

if (ipBlocklistURL !== undefined) {
void (async() => {
try {
const response = await fetch(ipBlocklistURL);
ipBlocklist = (await response.text()).split("\n").map(line => line.split("/")[0]);
} catch (e) {
console.error("Error: Unable to load IP blocklist. Details:", e);
}
})();
}
}
});
}

0 comments on commit 8d9b2cb

Please sign in to comment.