*Added onlineCount endpoint

*Added launch with PM2
*Code clenaup
This commit is contained in:
Junior 2023-07-04 08:25:46 -03:00
parent 5dfa4d83e7
commit b400beea6a
18 changed files with 381 additions and 321 deletions

View file

@ -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('<status>failed</status>');
}
// 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('<status>failed</status>');
}
// Send the authentication response
const response = `<userid>${authRow.AuthID}</userid><user-type>F</user-type><status>success</status>`;
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('<status>failed</status>');
}
// 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;
// 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('<status>failed</status>');
}
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('<status>failed</status>');
}
// 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('<status>failed</status>');
}
// Send the authentication response
const response = `<userid>${authRow.AuthID}</userid><user-type>F</user-type><status>success</status>`;
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('<status>failed</status>');
}
});
module.exports = router;

View file

@ -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 = `<result><balance>${row.Zen}</balance></result>`;
res.set('Content-Type', 'text/xml; charset=utf-8');
res.send(response);
} else {
res.send('<status>failed</status>');
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('<status>failed</status>');
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 = `<result><status>success</status><new-balance>${newbalance}</new-balance></result>`;
res.set('Content-Type', 'text/xml; charset=utf-8');
res.send(response);
}
} else {
const response = `<result><balance>${currencyRow.Zen}</balance></result>`;
res.set('Content-Type', 'text/xml; charset=utf-8');
res.send(response);
}
} else {
res.send('<status>failed</status>');
}
break;
default:
res.send('<status>failed</status>');
break;
}
} catch (error) {
billingLogger.error(`[Billing] Error handling request: $ {
error.message
}`);
res.status(500).send('<status>failed</status>');
if (error) {
billingLogger.info(`[Billing] Invalid request: ${error.message}`);
return res.status(400).send('<status>failed</status>');
}
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 = `<result><balance>${row.Zen}</balance></result>`;
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('<status>failed</status>');
}
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('<status>failed</status>');
} 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 = `<result><status>success</status><new-balance>${newbalance}</new-balance></result>`;
res.set('Content-Type', 'text/xml; charset=utf-8');
return res.send(response);
}
} else {
const response = `<result><balance>${currencyRow.Zen}</balance></result>`;
res.set('Content-Type', 'text/xml; charset=utf-8');
return res.send(response);
}
} else {
return res.status(400).send('<status>failed</status>');
}
default:
return res.status(400).send('<status>failed</status>');
}
} catch (error) {
billingLogger.error(`[Billing] Error handling request: ${error.message}`);
return res.status(500).send('<status>failed</status>');
}
});
module.exports = router;
module.exports = router;

View file

@ -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();
});

View file

@ -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.');
}
});

View file

@ -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.');
}
});

View file

@ -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.');
}
});

View file

@ -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.');
}
});

View file

@ -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.');
}
});

View file

@ -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.');
}
});

47
src/routes/onlineCount.js Normal file
View file

@ -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;