mirror of
https://github.com/funkemunky/AntiVPN.git
synced 2026-06-22 19:40:39 +00:00
Add webhook functionality for VPN detection
Co-authored-by: funkemunky <30784509+funkemunky@users.noreply.github.com>
This commit is contained in:
@@ -27,6 +27,10 @@ public class VPNConfig {
|
||||
defaultIp = new ConfigDefault<>("localhost", "database.ip", AntiVPN.getInstance()),
|
||||
defaultAlertMsg = new ConfigDefault<>("&8[&6KauriVPN&8] &e%player% &7has joined on a VPN/proxy" +
|
||||
" &8(&f%reason%&8) &7in location &8(&f%city%&7, &f%country%&8)", "alerts.message",
|
||||
AntiVPN.getInstance()),
|
||||
defaultWebhookUrl = new ConfigDefault<>("", "webhooks.url",
|
||||
AntiVPN.getInstance()),
|
||||
defaultWebhookAuthToken = new ConfigDefault<>("", "webhooks.authToken",
|
||||
AntiVPN.getInstance());
|
||||
private final ConfigDefault<Boolean> cacheResultsDefault = new ConfigDefault<>(true,
|
||||
"cachedResults", AntiVPN.getInstance()),
|
||||
@@ -40,9 +44,15 @@ public class VPNConfig {
|
||||
AntiVPN.getInstance()),
|
||||
defaultWhitelistCountries = new ConfigDefault<>(true, "countries.whitelist",
|
||||
AntiVPN.getInstance()),
|
||||
defaultMetrics = new ConfigDefault<>(true, "bstats", AntiVPN.getInstance());
|
||||
defaultMetrics = new ConfigDefault<>(true, "bstats", AntiVPN.getInstance()),
|
||||
defaultWebhookEnabled = new ConfigDefault<>(false, "webhooks.enabled",
|
||||
AntiVPN.getInstance()),
|
||||
defaultWebhookUseAuth = new ConfigDefault<>(false, "webhooks.useAuthentication",
|
||||
AntiVPN.getInstance());
|
||||
private final ConfigDefault<Integer>
|
||||
defaultPort = new ConfigDefault<>(-1, "database.port", AntiVPN.getInstance());
|
||||
defaultPort = new ConfigDefault<>(-1, "database.port", AntiVPN.getInstance()),
|
||||
defaultWebhookTimeout = new ConfigDefault<>(5, "webhooks.timeout",
|
||||
AntiVPN.getInstance());
|
||||
private final ConfigDefault<List<String>> prefixWhitelistsDefault = new ConfigDefault<>(new ArrayList<>(),
|
||||
"prefixWhitelists", AntiVPN.getInstance()), defaultCommands = new ConfigDefault<>(
|
||||
Collections.singletonList("kick %player% VPNs are not allowed on our server!"), "commands.execute",
|
||||
@@ -53,11 +63,11 @@ public class VPNConfig {
|
||||
AntiVPN.getInstance());
|
||||
|
||||
private String license, kickMessage, databaseType, databaseName, mongoURL, username, password, ip, alertMsg,
|
||||
countryVanillaKickReason;
|
||||
countryVanillaKickReason, webhookUrl, webhookAuthToken;
|
||||
private List<String> prefixWhitelists, commands, countryList, countryKickCommands;
|
||||
private int port;
|
||||
private int port, webhookTimeout;
|
||||
private boolean cacheResults, databaseEnabled, useCredentials, commandsEnabled, kickPlayers, alertToStaff,
|
||||
metrics, whitelistCountries;
|
||||
metrics, whitelistCountries, webhookEnabled, webhookUseAuth;
|
||||
|
||||
/**
|
||||
* License from https://funkemunky.cc/shop to be used for more queries.
|
||||
@@ -258,6 +268,46 @@ public class VPNConfig {
|
||||
return metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, webhook notifications will be sent when a VPN is detected.
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean webhookEnabled() {
|
||||
return webhookEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* The webhook URL to send POST requests to when a VPN is detected.
|
||||
* @return String
|
||||
*/
|
||||
public String webhookUrl() {
|
||||
return webhookUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, an authentication header will be included in webhook requests.
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean webhookUseAuth() {
|
||||
return webhookUseAuth;
|
||||
}
|
||||
|
||||
/**
|
||||
* The authentication token to use for webhook requests.
|
||||
* @return String
|
||||
*/
|
||||
public String webhookAuthToken() {
|
||||
return webhookAuthToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* The timeout in seconds for webhook requests.
|
||||
* @return int
|
||||
*/
|
||||
public int webhookTimeout() {
|
||||
return webhookTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs all information from the config.yml
|
||||
*/
|
||||
@@ -285,6 +335,11 @@ public class VPNConfig {
|
||||
whitelistCountries = defaultWhitelistCountries.get();
|
||||
countryKickCommands = defCountryKickCommands.get();
|
||||
countryVanillaKickReason = defaultCountryKickReason.get();
|
||||
webhookEnabled = defaultWebhookEnabled.get();
|
||||
webhookUrl = defaultWebhookUrl.get();
|
||||
webhookUseAuth = defaultWebhookUseAuth.get();
|
||||
webhookAuthToken = defaultWebhookAuthToken.get();
|
||||
webhookTimeout = defaultWebhookTimeout.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import dev.brighten.antivpn.utils.Tuple;
|
||||
import dev.brighten.antivpn.utils.json.JSONException;
|
||||
import dev.brighten.antivpn.web.FunkemunkyAPI;
|
||||
import dev.brighten.antivpn.web.objects.VPNResponse;
|
||||
import dev.brighten.antivpn.webhook.WebhookNotifier;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -66,6 +67,9 @@ public abstract class VPNExecutor {
|
||||
}
|
||||
|
||||
public void handleKickingOfPlayer(CheckResult result, APIPlayer player) {
|
||||
// Send webhook notification if enabled
|
||||
WebhookNotifier.sendWebhookNotification(player, result);
|
||||
|
||||
if (AntiVPN.getInstance().getVpnConfig().alertToStaff()) AntiVPN.getInstance().getPlayerExecutor()
|
||||
.getOnlinePlayers()
|
||||
.stream()
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package dev.brighten.antivpn.webhook;
|
||||
|
||||
import dev.brighten.antivpn.AntiVPN;
|
||||
import dev.brighten.antivpn.api.APIPlayer;
|
||||
import dev.brighten.antivpn.api.CheckResult;
|
||||
import dev.brighten.antivpn.utils.json.JSONException;
|
||||
import dev.brighten.antivpn.utils.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Handles sending webhook notifications when a VPN is detected.
|
||||
*/
|
||||
public class WebhookNotifier {
|
||||
|
||||
/**
|
||||
* Sends a webhook notification asynchronously when a player is detected using a VPN.
|
||||
*
|
||||
* @param player The player that was detected
|
||||
* @param result The check result containing VPN information
|
||||
*/
|
||||
public static void sendWebhookNotification(APIPlayer player, CheckResult result) {
|
||||
if (!AntiVPN.getInstance().getVpnConfig().webhookEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String webhookUrl = AntiVPN.getInstance().getVpnConfig().webhookUrl();
|
||||
if (webhookUrl == null || webhookUrl.trim().isEmpty()) {
|
||||
AntiVPN.getInstance().getExecutor().log(Level.WARNING,
|
||||
"Webhook is enabled but no URL is configured. Please set webhooks.url in config.yml");
|
||||
return;
|
||||
}
|
||||
|
||||
// Send webhook asynchronously to avoid blocking
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
sendWebhook(webhookUrl, player, result);
|
||||
} catch (Exception e) {
|
||||
AntiVPN.getInstance().getExecutor().logException("Failed to send webhook notification", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually sends the HTTP POST request to the webhook URL.
|
||||
*
|
||||
* @param webhookUrl The URL to send the webhook to
|
||||
* @param player The player information
|
||||
* @param result The check result
|
||||
* @throws IOException If there's an error sending the request
|
||||
* @throws JSONException If there's an error creating the JSON payload
|
||||
*/
|
||||
private static void sendWebhook(String webhookUrl, APIPlayer player, CheckResult result)
|
||||
throws IOException, JSONException {
|
||||
URL url = new URL(webhookUrl);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
|
||||
try {
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setRequestProperty("User-Agent", "AntiVPN-Webhook/1.0");
|
||||
|
||||
// Add authentication header if configured
|
||||
if (AntiVPN.getInstance().getVpnConfig().webhookUseAuth()) {
|
||||
String token = AntiVPN.getInstance().getVpnConfig().webhookAuthToken();
|
||||
if (token != null && !token.trim().isEmpty()) {
|
||||
connection.setRequestProperty("Authorization", "Bearer " + token);
|
||||
}
|
||||
}
|
||||
|
||||
connection.setDoOutput(true);
|
||||
connection.setConnectTimeout(AntiVPN.getInstance().getVpnConfig().webhookTimeout() * 1000);
|
||||
connection.setReadTimeout(AntiVPN.getInstance().getVpnConfig().webhookTimeout() * 1000);
|
||||
|
||||
// Create JSON payload
|
||||
JSONObject payload = createPayload(player, result);
|
||||
byte[] payloadBytes = payload.toString().getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
// Send request
|
||||
try (OutputStream os = connection.getOutputStream()) {
|
||||
os.write(payloadBytes);
|
||||
os.flush();
|
||||
}
|
||||
|
||||
// Check response
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode >= 200 && responseCode < 300) {
|
||||
AntiVPN.getInstance().getExecutor().log(Level.FINE,
|
||||
"Successfully sent webhook notification for player %s (response: %d)",
|
||||
player.getName(), responseCode);
|
||||
} else {
|
||||
AntiVPN.getInstance().getExecutor().log(Level.WARNING,
|
||||
"Webhook notification returned non-success status code %d for player %s",
|
||||
responseCode, player.getName());
|
||||
}
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the JSON payload for the webhook notification.
|
||||
*
|
||||
* @param player The player information
|
||||
* @param result The check result
|
||||
* @return JSONObject containing the webhook payload
|
||||
* @throws JSONException If there's an error creating the JSON
|
||||
*/
|
||||
private static JSONObject createPayload(APIPlayer player, CheckResult result) throws JSONException {
|
||||
JSONObject payload = new JSONObject();
|
||||
|
||||
// Basic event information
|
||||
payload.put("event", "vpn_detected");
|
||||
payload.put("timestamp", System.currentTimeMillis());
|
||||
payload.put("resultType", result.resultType().name());
|
||||
|
||||
// Player information
|
||||
JSONObject playerInfo = new JSONObject();
|
||||
playerInfo.put("uuid", player.getUuid().toString());
|
||||
playerInfo.put("name", player.getName());
|
||||
playerInfo.put("ip", player.getIp().getHostAddress());
|
||||
payload.put("player", playerInfo);
|
||||
|
||||
// VPN detection information
|
||||
if (result.response() != null) {
|
||||
JSONObject detectionInfo = new JSONObject();
|
||||
detectionInfo.put("isProxy", result.response().isProxy());
|
||||
detectionInfo.put("countryCode", result.response().getCountryCode());
|
||||
detectionInfo.put("countryName", result.response().getCountryName());
|
||||
detectionInfo.put("city", result.response().getCity());
|
||||
detectionInfo.put("isp", result.response().getIsp());
|
||||
detectionInfo.put("asn", result.response().getAsn());
|
||||
|
||||
if (result.response().getMethod() != null) {
|
||||
detectionInfo.put("method", result.response().getMethod());
|
||||
}
|
||||
|
||||
payload.put("detection", detectionInfo);
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,18 @@ commands:
|
||||
- kick %player% VPNs are not allowed on our server!
|
||||
# Enable/disable the default kicking feature of KauriVPN.
|
||||
kickPlayers: true
|
||||
# Configure webhook notifications when VPN is detected
|
||||
webhooks:
|
||||
# Enable/disable webhook notifications
|
||||
enabled: false
|
||||
# The webhook URL to send POST requests to when a VPN is detected
|
||||
url: ''
|
||||
# Optional: Set to true to include authentication header (Authorization: Bearer <token>)
|
||||
useAuthentication: false
|
||||
# The authentication token to use when useAuthentication is true
|
||||
authToken: ''
|
||||
# Timeout in seconds for webhook requests (default: 5)
|
||||
timeout: 5
|
||||
# Configure all alerting functionality
|
||||
alerts:
|
||||
# You may set to 'false' to disable all alerts functionality
|
||||
|
||||
Reference in New Issue
Block a user