2022-04-20 11:45:30 -04:00
|
|
|
let vpnExceptionIds = [];
|
2022-10-17 10:17:43 -04:00
|
|
|
const vpnAllowListKey = 'Webfront::Nav::Admin::VPNAllowList';
|
|
|
|
const vpnWhitelistKey = 'Webfront::Profile::VPNWhitelist';
|
|
|
|
|
2023-04-04 19:24:13 -04:00
|
|
|
const init = (registerNotify, serviceResolver, config, pluginHelper) => {
|
|
|
|
registerNotify('IManagementEventSubscriptions.ClientStateAuthorized', (authorizedEvent, token) => plugin.onClientAuthorized(authorizedEvent, token));
|
2022-10-17 10:17:43 -04:00
|
|
|
|
2023-04-04 19:24:13 -04:00
|
|
|
plugin.onLoad(serviceResolver, config, pluginHelper);
|
2023-02-11 22:02:20 -05:00
|
|
|
return plugin;
|
|
|
|
};
|
2022-10-17 10:17:43 -04:00
|
|
|
|
2022-03-23 14:34:04 -04:00
|
|
|
const plugin = {
|
2018-08-26 20:20:47 -04:00
|
|
|
author: 'RaidMax',
|
2023-04-04 19:24:13 -04:00
|
|
|
version: '2.0',
|
2018-09-04 13:40:29 -04:00
|
|
|
name: 'VPN Detection Plugin',
|
2018-08-26 20:20:47 -04:00
|
|
|
manager: null,
|
2023-02-11 22:02:20 -05:00
|
|
|
config: null,
|
2018-08-26 20:20:47 -04:00
|
|
|
logger: null,
|
2023-02-11 22:02:20 -05:00
|
|
|
serviceResolver: null,
|
|
|
|
translations: null,
|
2023-04-04 19:24:13 -04:00
|
|
|
pluginHelper: null,
|
2023-02-11 22:02:20 -05:00
|
|
|
|
|
|
|
commands: [{
|
|
|
|
name: 'whitelistvpn',
|
|
|
|
description: 'whitelists a player\'s client id from VPN detection',
|
|
|
|
alias: 'wv',
|
|
|
|
permission: 'SeniorAdmin',
|
|
|
|
targetRequired: true,
|
|
|
|
arguments: [{
|
|
|
|
name: 'player',
|
|
|
|
required: true
|
|
|
|
}],
|
|
|
|
execute: (gameEvent) => {
|
|
|
|
vpnExceptionIds.push(gameEvent.Target.ClientId);
|
|
|
|
plugin.config.setValue('vpnExceptionIds', vpnExceptionIds);
|
|
|
|
|
|
|
|
gameEvent.origin.tell(`Successfully whitelisted ${gameEvent.target.name}`);
|
2018-08-26 20:20:47 -04:00
|
|
|
}
|
|
|
|
},
|
2023-02-11 22:02:20 -05:00
|
|
|
{
|
|
|
|
name: 'disallowvpn',
|
|
|
|
description: 'disallows a player from connecting with a VPN',
|
|
|
|
alias: 'dv',
|
|
|
|
permission: 'SeniorAdmin',
|
|
|
|
targetRequired: true,
|
|
|
|
arguments: [{
|
|
|
|
name: 'player',
|
|
|
|
required: true
|
|
|
|
}],
|
|
|
|
execute: (gameEvent) => {
|
|
|
|
vpnExceptionIds = vpnExceptionIds.filter(exception => parseInt(exception) !== parseInt(gameEvent.Target.ClientId));
|
|
|
|
plugin.config.setValue('vpnExceptionIds', vpnExceptionIds);
|
|
|
|
|
|
|
|
gameEvent.origin.tell(`Successfully disallowed ${gameEvent.target.name} from connecting with VPN`);
|
|
|
|
}
|
2018-08-26 20:20:47 -04:00
|
|
|
}
|
2023-02-11 22:02:20 -05:00
|
|
|
],
|
2022-09-08 16:03:38 -04:00
|
|
|
|
2023-02-11 22:02:20 -05:00
|
|
|
interactions: [{
|
2022-10-17 10:17:43 -04:00
|
|
|
// registers the profile action
|
2023-02-11 22:02:20 -05:00
|
|
|
name: vpnWhitelistKey,
|
2023-04-04 19:24:13 -04:00
|
|
|
action: function (targetId, game, token) {
|
2022-09-08 16:03:38 -04:00
|
|
|
const helpers = importNamespace('SharedLibraryCore.Helpers');
|
|
|
|
const interactionData = new helpers.InteractionData();
|
|
|
|
|
2023-02-11 22:02:20 -05:00
|
|
|
interactionData.actionPath = 'DynamicAction';
|
|
|
|
interactionData.interactionId = vpnWhitelistKey;
|
|
|
|
interactionData.entityId = targetId;
|
|
|
|
interactionData.minimumPermission = 3;
|
|
|
|
interactionData.source = plugin.name;
|
|
|
|
interactionData.actionMeta.add('InteractionId', 'command'); // indicate we're wanting to execute a command
|
|
|
|
interactionData.actionMeta.add('ShouldRefresh', true.toString()); // indicates that the page should refresh after performing the action
|
2022-09-08 16:03:38 -04:00
|
|
|
|
2022-10-17 10:17:43 -04:00
|
|
|
if (vpnExceptionIds.includes(targetId)) {
|
2023-02-11 22:02:20 -05:00
|
|
|
interactionData.name = plugin.translations['WEBFRONT_VPN_BUTTON_DISALLOW']; // text for the profile button
|
|
|
|
interactionData.displayMeta = 'oi-circle-x';
|
2022-10-17 10:17:43 -04:00
|
|
|
|
2023-02-11 22:02:20 -05:00
|
|
|
interactionData.actionMeta.add('Data', `disallowvpn`); // command to execute
|
|
|
|
interactionData.actionMeta.add('ActionButtonLabel', plugin.translations['WEBFRONT_VPN_ACTION_DISALLOW_CONFIRM']); // confirm button on the dialog
|
|
|
|
interactionData.actionMeta.add('Name', plugin.translations['WEBFRONT_VPN_ACTION_DISALLOW_TITLE']); // title on the confirm dialog
|
2022-10-17 10:17:43 -04:00
|
|
|
} else {
|
2023-02-11 22:02:20 -05:00
|
|
|
interactionData.name = plugin.translations['WEBFRONT_VPN_ACTION_ALLOW']; // text for the profile button
|
|
|
|
interactionData.displayMeta = 'oi-circle-check';
|
|
|
|
|
|
|
|
interactionData.actionMeta.add('Data', `whitelistvpn`); // command to execute
|
|
|
|
interactionData.actionMeta.add('ActionButtonLabel', plugin.translations['WEBFRONT_VPN_ACTION_ALLOW_CONFIRM']); // confirm button on the dialog
|
|
|
|
interactionData.actionMeta.add('Name', plugin.translations['WEBFRONT_VPN_ACTION_ALLOW_TITLE']); // title on the confirm dialog
|
2022-10-17 10:17:43 -04:00
|
|
|
}
|
2022-09-08 16:03:38 -04:00
|
|
|
|
2022-10-17 10:17:43 -04:00
|
|
|
return interactionData;
|
2023-02-11 22:02:20 -05:00
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: vpnAllowListKey,
|
2023-04-04 19:24:13 -04:00
|
|
|
action: function (targetId, game, token) {
|
2023-02-11 22:02:20 -05:00
|
|
|
const helpers = importNamespace('SharedLibraryCore.Helpers');
|
|
|
|
const interactionData = new helpers.InteractionData();
|
|
|
|
|
|
|
|
interactionData.name = plugin.translations['WEBFRONT_NAV_VPN_TITLE']; // navigation link name
|
|
|
|
interactionData.description = plugin.translations['WEBFRONT_NAV_VPN_DESC']; // alt and title
|
|
|
|
interactionData.displayMeta = 'oi-circle-check'; // nav icon
|
|
|
|
interactionData.interactionId = vpnAllowListKey;
|
|
|
|
interactionData.minimumPermission = 3; // moderator
|
|
|
|
interactionData.interactionType = 2; // 1 is RawContent for apis etc..., 2 is
|
|
|
|
interactionData.source = plugin.name;
|
|
|
|
|
|
|
|
interactionData.scriptAction = (sourceId, targetId, game, meta, token) => {
|
|
|
|
const clientsData = plugin.getClientsData(vpnExceptionIds);
|
|
|
|
|
|
|
|
let table = '<table class="table bg-dark-dm bg-light-lm">';
|
|
|
|
|
|
|
|
const disallowInteraction = {
|
|
|
|
InteractionId: 'command',
|
|
|
|
Data: 'disallowvpn',
|
|
|
|
ActionButtonLabel: plugin.translations['WEBFRONT_VPN_ACTION_DISALLOW_CONFIRM'],
|
|
|
|
Name: plugin.translations['WEBFRONT_VPN_ACTION_DISALLOW_TITLE']
|
|
|
|
};
|
|
|
|
|
|
|
|
if (clientsData.length === 0) {
|
|
|
|
table += `<tr><td>No players are whitelisted.</td></tr>`;
|
|
|
|
}
|
|
|
|
|
|
|
|
clientsData.forEach(client => {
|
|
|
|
table += `<tr>
|
2022-10-17 10:17:43 -04:00
|
|
|
<td>
|
2023-02-11 22:02:20 -05:00
|
|
|
<a href="/Client/Profile/${client.clientId}" class="level-color-${client.level.toLowerCase()} no-decoration">${client.currentAlias.name.stripColors()}</a>
|
2022-10-17 10:17:43 -04:00
|
|
|
</td>
|
|
|
|
<td>
|
2023-02-11 22:02:20 -05:00
|
|
|
<a href="#" class="profile-action no-decoration float-right" data-action="DynamicAction" data-action-id="${client.clientId}"
|
2022-10-17 10:17:43 -04:00
|
|
|
data-action-meta="${encodeURI(JSON.stringify(disallowInteraction))}">
|
|
|
|
<div class="btn">
|
|
|
|
<i class="oi oi-circle-x mr-5 font-size-12"></i>
|
2023-02-11 22:02:20 -05:00
|
|
|
<span class="text-truncate">${plugin.translations['WEBFRONT_VPN_BUTTON_DISALLOW']}</span>
|
2022-10-17 10:17:43 -04:00
|
|
|
</div>
|
|
|
|
</a>
|
|
|
|
</td>
|
|
|
|
</tr>`;
|
2023-02-11 22:02:20 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
table += '</table>';
|
2022-10-17 10:17:43 -04:00
|
|
|
|
2023-02-11 22:02:20 -05:00
|
|
|
return table;
|
|
|
|
};
|
|
|
|
|
|
|
|
return interactionData;
|
2022-10-17 10:17:43 -04:00
|
|
|
}
|
2023-02-11 22:02:20 -05:00
|
|
|
}
|
|
|
|
],
|
|
|
|
|
2023-04-04 19:24:13 -04:00
|
|
|
onClientAuthorized: async function (authorizeEvent, token) {
|
|
|
|
if (authorizeEvent.client.isBot) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
await this.checkForVpn(authorizeEvent.client, token);
|
2018-08-26 20:20:47 -04:00
|
|
|
},
|
|
|
|
|
2023-04-04 19:24:13 -04:00
|
|
|
onLoad: function (serviceResolver, config, pluginHelper) {
|
2023-02-11 22:02:20 -05:00
|
|
|
this.serviceResolver = serviceResolver;
|
|
|
|
this.config = config;
|
2023-04-04 19:24:13 -04:00
|
|
|
this.pluginHelper = pluginHelper;
|
2023-02-11 22:02:20 -05:00
|
|
|
this.manager = this.serviceResolver.resolveService('IManager');
|
|
|
|
this.logger = this.serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
|
|
|
|
this.translations = this.serviceResolver.resolveService('ITranslationLookup');
|
|
|
|
|
|
|
|
this.config.setName(this.name); // use legacy key
|
|
|
|
this.config.getValue('vpnExceptionIds').forEach(element => vpnExceptionIds.push(parseInt(element)));
|
|
|
|
this.logger.logInformation(`Loaded ${vpnExceptionIds.length} ids into whitelist`);
|
|
|
|
|
|
|
|
this.interactionRegistration = this.serviceResolver.resolveService('IInteractionRegistration');
|
|
|
|
this.interactionRegistration.unregisterInteraction(vpnWhitelistKey);
|
|
|
|
this.interactionRegistration.unregisterInteraction(vpnAllowListKey);
|
2018-09-04 13:40:29 -04:00
|
|
|
},
|
2018-08-26 20:20:47 -04:00
|
|
|
|
2023-04-04 19:24:13 -04:00
|
|
|
checkForVpn: async function (origin, token) {
|
2023-02-11 22:02:20 -05:00
|
|
|
let exempt = false;
|
|
|
|
// prevent players that are exempt from being kicked
|
2023-04-04 19:24:13 -04:00
|
|
|
vpnExceptionIds.forEach(function (id) {
|
2023-02-11 22:02:20 -05:00
|
|
|
if (parseInt(id) === parseInt(origin.clientId)) {
|
|
|
|
exempt = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (exempt) {
|
|
|
|
this.logger.logInformation(`{origin} is whitelisted, so we are not checking VPN status`, origin);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-04-04 19:24:13 -04:00
|
|
|
if (origin.IPAddressString === null) {
|
|
|
|
this.logger.logDebug('{Client} does not have an IP Address yet, so we are no checking their VPN status', origin);
|
|
|
|
}
|
|
|
|
|
|
|
|
const userAgent = `IW4MAdmin-${this.manager.getApplicationSettings().configuration().id}`;
|
|
|
|
const headers = {
|
|
|
|
'User-Agent': userAgent
|
|
|
|
};
|
2023-02-11 22:02:20 -05:00
|
|
|
|
|
|
|
try {
|
2023-04-04 19:24:13 -04:00
|
|
|
this.pluginHelper.getUrl(`https://api.xdefcon.com/proxy/check/?ip=${origin.IPAddressString}`, headers,
|
|
|
|
(response) => this.onVpnResponse(response, origin));
|
|
|
|
|
2023-02-11 22:02:20 -05:00
|
|
|
} catch (ex) {
|
2023-04-04 19:24:13 -04:00
|
|
|
this.logger.logWarning('There was a problem checking client IP ({IP}) for VPN - {message}',
|
|
|
|
origin.IPAddressString, ex.message);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onVpnResponse: function (response, origin) {
|
|
|
|
let parsedJSON = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
parsedJSON = JSON.parse(response);
|
|
|
|
} catch {
|
|
|
|
this.logger.logWarning('There was a problem checking client IP ({IP}) for VPN - {message}',
|
|
|
|
origin.IPAddressString, response);
|
|
|
|
return;
|
2023-02-11 22:02:20 -05:00
|
|
|
}
|
|
|
|
|
2023-04-04 19:24:13 -04:00
|
|
|
const usingVPN = parsedJSON.success && parsedJSON.proxy;
|
|
|
|
|
2023-02-11 22:02:20 -05:00
|
|
|
if (usingVPN) {
|
2023-04-04 19:24:13 -04:00
|
|
|
this.logger.logInformation('{origin} is using a VPN ({ip})', origin.toString(), origin.IPAddressString);
|
2023-02-11 22:02:20 -05:00
|
|
|
const contactUrl = this.manager.getApplicationSettings().configuration().contactUri;
|
|
|
|
let additionalInfo = '';
|
|
|
|
if (contactUrl) {
|
|
|
|
additionalInfo = this.translations['SERVER_KICK_VPNS_NOTALLOWED_INFO'] + ' ' + contactUrl;
|
|
|
|
}
|
|
|
|
origin.kick(this.translations['SERVER_KICK_VPNS_NOTALLOWED'] + ' ' + additionalInfo, origin.currentServer.asConsoleClient());
|
|
|
|
} else {
|
2023-04-04 19:24:13 -04:00
|
|
|
this.logger.logDebug('{Client} is not using a VPN', origin);
|
2023-02-11 22:02:20 -05:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2023-04-04 19:24:13 -04:00
|
|
|
getClientsData: function (clientIds) {
|
2023-02-11 22:02:20 -05:00
|
|
|
const contextFactory = this.serviceResolver.resolveService('IDatabaseContextFactory');
|
|
|
|
const context = contextFactory.createContext(false);
|
|
|
|
const clientSet = context.clients;
|
|
|
|
const clients = clientSet.getClientsBasicData(clientIds);
|
|
|
|
context.dispose();
|
|
|
|
|
|
|
|
return clients;
|
2018-09-04 13:40:29 -04:00
|
|
|
}
|
2022-03-23 14:34:04 -04:00
|
|
|
};
|