Add project files.

This commit is contained in:
Junior 2023-05-12 17:44:41 -03:00
commit 0a12c6baa0
41 changed files with 2698 additions and 0 deletions

77
src/routes/auth.js Normal file
View file

@ -0,0 +1,77 @@
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');
// Set up database connection
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];
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;

122
src/routes/billing.js Normal file
View file

@ -0,0 +1,122 @@
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');;
// Set up database connection
const { connAccount } = require('../utils/dbConfig');
// Route for handling billing requests
router.post('/', bodyParser.text({
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];
billingLogger.info(`[Billing] Received [${name}] from user [${userid}]`);
// 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>');
}
});
module.exports = router;

54
src/routes/gateway.js Normal file
View file

@ -0,0 +1,54 @@
const express = require('express');
const router = express.Router();
const net = require('net');
// Define the gateway route
router.get('/', (req, res) => {
const ip = process.env.GATESERVER_IP;
const port = process.env.GATESERVER_PORT || '50001';
// Generate the XML content with the IP and port values
const xml = `<?xml version="1.0" encoding="ISO-8859-1"?>
<network>
<gateserver ip="${ip}" port="${port}" />
</network>`;
res.set('Content-Type', 'application/xml');
res.send(xml);
});
// Define the gateway info route
router.get('/info', (req, res) => {
const gatewayRoute = `1|${req.protocol}://${req.headers.host}/serverApi/gateway|${req.protocol}://${req.headers.host}/serverApi/gateway|`;
res.send(gatewayRoute);
});
// Define the gateway status route
router.get('/status', async (req, res) => {
const ip = process.env.GATESERVER_IP;
const port = process.env.GATESERVER_PORT || '50001';
const timeout = 2000;
// Create a new socket and connect to the gateserver
const socket = new net.Socket();
socket.setTimeout(timeout);
socket.connect(port, ip);
// Handle the socket events to check the connection status
socket.on('connect', () => {
res.status(200).json({ status: 'online' });
socket.destroy();
});
socket.on('timeout', () => {
res.status(408).json({ status: 'offline' });
socket.destroy();
});
socket.on('error', () => {
res.status(503).json({ status: 'offline' });
socket.destroy();
});
});
module.exports = router;

View file

@ -0,0 +1,108 @@
const sql = require('mssql');
const express = require('express');
const router = express.Router();
const bcrypt = require('bcrypt');
const crypto = require('crypto');
const { logger, accountLogger } = require('../../utils/logger');
const { sendPasswordChangedEmail } = require('../../mailer/mailer');
const Joi = require('joi');
// Set up database connection
const { connAccount } = require('../../utils/dbConfig');
// Joi schema for request body validation
const schema = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().required(),
verification_code: Joi.string().pattern(new RegExp('^[0-9]+$')).required()
});
// Route for registering an account
router.post('/', async (req, res) => {
try {
// Validate request body
const { error, value } = schema.validate(req.body);
if (error) {
return res.status(400).send(error.details[0].message);
}
const email = value.email;
const password = value.password;
const verificationCode = value.verification_code;
// Use a prepared statement to get the verification code
const pool = await connAccount;
const request = pool.request();
request.input('Email', sql.VarChar, email);
request.input('VerificationCode', sql.VarChar, verificationCode);
request.input('VerificationCodeType', sql.VarChar, 'Password');
const inputResult = await request.execute('GetVerificationCode');
const inputRow = inputResult.recordset[0];
if (inputRow && inputRow.Result === 'ValidVerificationCode') {
// Use a prepared statement to retrieve the account information
const pool = await connAccount;
const request = pool.request();
request.input('Identifier', sql.VarChar, email);
const getResult = await request.execute('GetAccount');
const getRow = getResult.recordset[0];
if (getRow && getRow.Result === 'AccountExists') {
const windyCode = getRow.WindyCode
const hash = getRow.AccountPwd;
// Verify the password
const md5_password = crypto
.createHash('md5')
.update(windyCode + password)
.digest('hex');
const password_verify_result = await bcrypt.compare(
md5_password,
hash
);
if (password_verify_result === true) {
return res.status(400).send('SamePassword');
} else {
const passwordHash = await bcrypt.hash(md5_password, 10);
// Use a prepared statement to update the password
const pool = await connAccount;
const request = pool.request();
request.input('Email', sql.VarChar, email);
request.input('AccountPwd', sql.VarChar, passwordHash);
const updateResult = await request.execute('UpdateAccountPassword');
const updateRow = updateResult.recordset[0];
if (updateRow && updateRow.Result === 'PasswordChanged') {
accountLogger.info(`[Account] Password for [${windyCode}] changed successfully`);
sendPasswordChangedEmail(email, windyCode);
const pool = await connAccount;
const request = pool.request();
request.input('Email', sql.VarChar, email);
const clearResult = await request.execute('ClearVerificationCode');
const clearRow = clearResult.recordset[0];
return res.status(200).send('PasswordChanged');
} else {
accountLogger.info(`[Account] Password change for [${windyCode}] failed: ${row.Result}`);
return res.status(400).send(updateRow.Result);
}
}
} else {
return res.status(400).send(getRow.Result);
}
} else {
return res.status(400).send(inputRow.Result);
}
} catch (error) {
logger.error('[Account] Database query failed: ' + error.message);
return res.status(500).send('[Account] Database query failed: ' + error.message);
}
});
module.exports = router;

