initial commit

This commit is contained in:
y.campiontrebouta@innotexnas.ovh
2026-02-04 19:04:46 +01:00
commit abb51904d7
27 changed files with 4011 additions and 0 deletions

6
backend/.env.example Normal file
View File

@@ -0,0 +1,6 @@
NODE_ENV=development
PORT=3000
SESSION_SECRET=your-very-secret-session-key-change-in-production
SERVER_DIR=/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red
RCON_HOST=localhost
RCON_PORT=25575

25
backend/package.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "nations-glory-admin-backend",
"version": "1.0.0",
"description": "Backend API pour l'interface web de gestion du serveur Minecraft NationsGlory",
"main": "src/server.js",
"scripts": {
"start": "node src/server.js",
"dev": "nodemon src/server.js"
},
"keywords": ["minecraft", "admin", "api"],
"author": "Admin",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"express-session": "^1.17.3",
"bcryptjs": "^2.4.3",
"dotenv": "^16.0.3",
"cors": "^2.8.5",
"multer": "^1.4.5-lts.1",
"fs-extra": "^11.1.0"
},
"devDependencies": {
"nodemon": "^2.0.20"
}
}

200
backend/src/routes/auth.js Normal file
View File

@@ -0,0 +1,200 @@
const express = require('express');
const bcrypt = require('bcryptjs');
const fs = require('fs-extra');
const path = require('path');
const { SERVER_DIR } = require('../server');
const router = express.Router();
const USERS_FILE = path.join(__dirname, '../../data/users.json');
async function initUsersFile() {
await fs.ensureDir(path.dirname(USERS_FILE));
if (!await fs.pathExists(USERS_FILE)) {
await fs.writeJson(USERS_FILE, [], { spaces: 2 });
}
}
function isAuthenticated(req, res, next) {
if (req.session.user) {
next();
} else {
res.status(401).json({ error: 'Non authentifié' });
}
}
async function getServerOps() {
const opsFile = path.join(SERVER_DIR, 'ops.txt');
try {
if (await fs.pathExists(opsFile)) {
const content = await fs.readFile(opsFile, 'utf-8');
return content
.split('\n')
.map(line => line.trim())
.filter(line => line && !line.startsWith('#'));
}
} catch (e) {
console.error('Erreur lecture ops.txt:', e);
}
return [];
}
async function getServerOpsJson() {
const opsFile = path.join(SERVER_DIR, 'ops.json');
try {
if (await fs.pathExists(opsFile)) {
const ops = await fs.readJson(opsFile);
return ops.map(op => typeof op === 'string' ? op : op.name).filter(Boolean);
}
} catch (e) {
console.error('Erreur lecture ops.json:', e);
}
return [];
}
router.post('/register', async (req, res) => {
try {
await initUsersFile();
const users = await fs.readJson(USERS_FILE);
if (users.length > 0) {
return res.status(403).json({ error: 'Enregistrement déjà effectué' });
}
const { username, password, mcUsername } = req.body;
if (!username || !password || !mcUsername) {
return res.status(400).json({ error: 'Données manquantes' });
}
const serverOps = await getServerOps();
const serverOpsJson = await getServerOpsJson();
const allOps = [...new Set([...serverOps, ...serverOpsJson])];
if (!allOps.includes(mcUsername)) {
return res.status(403).json({
error: 'Le joueur n\'est pas OP sur le serveur',
availableOps: allOps
});
}
const hashedPassword = await bcrypt.hash(password, 10);
users.push({
id: Date.now().toString(),
username,
mcUsername,
password: hashedPassword,
createdAt: new Date()
});
await fs.writeJson(USERS_FILE, users, { spaces: 2 });
res.json({
message: 'Admin créé avec succès',
user: { username, mcUsername }
});
} catch (error) {
console.error('Erreur enregistrement:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.post('/login', async (req, res) => {
try {
await initUsersFile();
const users = await fs.readJson(USERS_FILE);
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ error: 'Identifiants manquants' });
}
const user = users.find(u => u.username === username);
if (!user) {
return res.status(401).json({ error: 'Identifiants incorrects' });
}
const passwordMatch = await bcrypt.compare(password, user.password);
if (!passwordMatch) {
return res.status(401).json({ error: 'Identifiants incorrects' });
}
req.session.user = {
id: user.id,
username: user.username,
mcUsername: user.mcUsername
};
res.json({
message: 'Connecté avec succès',
user: req.session.user
});
} catch (error) {
console.error('Erreur connexion:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.get('/check', (req, res) => {
if (req.session.user) {
res.json({ authenticated: true, user: req.session.user });
} else {
res.json({ authenticated: false });
}
});
router.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ error: 'Erreur déconnexion' });
}
res.json({ message: 'Déconnecté' });
});
});
router.post('/change-rcon-password', isAuthenticated, async (req, res) => {
try {
const { oldPassword, newPassword } = req.body;
const serverPropsFile = path.join(SERVER_DIR, 'server.properties');
if (!oldPassword || !newPassword) {
return res.status(400).json({ error: 'Données manquantes' });
}
let content = await fs.readFile(serverPropsFile, 'utf-8');
const lines = content.split('\n');
let currentPassword = null;
for (let line of lines) {
if (line.startsWith('rcon.password=')) {
currentPassword = line.split('=')[1].trim();
break;
}
}
if (currentPassword !== oldPassword) {
return res.status(401).json({ error: 'Ancien mot de passe RCON incorrect' });
}
content = lines.map(line => {
if (line.startsWith('rcon.password=')) {
return `rcon.password=${newPassword}`;
}
return line;
}).join('\n');
await fs.writeFile(serverPropsFile, content, 'utf-8');
res.json({ message: 'Mot de passe RCON changé (redémarrage du serveur requis)' });
} catch (error) {
console.error('Erreur changement RCON:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
module.exports = router;

View File

@@ -0,0 +1,99 @@
const express = require('express');
const fs = require('fs-extra');
const path = require('path');
const { exec } = require('child_process');
const { SERVER_DIR } = require('../server');
const router = express.Router();
function isAuthenticated(req, res, next) {
if (req.session.user) {
next();
} else {
res.status(401).json({ error: 'Non authentifié' });
}
}
router.get('/', isAuthenticated, async (req, res) => {
try {
const backupDir = path.join(SERVER_DIR, 'backups');
if (!await fs.pathExists(backupDir)) {
return res.json({ backups: [] });
}
const files = await fs.readdir(backupDir);
const backups = await Promise.all(
files.map(async (file) => {
const filePath = path.join(backupDir, file);
const stats = await fs.stat(filePath);
return {
name: file,
size: (stats.size / (1024 * 1024)).toFixed(2) + ' MB',
created: stats.mtime,
path: filePath
};
})
);
res.json(backups.sort((a, b) => new Date(b.created) - new Date(a.created)));
} catch (error) {
console.error('Erreur backups:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.post('/create', isAuthenticated, async (req, res) => {
try {
const backupDir = path.join(SERVER_DIR, 'backups');
await fs.ensureDir(backupDir);
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupName = `backup-${timestamp}.tar.gz`;
const backupPath = path.join(backupDir, backupName);
res.json({ message: 'Backup en cours...', backupName });
// Exécuter la création de backup en arrière-plan
exec(
`cd "${SERVER_DIR}" && tar --exclude='.web-admin' --exclude='*.log.lck' -czf "${backupPath}" . 2>/dev/null`,
(error) => {
if (error) {
console.error('Erreur création backup:', error);
} else {
console.log(`✓ Backup créé: ${backupName}`);
}
}
);
} catch (error) {
console.error('Erreur création backup:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.post('/delete/:filename', isAuthenticated, async (req, res) => {
try {
const { filename } = req.params;
if (filename.includes('..')) {
return res.status(400).json({ error: 'Accès non autorisé' });
}
const backupPath = path.join(SERVER_DIR, 'backups', filename);
if (!await fs.pathExists(backupPath)) {
return res.status(404).json({ error: 'Fichier non trouvé' });
}
await fs.remove(backupPath);
res.json({ message: 'Backup supprimé' });
} catch (error) {
console.error('Erreur suppression backup:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
module.exports = router;

138
backend/src/routes/logs.js Normal file
View File

@@ -0,0 +1,138 @@
const express = require('express');
const fs = require('fs-extra');
const path = require('path');
const { SERVER_DIR } = require('../server');
const router = express.Router();
function isAuthenticated(req, res, next) {
if (req.session.user) {
next();
} else {
res.status(401).json({ error: 'Non authentifié' });
}
}
router.get('/', isAuthenticated, async (req, res) => {
try {
const { lines = 100 } = req.query;
const logsDir = SERVER_DIR;
let logFile = path.join(logsDir, 'latest.log');
if (!await fs.pathExists(logFile)) {
logFile = path.join(logsDir, 'ForgeModLoader-server-0.log');
}
if (!await fs.pathExists(logFile)) {
return res.json({ logs: [], message: 'Aucun fichier log trouvé' });
}
const content = await fs.readFile(logFile, 'utf-8');
const logLines = content.split('\n');
const lastLines = logLines.slice(-parseInt(lines));
res.json({
logs: lastLines,
file: path.basename(logFile),
totalLines: logLines.length
});
} catch (error) {
console.error('Erreur logs:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.get('/files', isAuthenticated, async (req, res) => {
try {
const logsDir = SERVER_DIR;
const files = await fs.readdir(logsDir);
const logFiles = files.filter(f =>
f.includes('log') || f.includes('Log')
);
const filesWithStats = await Promise.all(
logFiles.map(async (f) => {
const stats = await fs.stat(path.join(logsDir, f));
return {
name: f,
size: (stats.size / 1024).toFixed(2) + ' KB',
modified: stats.mtime
};
})
);
res.json(filesWithStats.sort((a, b) => new Date(b.modified) - new Date(a.modified)));
} catch (error) {
console.error('Erreur liste logs:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.get('/file/:filename', isAuthenticated, async (req, res) => {
try {
const { filename } = req.params;
const { lines = 100 } = req.query;
if (filename.includes('..')) {
return res.status(400).json({ error: 'Accès non autorisé' });
}
const filePath = path.join(SERVER_DIR, filename);
if (!await fs.pathExists(filePath)) {
return res.status(404).json({ error: 'Fichier non trouvé' });
}
const content = await fs.readFile(filePath, 'utf-8');
const fileLines = content.split('\n');
const lastLines = fileLines.slice(-parseInt(lines));
res.json({
logs: lastLines,
file: filename,
totalLines: fileLines.length
});
} catch (error) {
console.error('Erreur lecture log:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.get('/search', isAuthenticated, async (req, res) => {
try {
const { query } = req.query;
if (!query || query.length < 2) {
return res.status(400).json({ error: 'Requête trop courte' });
}
let logFile = path.join(SERVER_DIR, 'latest.log');
if (!await fs.pathExists(logFile)) {
logFile = path.join(SERVER_DIR, 'ForgeModLoader-server-0.log');
}
if (!await fs.pathExists(logFile)) {
return res.json({ results: [] });
}
const content = await fs.readFile(logFile, 'utf-8');
const lines = content.split('\n');
const results = lines
.map((line, index) => ({ line, index }))
.filter(({ line }) => line.toLowerCase().includes(query.toLowerCase()))
.slice(-50);
res.json({ results });
} catch (error) {
console.error('Erreur recherche:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
module.exports = router;

View File

@@ -0,0 +1,62 @@
const express = require('express');
const fs = require('fs-extra');
const path = require('path');
const { SERVER_DIR } = require('../server');
const router = express.Router();
function isAuthenticated(req, res, next) {
if (req.session.user) {
next();
} else {
res.status(401).json({ error: 'Non authentifié' });
}
}
router.get('/', isAuthenticated, async (req, res) => {
try {
const playersDir = path.join(SERVER_DIR, 'playerdata');
const statsDir = path.join(SERVER_DIR, 'stats');
if (!await fs.pathExists(playersDir)) {
return res.json({ players: [] });
}
const files = await fs.readdir(playersDir);
const uuidRegex = /^[0-9a-f-]{36}\.dat$/i;
const playerFiles = files.filter(f => uuidRegex.test(f));
// Récupérer les noms des joueurs
const usercacheFile = path.join(SERVER_DIR, 'usercache.json');
let usercache = {};
if (await fs.pathExists(usercacheFile)) {
try {
const cache = await fs.readJson(usercacheFile);
usercache = cache.reduce((acc, entry) => {
acc[entry.uuid] = entry.name;
return acc;
}, {});
} catch (e) {
console.error('Erreur lecture usercache:', e);
}
}
const players = playerFiles.map(file => {
const uuid = file.replace('.dat', '');
return {
uuid,
name: usercache[uuid] || 'Inconnu',
lastPlayed: new Date()
};
});
res.json({ players });
} catch (error) {
console.error('Erreur joueurs:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
module.exports = router;

183
backend/src/routes/rcon.js Normal file
View File

@@ -0,0 +1,183 @@
const express = require('express');
const RconClient = require('../utils/rcon');
const fs = require('fs-extra');
const path = require('path');
const { SERVER_DIR } = require('../server');
const router = express.Router();
function isAuthenticated(req, res, next) {
if (req.session.user) {
next();
} else {
res.status(401).json({ error: 'Non authentifié' });
}
}
async function getRconConfig() {
const serverPropsFile = path.join(SERVER_DIR, 'server.properties');
try {
const content = await fs.readFile(serverPropsFile, 'utf-8');
const lines = content.split('\n');
let config = { host: 'localhost', port: 25575, password: '' };
for (let line of lines) {
if (line.startsWith('rcon.port=')) {
config.port = parseInt(line.split('=')[1].trim());
} else if (line.startsWith('rcon.password=')) {
config.password = line.split('=')[1].trim();
}
}
return config;
} catch (e) {
console.error('Erreur lecture config RCON:', e);
return null;
}
}
async function addToHistory(command, response) {
try {
const historyFile = path.join(SERVER_DIR, '.web-admin/rcon-history.json');
await fs.ensureDir(path.dirname(historyFile));
let history = [];
if (await fs.pathExists(historyFile)) {
history = await fs.readJson(historyFile);
}
history.push({
timestamp: new Date(),
command,
response: response.substring(0, 500)
});
await fs.writeJson(historyFile, history.slice(-100), { spaces: 2 });
} catch (e) {
console.error('Erreur sauvegarde historique:', e);
}
}
router.post('/command', isAuthenticated, async (req, res) => {
try {
const { command } = req.body;
if (!command) {
return res.status(400).json({ error: 'Commande manquante' });
}
const config = await getRconConfig();
if (!config || !config.password) {
return res.status(500).json({ error: 'Configuration RCON non trouvée' });
}
const rcon = new RconClient(config.host, config.port, config.password);
try {
await rcon.connect();
const response = await rcon.sendCommand(command);
rcon.disconnect();
await addToHistory(command, response);
res.json({ response, command });
} catch (e) {
console.error('Erreur RCON:', e);
rcon.disconnect();
res.status(500).json({ error: `Erreur RCON: ${e.message}` });
}
} catch (error) {
console.error('Erreur commande:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.get('/history', isAuthenticated, async (req, res) => {
try {
const historyFile = path.join(SERVER_DIR, '.web-admin/rcon-history.json');
if (await fs.pathExists(historyFile)) {
const history = await fs.readJson(historyFile);
res.json(history);
} else {
res.json([]);
}
} catch (e) {
console.error('Erreur historique:', e);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.post('/restart', isAuthenticated, async (req, res) => {
try {
const config = await getRconConfig();
if (!config || !config.password) {
return res.status(500).json({ error: 'Configuration RCON non trouvée' });
}
const rcon = new RconClient(config.host, config.port, config.password);
try {
await rcon.connect();
await rcon.sendCommand('say §6[ADMIN] Serveur redémarrage dans 30 secondes!');
await new Promise(resolve => setTimeout(resolve, 5000));
await rcon.sendCommand('say §6[ADMIN] 25 secondes...');
await new Promise(resolve => setTimeout(resolve, 5000));
await rcon.sendCommand('say §6[ADMIN] 20 secondes...');
await new Promise(resolve => setTimeout(resolve, 10000));
await rcon.sendCommand('say §6[ADMIN] Redémarrage maintenant!');
const response = await rcon.sendCommand('stop');
rcon.disconnect();
await addToHistory('stop', response);
res.json({ message: 'Redémarrage en cours...' });
} catch (e) {
console.error('Erreur redémarrage:', e);
rcon.disconnect();
res.status(500).json({ error: `Erreur: ${e.message}` });
}
} catch (error) {
console.error('Erreur:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.post('/save', isAuthenticated, async (req, res) => {
try {
const config = await getRconConfig();
if (!config || !config.password) {
return res.status(500).json({ error: 'Configuration RCON non trouvée' });
}
const rcon = new RconClient(config.host, config.port, config.password);
try {
await rcon.connect();
await rcon.sendCommand('say §6[ADMIN] Sauvegarde en cours...');
const r1 = await rcon.sendCommand('save-off');
const r2 = await rcon.sendCommand('save-all');
const r3 = await rcon.sendCommand('save-on');
await rcon.sendCommand('say §6[ADMIN] Sauvegarde terminée!');
rcon.disconnect();
await addToHistory('save-all', r2);
res.json({ message: 'Sauvegarde effectuée' });
} catch (e) {
console.error('Erreur sauvegarde:', e);
rcon.disconnect();
res.status(500).json({ error: `Erreur: ${e.message}` });
}
} catch (error) {
console.error('Erreur:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
module.exports = router;

View File

@@ -0,0 +1,79 @@
const express = require('express');
const fs = require('fs-extra');
const path = require('path');
const { SERVER_DIR } = require('../server');
const router = express.Router();
function isAuthenticated(req, res, next) {
if (req.session.user) {
next();
} else {
res.status(401).json({ error: 'Non authentifié' });
}
}
router.get('/', isAuthenticated, async (req, res) => {
try {
const serverPropsFile = path.join(SERVER_DIR, 'server.properties');
if (!await fs.pathExists(serverPropsFile)) {
return res.status(404).json({ error: 'server.properties non trouvé' });
}
const content = await fs.readFile(serverPropsFile, 'utf-8');
const lines = content.split('\n');
const properties = {};
lines.forEach(line => {
if (line && !line.startsWith('#')) {
const [key, ...valueParts] = line.split('=');
properties[key.trim()] = valueParts.join('=').trim();
}
});
res.json({ properties });
} catch (error) {
console.error('Erreur serveur:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.post('/update', isAuthenticated, async (req, res) => {
try {
const { property, value } = req.body;
if (!property || value === undefined) {
return res.status(400).json({ error: 'Données manquantes' });
}
const serverPropsFile = path.join(SERVER_DIR, 'server.properties');
let content = await fs.readFile(serverPropsFile, 'utf-8');
const lines = content.split('\n');
let found = false;
const newContent = lines.map(line => {
if (line.startsWith(property + '=')) {
found = true;
return `${property}=${value}`;
}
return line;
}).join('\n');
if (!found) {
content = newContent + '\n' + property + '=' + value;
} else {
content = newContent;
}
await fs.writeFile(serverPropsFile, content, 'utf-8');
res.json({ message: 'Propriété mise à jour' });
} catch (error) {
console.error('Erreur mise à jour:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
module.exports = router;

View File

@@ -0,0 +1,123 @@
const express = require('express');
const fs = require('fs-extra');
const path = require('path');
const { SERVER_DIR } = require('../server');
const router = express.Router();
function isAuthenticated(req, res, next) {
if (req.session.user) {
next();
} else {
res.status(401).json({ error: 'Non authentifié' });
}
}
async function getWhitelistFile() {
const jsonFile = path.join(SERVER_DIR, 'whitelist.json');
const txtFile = path.join(SERVER_DIR, 'whitelist.txt');
if (await fs.pathExists(jsonFile)) {
return jsonFile;
} else if (await fs.pathExists(txtFile)) {
return txtFile;
}
return jsonFile;
}
router.get('/', isAuthenticated, async (req, res) => {
try {
const whitelistFile = await getWhitelistFile();
if (!await fs.pathExists(whitelistFile)) {
return res.json({ whitelist: [], format: 'json' });
}
if (whitelistFile.endsWith('.json')) {
const whitelist = await fs.readJson(whitelistFile);
res.json({ whitelist, format: 'json' });
} else {
const content = await fs.readFile(whitelistFile, 'utf-8');
const whitelist = content.split('\n').filter(line => line.trim());
res.json({ whitelist, format: 'txt' });
}
} catch (error) {
console.error('Erreur whitelist:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.post('/add', isAuthenticated, async (req, res) => {
try {
const { username } = req.body;
if (!username) {
return res.status(400).json({ error: 'Nom manquant' });
}
const whitelistFile = await getWhitelistFile();
if (whitelistFile.endsWith('.json')) {
let whitelist = [];
if (await fs.pathExists(whitelistFile)) {
whitelist = await fs.readJson(whitelistFile);
}
if (whitelist.some(w => (typeof w === 'string' ? w : w.name) === username)) {
return res.status(400).json({ error: 'Joueur déjà en whitelist' });
}
whitelist.push({ name: username, uuid: '' });
await fs.writeJson(whitelistFile, whitelist, { spaces: 2 });
} else {
let content = '';
if (await fs.pathExists(whitelistFile)) {
content = await fs.readFile(whitelistFile, 'utf-8');
}
if (content.includes(username)) {
return res.status(400).json({ error: 'Joueur déjà en whitelist' });
}
content += (content ? '\n' : '') + username;
await fs.writeFile(whitelistFile, content, 'utf-8');
}
res.json({ message: 'Joueur ajouté à la whitelist' });
} catch (error) {
console.error('Erreur ajout whitelist:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.post('/remove', isAuthenticated, async (req, res) => {
try {
const { username } = req.body;
if (!username) {
return res.status(400).json({ error: 'Nom manquant' });
}
const whitelistFile = await getWhitelistFile();
if (whitelistFile.endsWith('.json')) {
let whitelist = await fs.readJson(whitelistFile);
whitelist = whitelist.filter(w => (typeof w === 'string' ? w : w.name) !== username);
await fs.writeJson(whitelistFile, whitelist, { spaces: 2 });
} else {
let content = await fs.readFile(whitelistFile, 'utf-8');
content = content.split('\n').filter(line => line.trim() !== username).join('\n');
await fs.writeFile(whitelistFile, content, 'utf-8');
}
res.json({ message: 'Joueur retiré de la whitelist' });
} catch (error) {
console.error('Erreur suppression whitelist:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
module.exports = router;

73
backend/src/server.js Normal file
View File

@@ -0,0 +1,73 @@
const express = require('express');
const session = require('express-session');
const path = require('path');
const dotenv = require('dotenv');
const cors = require('cors');
const fs = require('fs-extra');
dotenv.config();
const app = express();
const PORT = process.env.PORT || 4001;
const SERVER_DIR = process.env.SERVER_DIR || '/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red';
// Middlewares
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors({
origin: process.env.NODE_ENV === 'production' ? false : true,
credentials: true
}));
// Session config
app.use(session({
secret: process.env.SESSION_SECRET || 'your-secret-key-change-in-prod',
resave: false,
saveUninitialized: true,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 24 heures
}
}));
// Importation des routes
const authRoutes = require('./routes/auth');
const serverRoutes = require('./routes/server');
const rconRoutes = require('./routes/rcon');
const logsRoutes = require('./routes/logs');
const playersRoutes = require('./routes/players');
const whitelistRoutes = require('./routes/whitelist');
const backupRoutes = require('./routes/backup');
// Routes API
app.use('/api/auth', authRoutes);
app.use('/api/server', serverRoutes);
app.use('/api/rcon', rconRoutes);
app.use('/api/logs', logsRoutes);
app.use('/api/players', playersRoutes);
app.use('/api/whitelist', whitelistRoutes);
app.use('/api/backup', backupRoutes);
// Health check
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date() });
});
// Erreur 404
app.use((req, res) => {
res.status(404).json({ error: 'Route non trouvée' });
});
// Error handler
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Erreur serveur interne' });
});
app.listen(PORT, () => {
console.log(`\n🚀 Backend Admin NationsGlory démarré sur http://localhost:${PORT}`);
console.log(`📁 Répertoire du serveur: ${SERVER_DIR}\n`);
});
module.exports = { app, SERVER_DIR };

141
backend/src/utils/rcon.js Normal file
View File

@@ -0,0 +1,141 @@
const net = require('net');
class RconClient {
constructor(host, port, password) {
this.host = host;
this.port = port;
this.password = password;
this.socket = null;
this.authenticated = false;
this.packetId = 0;
this.responseBuffer = Buffer.alloc(0);
}
createPacket(type, payload) {
const payloadBuffer = Buffer.from(payload, 'utf8');
const body = Buffer.alloc(4 + payloadBuffer.length + 1);
body.writeInt32BE(type, 0);
payloadBuffer.copy(body, 4);
body[4 + payloadBuffer.length] = 0;
const size = body.length;
const packet = Buffer.alloc(4 + size);
packet.writeInt32LE(size, 0);
body.copy(packet, 4);
return packet;
}
parsePacket(buffer) {
if (buffer.length < 12) return null;
const size = buffer.readInt32LE(0);
if (buffer.length < 4 + size) return null;
const id = buffer.readInt32LE(4);
const type = buffer.readInt32LE(8);
let payload = '';
if (size > 8) {
payload = buffer.slice(12, 4 + size - 1).toString('utf8');
}
return { id, type, payload, totalSize: 4 + size };
}
async connect() {
return new Promise((resolve, reject) => {
this.socket = net.createConnection({
host: this.host,
port: this.port
});
this.socket.on('connect', () => {
console.log('✓ Connecté au serveur RCON');
this.authenticate().then(resolve).catch(reject);
});
this.socket.on('error', reject);
setTimeout(() => {
reject(new Error('Timeout de connexion'));
}, 5000);
});
}
async authenticate() {
return new Promise((resolve, reject) => {
this.packetId++;
const packet = this.createPacket(3, this.password);
this.socket.write(packet);
const handleData = (data) => {
this.responseBuffer = Buffer.concat([this.responseBuffer, data]);
const response = this.parsePacket(this.responseBuffer);
if (response && response.id === this.packetId) {
this.responseBuffer = this.responseBuffer.slice(response.totalSize);
this.socket.removeListener('data', handleData);
if (response.id !== -1) {
this.authenticated = true;
console.log('✓ Authentifié RCON');
resolve();
} else {
reject(new Error('Mot de passe RCON incorrect'));
}
}
};
this.socket.on('data', handleData);
setTimeout(() => {
reject(new Error('Timeout d\'authentification'));
}, 5000);
});
}
async sendCommand(command) {
return new Promise((resolve, reject) => {
if (!this.authenticated) {
reject(new Error('Non authentifié'));
return;
}
this.packetId++;
const id = this.packetId;
const packet = this.createPacket(2, command);
this.socket.write(packet);
const handleData = (data) => {
this.responseBuffer = Buffer.concat([this.responseBuffer, data]);
const response = this.parsePacket(this.responseBuffer);
if (response && response.id === id) {
this.responseBuffer = this.responseBuffer.slice(response.totalSize);
this.socket.removeListener('data', handleData);
resolve(response.payload);
}
};
this.socket.on('data', handleData);
setTimeout(() => {
reject(new Error('Timeout de commande'));
}, 10000);
});
}
disconnect() {
if (this.socket) {
this.socket.destroy();
this.socket = null;
this.authenticated = false;
}
}
}
module.exports = RconClient;