From b400beea6ab7abab1f7bf117dc6a0fd16bc635f8 Mon Sep 17 00:00:00 2001 From: Junior Date: Tue, 4 Jul 2023 08:25:46 -0300 Subject: [PATCH] *Added onlineCount endpoint *Added launch with PM2 *Code clenaup --- README.md | 7 +- api.png | Bin 47718 -> 67634 bytes package.json | 4 +- rh-api_with_pm2.bat | 4 + src/app.js | 43 +++- src/routes/auth.js | 143 +++++++------- src/routes/billing.js | 226 ++++++++++++---------- src/routes/gateway.js | 4 + src/routes/launcher/changePassword.js | 4 +- src/routes/launcher/codeVerification.js | 2 +- src/routes/launcher/login.js | 23 ++- src/routes/launcher/passwordResetEmail.js | 4 +- src/routes/launcher/register.js | 13 +- src/routes/launcher/verificationEmail.js | 2 +- src/routes/onlineCount.js | 47 +++++ src/utils/dbConfig.js | 25 ++- src/utils/logger.js | 147 +++----------- stop_rh-api_with_pm2.bat | 4 + 18 files changed, 381 insertions(+), 321 deletions(-) create mode 100644 rh-api_with_pm2.bat create mode 100644 src/routes/onlineCount.js create mode 100644 stop_rh-api_with_pm2.bat diff --git a/README.md b/README.md index f547f73..8b57b39 100644 --- a/README.md +++ b/README.md @@ -111,19 +111,20 @@ The api provides the following endpoints: Endpoint | Method | Arguments | Description --- | --- | --- | --- -/serverApi/auth | POST | XML with account, password, game and IP | Authenticates a user based on their account information and sends an XML response with their user ID, user type, and success status. If authentication fails, it sends an XML response with a failure status. +/serverApi/auth | POST | XML with account, password, game and IP | Authenticates a user game login based on their account information and sends an XML response with their user ID, user type, and success status. If authentication fails, it sends an XML response with a failure status. /serverApi/billing | POST | XML with currency-request or item-purchase-request and associated arguments | Handles billing requests. For currency requests, it retrieves the user's Zen balance from the database and sends an XML response with the balance. For item purchase requests, it deducts the cost of the item from the user's Zen balance and logs the transaction in the database. If the transaction is successful, it sends an XML response with the success status. If the transaction fails, it sends an XML response with a failure status and an error message. /serverApi/gateway | GET | | Returns an XML response containing the IP address and port number of the gateway server. /serverApi/gateway/info | GET | | Returns an response containing the gateway endpoint. Used by the **chn** region. /serverApi/gateway/status | GET | | Checks the status of the gateway server by attempting to establish a connection to the server. Returns a JSON object with the status of the server (online or offline) and an HTTP status code indicating the success or failure of the connection attempt. /accountApi/register | POST | windyCode, email, password | Create a new account with the provided windyCode, email, and password. The password is first combined with the windyCode to create an MD5 hash, which is then salted and hashed again using bcrypt before being stored in the database. An email confirmation is sent to the provided email address, and a success or error message is returned. -/accountApi/login | POST | account, password | Authenticates a user account in the launcher by username or email address and password. Return a token if the authentication is successful (currently unsued). +/accountApi/login | POST | account, password | Authenticates a user account in the launcher by username or email address and password. Return a token if the authentication is successful (token is currently unsued). /accountApi/codeVerification | POST | email, verification_code_type, verification_code | Verify a user's email by checking the verification code /accountApi/sendPasswordResetEmail | POST | email | Sends an email with a password reset verification code to the specified email address /accountApi/changePassword | POST | email, password, verification_code | Change the password of a user's account, given the email and password verification code /accountApi/sendVerificationEmail | POST | email | Sends a verification email to the specified email address. /launcherApi/launcherUpdater/getLauncherVersion | GET | | Returns the version of the launcher by reading the launcher_info.ini file. -/launcherApi/launcherUpdater/updateLauncherVersion | POST | version | Downloads the new version of the launcher from the launcher_update folder. +/launcherApi/launcherUpdater/updateLauncherVersion | POST | version | Download the specified launcher versionr from the launcher_update folder. +/serverApi/onlineCount | GET | | Returns the number of online players. Returns a JSON object with the count. ### Preview ![image](api.png) diff --git a/api.png b/api.png index 29e655339a269c929c858a1449e7729917238522..8a4e2df6a1998c0427d7d62bd1ad63ec81062676 100644 GIT binary patch literal 67634 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 literal 47718 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 I6wHJE3;uk300000 diff --git a/package.json b/package.json index ff65e3e..b8f8f70 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "RustyHearts-API", - "version": "1.0.0", + "version": "1.1.0", "description": "Rusty Hearts REST API implementation on node.js", "main": "src/app.js", "scripts": { @@ -35,7 +35,9 @@ "logger": "^0.0.1", "moment-timezone": "^0.5.43", "mssql": "^9.1.1", + "node-cache": "^5.1.2", "nodemailer": "^6.9.1", + "pm2": "^5.3.0", "winston": "^3.8.2", "xml2js": "^0.6.0" }, diff --git a/rh-api_with_pm2.bat b/rh-api_with_pm2.bat new file mode 100644 index 0000000..c43c82a --- /dev/null +++ b/rh-api_with_pm2.bat @@ -0,0 +1,4 @@ +@echo off +title API +cmd /k "npx pm2 start src/app.js --name rh-api && npx pm2 logs rh-api" +pause diff --git a/src/app.js b/src/app.js index 0af9e87..55910a3 100644 --- a/src/app.js +++ b/src/app.js @@ -8,6 +8,7 @@ const cors = require('cors'); const compression = require('compression'); const rateLimit = require('express-rate-limit'); const expressWinston = require('express-winston'); +const moment = require('moment-timezone'); const { logger } = require('./utils/logger'); const path = require('path'); @@ -22,6 +23,7 @@ const passwordResetEmailRouter = require('./routes/launcher/passwordResetEmail') const passwordChangeRouter = require('./routes/launcher/changePassword'); const verificationEmailRouter = require('./routes/launcher/verificationEmail'); const launcherUpdaterRouter = require('./routes/launcher/launcherUpdater'); +const onlineCountRouter = require('./routes/onlineCount'); // Set up rate limiter const limiter = rateLimit({ @@ -60,6 +62,7 @@ app.use('/accountApi/sendPasswordResetEmail', limiter , passwordResetEmailRouter app.use('/accountApi/changePassword', limiter , passwordChangeRouter); app.use('/accountApi/sendVerificationEmail', limiter , verificationEmailRouter); app.use('/launcherApi/launcherUpdater', launcherUpdaterRouter); +app.use('/serverApi/onlineCount', limiter , onlineCountRouter); // Serve static files from public folder app.use(express.static('../public')); @@ -89,14 +92,50 @@ app.use((err, req, res, next) => { } else { logger.info(err.stack); } - res.status(500).send('Something went wrong' + err.stack); + res.status(500).send('A error ocurred. Try again later.'); }); +// Node.js version +const nodeVersion = process.version; + +// timezone +const timezone = process.env.TZ || new Date().toLocaleString('en-US', { timeZoneName: 'short' }); +const offsetInMinutes = moment.tz(timezone).utcOffset(); +const offsetHours = Math.floor(Math.abs(offsetInMinutes) / 60); +const offsetMinutes = Math.abs(offsetInMinutes) % 60; +const offsetSign = offsetInMinutes >= 0 ? '+' : '-'; +const offsetString = `${offsetSign}${offsetHours.toString().padStart(2, '0')}:${offsetMinutes.toString().padStart(2, '0')}`; + +const memoryUsage = process.memoryUsage(); + +// Function to format bytes as human-readable string +function formatBytes(bytes) { + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + if (bytes === 0) { + return '0 B'; + } + const i = Math.floor(Math.log2(bytes) / 10); + return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${units[i]}`; +} + // Start server const port = process.env.PORT || 3000; const publicIP = process.env.PUBLIC_IP || '0.0.0.0'; + +console.log('--------------------------------------------------'); +console.log(`Rusty Hearts API Version: 1.1`) +console.log(`Node.js Version: ${nodeVersion}`); +console.log(`Timezone: ${timezone} (${offsetString})`); +console.log('Memory Usage:'); +console.log(` RSS: ${formatBytes(memoryUsage.rss)}`); +console.log(` Heap Total: ${formatBytes(memoryUsage.heapTotal)}`); +console.log(` Heap Used: ${formatBytes(memoryUsage.heapUsed)}`); +console.log(` External: ${formatBytes(memoryUsage.external)}`); +console.log(` Array Buffers: ${formatBytes(memoryUsage.arrayBuffers)}`); +console.log('--------------------------------------------------'); + app.listen(port, publicIP, () => { logger.info(`API listening on ${publicIP}:${port}`); logger.info(`Auth API listening on 127.0.0.1:${authPort}`); logger.info(`Billing API listening on 127.0.0.1:${billingPort}`); -}); \ No newline at end of file +}); diff --git a/src/routes/auth.js b/src/routes/auth.js index 6882198..45422df 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -1,77 +1,86 @@ const express = require('express'); -const bodyParser = require('body-parser'); const bcrypt = require('bcrypt'); const xml2js = require('xml2js'); -const parser = new xml2js.Parser(); const sql = require('mssql'); -const router = express.Router(); -const { authLogger } = require('../utils/logger'); +const Joi = require('joi'); -// Set up database connection +const { authLogger } = require('../utils/logger'); const { connAccount } = require('../utils/dbConfig'); -// Route for handling login requests -router.post('/', bodyParser.text({ - type: '*/xml' -}), async (req, res) => { - try { - const xml = req.body; - const result = await parser.parseStringPromise(xml); - const loginRequest = result['login-request']; - const account = loginRequest.account[0]; - const password = loginRequest.password[0]; - const game = loginRequest.game[0]; - const ip = loginRequest.ip[0]; +const router = express.Router(); +const parser = new xml2js.Parser({ explicitArray: false }); - authLogger.info(`[Auth] Account [${account}] is trying to login from [${ip}]`); - - // Create a connection pool for the database - const pool = await connAccount; - - // Get the account information from the database - const { - recordset - } = await pool - .request() - .input('Identifier', sql.VarChar(50), account) - .execute('GetAccount'); - - // Check if the account exists - const row = recordset[0]; - if (!row || row.Result !== 'AccountExists') { - authLogger.info(`[Auth] Account [${account}] login failed from [${ip}]`); - return res.send('failed'); - } - - // Verify the password using bcrypt - const passwordMatch = await bcrypt.compare(password, row.AccountPwd); - - // Authenticate the user and update the database - const { - recordset: authRecordset - } = await pool - .request() - .input('Identifier', sql.VarChar(50), account) - .input('password_verify_result', sql.Bit, passwordMatch ? 1 : 0) - .input('LastLoginIP', sql.VarChar(50), ip) - .execute('AuthenticateUser'); - - const authRow = authRecordset[0]; - if (!authRow || authRow.Result !== 'LoginSuccess') { - authLogger.info(`[Auth] Account [${account}] login failed from [${ip}]`); - return res.send('failed'); - } - - // Send the authentication response - const response = `${authRow.AuthID}Fsuccess`; - res.set('Content-Type', 'text/xml; charset=utf-8'); - res.send(response); - - authLogger.info(`[Auth] Account [${account}] successfully logged in from [${ip}]`); - } catch (error) { - authLogger.error('Error handling login request: ' + error.message); - res.status(500).send('failed'); - } +// Joi schema for request body validation +const schema = Joi.object({ + 'login-request': Joi.object({ + account: Joi.string().required(), + password: Joi.string().required(), + game: Joi.string().required(), + ip: Joi.string().required(), + }).required(), }); -module.exports = router; \ No newline at end of file +// Route for handling login requests +router.post('/', express.text({ type: '*/xml' }), async (req, res) => { + try { + const xml = req.body; + const result = await parser.parseStringPromise(xml); + + // Validate the request body against the schema + const { error, value } = schema.validate(result); + if (error) { + authLogger.info(`[Auth] Invalid login request: ${error.message}`); + return res.send('failed'); + } + + const { 'login-request': loginRequest } = value; + const { account, password, game, ip } = loginRequest; + + authLogger.info(`[Auth] Account [${account}] is trying to login from [${ip}]`); + + // Create a connection pool for the database + const pool = await connAccount; + + // Get the account information from the database + const { recordset } = await pool + .request() + .input('Identifier', sql.VarChar(50), account) + .execute('GetAccount'); + + // Check if the account exists + const row = recordset[0]; + if (!row || row.Result !== 'AccountExists') { + authLogger.info(`[Auth] Account [${account}] login failed from [${ip}]`); + return res.send('failed'); + } + + // Verify the password using bcrypt + const passwordMatch = await bcrypt.compare(password, row.AccountPwd); + + // Authenticate the user and update the database + const { recordset: authRecordset } = await pool + .request() + .input('Identifier', sql.VarChar(50), account) + .input('password_verify_result', sql.Bit, passwordMatch ? 1 : 0) + .input('LastLoginIP', sql.VarChar(50), ip) + .execute('AuthenticateUser'); + + const authRow = authRecordset[0]; + if (!authRow || authRow.Result !== 'LoginSuccess') { + authLogger.info(`[Auth] Account [${account}] login failed from [${ip}]`); + return res.send('failed'); + } + + // Send the authentication response + const response = `${authRow.AuthID}Fsuccess`; + res.set('Content-Type', 'text/xml; charset=utf-8'); + res.send(response); + + authLogger.info(`[Auth] Account [${account}] successfully logged in from [${ip}]`); + } catch (error) { + authLogger.error(`Error handling login request: ${error.message}`); + res.status(500).send('failed'); + } +}); + +module.exports = router; diff --git a/src/routes/billing.js b/src/routes/billing.js index 1272e42..7f9a9ba 100644 --- a/src/routes/billing.js +++ b/src/routes/billing.js @@ -1,122 +1,138 @@ const express = require('express'); const bodyParser = require('body-parser'); const xml2js = require('xml2js'); -const parser = new xml2js.Parser(); const sql = require('mssql'); const router = express.Router(); -const { billingLogger} = require('../utils/logger');; +const { billingLogger } = require('../utils/logger'); +const Joi = require('joi'); // Set up database connection const { connAccount } = require('../utils/dbConfig'); +// Define the validation schema for currency requests +const currencySchema = Joi.object({ + userid: Joi.string().required(), + server: Joi.string().required(), + game: Joi.string().required(), +}).required(); + +// Define the validation schema for item purchase requests +const itemPurchaseSchema = Joi.object({ + userid: Joi.string().required(), + server: Joi.string().required(), + charid: Joi.string().required(), + game: Joi.number().required(), + uniqueid: Joi.string().required(), + amount: Joi.number().required(), + itemid: Joi.string().required(), + count: Joi.number().required(), +}).required(); + + // Route for handling billing requests router.post('/', bodyParser.text({ - type: '*/xml' + type: '*/xml' }), async (req, res) => { - try { - const xml = req.body; - const result = await parser.parseStringPromise(xml); - const name = result['currency-request'] ? 'currency-request' : 'item-purchase-request'; - const request = result[name]; - const userid = request.userid[0]; - const server = request.server[0]; + try { + const xml = req.body; + const result = await xml2js.parseStringPromise(xml, { explicitArray: false }); + const name = result['currency-request'] ? 'currency-request' : 'item-purchase-request'; + const request = result[name]; - billingLogger.info(`[Billing] Received [${name}] from user [${userid}]`); + // Validate the request against the appropriate schema + const { error, value } = name === 'currency-request' + ? currencySchema.validate(request) + : itemPurchaseSchema.validate(request); - // Create a connection pool for the database - const pool = await connAccount; - - switch (name) { - case 'currency-request': - const { - recordset - } = await pool - .request() - .input('UserId', sql.VarChar(50), userid) - .input('ServerId', sql.VarChar(50), server) - .execute('GetCurrency'); - - const row = recordset[0]; - - if (row && row.Result === 'Success') { - const response = `${row.Zen}`; - res.set('Content-Type', 'text/xml; charset=utf-8'); - res.send(response); - } else { - res.send('failed'); - return res.status(400).send(row.Result); - } - break; - case 'item-purchase-request': - const charid = request.charid[0]; - const uniqueid = request.uniqueid[0]; - const amount = request.amount[0]; - const itemid = request.itemid[0]; - const itemcount = request.count[0]; - - const { - recordset: currencyRecordset - } = await pool - .request() - .input('UserId', sql.VarChar(50), userid) - .input('ServerId', sql.VarChar(50), server) - .execute('GetCurrency'); - - const currencyRow = currencyRecordset[0]; - - if (currencyRow && currencyRow.Result === 'Success') { - const balance = currencyRow.Zen; - - if (amount > 0) { - if (amount > balance) { - res.send('failed'); - billingLogger.info(`[Billing] Item purchase with id [${uniqueid}] from user [${userid}] failed. Not enough Zen [${balance}]. charid: [${charid}] itemid: [${itemid}] itemcount: [${itemcount}] price: [${amount}]`); - } else { - const newbalance = balance - amount; - - await pool - .request() - .input('UserId', sql.VarChar(50), userid) - .input('ServerId', sql.VarChar(50), server) - .input('NewBalance', sql.BigInt, newbalance) - .execute('SetCurrency'); - - await pool - .request() - .input('userid', sql.VarChar(50), userid) - .input('charid', sql.VarChar(50), charid) - .input('uniqueid', sql.VarChar(50), uniqueid) - .input('amount', sql.BigInt, amount) - .input('itemid', sql.VarChar(50), itemid) - .input('itemcount', sql.Int, itemcount) - .execute('SetBillingLog'); - - billingLogger.info(`[Billing] Item purchase with id [${uniqueid}] from user [${userid}] success. charid: [${charid}] itemid: [${itemid}] itemcount: [${itemcount}] price: [${amount}]`); - billingLogger.info(`[Billing] Item purchase from user [${userid}] success. New zen balance: [${newbalance}]`); - - const response = `success${newbalance}`; - res.set('Content-Type', 'text/xml; charset=utf-8'); - res.send(response); - } - } else { - const response = `${currencyRow.Zen}`; - res.set('Content-Type', 'text/xml; charset=utf-8'); - res.send(response); - } - } else { - res.send('failed'); - } - break; - default: - res.send('failed'); - break; - } - } catch (error) { - billingLogger.error(`[Billing] Error handling request: $ { - error.message - }`); - res.status(500).send('failed'); + if (error) { + billingLogger.info(`[Billing] Invalid request: ${error.message}`); + return res.status(400).send('failed'); } + + const { userid, server, game, charid, uniqueid, amount, itemid, count } = value; + + // Create a connection pool for the database + const pool = await connAccount; + + switch (name) { + case 'currency-request': + const { recordset } = await pool + .request() + .input('UserId', sql.VarChar(50), userid) + .input('ServerId', sql.VarChar(50), server) + .execute('GetCurrency'); + + const row = recordset[0]; + + if (row && row.Result === 'Success') { + const response = `${row.Zen}`; + res.set('Content-Type', 'text/xml; charset=utf-8'); + return res.send(response); + } else { + billingLogger.error(`[Billing] Currency request from user [${userid}] failed: ${row.Result}`); + return res.status(400).send('failed'); + } + + case 'item-purchase-request': + billingLogger.info(`[Billing] Received [${name}] from user [${userid}]`); + const { recordset: currencyRecordset } = await pool + .request() + .input('UserId', sql.VarChar(50), userid) + .input('ServerId', sql.VarChar(50), server) + .execute('GetCurrency'); + + const currencyRow = currencyRecordset[0]; + + if (currencyRow && currencyRow.Result === 'Success') { + const balance = currencyRow.Zen; + + if (amount > 0) { + if (amount > balance) { + billingLogger.warn(`[Billing] Item purchase with id [${uniqueid}] from user [${userid}] failed. Not enough Zen [${balance}]. charid: [${charid}] itemid: [${itemid}] itemcount: [${count}] price: [${amount}]`); + return res.status(400).send('failed'); + } else { + const newbalance = balance - amount; + + await pool + .request() + .input('UserId', sql.VarChar(50), userid) + .input('ServerId', sql.VarChar(50), server) + .input('NewBalance', sql.BigInt, newbalance) + .execute('SetCurrency'); + + await pool + .request() + .input('userid', sql.VarChar(50), userid) + .input('charid', sql.VarChar(50), charid) + .input('uniqueid', sql.VarChar(50), uniqueid) + .input('amount', sql.BigInt, amount) + .input('itemid', sql.VarChar(50), itemid) + .input('itemcount', sql.Int, count) + .execute('SetBillingLog'); + + billingLogger.info(`[Billing] Item purchase with id [${uniqueid}] from user [${userid}] success. charid: [${charid}] itemid: [${itemid}] itemcount: [${count}] price: [${amount}]`); + billingLogger.info(`[Billing] [${userid}] Zen balance before purchase: [${balance}] | New zen balance: [${newbalance}]`); + + const response = `success${newbalance}`; + res.set('Content-Type', 'text/xml; charset=utf-8'); + return res.send(response); + } + } else { + const response = `${currencyRow.Zen}`; + res.set('Content-Type', 'text/xml; charset=utf-8'); + return res.send(response); + } + } else { + return res.status(400).send('failed'); + } + + default: + return res.status(400).send('failed'); + } + } catch (error) { + billingLogger.error(`[Billing] Error handling request: ${error.message}`); + return res.status(500).send('failed'); + } }); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/src/routes/gateway.js b/src/routes/gateway.js index 3772ae1..69b20e7 100644 --- a/src/routes/gateway.js +++ b/src/routes/gateway.js @@ -1,6 +1,7 @@ const express = require('express'); const router = express.Router(); const net = require('net'); +const { logger } = require('../utils/logger'); // Define the gateway route router.get('/', (req, res) => { @@ -38,14 +39,17 @@ router.get('/status', async (req, res) => { // Handle the socket events to check the connection status socket.on('connect', () => { + logger.info(`[Gateway] Connection attempt success from IP: ${req.ip}`); res.status(200).json({ status: 'online' }); socket.destroy(); }); socket.on('timeout', () => { + logger.warn(`[Gateway] Connection attempt timeout from IP: ${req.ip}`); res.status(408).json({ status: 'offline' }); socket.destroy(); }); socket.on('error', () => { + logger.error(`[Gateway] Connection failed from IP: ${req.ip}`); res.status(503).json({ status: 'offline' }); socket.destroy(); }); diff --git a/src/routes/launcher/changePassword.js b/src/routes/launcher/changePassword.js index 185df6e..8ee8a51 100644 --- a/src/routes/launcher/changePassword.js +++ b/src/routes/launcher/changePassword.js @@ -100,8 +100,8 @@ router.post('/', async (req, res) => { } } catch (error) { - logger.error('[Account] Database query failed: ' + error.message); - return res.status(500).send('[Account] Database query failed: ' + error.message); + logger.error('[Account] A error ocourred: ' + error.message); + return res.status(500).send('A error ocourred. Please try again later.'); } }); diff --git a/src/routes/launcher/codeVerification.js b/src/routes/launcher/codeVerification.js index 20ac433..93c0458 100644 --- a/src/routes/launcher/codeVerification.js +++ b/src/routes/launcher/codeVerification.js @@ -46,7 +46,7 @@ router.post('/', async (req, res) => { return res.status(200).send(row.Result); } catch (error) { logger.error('Database query failed: ' + error.message); - return res.status(500).send('Database query failed: ' + error.message); + return res.status(500).send('A error ocourred. Please try again later.'); } }); diff --git a/src/routes/launcher/login.js b/src/routes/launcher/login.js index e0e957b..da83906 100644 --- a/src/routes/launcher/login.js +++ b/src/routes/launcher/login.js @@ -23,17 +23,17 @@ router.post('/', async (req, res) => { return res.status(400).send(error.details[0].message); } - const account = value.account; - const password = value.password; + const account = req.body.account; + const password = req.body.password; const userIp = req.ip; // Check the format of the account identifier - if ( - !/^[A-Za-z0-9_-]{6,50}$/.test(account) && - !/^[\w\d._%+-]+@[\w\d.-]+\.[\w]{2,}$/i.test(account) - ) { - return res.status(400).json({ Result: 'InvalidUsernameFormat' }); - } + if ( + !/^[a-z0-9_-]{6,50}$/.test(account) && + !/^[\w\d._%+-]+@[\w\d.-]+\.[\w]{2,}$/i.test(account) + ) { + return res.status(400).json({ Result: 'InvalidUsernameFormat' }); + } // Use a prepared statement to retrieve the account information const pool = await connAccount; @@ -51,6 +51,7 @@ router.post('/', async (req, res) => { .createHash('md5') .update(windyCode + password) .digest('hex'); + const password_verify_result = await bcrypt.compare( md5_password, hash @@ -85,15 +86,13 @@ router.post('/', async (req, res) => { }); } } else { - return res.status(400).json({ Result: 'AccountNotFound' }); + return res.status(400).json({ Result: 'AccountNotFound' }); } } catch (error) { logger.error( '[Account] Launcher Login: Database query failed: ' + error.message ); - return res - .status(500) - .send('Database query failed: ' + error.message); + return res.status(500).send('Login failed. Please try again later.'); } }); diff --git a/src/routes/launcher/passwordResetEmail.js b/src/routes/launcher/passwordResetEmail.js index 8bdddd6..cdcdd02 100644 --- a/src/routes/launcher/passwordResetEmail.js +++ b/src/routes/launcher/passwordResetEmail.js @@ -57,7 +57,7 @@ router.post('/', async (req, res) => { return res.status(200).send('EmailSent'); } else { - accountLogger.info(`[Account] Failed to insert verification code for email: ${email}`); + accountLogger.error(`[Account] Failed to insert verification code for email: ${email}`); return res.status(500).send(insertRow.Result); } } else if (row && row.Result === 'AccountNotFound') { @@ -67,7 +67,7 @@ router.post('/', async (req, res) => { } } catch (error) { logger.error('[Account] Database query failed: ' + error.message); - return res.status(500).send('Database query failed: ' + error.message); + return res.status(500).send('A error ocourred. Please try again later.'); } }); diff --git a/src/routes/launcher/register.js b/src/routes/launcher/register.js index 3bfe745..57ebcb9 100644 --- a/src/routes/launcher/register.js +++ b/src/routes/launcher/register.js @@ -12,7 +12,7 @@ const { connAccount } = require('../../utils/dbConfig'); // Joi schema for validating request data const schema = Joi.object({ - windyCode: Joi.string().alphanum().min(1).max(16).required(), + windyCode: Joi.string().alphanum().min(6).max(16).required(), email: Joi.string().email().required(), password: Joi.string().min(6).required(), }); @@ -29,6 +29,13 @@ router.post('/', async (req, res) => { const email = value.email; const password = value.password; const userIp = req.ip; + + if ( + !/^[a-z0-9_-]{6,50}$/.test(windyCode) && + !/^[\w\d._%+-]+@[\w\d.-]+\.[\w]{2,}$/i.test(email) + ) { + return res.status(400).send('InvalidUsernameFormat'); + } const md5_password = crypto.createHash('md5').update(windyCode + password).digest('hex'); // Generate MD5 hash @@ -55,12 +62,12 @@ router.post('/', async (req, res) => { return res.status(200).send('Success'); } else { - accountLogger.info(`[Account] Account [${windyCode}] creation failed: ${row.Result}`); + accountLogger.error(`[Account] Account [${windyCode}] creation failed: ${row.Result}`); return res.status(400).send(row.Result); } } catch (error) { logger.error('[Account] Database query failed: ' + error.message); - return res.status(500).send('Database query failed: ' + error.message); + return res.status(500).send('A error ocourred. Please try again later.'); } }); diff --git a/src/routes/launcher/verificationEmail.js b/src/routes/launcher/verificationEmail.js index 0c49dd7..d8932c9 100644 --- a/src/routes/launcher/verificationEmail.js +++ b/src/routes/launcher/verificationEmail.js @@ -61,7 +61,7 @@ router.post('/', async (req, res) => { } } catch (error) { logger.error('[Account] Database query failed: ' + error.message); - return res.status(500).send('Database query failed: ' + error.message); + return res.status(500).send('A error ocourred. Please try again later.'); } }); diff --git a/src/routes/onlineCount.js b/src/routes/onlineCount.js new file mode 100644 index 0000000..74c8cbb --- /dev/null +++ b/src/routes/onlineCount.js @@ -0,0 +1,47 @@ +const express = require('express'); +const router = express.Router(); +const NodeCache = require('node-cache'); +const { logger } = require('../utils/logger'); +const sql = require('mssql'); +const { authDBConfig } = require('../utils/dbConfig.js'); + +// Set up the cache +const cache = new NodeCache({ stdTTL: 60, checkperiod: 120 }); + +// Route for getting the count of online players +router.get('/', async (req, res) => { + try { + // Check if the count exists in the cache + const cacheKey = 'onlineCount'; + let count = cache.get(cacheKey); + + if (count === undefined) { + // Count not found in cache, fetch it from the database + const connAuth = new sql.ConnectionPool(authDBConfig); + await connAuth.connect(); + + const request = connAuth.request(); + + // Declare the @online parameter and set its value to 1 + request.input('online', sql.Int, 1); + + const result = await request.query('SELECT COUNT(*) AS OnlineCount FROM AuthTable WHERE online = @online'); + + count = result.recordset[0].OnlineCount; + + // Store the count in the cache + cache.set(cacheKey, count); + + // Close the database connection + await connAuth.close(); + } + + // Return the count as the response + return res.status(200).json({ count }); + } catch (error) { + logger.error('Database query failed: ' + error.message); + return res.status(500).send('Database query failed. Please try again later.'); + } +}); + +module.exports = router; diff --git a/src/utils/dbConfig.js b/src/utils/dbConfig.js index 5c11435..8d6468a 100644 --- a/src/utils/dbConfig.js +++ b/src/utils/dbConfig.js @@ -9,13 +9,30 @@ const dbConfig = { database: process.env.DB_DATABASE, options: { encrypt: process.env.DB_ENCRYPT === 'true' - } + }, }; const connAccount = new sql.ConnectionPool(dbConfig); -connAccount.connect(); -logger.info(`Database: Connected.`); + +connAccount.connect().then(() => { + logger.info(`Account Database: Connected.`); + logger.info(`$ Ready $`); + +}).catch((error) => { + logger.error(`Failed to connect to the Account Database: ${error}`); +}); + +const authDBConfig = { + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + server: process.env.DB_SERVER, + database: 'RustyHearts_Auth', + options: { + encrypt: process.env.DB_ENCRYPT === 'true' + }, +}; module.exports = { - connAccount + connAccount, + authDBConfig }; diff --git a/src/utils/logger.js b/src/utils/logger.js index f0b29e7..4b66136 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -1,6 +1,4 @@ const fs = require("fs"); -const path = require("path"); -const util = require("util"); const winston = require("winston"); const logsDirectory = 'logs'; @@ -9,131 +7,44 @@ if (!fs.existsSync(logsDirectory)) { fs.mkdirSync(logsDirectory); } -const authLogger = winston.createLogger({ - level: 'info', - format: winston.format.combine( - winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}] `) - ), - transports: [ +function createLogger(filename, level, filter, showConsole) { + const transports = [ new winston.transports.File({ - filename: `logs/auth-${new Date().toISOString().slice(0, 10)}.log`, - level: 'info', - filter: (log) => log.message.includes('[Auth]') + filename: `${logsDirectory}/${filename}-${new Date().toISOString().slice(0, 10)}.log`, + level, + filter }) - ] -}); + ]; -if (process.env.LOG_AUTH_CONSOLE === 'true') { - authLogger.add(new winston.transports.Console({ - format: winston.format.combine( - winston.format.colorize(), - winston.format.simple(), - winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}]`) - ) - })); -} - -const billingLogger = winston.createLogger({ - level: 'info', - format: winston.format.combine( - winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}] `) - ), - transports: [ - new winston.transports.File({ - filename: `logs/billing-${new Date().toISOString().slice(0, 10)}.log`, - level: 'info', - filter: (log) => log.message.includes('[Billing]') - }) - ] -}); - -if (process.env.LOG_BILLING_CONSOLE === 'true') { - billingLogger.add(new winston.transports.Console({ - format: winston.format.combine( - winston.format.colorize(), - winston.format.simple(), - winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}]`) - ) - })); -} - -const mailerLogger = winston.createLogger({ - level: 'info', - format: winston.format.combine( - winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}] `) - ), - transports: [ - new winston.transports.File({ - filename: `logs/mailer-${new Date().toISOString().slice(0, 10)}.log`, - level: 'info', - filter: (log) => log.message.includes('[Mailer]') - }) - ] -}); - -if (process.env.LOG_MAILER_CONSOLE === 'true') { - mailerLogger.add(new winston.transports.Console({ - format: winston.format.combine( - winston.format.colorize(), - winston.format.simple(), - winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}]`) - ) - })); -} - -const accountLogger = winston.createLogger({ - level: 'info', - format: winston.format.combine( - winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}] `) - ), - transports: [ - new winston.transports.File({ - filename: `logs/account-${new Date().toISOString().slice(0, 10)}.log`, - level: 'info', - filter: (log) => log.message.includes('[Account]') - }) - ] -}); - -if (process.env.LOG_ACCOUNT_CONSOLE === 'true') { - accountLogger.add(new winston.transports.Console({ - format: winston.format.combine( - winston.format.colorize(), - winston.format.simple(), - winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}]`) - ) - })); -} - -const logger = winston.createLogger({ - level: 'info', - format: winston.format.combine( - winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}] `) - ), - transports: [ - new winston.transports.Console({ + if (showConsole) { + transports.push(new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple(), winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}]`) ) - }), - new winston.transports.File({ - filename: `logs/api-${new Date().toISOString().slice(0, 10)}.log`, - level: 'info' - }), - new winston.transports.File({ - filename: `logs/error-${new Date().toISOString().slice(0, 10)}.log`, - level: 'error' - }) - ] -}); + })); + } + const logger = winston.createLogger({ + level, + format: winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}] `) + ), + transports + }); + + return logger; +} + +const logLevel = process.env.LOG_LEVEL || 'info'; + +const authLogger = createLogger('auth', logLevel, log => log.message.includes('[Auth]'), process.env.LOG_AUTH_CONSOLE === 'true'); +const billingLogger = createLogger('billing', logLevel, log => log.message.includes('[Billing]'), process.env.LOG_BILLING_CONSOLE === 'true'); +const mailerLogger = createLogger('mailer', logLevel, log => log.message.includes('[Mailer]'), process.env.LOG_MAILER_CONSOLE === 'true'); +const accountLogger = createLogger('account', logLevel, log => log.message.includes('[Account]'), process.env.LOG_ACCOUNT_CONSOLE === 'true'); +const logger = createLogger('api', logLevel, null, true); module.exports = { authLogger, diff --git a/stop_rh-api_with_pm2.bat b/stop_rh-api_with_pm2.bat new file mode 100644 index 0000000..77351b0 --- /dev/null +++ b/stop_rh-api_with_pm2.bat @@ -0,0 +1,4 @@ +@echo off +title API +cmd /k "npx pm2 stop rh-api" +pause