View file

@ -0,0 +1,53 @@
const sql = require('mssql');
const express = require('express');
const router = express.Router();
const { logger } = require('../../utils/logger');
const Joi = require('joi');
// Set up database connection
const { connAccount } = require('../../utils/dbConfig');
// Joi schema for request body validation
const schema = Joi.object({
email: Joi.string().email().required(),
verification_code_type: Joi.string().required(),
verification_code: Joi.string().pattern(new RegExp('^[0-9]+$')).required()
});
// Route for registering an account
router.post('/', async (req, res) => {
try {
// Validate request body
const { error, value } = schema.validate(req.body);
if (error) {
return res.status(400).send(error.details[0].message);
}
const email = req.body.email;
const verificationCode = req.body.verification_code;
const verificationCodeType = req.body.verification_code_type;
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
return res.status(400).send('InvalidEmailFormat');
}
if (!/^\d+$/.test(verificationCode)) {
return res.status(400).send('InvalidVerificationCodeFormat');
}
// Use a prepared statement to check verification code
const pool = await connAccount;
const request = pool.request();
request.input('Email', sql.VarChar, email);
request.input('VerificationCode', sql.VarChar, verificationCode);
request.input('VerificationCodeType', sql.VarChar, verificationCodeType);
const result = await request.execute('GetVerificationCode');
const row = result.recordset[0];
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);
}
});
module.exports = router;

View file

@ -0,0 +1,41 @@
const express = require('express');
const fs = require('fs');
const path = require('path');
const { logger } = require('../../utils/logger');
const router = express.Router();
// Endpoint to get the launcher version from the launcher_info.ini file
router.get('/getLauncherVersion', (req, res) => {
const launcherInfoPath = path.join(__dirname, '..', '..', '..', 'public', 'launcher', 'launcher_update', 'launcher_info.ini');
fs.readFile(launcherInfoPath, 'utf8', (err, data) => {
if (err) {
console.error(err);
return res.status(500).send('Error reading launcher_info.ini');
}
const versionRegex = /version=(.*)/i;
const match = data.match(versionRegex);
if (match) {
const launcherVersion = match[1];
return res.json({ version: launcherVersion });
}
return res.status(500).send('Invalid launcher_info.ini format');
});
});
// Endpoint to download the new launcher version from the launcher_update folder
router.post('/updateLauncherVersion', (req, res) => {
const launcherUpdatePath = path.join(__dirname, '..', '..', '..', 'public', 'launcher', 'launcher_update');
const version = req.body.version;
if (!req.body.version) {
return res.status(400).send('Missing version parameter');
}
const file = path.join(launcherUpdatePath, `launcher_${version}.zip`);
if (!fs.existsSync(file)) {
return res.status(404).send(`File ${file} not found`);
logger.error(`[Launcher Updater] File ${file} not found`);
}
res.download(file);
});
module.exports = router;

