forked from PhlexPlexico/G5API
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.js
More file actions
375 lines (350 loc) · 12.2 KB
/
utils.js
File metadata and controls
375 lines (350 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
/** Utilities class for help with basic functions.
* @module utils
*/
/** AES Module for Encryption/Decryption
* @const
*/
const aes = require("aes-js");
/** Crypto for assigning random */
const crypto = require("crypto");
/** Config to get database key.
* @const
*/
const config = require("config");
/** Steam API Handler for custom URLs
* @const
*/
const SteamURLResolver = require("steamapi");
const SteamAPI = new SteamURLResolver(config.get("server.steamAPIKey"));
/** Steam ID Handler for other IDs.
* @const
*/
const SteamIDResolver = require("@node-steam/id");
const db = require("../db");
class Utils {
/** Function to get an HLTV rating for a user.
* @function
* @memberof module:utils
* @param {integer} [kills=0] - Amount of kills.
* @param {integer} [roundsplayed=0] - Amount of rounds played.
* @param {integer} [deaths=0] - Amount of deaths.
* @param {integer} [k1=0] - Amount of 1-kill rounds.
* @param {integer} [k2=0] - Amount of 2-kill rounds.
* @param {integer} [k3=0] - Amount of 3-kill rounds.
* @param {integer} [k4=0] - Amount of 4-kill rounds.
* @param {integer} [k5=0] - Amount of 5-kill rounds.
* @function
* @static */
static getRating(
kills = 0,
roundsplayed = 0,
deaths = 0,
k1 = 0,
k2 = 0,
k3 = 0,
k4 = 0,
k5 = 0
) {
try {
let AverageKPR = 0.679;
let AverageSPR = 0.317;
let AverageRMK = 1.277;
let KillRating =
roundsplayed === 0 ? 0 : kills / roundsplayed / AverageKPR;
let SurvivalRating =
roundsplayed === 0
? 0
: (roundsplayed - deaths) / roundsplayed / AverageSPR;
let killcount = k1 + 4 * k2 + 9 * k3 + 16 * k4 + 25 * k5;
let RoundsWithMultipleKillsRating =
roundsplayed === 0 ? 0 : killcount / roundsplayed / AverageRMK;
let rating =
(KillRating + 0.7 * SurvivalRating + RoundsWithMultipleKillsRating) /
2.7;
return rating.toFixed(2);
} catch (err) {
console.log("HELPER getRating Failed -- " + err);
return 0;
}
}
/** Inner function - Supports encryption and decryption for the database keys to get server RCON passwords.
* @name decrypt
* @function
* @inner
* @memberof module:utils
* @param {string} source - The source to be decrypted.
*/
static decrypt(source) {
try {
if (source === null) return;
let byteSource = aes.utils.hex.toBytes(source.substring(32));
let IV = aes.utils.hex.toBytes(source.substring(0, 32));
let key = aes.utils.utf8.toBytes(config.get("server.dbKey"));
let aesCbc = new aes.ModeOfOperation.ofb(key, IV);
let decryptedBytes = aesCbc.decrypt(byteSource);
let decryptedText = aes.utils.utf8.fromBytes(decryptedBytes);
return decryptedText;
} catch (err) {
console.error(err);
// fail silently.
return null;
}
}
/** Inner function - Supports encryption and decryption for the database keys to get server RCON passwords.
* @name encrypt
* @function
* @inner
* @memberof module:utils
* @param {string} source - The source to be decrypted.
*/
static encrypt(source) {
try {
if (source === null) return;
let byteSource = aes.utils.utf8.toBytes(source);
let IV = crypto.randomBytes(16);
let key = aes.utils.utf8.toBytes(config.get("server.dbKey"));
let aesCbc = new aes.ModeOfOperation.ofb(key, IV);
let encryptedBytes = aesCbc.encrypt(byteSource);
let encryptedHex = aes.utils.hex.fromBytes(encryptedBytes);
let hexIV = aes.utils.hex.fromBytes(IV);
encryptedHex = hexIV + encryptedHex;
return encryptedHex;
} catch (err) {
console.error(err);
throw err;
}
}
/** Ensures the user was authenticated through steam OAuth.
* @function
* @memberof module:utils
* @inner */
static async ensureAuthenticated(req, res, next) {
// Check the user based on API.
const apiKey = req.get("user-api") || req.body[0]?.user_api;
const userId = req.get("user-id") || req.body[0]?.user_id;
if (apiKey && userId) {
let sqlQuery = "SELECT * FROM user WHERE id = ?";
const ourUser = await db.query(sqlQuery, userId);
if (ourUser.length > 0) {
let uncDb = await Utils.decrypt(ourUser[0].api_key);
if (uncDb == apiKey) {
let curUser = {
steam_id: ourUser[0].steam_id,
name: ourUser[0].name,
super_admin: ourUser[0].super_admin,
admin: ourUser[0].admin,
id: ourUser[0].id,
small_image: ourUser[0].small_image,
medium_image: ourUser[0].medium_image,
large_image: ourUser[0].large_image,
};
req.user = curUser;
return next();
}
}
}
if (req.isAuthenticated()) {
return next();
}
res.redirect("auth/steam");
}
/** Checks if a user is an admin in the system during their session.
* @function
* @memberof module:utils
* @inner
* @name adminCheck
* @param {user} User - the users session object.
*/
static adminCheck(user) {
if (user) return user.super_admin === 0 && user.admin === 0 ? false : true;
else return false;
}
/** Checks if a user is a super admin in the system during their session.
* @function
* @memberof module:utils
* @inner
* @name superAdminCheck
* @param {user} User - the users session object.
*/
static superAdminCheck(user) {
if (user) return user.super_admin === 0 ? false : true;
else return false;
}
/** Converts a Steam ID to Steam64.
* @function
* @memberof module:utils
* @inner
* @name convertToSteam64
* @param {String} anySteamID - String value of a Steam ID.
* @returns A string representing a 64bit steam ID, or nothing if the profile is not found.
*/
static async convertToSteam64(anySteamID) {
//console.log(anySteamID);
const steam64ID = new SteamIDResolver.ID(anySteamID);
if (steam64ID.isValid() && steam64ID.getType() == "INDIVIDUAL")
return steam64ID.get64();
else return "";
}
/** Retrieves a Steam64 value from a given value.
* @function
* @memberof module:utils
* @inner
* @name getSteamPID
* @param {String} authString - String value of a steam username/profile/url/steamID.
* @returns A Steam64 value in string form, or nothing.
*/
static async getSteamPID(authString) {
// Remove any https tags, as they aren't needed.
authString = authString.replace(new RegExp("^(http|https)://", "i"), "");
if (authString.includes("steamcommunity.com/id/")) {
let steamID = await SteamAPI.resolve(authString);
return steamID;
} else if (authString.includes("steamcommunity.com/profiles/")) {
return authString.split("/")[2];
} else if (authString.startsWith("STEAM_")) {
return this.convertToSteam64(authString);
} else if (authString.startsWith("1:0:") || authString.startsWith("1:1:")) {
return this.convertToSteam64("STEAM_" + authString);
} else if (authString.startsWith("[U:1:")) {
return this.convertToSteam64(authString);
} else if (authString.startsWith("U:1:")) {
return this.convertToSteam64("[" + authString + "]");
} else if (authString.startsWith("7656119")) {
return authString;
} else {
let steamID = await SteamAPI.resolve(
"steamcommunity.com/id/" + authString
);
return steamID;
}
}
/** Retrieves a profile name from steam.
* @function
* @memberof module:utils
* @inner
* @name getSteamName
* @param {String} auth64 - String value of a steam 64 ID.
* @returns A username from a given Steam64 ID.
*/
static async getSteamName(auth64) {
let summaryInfo = await SteamAPI.getUserSummary(auth64);
return summaryInfo.nickname;
}
/** Retrieves a profile image from steam.
* @function
* @memberof module:utils
* @inner
* @name getSteamImage
* @param {String} auth64 - String value of a steam 64 ID.
* @returns A profile image link.
*/
static async getSteamImage(auth64) {
let summaryInfo = await SteamAPI.getUserSummary(auth64);
return summaryInfo.avatar.medium;
}
/** Checks whether or not a user has access to edit a match. Also checks if a match is currently being played.
* @function
* @memberof module:utils
* @inner
* @name getUserMatchAccess
* @param {String} matchid - The ID of a match
* @param {user} user - the users session object.
* @param {boolean} [onlyAdmin = false] - Set to true to only check admin status and not super admin status.
* @param {boolean} [serverCheck = false] - Optional parameter to check if a user owns a server, for elevated calls.
* @returns An object containing the HTTP error, followed by a message.
*/
static async getUserMatchAccess(matchid, user, onlyAdmin = false, serverCheck = false) {
try {
let retMessage = null;
retMessage = await this.getUserMatchAccessNoFinalize(matchid, user, onlyAdmin, serverCheck);
if(retMessage != null)
return retMessage;
let newSingle = await db.getConnection();
await db.withNewTransaction(newSingle, async () => {
let currentMatchInfo = "SELECT cancelled, forfeit, end_time FROM `match` WHERE id = ?";
const matchRow = await newSingle.query(currentMatchInfo, matchid);
if (
matchRow[0][0].cancelled == 1 ||
matchRow[0][0].forfeit == 1 ||
matchRow[0][0].end_time != null
) {
retMessage = {status: 422, message: "Match is already finished."};
}
});
return retMessage;
} catch (err) {
throw err;
}
}
/** Checks whether or not a match exists, ignoring finalized matches.
* @function
* @memberof module:utils
* @inner
* @name getUserMatchAccessNoFinalize
* @param {String} matchid - The ID of a match
* @param {user} user - the users session object.
* @param {boolean} [onlyAdmin = false] - Set to true to only check admin status and not super admin status.
* @param {boolean} [serverCheck = false] - Optional parameter to check if a user owns a server, for elevated calls.
* @returns An object containing the HTTP error, followed by a message.
*/
static async getUserMatchAccessNoFinalize(matchid, user, onlyAdmin = false, serverCheck = false) {
try {
let adminCheck = onlyAdmin ? this.adminCheck(user) : this.superAdminCheck(user);
let retMessage = null;
retMessage = await this.checkIfMatchExists(matchid);
if(retMessage != null)
return retMessage;
let newSingle = await db.getConnection();
await db.withNewTransaction(newSingle, async () => {
let currentMatchInfo = "SELECT user_id, server_id FROM `match` WHERE id = ?";
let currentServerInfo = "SELECT user_id FROM game_server WHERE id = ?"
const matchRow = await newSingle.query(currentMatchInfo, matchid);
const serverRow = await newSingle.query(currentServerInfo, matchRow[0][0].server_id);
if (
matchRow[0][0].user_id != user.id &&
!adminCheck
) {
retMessage = {status: 403, message: "User is not authorized to perform action."};
}
if (serverCheck) {
if (
!this.superAdminCheck(user) &&
serverRow[0][0].user_id != user.id
) {
retMessage = {status: 403, message: "User is not authorized to perform action."};
}
}
});
return retMessage;
} catch (err) {
throw err;
}
}
/** Checks whether or not a match exists.
* @function
* @memberof module:utils
* @inner
* @name checkIfMatchExists
* @param {String} matchid - The ID of a match
* @returns An object containing the HTTP error, followed by a message.
*/
static async checkIfMatchExists(matchid) {
try {
if (matchid == null) {
return {status: 400, message: "Match ID Not Provided"};
}
let newSingle = await db.getConnection();
await db.withNewTransaction(newSingle, async () => {
let currentMatchInfo = "SELECT id FROM `match` WHERE id = ?";
const matchRow = await newSingle.query(currentMatchInfo, matchid);
if (matchRow[0].length === 0) {
return {status: 404, message: "No match found."};
}
});
} catch (err) {
throw err;
}
return null;
}
}
module.exports = Utils;