From 0036d4448c790a457fe0910164b248ce79f721bc Mon Sep 17 00:00:00 2001 From: "Jonny_Bro (Nikita)" <48434875+JonnyBro@users.noreply.github.com> Date: Sat, 25 Jan 2025 19:47:47 +0500 Subject: [PATCH] refactor: move functions fix: locked accounts was not implemented lol --- index.js | 125 +----------------- package.json | 12 +- pnpm-lock.yaml | 307 +++++++++++++++++++++++++-------------------- routes/api.js | 56 +++++---- routes/index.js | 2 +- routes/key.js | 4 +- routes/stats.js | 2 +- utils/functions.js | 141 ++++++++++++++++++++- 8 files changed, 350 insertions(+), 299 deletions(-) diff --git a/index.js b/index.js index 682f66a..d1b33cc 100644 --- a/index.js +++ b/index.js @@ -6,8 +6,7 @@ const express = require("express"), passport = require("passport"), SteamStrategy = require("passport-steam").Strategy; -const { JsonDB, Config } = require("node-json-db"), - { generateRandomString, log } = require("./utils/functions"); +const { JsonDB, Config } = require("node-json-db"); const config = require("./config"); const db = new JsonDB(new Config(`data/${config.production ? "main" : "test"}_db`, true, true, "/")); @@ -110,130 +109,8 @@ db.getData("/admins").then(data => { app.locals = { config: config, db: db, - getKey: getKey, - isRatelimited: isRatelimited, - isMultiAccount: isMultiAccount, }; -/** - * Gets a user's key from the database. - * - * Checks if the user already has a key, and returns it if so. - * Otherwise generates a new one. - * - * @param {Object | string} user The user object or SteamID64. - * @returns {Promise} The user's key. - */ -async function getKey(user) { - user = typeof user === "string" ? user : user.steamid; - - const keys = await db.getData("/keys"); - const key = keys[user]; - - if (key) { - await log( - `[KEY] User logged in (SteamID: ${user}, Key ${key}).`, - `[KEY] User logged in (SteamID: \`${user}\`, Key \`${key}\`).`, - ); - return key; - } else return await _createKey(user); -} - -/** - * Creates a new unique key for the given user and saves it to the database. - * - * @param {Object | string} user The OpenID Steam user object or SteamID64. - * @returns {Promise} The new unique key generated for the user. - */ -async function _createKey(user) { - user = typeof user === "string" ? user : user.steamid; - - const keys = await db.getData("/keys"); - const key = generateRandomString(); - const isFound = keys[key]; - - if (!isFound) { - keys[user] = key; - - const now = Date.now(); - - await log( - `[KEY] New user (SteamID: ${user}, Key: ${key}, TimeCreated: ${new Date(now).toLocaleString("ru-RU")}).`, - `[KEY] New user (SteamID: \`${user}\`, Key: \`${key}\`, TimeCreated: ).`, - ); - await db.push("/keys", keys); - - return key; - } else return await _createKey(user); -} - -/** - * Checks if an IP address is currently rate limited. - * - * Gets the current rate limits from the database. - * If the IP already has a recent rate limit, returns true. - * Otherwise, saves a new rate limit for the IP and returns false. - * - * @param {string} ip The IP address to check - * @returns {Promise} Whether the IP is currently rate limited - */ -async function isRatelimited(ip) { - const rateLimits = await db.getData("/ratelimits"); - - if (rateLimits[ip] && Date.now() - rateLimits[ip] <= config.rateLimitTime) return true; - - rateLimits[ip] = Date.now(); - - await db.push("/ratelimits", rateLimits); - - return false; -} - -/** - * Checks if a user is using multiple accounts based on their IP address and Steam ID. - * - * Retrieves the locked accounts and user records from the database. If the user's account is locked, returns true. - * Otherwise, checks if the user has changed their IP address more than the configured `ipChangeTime`. If so, clears the - * user's IP address history and locks the account if the user has changed their IP more than 3 times. Updates the - * database with the new account status and returns the result. - * - * @param {string} ip The IP address of the user. - * @param {string} steamid The Steam ID of the user. - * @returns {Promise} Whether the user is using multiple accounts. - */ -async function isMultiAccount(ip, steamid) { - const locked = await db.getData("/locked"); - const records = await db.getData("/records"); - - if (locked[steamid]) return true; - - if (!records[steamid]) - records[steamid] = { - ips: { - [ip]: true, - }, - lastchanged: Date.now(), - }; - - // Clear IPs if the user has changed their ip more than ipChangeTime - if (Date.now() - records[steamid]["lastchanged"] > config.ipChangeTime) { - records[steamid]["ips"] = []; - records[steamid]["lastchanged"] = Date.now(); - } - - // Lock account if the user changed their IP more than 3 time in ipChangeTime - if (Object.keys(records[steamid]["ips"]).length > 2) { - locked[steamid] = true; - - await db.push("/locked", locked); - return true; - } - - await db.push("/records", records); - - return false; -} - // HTTP server const http = require("http"); diff --git a/package.json b/package.json index d2cc105..ce4622c 100644 --- a/package.json +++ b/package.json @@ -7,16 +7,16 @@ "start": "node ." }, "dependencies": { - "daisyui": "^4.11.1", + "daisyui": "^4.12.23", "ejs": "^2.6.1", - "express": "^4.16.1", - "express-session": "^1.18.0", + "express": "^4.21.2", + "express-session": "^1.18.1", "formidable": "^2.1.2", "lzma": "^2.3.2", - "mongoose": "^8.9.0", - "morgan": "^1.9.1", + "mongoose": "^8.9.5", + "morgan": "^1.10.0", "node-fetch": "^2.7.0", - "node-json-db": "^2.3.0", + "node-json-db": "^2.3.1", "open-graph-scraper": "^6.5.0", "passport": "^0.7.0", "passport-steam": "^1.0.18" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c6b0b6f..36b49b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,17 +9,17 @@ importers: .: dependencies: daisyui: - specifier: ^4.11.1 - version: 4.11.1(postcss@8.4.36) + specifier: ^4.12.23 + version: 4.12.23(postcss@8.4.36) ejs: specifier: ^2.6.1 version: 2.6.2 express: - specifier: ^4.16.1 - version: 4.16.4 + specifier: ^4.21.2 + version: 4.21.2 express-session: - specifier: ^1.18.0 - version: 1.18.0 + specifier: ^1.18.1 + version: 1.18.1 formidable: specifier: ^2.1.2 version: 2.1.2 @@ -27,17 +27,17 @@ importers: specifier: ^2.3.2 version: 2.3.2 mongoose: - specifier: ^8.9.0 - version: 8.9.0 + specifier: ^8.9.5 + version: 8.9.5 morgan: - specifier: ^1.9.1 - version: 1.9.1 + specifier: ^1.10.0 + version: 1.10.0 node-fetch: specifier: ^2.7.0 version: 2.7.0 node-json-db: - specifier: ^2.3.0 - version: 2.3.0 + specifier: ^2.3.1 + version: 2.3.1 open-graph-scraper: specifier: ^6.5.0 version: 6.5.0 @@ -224,9 +224,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - body-parser@1.18.3: - resolution: {integrity: sha512-YQyoqQG3sO8iCmf8+hyVpgHHOv0/hCEFiS4zTGUwTA1HjAFX66wRcNQrVCeJq9pgESMRvUAOvSil5MJlmccuKQ==} - engines: {node: '>= 0.8'} + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -245,8 +245,8 @@ packages: resolution: {integrity: sha512-P92xmHDQjSKPLHqFxefqMxASNq/aWJMEZugpCjf+AF/pgcUpMMQCg7t7+ewko0/u8AapvF3luf/FoehddEK+sA==} engines: {node: '>=16.20.1'} - bytes@3.0.0: - resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} call-bind@1.0.7: @@ -297,8 +297,8 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - content-disposition@0.5.2: - resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} content-type@1.0.5: @@ -311,12 +311,12 @@ packages: cookie-signature@1.0.7: resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} - cookie@0.3.1: - resolution: {integrity: sha512-+IJOX0OqlHCszo2mBUq+SrEbCj6w7Kpffqx60zYbPTFaO4+yYgRjHwcZNpWvaTylDHaV7PPmBHzSecZiMhtPgw==} + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} - cookie@0.6.0: - resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} cross-spawn@7.0.3: @@ -342,8 +342,8 @@ packages: resolution: {integrity: sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - daisyui@4.11.1: - resolution: {integrity: sha512-obT9CUbQdW6eoHwSeT5VwaRrWlwrM4OT5qlfdJ0oQlSIEYhwnEl2+L2fwu5PioLbitwuMdYC2X8I1cyy8Pf6LQ==} + daisyui@4.12.23: + resolution: {integrity: sha512-EM38duvxutJ5PD65lO/AFMpcw+9qEy6XAZrTpzp7WyaPeO/l+F/Qiq0ECHHmFNcFXh5aVoALY4MGrrxtCiaQCQ==} engines: {node: '>=16.9.0'} debug@2.6.9: @@ -374,16 +374,13 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - depd@1.1.2: - resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} - engines: {node: '>= 0.6'} - depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} - destroy@1.0.4: - resolution: {integrity: sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==} + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} dezalgo@1.0.4: resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} @@ -431,6 +428,10 @@ packages: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -487,12 +488,12 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - express-session@1.18.0: - resolution: {integrity: sha512-m93QLWr0ju+rOwApSsyso838LQwgfs44QtOP/WBiwtAgPIo/SAh1a5c6nn2BR6mFNZehTpqKDESzP+fRHVbxwQ==} + express-session@1.18.1: + resolution: {integrity: sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==} engines: {node: '>= 0.8.0'} - express@4.16.4: - resolution: {integrity: sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==} + express@4.21.2: + resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} fast-deep-equal@3.1.3: @@ -522,8 +523,8 @@ packages: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} - finalhandler@1.1.1: - resolution: {integrity: sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==} + finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} find-up@5.0.0: @@ -632,12 +633,12 @@ packages: htmlparser2@8.0.2: resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} - http-errors@1.6.3: - resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} - engines: {node: '>= 0.6'} + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} - iconv-lite@0.4.23: - resolution: {integrity: sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==} + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} iconv-lite@0.6.3: @@ -662,6 +663,9 @@ packages: inherits@2.0.3: resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -761,8 +765,8 @@ packages: memory-pager@1.5.0: resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} - merge-descriptors@1.0.1: - resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} @@ -784,8 +788,9 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - mime@1.4.1: - resolution: {integrity: sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==} + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} hasBin: true minimatch@3.1.2: @@ -829,12 +834,12 @@ packages: socks: optional: true - mongoose@8.9.0: - resolution: {integrity: sha512-b58zY3PLNBcoz6ZXFckr0leJcVVBMAOBvD+7Bj2ZjghAwntXmNnqwlDixTKQU3UYoQIGTv+AQx/0ThsvaeVrCA==} + mongoose@8.9.5: + resolution: {integrity: sha512-SPhOrgBm0nKV3b+IIHGqpUTOmgVL5Z3OO9AwkFEmvOZznXTvplbomstCnPOGAyungtRXE5pJTgKpKcZTdjeESg==} engines: {node: '>=16.20.1'} - morgan@1.9.1: - resolution: {integrity: sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==} + morgan@1.10.0: + resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==} engines: {node: '>= 0.8.0'} mpath@0.9.0: @@ -878,8 +883,8 @@ packages: encoding: optional: true - node-json-db@2.3.0: - resolution: {integrity: sha512-B8T+w4q6zXZ20YcfQINLSjMGgImRKzkvR0ShYYoNRdLxtMhVvbzaMBzNdEaRcCjilW/lKS+g9CwVXNoK5uTncw==} + node-json-db@2.3.1: + resolution: {integrity: sha512-cZ0HGQuMUhMg9iGLgS7eOGK5gGYfAyEsPXMD16mcHvUNhMMtOxgUAMuiCED7qh+c2IOeN/6l9FXUCgnGtXvLIA==} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -903,6 +908,10 @@ packages: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + on-headers@1.0.2: resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} engines: {node: '>= 0.8'} @@ -975,8 +984,8 @@ packages: resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} engines: {node: '>=16 || 14 >=14.17'} - path-to-regexp@0.1.7: - resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} pause@0.0.1: resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} @@ -1061,6 +1070,10 @@ packages: resolution: {integrity: sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==} engines: {node: '>=0.6'} + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + qs@6.5.2: resolution: {integrity: sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==} engines: {node: '>=0.6'} @@ -1076,8 +1089,8 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - raw-body@2.3.3: - resolution: {integrity: sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==} + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} read-cache@1.0.0: @@ -1118,20 +1131,20 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - send@0.16.2: - resolution: {integrity: sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==} + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} - serve-static@1.13.2: - resolution: {integrity: sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==} + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} - setprototypeof@1.1.0: - resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} @@ -1159,13 +1172,9 @@ packages: sparse-bitfield@3.0.3: resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} - statuses@1.4.0: - resolution: {integrity: sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==} - engines: {node: '>= 0.6'} - - statuses@1.5.0: - resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} - engines: {node: '>= 0.6'} + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} steam-web@0.4.0: resolution: {integrity: sha512-FgSYhL7GaP4Va5JKT09yZ+WrTZttFtLwenIPuZd7GUA1z3W7vu7qqPT/qTC76Pd9+sf85txMgIPr/y0+3gNlUA==} @@ -1223,6 +1232,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -1478,18 +1491,20 @@ snapshots: binary-extensions@2.3.0: {} - body-parser@1.18.3: + body-parser@1.20.3: dependencies: - bytes: 3.0.0 + bytes: 3.1.2 content-type: 1.0.5 debug: 2.6.9 - depd: 1.1.2 - http-errors: 1.6.3 - iconv-lite: 0.4.23 - on-finished: 2.3.0 - qs: 6.5.2 - raw-body: 2.3.3 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 2.5.2 type-is: 1.6.18 + unpipe: 1.0.0 transitivePeerDependencies: - supports-color @@ -1510,7 +1525,7 @@ snapshots: bson@6.10.1: {} - bytes@3.0.0: {} + bytes@3.1.2: {} call-bind@1.0.7: dependencies: @@ -1576,7 +1591,9 @@ snapshots: concat-map@0.0.1: {} - content-disposition@0.5.2: {} + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 content-type@1.0.5: {} @@ -1584,9 +1601,9 @@ snapshots: cookie-signature@1.0.7: {} - cookie@0.3.1: {} + cookie@0.7.1: {} - cookie@0.6.0: {} + cookie@0.7.2: {} cross-spawn@7.0.3: dependencies: @@ -1613,7 +1630,7 @@ snapshots: culori@3.3.0: {} - daisyui@4.11.1(postcss@8.4.36): + daisyui@4.12.23(postcss@8.4.36): dependencies: css-selector-tokenizer: 0.8.0 culori: 3.3.0 @@ -1640,11 +1657,9 @@ snapshots: delayed-stream@1.0.0: {} - depd@1.1.2: {} - depd@2.0.0: {} - destroy@1.0.4: {} + destroy@1.2.0: {} dezalgo@1.0.4: dependencies: @@ -1689,6 +1704,8 @@ snapshots: encodeurl@1.0.2: {} + encodeurl@2.0.0: {} + entities@4.5.0: {} es-define-property@1.0.0: @@ -1771,9 +1788,9 @@ snapshots: etag@1.8.1: {} - express-session@1.18.0: + express-session@1.18.1: dependencies: - cookie: 0.6.0 + cookie: 0.7.2 cookie-signature: 1.0.7 debug: 2.6.9 depd: 2.0.0 @@ -1784,35 +1801,36 @@ snapshots: transitivePeerDependencies: - supports-color - express@4.16.4: + express@4.21.2: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.18.3 - content-disposition: 0.5.2 + body-parser: 1.20.3 + content-disposition: 0.5.4 content-type: 1.0.5 - cookie: 0.3.1 + cookie: 0.7.1 cookie-signature: 1.0.6 debug: 2.6.9 - depd: 1.1.2 - encodeurl: 1.0.2 + depd: 2.0.0 + encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 1.1.1 + finalhandler: 1.3.1 fresh: 0.5.2 - merge-descriptors: 1.0.1 + http-errors: 2.0.0 + merge-descriptors: 1.0.3 methods: 1.1.2 - on-finished: 2.3.0 + on-finished: 2.4.1 parseurl: 1.3.3 - path-to-regexp: 0.1.7 + path-to-regexp: 0.1.12 proxy-addr: 2.0.7 - qs: 6.5.2 + qs: 6.13.0 range-parser: 1.2.1 - safe-buffer: 5.1.2 - send: 0.16.2 - serve-static: 1.13.2 - setprototypeof: 1.1.0 - statuses: 1.4.0 + safe-buffer: 5.2.1 + send: 0.19.0 + serve-static: 1.16.2 + setprototypeof: 1.2.0 + statuses: 2.0.1 type-is: 1.6.18 utils-merge: 1.0.1 vary: 1.1.2 @@ -1847,14 +1865,14 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@1.1.1: + finalhandler@1.3.1: dependencies: debug: 2.6.9 - encodeurl: 1.0.2 + encodeurl: 2.0.0 escape-html: 1.0.3 - on-finished: 2.3.0 + on-finished: 2.4.1 parseurl: 1.3.3 - statuses: 1.4.0 + statuses: 2.0.1 unpipe: 1.0.0 transitivePeerDependencies: - supports-color @@ -1969,14 +1987,15 @@ snapshots: domutils: 3.1.0 entities: 4.5.0 - http-errors@1.6.3: + http-errors@2.0.0: dependencies: - depd: 1.1.2 - inherits: 2.0.3 - setprototypeof: 1.1.0 - statuses: 1.5.0 + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 - iconv-lite@0.4.23: + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -2000,6 +2019,8 @@ snapshots: inherits@2.0.3: {} + inherits@2.0.4: {} + ipaddr.js@1.9.1: {} is-binary-path@2.1.0: @@ -2073,7 +2094,7 @@ snapshots: memory-pager@1.5.0: {} - merge-descriptors@1.0.1: {} + merge-descriptors@1.0.3: {} merge2@1.4.1: {} @@ -2090,7 +2111,7 @@ snapshots: dependencies: mime-db: 1.52.0 - mime@1.4.1: {} + mime@1.6.0: {} minimatch@3.1.2: dependencies: @@ -2113,7 +2134,7 @@ snapshots: bson: 6.10.1 mongodb-connection-string-url: 3.0.1 - mongoose@8.9.0: + mongoose@8.9.5: dependencies: bson: 6.10.1 kareem: 2.6.3 @@ -2132,11 +2153,11 @@ snapshots: - socks - supports-color - morgan@1.9.1: + morgan@1.10.0: dependencies: basic-auth: 2.0.1 debug: 2.6.9 - depd: 1.1.2 + depd: 2.0.0 on-finished: 2.3.0 on-headers: 1.0.2 transitivePeerDependencies: @@ -2172,7 +2193,7 @@ snapshots: dependencies: whatwg-url: 5.0.0 - node-json-db@2.3.0: + node-json-db@2.3.1: dependencies: rwlock: 5.0.0 @@ -2192,6 +2213,10 @@ snapshots: dependencies: ee-first: 1.1.1 + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + on-headers@1.0.2: {} once@1.4.0: @@ -2273,7 +2298,7 @@ snapshots: lru-cache: 10.2.0 minipass: 7.0.4 - path-to-regexp@0.1.7: {} + path-to-regexp@0.1.12: {} pause@0.0.1: {} @@ -2339,6 +2364,10 @@ snapshots: dependencies: side-channel: 1.0.6 + qs@6.13.0: + dependencies: + side-channel: 1.0.6 + qs@6.5.2: {} queue-microtask@1.2.3: {} @@ -2347,11 +2376,11 @@ snapshots: range-parser@1.2.1: {} - raw-body@2.3.3: + raw-body@2.5.2: dependencies: - bytes: 3.0.0 - http-errors: 1.6.3 - iconv-lite: 0.4.23 + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 unpipe: 1.0.0 read-cache@1.0.0: @@ -2388,30 +2417,30 @@ snapshots: safer-buffer@2.1.2: {} - send@0.16.2: + send@0.19.0: dependencies: debug: 2.6.9 - depd: 1.1.2 - destroy: 1.0.4 + depd: 2.0.0 + destroy: 1.2.0 encodeurl: 1.0.2 escape-html: 1.0.3 etag: 1.8.1 fresh: 0.5.2 - http-errors: 1.6.3 - mime: 1.4.1 - ms: 2.0.0 - on-finished: 2.3.0 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 range-parser: 1.2.1 - statuses: 1.4.0 + statuses: 2.0.1 transitivePeerDependencies: - supports-color - serve-static@1.13.2: + serve-static@1.16.2: dependencies: - encodeurl: 1.0.2 + encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 0.16.2 + send: 0.19.0 transitivePeerDependencies: - supports-color @@ -2424,7 +2453,7 @@ snapshots: gopd: 1.0.1 has-property-descriptors: 1.0.2 - setprototypeof@1.1.0: {} + setprototypeof@1.2.0: {} shebang-command@2.0.0: dependencies: @@ -2449,9 +2478,7 @@ snapshots: dependencies: memory-pager: 1.5.0 - statuses@1.4.0: {} - - statuses@1.5.0: {} + statuses@2.0.1: {} steam-web@0.4.0: dependencies: @@ -2536,6 +2563,8 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + tr46@0.0.3: {} tr46@4.1.1: diff --git a/routes/api.js b/routes/api.js index f77cea5..9e14a8b 100644 --- a/routes/api.js +++ b/routes/api.js @@ -4,7 +4,7 @@ const express = require("express"), openGraphScraper = require("open-graph-scraper"), lzma = require("lzma"); -const { isAdmin, isUser, isUserGame, generateCode, isCourseFileValid, log } = require("../utils/functions"), +const { isAdmin, isUser, isUserGame, isRatelimited, isMultiAccount, isLocked, getKey, generateCode, isCourseFileValid, log } = require("../utils/functions"), { formidable } = require("formidable"); router.post("/", isUser, async (req, res) => { @@ -21,19 +21,21 @@ router.get("/download", isUserGame, async (req, res) => { if (!headers.map) return res.status(401).json({ res: res.statusCode, message: "No map provided. Please provide a valid map." }); const ip = headers["cf-connecting-ip"] || "Unknown"; + const db = req.app.locals.db; const key = headers.authorization; - const keys = await req.app.locals.db.getData("/keys"); + const keys = await db.getData("/keys"); const steamIds = Object.fromEntries(Object.entries(keys).map(([k, v]) => [v, k])); const steamid = steamIds[key]; - if (ip !== "Unknown" && (await req.app.locals.isRatelimited(ip))) return res.status(401).json({ res: res.statusCode, message: "Too many requests. Please try again later." }); - if (ip !== "Unknown" && (await req.app.locals.isMultiAccount(ip, steamid))) return res.status(401).json({ res: res.statusCode, message: "Your account was detected as multiaccount. Please open a ticket on our Discord server." }); + if (ip !== "Unknown" && (await isRatelimited(db, ip))) return res.status(401).json({ res: res.statusCode, message: "Too many requests. Please try again later." }); + if (ip !== "Unknown" && (await isMultiAccount(db, ip, steamid))) return res.status(401).json({ res: res.statusCode, message: "Your account was detected as multiaccount. Please open a ticket on our Discord server." }); + if (await isLocked(db, steamid)) return res.status(401).json({ res: res.statusCode, message: "Your account is locked. Please open a ticket on our Discord server." }); let courseData; try { - courseData = await req.app.locals.db.getData(`/courses/${headers.code.toUpperCase()}`); - } catch (e) { + courseData = await db.getData(`/courses/${headers.code.toUpperCase()}`); + } catch { return res.status(401).json({ res: res.statusCode, message: "Invalid course code provided." }); } @@ -49,7 +51,7 @@ router.get("/download", isUserGame, async (req, res) => { if (!courseData.plays) courseData.plays = 1; else courseData.plays++; - await req.app.locals.db.push(`/courses/${headers.code.toUpperCase()}`, courseData); + await db.push(`/courses/${headers.code.toUpperCase()}`, courseData); res.send({ res: res.statusCode, file: file }); }); @@ -61,18 +63,20 @@ router.post("/upload", isUserGame, async (req, res) => { if (headers.mapid === null || headers.mapid === undefined) return res.status(401).json({ res: res.statusCode, message: "No map id provided. Please provide a valid map id." }); const ip = headers["cf-connecting-ip"] || "Unknown"; + const db = req.app.locals.db; const key = headers.authorization; - const keys = await req.app.locals.db.getData("/keys"); + const keys = await db.getData("/keys"); const steamIds = Object.fromEntries(Object.entries(keys).map(([k, v]) => [v, k])); const steamid = steamIds[key]; - if (ip !== "Unknown" && (await req.app.locals.isRatelimited(ip))) return res.status(401).json({ res: res.statusCode, message: "Too many requests. Please try again later." }); - if (ip !== "Unknown" && (await req.app.locals.isMultiAccount(ip, steamid))) return res.status(401).json({ res: res.statusCode, message: "Your account was detected as multiaccount. Please open a ticket on our Discord server." }); + if (ip !== "Unknown" && (await isRatelimited(db, ip))) return res.status(401).json({ res: res.statusCode, message: "Too many requests. Please try again later." }); + if (ip !== "Unknown" && (await isMultiAccount(db, ip, steamid))) return res.status(401).json({ res: res.statusCode, message: "Your account was detected as multiaccount. Please open a ticket on our Discord server." }); + if (await isLocked(db, steamid)) return res.status(401).json({ res: res.statusCode, message: "Your account is locked. Please open a ticket on our Discord server." }); let course = ""; try { course = lzma.decompress(Buffer.from(headers.course, "base64")); - } catch (e) { + } catch { course = Buffer.from(headers.course, "base64").toString("utf-8"); } @@ -90,7 +94,7 @@ router.post("/upload", isUserGame, async (req, res) => { const mapImage = headers.mapid === "0" || headers.mapid === "no_map_id" ? "" : await openGraphScraper({ url: `https://steamcommunity.com/sharedfiles/filedetails/?id=${headers.mapid}` }).then(data => data.result.ogImage[0].url); - await req.app.locals.db.push("/courses", { + await db.push("/courses", { [code]: { map: headers.map, uploader: { @@ -115,9 +119,11 @@ router.post("/upload", isUserGame, async (req, res) => { router.post("/upload_site", isUser, async (req, res) => { const { headers, user } = req; const ip = headers["cf-connecting-ip"] || "Unknown"; + const db = req.app.locals.db; - if (ip !== "Unknown" && (await req.app.locals.isRatelimited(ip))) return res.status(401).json({ res: res.statusCode, message: "Too many requests. Please try again later." }); - if (ip !== "Unknown" && (await req.app.locals.isMultiAccount(ip, user.steamid))) return res.status(401).json({ res: res.statusCode, message: "Your account was detected as multiaccount. Please open a ticket on our Discord server." }); + if (ip !== "Unknown" && (await isRatelimited(db, ip))) return res.status(401).json({ res: res.statusCode, message: "Too many requests. Please try again later." }); + if (ip !== "Unknown" && (await isMultiAccount(db, ip, user.steamid))) return res.status(401).json({ res: res.statusCode, message: "Your account was detected as multiaccount. Please open a ticket on our Discord server." }); + if (await isLocked(db, user.steamid)) return res.status(401).json({ res: res.statusCode, message: "Your account is locked. Please open a ticket on our Discord server." }); const form = formidable({ maxFileSize: 10 * 1024 * 1024 }); @@ -129,7 +135,7 @@ router.post("/upload_site", isUser, async (req, res) => { let course = ""; try { course = lzma.decompress(uploaded); - } catch (e) { + } catch { course = uploaded; } @@ -148,7 +154,7 @@ router.post("/upload_site", isUser, async (req, res) => { const mapid = fields.link.match(/id=(\d+)/)[1]; const mapImage = await openGraphScraper({ url: `https://steamcommunity.com/sharedfiles/filedetails/?id=${mapid}` }).then(data => data.result.ogImage[0].url); - await req.app.locals.db.push("/courses", { + await db.push("/courses", { [code]: { map: fields.map, uploader: { @@ -178,15 +184,17 @@ router.post("/update", isUserGame, async (req, res) => { if (!headers.code) return res.status(401).json({ res: res.statusCode, message: "No code provided. Please provide a valid course code." }); const ip = headers["cf-connecting-ip"] || "Unknown"; + const db = req.app.locals.db; const key = headers.authorization; - const keys = await req.app.locals.db.getData("/keys"); + const keys = await db.getData("/keys"); const steamIds = Object.fromEntries(Object.entries(keys).map(([k, v]) => [v, k])); const steamid = steamIds[key]; - if (ip !== "Unknown" && (await req.app.locals.isRatelimited(ip))) return res.status(401).json({ message: "Too many requests. Please try again later." }); - if (ip !== "Unknown" && (await req.app.locals.isMultiAccount(ip, steamid))) return res.status(401).json({ message: "Your account was detected as multiaccount. Please open a ticket on our Discord server." }); + if (ip !== "Unknown" && (await isRatelimited(db, ip))) return res.status(401).json({ message: "Too many requests. Please try again later." }); + if (ip !== "Unknown" && (await isMultiAccount(db, ip, steamid))) return res.status(401).json({ message: "Your account was detected as multiaccount. Please open a ticket on our Discord server." }); + if (await isLocked(db, steamid)) return res.status(401).json({ res: res.statusCode, message: "Your account is locked. Please open a ticket on our Discord server." }); - const courseData = await req.app.locals.db.getData(`/courses/${headers.code.toUpperCase()}`); + const courseData = await db.getData(`/courses/${headers.code.toUpperCase()}`); if (courseData.map !== headers.map) return res.status(401).json({ res: res.statusCode, message: "Invalid map. You should provide the same map as before." }); if (courseData.uploader.userid !== steamIds[key]) return res.status(401).json({ res: res.statusCode, message: "Invalid key. You are not the uploader of this course. Only the uploader can update their course." }); @@ -194,7 +202,7 @@ router.post("/update", isUserGame, async (req, res) => { let course = ""; try { course = lzma.decompress(Buffer.from(headers.course, "base64")); - } catch (e) { + } catch { course = Buffer.from(headers.course, "base64").toString("utf-8"); } @@ -204,7 +212,7 @@ router.post("/update", isUserGame, async (req, res) => { courseData.time = Date.now(); - await req.app.locals.db.push(`/courses/${headers.code.toUpperCase()}`, courseData); + await db.push(`/courses/${headers.code.toUpperCase()}`, courseData); await log( `[UPDATE] User updated a course (Course: ${headers.code.toUpperCase()}, SteamID: ${steamIds[key]}, Key ${key}).`, @@ -242,7 +250,7 @@ router.post("/admin", isAdmin, async (req, res) => { if (action === "addKey") { if (!target) return res.send({ success: false, message: "Target not provided. Please provide a target." }); - const key = await req.app.locals.getKey(target); + const key = await getKey(req.app.locals.db, target); if (!key) return res.send({ success: false, message: "Internal error. Contact the developer." }); @@ -364,7 +372,7 @@ router.get("/info/:code", async (req, res) => { try { course = await req.app.locals.db.getData(`/courses/${req.params.code.toUpperCase()}`); - } catch (e) { + } catch { return res.status(401).json({ res: res.statusCode, message: "Invalid course code provided." }); } diff --git a/routes/index.js b/routes/index.js index 3a50852..686adc8 100644 --- a/routes/index.js +++ b/routes/index.js @@ -60,7 +60,7 @@ router.get("/", async (req, res) => { let codeFile = ""; try { codeFile = fs.readFileSync(`public/${codeData.path}`, "utf-8"); - } catch (e) { + } catch { return console.log(`[WARNING] Not found file for: ${code}`); } diff --git a/routes/key.js b/routes/key.js index ea4ee75..565972c 100644 --- a/routes/key.js +++ b/routes/key.js @@ -2,7 +2,7 @@ const express = require("express"), router = express.Router(), fetch = require("node-fetch"); -const { sanitize } = require("../utils/functions"); +const { getKey, sanitize } = require("../utils/functions"); router.get("/", async (req, res) => { if (req.user) { @@ -55,7 +55,7 @@ async function registerUser(locals, user) { if (!hasGame(locals, user)) return "Account doesn't have Garry's mod. Make sure your game details are public."; const usernames = await locals.db.getData("/usernames"); - const key = await locals.getKey(user); + const key = await getKey(locals.db, user); const username = sanitize(user.personaname, false, true); usernames[user.steamid] = username || "Unknown"; diff --git a/routes/stats.js b/routes/stats.js index b70124f..5169d91 100644 --- a/routes/stats.js +++ b/routes/stats.js @@ -7,7 +7,7 @@ router.get("/:code", async (req, res) => { try { course = await req.app.locals.db.getData(`/courses/${req.params.code.toUpperCase()}`); - } catch (e) { + } catch { return res.status(401).json({ res: res.statusCode, message: "Invalid course code provided." }); } diff --git a/utils/functions.js b/utils/functions.js index a14f5c0..4dccdff 100644 --- a/utils/functions.js +++ b/utils/functions.js @@ -2,6 +2,35 @@ const fs = require("fs"), config = require("../config"), fetch = require("node-fetch"); +/** + * Creates a new unique key for the given user and saves it to the database. + * + * @param {Object} db The database + * @param {Object | string} user The OpenID Steam user object or SteamID64. + * @returns {Promise} The new unique key generated for the user. + */ +async function createKey(db, user) { + user = typeof user === "string" ? user : user.steamid; + + const keys = await db.getData("/keys"); + const key = generateRandomString(); + const isFound = keys[key]; + + if (!isFound) { + keys[user] = key; + + const now = Date.now(); + + await log( + `[KEY] New user (SteamID: ${user}, Key: ${key}, TimeCreated: ${new Date(now).toLocaleString("ru-RU")}).`, + `[KEY] New user (SteamID: \`${user}\`, Key: \`${key}\`, TimeCreated: ).`, + ); + await db.push("/keys", keys); + + return key; + } else return await createKey(db, user); +} + /** * Middleware function that checks if the current user is an admin. * If the user is not an admin, it redirects them to the "/key" route. @@ -65,6 +94,115 @@ async function isUserGame(req, res, next) { return next(); } +/** + * Checks if an IP address is currently rate limited. + * + * Gets the current rate limits from the database. + * If the IP already has a recent rate limit, returns true. + * Otherwise, saves a new rate limit for the IP and returns false. + * + * @param {Object} db The database + * @param {string} ip The IP address to check + * @returns {Promise} Whether the IP is currently rate limited + */ +async function isRatelimited(db, ip) { + const rateLimits = await db.getData("/ratelimits"); + + if (rateLimits[ip] && Date.now() - rateLimits[ip] <= config.rateLimitTime) return true; + + rateLimits[ip] = Date.now(); + + await db.push("/ratelimits", rateLimits); + + return false; +} + +/** + * Checks if a user is using multiple accounts based on their IP address and Steam ID. + * + * Retrieves the locked accounts and user records from the database. If the user's account is locked, returns true. + * Otherwise, checks if the user has changed their IP address more than the configured `ipChangeTime`. If so, clears the + * user's IP address history and locks the account if the user has changed their IP more than 3 times. Updates the + * database with the new account status and returns the result. + * + * @param {Object} db The database + * @param {string} ip The IP address of the user. + * @param {string} steamid The Steam ID of the user. + * @returns {Promise} Whether the user is using multiple accounts. + */ +async function isMultiAccount(db, ip, steamid) { + const locked = await db.getData("/locked"); + const records = await db.getData("/records"); + + if (!records[steamid]) + records[steamid] = { + ips: { + [ip]: true, + }, + lastchanged: Date.now(), + }; + + // Clear IPs if the user has changed their ip more than ipChangeTime + if (Date.now() - records[steamid]["lastchanged"] > config.ipChangeTime) { + records[steamid] = { + ips: {}, + lastchanged: Date.now(), + }; + } + + // Lock account if the user changed their IP more than 3 time in ipChangeTime + if (Object.keys(records[steamid]["ips"]).length > 2) { + locked[steamid] = true; + await db.push("/locked", locked); + + return true; + } + + await db.push("/records", records); + + return false; +} + +/** + * Checks if user is locked from using the app. + * + * @param {Object} db The database + * @param {string} steamid The Steam ID of the user. + * @returns {Promise} Whether the user is locked. + */ +async function isLocked(db, steamid) { + const locks = await db.getData("/locked"); + + if (locks[steamid]) return true; + + return false; +} + +/** + * Gets a user's key from the database. + * + * Checks if the user already has a key, and returns it if so. + * Otherwise generates a new one. + * + * @param {Object} db The database + * @param {Object | string} user The user object or SteamID64. + * @returns {Promise} The user's key. + */ +async function getKey(db, user) { + user = typeof user === "string" ? user : user.steamid; + + const keys = await db.getData("/keys"); + const key = keys[user]; + + if (key) { + await log( + `[KEY] User logged in (SteamID: ${user}, Key ${key}).`, + `[KEY] User logged in (SteamID: \`${user}\`, Key \`${key}\`).`, + ); + return key; + } else return await createKey(db, user); +} + /** * Generates a random string of the given length. * @@ -134,7 +272,6 @@ function isCourseFileValid(content) { return true; } - /** * Logs a message to a log file and optionally sends it to a Discord webhook. * @@ -162,4 +299,4 @@ async function log(logs_message, discord_message) { }); } -module.exports = { isAdmin, isUser, isUserGame, generateRandomString, generateCode, sanitize, isCourseFileValid, log }; +module.exports = { isAdmin, isUser, isUserGame, isRatelimited, isMultiAccount, isLocked, getKey, generateRandomString, generateCode, sanitize, isCourseFileValid, log };