View file

@ -0,0 +1,100 @@
const sql = require('mssql');
const bcrypt = require('bcrypt');
const crypto = require('crypto');
const express = require('express');
const router = express.Router();
const { logger, accountLogger } = require('../../utils/logger');
const Joi = require('joi');
// Set up database connection
const { connAccount } = require('../../utils/dbConfig');
// Define the validation schema for the request body
const schema = Joi.object({
account: Joi.string().required(),
password: Joi.string().required(),
});
router.post('/', async (req, res) => {
try {
// Validate the request body against the schema
const { error, value } = schema.validate(req.body);
if (error) {
return res.status(400).send(error.details[0].message);
}
const account = value.account;
const password = value.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' });
}
// Use a prepared statement to retrieve the account information
const pool = await connAccount;
const request = pool.request();
request.input('Identifier', sql.VarChar, account);
const result = await request.execute('GetAccount');
const row = result.recordset[0];
if (row && row.Result === 'AccountExists') {
const windyCode = row.WindyCode;
const hash = row.AccountPwd;
// Verify the password
const md5_password = crypto
.createHash('md5')
.update(windyCode + password)
.digest('hex');
const password_verify_result = await bcrypt.compare(
md5_password,
hash
);
const authRequest = pool.request();
authRequest.input('Identifier', sql.VarChar, account);
authRequest.input(
'password_verify_result',
sql.Bit,
password_verify_result
);
authRequest.input('LastLoginIP', sql.VarChar, userIp);
const authResult = await authRequest.execute('AuthenticateUser');
const authRow = authResult.recordset[0];
if (authRow && authRow.Result === 'LoginSuccess') {
accountLogger.info(
`[Account] Launcher Login: Account [${windyCode}] successfully logged in from [${userIp}]`
);
return res.status(200).json({
Result: authRow.Result,
Token: authRow.Token,
WindyCode: authRow.WindyCode,
});
} else {
accountLogger.info(
`[Account] Launcher Login: Account [${windyCode}] login failed: ${authRow.Result} `
);
return res.status(400).json({
Result: authRow.Result,
});
}
} else {
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);
}
});
module.exports = router;

View file

@ -0,0 +1,74 @@
const sql = require('mssql');
const express = require('express');
const router = express.Router();
const { logger, accountLogger } = require('../../utils/logger');
const { sendPasswordResetEmail } = require('../../mailer/mailer');
const Joi = require('joi');
// Set up database connection
const { connAccount } = require('../../utils/dbConfig');
// Joi schema for request body validation
const schema = Joi.object({
email: Joi.string().email().required()
});
// Route for registering an account
router.post('/', async (req, res) => {
try {
// Validate request body
const { error, value } = schema.validate(req.body);
if (error) {
return res.status(400).send(error.details[0].message);
}
const email = req.body.email;
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
logger.info('Invalid email format');
return res.status(400).send('InvalidEmailFormat');
}
// Use a prepared statement to retrieve the account information
const pool = await connAccount;
const request = pool.request();
request.input('Identifier', sql.VarChar, email);
const result = await request.execute('GetAccount');
const row = result.recordset[0];
if (row && row.Result === 'AccountExists') {
const emailAdress = row.Email;
const windycode = row.WindyCode;
const verificationCode = Math.floor(10000 + Math.random() * 90000).toString();
const expirationTime = new Date(Date.now() + 600000).toISOString(); // 10 minutes from now
// Prepare the second statement to insert the verification code information
const insertRequest = pool.request();
insertRequest.input('Email', sql.VarChar, email);
insertRequest.input('VerificationCode', sql.VarChar, verificationCode);
insertRequest.input('ExpirationTime', sql.DateTime, expirationTime);
const insertResult = await insertRequest.execute('SetPasswordVerificationCode');
const insertRow = insertResult.recordset[0];
if (insertRow && insertRow.Result === 'Success') {
// Send verification code email
sendPasswordResetEmail(email, verificationCode);
return res.status(200).send('EmailSent');
}
else {
accountLogger.info(`[Account] Failed to insert verification code for email: ${email}`);
return res.status(500).send(insertRow.Result);
}
} else if (row && row.Result === 'AccountNotFound') {
return res.status(400).send('AccountNotFound');
} else {
return res.status(500).send(row.Result);
}
} catch (error) {
logger.error('[Account] Database query failed: ' + error.message);
return res.status(500).send('Database query failed: ' + error.message);
}
});
module.exports = router;

View file

@ -0,0 +1,67 @@
const sql = require('mssql');
const express = require('express');
const router = express.Router();
const bcrypt = require('bcrypt');
const crypto = require('crypto');
const { logger, accountLogger } = require('../../utils/logger');
const { sendConfirmationEmail } = require('../../mailer/mailer');
const Joi = require('joi');
// Set up database connection
const { connAccount } = require('../../utils/dbConfig');
// Joi schema for validating request data
const schema = Joi.object({
windyCode: Joi.string().alphanum().min(1).max(16).required(),
email: Joi.string().email().required(),
password: Joi.string().min(6).required(),
});
// Route for registering an account
router.post('/', async (req, res) => {
try {
const { error, value } = schema.validate(req.body);
if (error) {
return res.status(400).send(error.details[0].message);
}
const windyCode = value.windyCode;
const email = value.email;
const password = value.password;
const userIp = req.ip;
const md5_password = crypto.createHash('md5').update(windyCode + password).digest('hex'); // Generate MD5 hash
const passwordHash = await bcrypt.hash(md5_password, 10);
// Use a prepared statement to create the account
const pool = await connAccount;
const request = pool.request();
request.input('WindyCode', sql.VarChar, windyCode);
request.input('AccountPwd', sql.VarChar, passwordHash);
request.input('Email', sql.VarChar, email);
request.input('RegisterIP', sql.VarChar, userIp);
const result = await request.execute('CreateAccount');
const row = result.recordset[0];
if (row && row.Result === 'AccountCreated') {
accountLogger.info(`[Account] Account [${windyCode}] created successfully`);
sendConfirmationEmail(email, windyCode);
const clearRequest = pool.request();
clearRequest.input('Email', sql.VarChar, email);
const clearResult = await clearRequest.execute('ClearVerificationCode');
const clearRow = clearResult.recordset[0];
return res.status(200).send('Success');
} else {
accountLogger.info(`[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);
}
});
module.exports = router;

View file

@ -0,0 +1,68 @@
// Load environment variables
const env = require('../../utils/env');
const sql = require('mssql');
const express = require('express');
const router = express.Router();
const { logger } = require('../../utils/logger');
const { sendVerificationEmail } = require('../../mailer/mailer');
const Joi = require('joi');
// Set up database connection
const { connAccount } = require('../../utils/dbConfig');
// Joi schema for request body validation
const schema = Joi.object({
email: Joi.string().email().required()
});
// Route for registering an account
router.post('/', async (req, res) => {
try {
// Validate request body
const { error, value } = schema.validate(req.body);
if (error) {
return res.status(400).send(error.details[0].message);
}
const email = req.body.email;
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
return res.status(400).send('InvalidEmailFormat');
}
// Use a prepared statement to retrieve the account information
const pool = await connAccount;
const request = pool.request();
request.input('Identifier', sql.VarChar, email);
const result = await request.execute('GetAccount');
const row = result.recordset[0];
if (row && row.Result === 'AccountNotFound') {
const verificationCode = Math.floor(10000 + Math.random() * 90000).toString();
const timeZone = process.env.TZ;
// Set the expiration time 10 minutes from now in the specified timezone
const expirationTime = new Date(Date.now() + 600000).toLocaleString('en-US', { timeZone });
// Prepare the second statement to insert the verification code information
const insertRequest = pool.request();
insertRequest.input('Email', sql.VarChar, email);
insertRequest.input('VerificationCode', sql.VarChar, verificationCode);
insertRequest.input('ExpirationTime', sql.DateTime, expirationTime);
const insertResult = await insertRequest.execute('SetAccountVerificationCode');
const insertRow = insertResult.recordset[0];
// Send verification code email
sendVerificationEmail(email, verificationCode);
return res.status(200).send('EmailSent');
} else {
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);
}
});
module.exports = router;