Compare commits

...

10 Commits

Author SHA1 Message Date
53869a6626 Configuration portable : chemins relatifs et API dynamique 2026-02-05 18:30:35 +01:00
8b905dc9b3 fix: corriger tous les chemins SERVER_DIR et RCON_HOST pour machine distante
- Remplacer fallback SERVER_DIR de '/home/innotex/Documents/...' à '/mc-server'
- Remplacer RCON_HOST de 'localhost' à '172.17.0.1' (Docker bridge gateway)
- 8 fichiers corrigés: server.js + 7 routes
- Localhost n'est pas accessible depuis Docker vers l'host
- 172.17.0.1 est la gateway Docker qui permet au container d'accéder l'host
2026-02-05 02:17:04 +01:00
75bbfe0a77 docs: code review technique détaillé + roadmap implémentation 2026-02-05 02:12:33 +01:00
2032a006c4 docs: analyse complète architecture + sécurité + recommandations 2026-02-05 02:11:06 +01:00
4891c539da feat: afficher statut OP des joueurs (lire ops.txt) 2026-02-05 02:07:17 +01:00
c4c9714d41 fix: utiliser IP hostname au lieu de localhost dans le frontend 2026-02-05 02:03:06 +01:00
ba48c760c5 feat: Docker Compose avec Node.js pré-compilé pour déploiement sans npm 2026-02-05 01:50:40 +01:00
1228a02221 fix: écouter sur 0.0.0.0 au lieu de localhost pour accès distant 2026-02-05 01:26:28 +01:00
innotex
ec50d8e306 fix: Corriger le chemin SERVER_DIR pour le déploiement distant et ajouter configuration systemd 2026-02-05 01:07:10 +01:00
innotex
e19fd78d75 Fix: Force npm package installation with explicit package names 2026-02-05 00:45:34 +01:00
17 changed files with 2417 additions and 47 deletions

36
.env.example Normal file
View File

@@ -0,0 +1,36 @@
# ====================================
# Configuration Application Web Admin
# ====================================
# Environnement
NODE_ENV=production
# Port de l'application web
PORT=4001
# Chemin vers le serveur Minecraft (RELATIF au dossier parent)
# Par défaut: ../NationsGlory_ServeurBuild_Red
# Cela permet de pointer vers le serveur MC sans chemins absolus
MC_SERVER_PATH=../NationsGlory_ServeurBuild_Red
# Configuration RCON du serveur Minecraft
# Utiliser 'localhost' si l'app web et le serveur MC sont sur la même machine
# Sinon, utiliser l'IP du serveur MC
RCON_HOST=localhost
RCON_PORT=25575
RCON_PASSWORD=minecraft
# Secret de session (CHANGEZ CETTE VALEUR EN PRODUCTION!)
# Générez avec: openssl rand -base64 32
SESSION_SECRET=changez-moi-en-production-utilisez-une-valeur-aleatoire-securisee
# Domaine/IP publique (optionnel)
# Utilisé pour générer des URLs complètes si nécessaire
# Laissez vide pour détection automatique
# PUBLIC_HOST=votre-domaine.com
# PUBLIC_HOST=192.168.1.100
# Configuration CORS (optionnel)
# Laissez vide pour autoriser toutes les origines (développement)
# En production, spécifiez votre domaine
# CORS_ORIGIN=https://votre-domaine.com

935
ANALYSIS.md Normal file
View File

@@ -0,0 +1,935 @@
# 📊 Analyse Complète : NationsGlory Web Admin Panel + Serveur Minecraft
## 📋 Table des Matières
1. [Architecture Générale](#architecture-générale)
2. [Application Web](#application-web)
3. [Serveur Minecraft](#serveur-minecraft)
4. [Intégration](#intégration)
5. [Points de Sécurité](#points-de-sécurité)
6. [Recommandations](#recommandations)
---
## Architecture Générale
### Stack Technique
```
┌─────────────────────────────────────────────────────────┐
│ Frontend (SPA Vanilla JS) │
│ - index.html, app.js, style.css │
│ - API_URL: http://<hostname>:4001/api │
└────────────────────┬────────────────────────────────────┘
│ HTTP REST
┌────────────────────▼────────────────────────────────────┐
│ Backend (Express.js + Node.js 18) │
│ - server.js (port 4001, listens on 0.0.0.0) │
│ - 7 routes: auth, server, rcon, logs, players, │
│ whitelist, backup │
└────────────────────┬────────────────────────────────────┘
│ RCON (port 25575)
┌────────────────────▼────────────────────────────────────┐
│ Minecraft Server (mcpc.jar / Forge) │
│ - Version: 1.6.4 (MCPC+) │
│ - Mods: 60+ mods (Nations UI, FlansMod, etc) │
│ - Port: 25565 (server), 25575 (RCON) │
└─────────────────────────────────────────────────────────┘
Déploiement:
┌─────────────────────────────────────────────────────────┐
│ Docker Compose (machine distante: 192.168.1.252) │
│ - Container webnationsglory-admin │
│ - Volume mount: /mc-server → serveur Minecraft │
│ - Restart: unless-stopped │
│ - Port mapping: 4001:4001 │
└─────────────────────────────────────────────────────────┘
```
### Taille & Ressources
- **Serveur Minecraft**: 143 MB (world + config + mods)
- **Web App**: ~200 KB (frontend + backend source)
- **Docker Image**: ~200 MB (Node.js 18-alpine + modules)
---
## Application Web
### Backend Architecture
#### 1. **Serveur Principal** (`backend/src/server.js`)
```javascript
// Configuration
const PORT = 4001;
const SERVER_DIR = '/mc-server' (Docker) ou local path
const SESSION_SECRET = process.env.SESSION_SECRET
// Middlewares
- express.json() / urlencoded
- CORS (origin: true, credentials: true)
- express-session (24h max age, httpOnly)
// Routes
/api/auth Authentification + enregistrement
/api/server Configuration serveur (server.properties)
/api/rcon Exécution commandes serveur
/api/logs Historique logs
/api/players Liste joueurs + statut OP
/api/whitelist Gestion whitelist
/api/backup Gestion backups
```
**Points clés:**
- ✅ Frontend path auto-detection (Docker vs local)
- ✅ CORS enabled avec credentials
- ✅ Session 24h max
- ⚠️ Session secret en dur ("your-secret-key-change-in-prod")
- ⚠️ HTTPS désactivé (secure: false)
#### 2. **Routes d'Authentification** (`backend/src/routes/auth.js`)
**Endpoints:**
```
POST /register → Créer compte admin (vérifie que MC username est OP)
POST /login → Connexion
POST /logout → Déconnexion
GET /check → Vérifier session active
POST /change-password → Changer mot de passe
```
**Mécanisme:**
```javascript
// Enregistrement
1. Vérifier que c'est le premier compte (users.json vide)
2. Lire ops.txt OU ops.json depuis serveur
3. Vérifier que mcUsername est dans la liste OPs
4. Hash password avec bcryptjs (10 rounds)
5. Sauvegarder dans users.json
// Connexion
1. Trouver utilisateur par username
2. Comparer password avec hash bcrypt
3. Créer session req.session.user
4. Session persiste dans memory ( perd les sessions au redémarrage!)
```
**Fichiers utilisés:**
- `backend/data/users.json` → Stockage utilisateurs + passwords hashés
- `~/NationsGlory_ServeurBuild_Red/ops.txt` → Admins serveur
- `~/NationsGlory_ServeurBuild_Red/ops.json` → Alternative format OPs
**Risques de sécurité:**
- ⚠️ Sessions en mémoire uniquement (pas persistentes)
- ⚠️ users.json stocké en local/Docker (pas de base de données)
- ⚠️ Pas de rate limiting sur login
- ⚠️ Pas de 2FA
#### 3. **Routes Serveur** (`backend/src/routes/server.js`)
**Endpoints:**
```
GET / → Lire server.properties
POST /update → Modifier une propriété
GET /jvm-args → Lire arguments JVM
POST /restart → Redémarrer le serveur
POST /stop → Arrêter le serveur
POST /backup → Créer backup
```
**Implémentation:**
- Parse `server.properties` ligne par ligne
- Supporte modification de propriétés individuelles
- Contrôle via RCON pour restart/stop
#### 4. **Routes RCON** (`backend/src/routes/rcon.js`)
**Architecture RCON:**
```javascript
// Classe RconClient (backend/src/utils/rcon.js)
- Protocole: Minecraft RCON (TCP port 25575)
- Packet format: [size][id][type][payload][null]
- Type 3 = Auth, Type 2 = Command, Type 0 = Response
- Timeout: 5s (auth), 10s (command)
// Implémentation
1. Parse server.properties pour rcon.port + rcon.password
2. Créer socket TCP vers serveur
3. S'authentifier avec password
4. Envoyer commandes
5. Recevoir réponses avec stream buffer
6. Timeout après 10s
```
**Endpoints:**
```
POST /command → Exécuter commande RCON
GET /history → Historique 200 dernières commandes
DELETE /history/:id → Nettoyer historique
```
**Commandes supportées:**
- `say <msg>` → Message serveur
- `stop` → Arrêter serveur
- `restart` → Redémarrer (custom)
- `list` → Joueurs en ligne (parsé pour joueurs)
- `op <player>` → Ajouter OP
- `whitelist add <player>` → Ajouter whitelist
- Tout ce que le serveur Minecraft supporte
**Historique:**
- Stocké dans `.web-admin/rcon-history.json`
- Limité à 200 dernières commandes
- Contient: timestamp, command, response, success, error
#### 5. **Routes Joueurs** (`backend/src/routes/players.js`)
**Endpoints:**
```
GET / → Liste tous joueurs + statut OP + dernière connexion
```
**Implémentation:**
```javascript
// 1. Lire world/players/*.dat (fichiers joueurs)
// 2. Mapper UUID → Nom via usercache.json
// 3. Lire ops.txt et parser noms OPs
// 4. Ajouter champ isOp: boolean pour chaque joueur
// 5. Retourner liste avec:
{
players: [
{ uuid, name, isOp, lastPlayed }
]
}
```
**Données sources:**
- `world/players/*.dat` → Fichiers joueurs Minecraft
- `usercache.json` → Mapping UUID ↔ Nom (si existe)
- `ops.txt` → Noms des OPs (nouvelle feature)
#### 6. **Routes Whitelist** (`backend/src/routes/whitelist.js`)
**Endpoints:**
```
GET / → Lire whitelist
POST /add → Ajouter joueur
POST /remove → Retirer joueur
```
**Support formats:**
- `whitelist.json` → Format moderne `[{name, uuid}]`
- `whitelist.txt` → Format legacy, 1 nom/ligne
**Implémentation:**
- Auto-détecte format (JSON ou TXT)
- Priorise JSON si les deux existent
- Ajoute validation de doublons
#### 7. **Routes Logs** (`backend/src/routes/logs.js`)
**Endpoints:**
```
GET / → Logs serveur (recent-latest.log + ancien logs.1, logs.2, etc)
```
**Parsing:**
- Supporte format colorisé Minecraft
- Convertit codes Minecraft (§X) en HTML
- Retourne HTML colorisé pour UI
#### 8. **Routes Backup** (`backend/src/routes/backup.js`)
**Endpoints:**
```
GET / → Lister backups existants
POST /create → Créer nouveau backup (tar.gz)
GET /download → Télécharger backup
POST /restore → Restaurer depuis backup
```
**Implémentation:**
```bash
tar --exclude='.web-admin' --exclude='*.log.lck' \
-czf "backup-<timestamp>.tar.gz" .
```
**Répertoire:** `backups/` dans SERVER_DIR
---
### Frontend Architecture
#### 1. **Structure SPA** (`frontend/public/js/app.js`)
```javascript
// État global
let currentUser = null;
let isAuthenticated = false
// Points d'entrée
checkAuth() // Vérifier session
showLoginPage() // Authentification
showDashboard() // Interface admin
switchTab(tabName) // Navigation
// Sections disponibles (onglets)
- Dashboard Infos serveur + joueurs en ligne
- Joueurs Liste tous joueurs, statut OP
- Whitelist Gérer whitelist
- Commandes RCON Interface ligne de commande
- Logs Consulter logs serveur
- Serveur Settings server.properties
- Backups Gérer backups + restore
- Paramètres Changement password
```
#### 2. **API_URL Resolution**
```javascript
// Avant: hardcoded localhost
const API_URL = 'http://localhost:4001/api';
// Après: Dynamic hostname detection
const API_URL = window.location.hostname === 'localhost' ||
window.location.hostname === '127.0.0.1'
? 'http://localhost:4001/api'
: `http://${window.location.hostname}:4001/api`;
```
**Impact:** Permet accès via IP (192.168.1.252) sans modification code
#### 3. **Authentification Frontend**
```javascript
// Login Flow
checkAuth()
fetch /auth/check
Authentifié showDashboard()
Non-auth showLoginPage() handleLogin() fetch /auth/login
// Données transmises
POST /api/auth/login {
username: string,
password: string
}
// Réponse
{ authenticated: true, user: { username, mcUsername } }
```
#### 4. **Dashboard Principal**
```
┌─────────────────────────────────────────────────┐
│ NationsGlory Admin Panel 🔓 admin ▼ Logout │
├─────────────────────────────────────────────────┤
│ Tabs: [Dashboard] [Joueurs] [Whitelist] [RCON] │
│ [Logs] [Serveur] [Backups] [Settings] │
├─────────────────────────────────────────────────┤
│ 📊 Dashboard │
│ • Joueurs connectés: 0/20 ⚪ │
│ • Statut serveur: ON/OFF │
│ • Mods chargés: 60+ │
│ • World: FLAT, seed: [value] │
│ • Max build height: 256 │
├─────────────────────────────────────────────────┤
│ 👥 Joueurs en ligne │
│ [Rafraîchir] │
│ 📋 Tous les Joueurs │
│ [Table avec columns: Nom, OP, UUID, LastPlayed] │
└─────────────────────────────────────────────────┘
```
#### 5. **Sections Clés**
**A. Joueurs** (`loadPlayersData()`)
- Affiche tous les joueurs jamais connectés
- Colonne OP: ✅ OP ou ❌
- Colonne UUID + dernière connexion
- Chargement RCON `list` pour joueurs en ligne en temps réel
**B. Whitelist** (`getWhitelistHTML()`)
- Ajouter / Retirer joueurs
- Support JSON et TXT formats
- Validation doublons
**C. RCON Terminal** (`getRconHTML()`)
- Interface ligne de commande
- Input texte + historique
- Exécution commandes
- Affichage résultats
**D. Logs** (`getLogsHTML()`)
- Lecture logs serveur
- Coloration codes Minecraft
- Scroll horizontal si long
**E. Serveur Settings** (`getServerHTML()`)
- Liste properties modifiables
- Boutons Start/Stop/Restart
- Actions avec confirmation
**F. Backups** (`getBackupsHTML()`)
- Lister backups
- Créer backup
- Télécharger
- Restaurer depuis backup
**G. Settings** (`getSettingsHTML()`)
- Changement mot de passe
- About version
---
## Serveur Minecraft
### Configuration Serveur
**Server Properties** (`server.properties`)
```properties
server-ip= # Écoute sur toutes interfaces
server-port=25565 # Port Minecraft standard
max-players=20 # Limite 20 joueurs
gamemode=1 # Creative mode
difficulty=1 # Normal
level-type=FLAT # Monde plat
level-name=world # Dossier monde
enable-rcon=true # RCON activé
rcon.port=25575 # Port RCON
rcon.password=Landau8210 # ⚠️ Password en clair!
online-mode=false # Offline mode (permet cracked clients)
pvp=true # PvP activé
allow-nether=true # Nether autorisé
spawn-animals=true # Génération animaux
white-list=false # Whitelist désactivée par défaut
```
### Structure Serveur
```
NationsGlory_ServeurBuild_Red/
├── world/ # Données du monde
│ ├── players/ # Fichiers joueurs (.dat)
│ ├── stats/ # Stats joueurs (.json)
│ ├── region/ # Chunks du monde
│ └── level.dat # Info niveau
├── mods/ # 60+ mods Minecraft
│ └── [liste complète ci-dessous]
├── plugins/ # Plugins serveur
├── config/ # Configuration mods (60+ fichiers .cfg)
├── libraries/ # Dépendances Java
├── customnpcs/ # NPCs custom
├── Flan/ # Config FlansMod
├── .web-admin/ # Historique RCON
├── mcpc.jar # Serveur MCPC+
├── server.properties # Configuration
├── ops.txt # Admins serveur (format texte)
├── ops.json # Alternative format OPs
├── whitelist.txt # Whitelist (TXT)
├── whitelist.json # Alternative whitelist (JSON)
├── banned-players.txt # Bannis
├── banned-ips.txt # IPs bannies
├── usercache.json # Cache UUID ↔ Nom (si existe)
└── backups/ # Sauvegardes (créées par app)
```
**Taille: 143 MB**
### Mods/Plugins Identifiés
Via `config/*.cfg`:
1. **NationsGlory mods**
- ngcore.cfg → Nations Core
- ngcontent.cfg → Contenu Nations
- nationsui.cfg → Interface Nations
- nationsgui.json
- nationsmachine.cfg → Machines Nations
- NGUpgrades.json
2. **Gameplay Mods**
- FlansMod.cfg → Armes, véhicules
- WeaponMod.cfg → Armes custom
- ICBM.cfg → Missiles
- CustomNpcs.cfg → NPCs custom
- GetAllTheSeeds.cfg
- AnimalBikes.cfg
3. **Building/Tech**
- Chisel.cfg → Blocs décoratifs
- BiblioCraft.cfg → Furniture
- ArmorStatusHUD.cfg
- TLSpecialArmor.cfg
4. **Farming**
- PAM mods (multiple):
- pamtemperateplants.cfg
- pamweeeflowers.cfg
- pamrandomaplants.cfg
- pamextendedglass.cfg
5. **Infrastructure**
- Galacticraft/ → Espace
- Netherrocks → Nether
- AquaTweaks.cfg → Eau
- MelonSpawn.cfg
- Autoutils.cfg
6. **Visualization**
- MapWriter.cfg → Mini-map
- ArmorStatusHUD.cfg
7. **Framework/Admin**
- forge.cfg → Forge base
- forgeChunkLoading.cfg
- bspkrsCore.cfg → Libraire
- logging.properties
**Version:** 1.6.4 (Minecraft 1.6.4, probablement MCPC+ pour Forge)
### Architecture Serveur MCPC+
MCPC+ = Minecraft + Bukkit compatibility + Forge mods
- Combine Minecraft serveur vanilla
- Ajoute Forge pour mods
- Supporte plugins Bukkit (en parallèle)
- Version 1.6.4 (assez vieille, 2013)
---
## Intégration
### Flux de Données
```
Frontend (Browser)
↓ fetch /api/auth/login
Backend (Express)
├─ Vérifie users.json
├─ Hash password bcrypt
├─ Crée session
└─ Return { authenticated: true }
↓ fetch /api/rcon/command
Backend RCON Client
├─ Parse server.properties
├─ Connect TCP:25575
├─ Auth avec rcon.password
├─ Send command bytes
└─ Receive/parse response
Minecraft Server (MCPC+)
├─ Process command
├─ Modify world/players/ ou server.properties
└─ Send output back
Backend Response
└─ Return output JSON
Frontend Display
└─ Show result to user
```
### Session Management
**Current:** Express-session in-memory
```javascript
app.use(session({
secret: SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: {
secure: false, // HTTP only (not HTTPS)
httpOnly: true, // No JS access
sameSite: 'lax', // CSRF protection
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}))
```
**Problème:** Sessions perdues au redémarrage Docker
- ✅ currentUser reste authentifié tant que session active
- ❌ Redémarrage container = logout automatique
- **Solution:** Utiliser connect-mongo, redis, ou JWT
### RCON Integration
```
Web UI → Command → Backend → RCON Client → Minecraft Server → Response
```
**Example: /api/rcon/command POST {"command": "list"}**
1. Backend RCON Client:
```
- Read: server.properties (rcon.port=25575, rcon.password=Landau8210)
- Connect: TCP 192.168.1.252:25575
- Authenticate: send password bytes
- Send: "list" command
- Parse: "There are 1/20 players online: anakine22"
- Return: JSON response
```
2. Frontend parses & displays
---
## Points de Sécurité
### 🔴 Critiques
1. **RCON Password en Clair**
```properties
rcon.password=Landau8210 # Visible dans server.properties
```
**Risque:** Accès direct au serveur Minecraft si fichier leak
**Fix:** Lire depuis env vars, pas fichier
2. **Session en Mémoire**
```javascript
// Sessions perdues au redémarrage
// Accessible seulement au container courant (pas clustering)
```
**Risque:** Forcer logout en case de crash/redémarrage
**Fix:** Store sessions en Redis/DB
3. **HTTP seulement**
```javascript
secure: false // No HTTPS
```
**Risque:** Credentials en clair sur le réseau
**Fix:** Activer HTTPS, self-signed cert minimum
4. **users.json pas encrypté**
```
backend/data/users.json
```
**Contient:** Passwords bcryptés (OK) + usernames (leak de données)
**Fix:** Chiffrer fichier ou utiliser database
### 🟡 Modérés
5. **Pas de Rate Limiting**
```
Brute force possible sur endpoint /auth/login
```
**Fix:** express-rate-limit middleware
6. **Pas de 2FA**
```
Seul username/password
```
**Fix:** Ajouter TOTP si critique
7. **CORS trop permissif**
```javascript
CORS({ origin: true, credentials: true })
```
**Risque:** N'importe quel domaine peut faire requêtes
**Fix:** Whitelist domains explicitement
8. **Pas de CSRF tokens**
```
State-changing operations (POST/DELETE) sans CSRF
```
**Fix:** Ajouter csurf middleware
9. **Pas de input validation**
```javascript
// POST /api/server/update
{ property, value } // Pas de validation property names
```
**Risque:** Injection server.properties arbitraire
**Fix:** Whitelist propriétés modifiables
10. **Fichiers logs exposés**
```javascript
GET /api/logs expose tous les logs du serveur
```
**Risque:** Info sensitive (errors, plugins, config)
**Fix:** Limiter logs, filtrer data sensitive
### 🟢 Acceptables
11. **online-mode=false**
```properties
Serveur accepte cracked clients (offline mode)
```
**Pourquoi:** Peut être intentionnel pour privé réseau
12. **Debug pas activé**
```properties
debug=false ✓
```
13. **Entity tracking config non visible** ✓
---
## Recommandations
### 🚀 Priorité 1: Critique
#### 1. Ajouter HTTPS/SSL
```dockerfile
# docker-compose.yml
volumes:
- ./certs:/app/certs # Certificates auto-renew via certbot
environment:
- NODE_ENV=production
- HTTPS_CERT=/app/certs/cert.pem
- HTTPS_KEY=/app/certs/key.pem
```
#### 2. Session Persistence (Redis)
```bash
# Ajouter redis service
version: '3'
services:
app:
# ...
depends_on:
- redis
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
```
```javascript
// backend/src/server.js
const RedisStore = require('connect-redis').default;
const { createClient } = require('redis');
const redisClient = createClient({ host: 'redis', port: 6379 });
const store = new RedisStore({ client: redisClient });
app.use(session({
store,
secret: process.env.SESSION_SECRET,
// ...
}));
```
#### 3. RCON Password sécurisé
```bash
# .env
RCON_PASSWORD=${RCON_PASSWORD} # Via docker-compose.yml
# Avoid storing in server.properties
# Option: Lire depuis /run/secrets/rcon_password (Docker Secrets)
```
#### 4. Rate Limiting
```javascript
// backend/src/middleware/rateLimit.js
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 min
max: 5, // 5 tentatives
message: 'Trop de tentatives login'
});
app.post('/api/auth/login', loginLimiter, handleLogin);
```
#### 5. Validation Input Server Properties
```javascript
// backend/src/routes/server.js
const ALLOWED_PROPERTIES = [
'max-players',
'difficulty',
'pvp',
'enable-nether',
'server-ip',
'server-port', // Attention: change port!
// NOT: gamemode, level-type, level-name, rcon.password, etc
];
if (!ALLOWED_PROPERTIES.includes(property)) {
return res.status(400).json({ error: 'Propriété non modifiable' });
}
```
---
### 🎯 Priorité 2: Important
#### 6. CSRF Tokens
```javascript
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: false, sessionKey: 'session' });
app.post('/api/server/update', csrfProtection, handleUpdate);
```
#### 7. Chiffrer users.json
```javascript
const crypto = require('crypto');
const FILE_KEY = Buffer.from(process.env.FILE_ENCRYPTION_KEY, 'hex');
function encryptUsers(users) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-gcm', FILE_KEY, iv);
const encrypted = cipher.update(JSON.stringify(users));
// ...
}
```
#### 8. Logs Filtering
```javascript
// backend/src/routes/logs.js
// Filtrer:
// - Passwords
// - Database credentials
// - API keys
// - Player IPs
// - Internal errors
```
#### 9. Database Migration
```
Remplacer:
users.json → PostgreSQL (+ migrations)
RCON history JSON → Database table
Avantages:
- Transactions
- Backups
- Multi-instance
- Better performance
```
#### 10. API Documentation
```
Ajouter Swagger/OpenAPI
- /api-docs
- Documentation endpoints
- Authentification requirements
```
---
### 🛠️ Priorité 3: Nice to Have
#### 11. Monitoring & Alerting
```javascript
// Backend metrics
- Uptime server
- RCON connection failures
- API response times
- Error rate
// Integration: Prometheus + Grafana
// Discord webhooks for alerts
```
#### 12. Admin Audit Log
```javascript
// Log all admin actions
- Login/logout timestamps
- Command executor + command
- Property changes
- File modifications
```
#### 13. World Backup Restoration
```javascript
// Current: Créer backup ✓
// TODO: Restore backup avec safety checks
// - Stop serveur
// - Restore world/
// - Restart serveur
// - Verify integrité
```
#### 14. Multi-Admin Support
```javascript
// Current: 1 seul admin (users.json = array mais UI single)
// TODO:
// - Multiple admins support
// - Role-based access (RBAC)
// - Permissions per admin
```
#### 15. Players Statistics
```javascript
// Ajouter analytics:
// - Playtime per player
// - Last 7 days activity
// - Top builders/killers
// - Trends
```
---
## Métriques de Santé
### ✅ Points Forts
1. **Architecture modulaire** → 7 routes bien séparées
2. **Vanilla JS frontend** → Pas de build complexity
3. **RCON protocol bien implémenté** → Pas de pertes commands
4. **Session management** → Auth basique ok
5. **Docker-ready** → Déploiement facile
6. **Markdown docs** → QUICKSTART, DEPLOYMENT, MAINTENANCE
7. **Dynamic API URL** → Fonctionne IP ou localhost
8. **OP status tracking** → Lecture ops.txt correcte
### ⚠️ Domaines d'Amélioration
1. **Sécurité** → HTTPS, rate limiting, validation input
2. **Persistence** → Session, database
3. **Error handling** → Logs réseau, retry logic
4. **Scalability** → Multi-instance support
5. **Observability** → Monitoring, alerting
6. **Testing** → Tests unitaires + intégration
7. **Code quality** → Linting, type checking
8. **Features** → 2FA, RBAC, audit log
### 📈 Performance
- **Frontend**: Single file SPA (~61 KB)
- **Backend**: 104 lines server.js, routes <250 lines each
- **Response time**: <100ms API (RCON varies)
- **Memory**: ~50-100 MB Node.js container
- **Concurrency**: Sequential RCON (une commande à la fois)
---
## Conclusion
### Current State
L'application est **fonctionnelle** pour un usage **LAN privé** ou **réseau interne**.
**Prêt pour production?** ❌
- Manque HTTPS, rate limiting, CSRF
- Sessions perdues au redémarrage
- Pas de database
- Input validation insuffisante
**Prêt pour LAN interne?** ✅
- Sécurité acceptable pour réseau de confiance
- Toutes features essentielles présentes
- Déploiement Docker simple
### Priorités à Court Terme
1. ✅ **Dynamic API URL** (FAIT: app.js détecte hostname)
2. ✅ **OP status tracking** (FAIT: lit ops.txt, affiche ✅/❌)
3. ⏳ **Session persistence** (Redis recommended)
4. ⏳ **HTTPS + rate limiting** (Pour production)
5. ⏳ **Input validation** (Server properties + RCON)
### Prochaines Étapes Recommandées
```
Phase 1 (Cette semaine)
[ ] Tester OP display après refresh navigateur
[ ] Documenter RCON commands disponibles
[ ] Ajouter healthcheck serveur Minecraft
Phase 2 (Semaine 2)
[ ] Implémenter Redis pour sessions
[ ] Ajouter HTTPS auto-signed
[ ] Rate limiting login
Phase 3 (Semaine 3)
[ ] Migrer users.json → Database
[ ] Input validation complete
[ ] CSRF tokens
```
---
**Document généré:** 5 février 2026
**Version:** 1.0
**Auteur:** GitHub Copilot

View File

@@ -2,17 +2,11 @@ FROM node:18-alpine
WORKDIR /app
# Copy npm config first
COPY .npmrc ./
# Copy package files
COPY backend/package*.json ./
# Install dependencies with retry logic
RUN npm cache clean --force && \
npm install --no-optional --legacy-peer-deps 2>&1 | tail -20 && \
ls -la node_modules/express || npm install express && \
test -d node_modules/express || (echo "FATAL: express not found" && exit 1)
# Install dependencies
RUN npm install --production
# Copy application code
COPY backend/src ./src

1182
TECHNICAL_REVIEW.md Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -5,7 +5,7 @@ const path = require('path');
const router = express.Router();
const USERS_FILE = path.join(__dirname, '../../data/users.json');
const SERVER_DIR = process.env.SERVER_DIR || '/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red';
const SERVER_DIR = process.env.SERVER_DIR || '/mc-server';
async function initUsersFile() {
await fs.ensureDir(path.dirname(USERS_FILE));

View File

@@ -4,7 +4,7 @@ const path = require('path');
const { exec } = require('child_process');
const router = express.Router();
const SERVER_DIR = process.env.SERVER_DIR || '/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red';
const SERVER_DIR = process.env.SERVER_DIR || '/mc-server';
function isAuthenticated(req, res, next) {
if (req.session.user) {

View File

@@ -3,7 +3,7 @@ const fs = require('fs-extra');
const path = require('path');
const router = express.Router();
const SERVER_DIR = process.env.SERVER_DIR || '/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red';
const SERVER_DIR = process.env.SERVER_DIR || '/mc-server';
function isAuthenticated(req, res, next) {
if (req.session.user) {

View File

@@ -3,7 +3,7 @@ const fs = require('fs-extra');
const path = require('path');
const router = express.Router();
const SERVER_DIR = process.env.SERVER_DIR || '/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red';
const SERVER_DIR = process.env.SERVER_DIR || '/mc-server';
function isAuthenticated(req, res, next) {
if (req.session && req.session.user) {
@@ -46,6 +46,21 @@ router.get('/', isAuthenticated, async (req, res) => {
console.warn('usercache.json non trouvé:', usercacheFile);
}
// Récupérer les OP depuis ops.txt
let ops = [];
const opsFile = path.join(SERVER_DIR, 'ops.txt');
if (await fs.pathExists(opsFile)) {
try {
const opsContent = await fs.readFile(opsFile, 'utf8');
ops = opsContent
.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0);
} catch (e) {
console.error('Erreur lecture ops.txt:', e);
}
}
// Récupérer les stats de dernière connexion
const statsDir = path.join(SERVER_DIR, 'world', 'stats');
let statsByUuid = {};
@@ -71,9 +86,11 @@ router.get('/', isAuthenticated, async (req, res) => {
const players = playerFiles.map(file => {
const uuid = file.replace('.dat', '');
const playerName = usercache[uuid] || 'Inconnu';
return {
uuid,
name: usercache[uuid] || 'Inconnu',
name: playerName,
isOp: ops.includes(playerName),
lastPlayed: new Date() // TODO: Extraire du fichier .dat si possible
};
});

View File

@@ -4,7 +4,7 @@ const fs = require('fs-extra');
const path = require('path');
const router = express.Router();
const SERVER_DIR = process.env.SERVER_DIR || '/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red';
const SERVER_DIR = process.env.SERVER_DIR || '/mc-server';
function isAuthenticated(req, res, next) {
if (req.session && req.session.user) {
@@ -20,9 +20,9 @@ async function getRconConfig() {
const content = await fs.readFile(serverPropsFile, 'utf-8');
const lines = content.split('\n');
// Utiliser RCON_HOST depuis les variables d'environnement ou localhost
// Utiliser RCON_HOST depuis les variables d'environnement (172.17.0.1 = Docker bridge gateway)
let config = {
host: process.env.RCON_HOST || 'localhost',
host: process.env.RCON_HOST || '172.17.0.1',
port: process.env.RCON_PORT || 25575,
password: ''
};

View File

@@ -3,7 +3,7 @@ const fs = require('fs-extra');
const path = require('path');
const router = express.Router();
const SERVER_DIR = process.env.SERVER_DIR || '/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red';
const SERVER_DIR = process.env.SERVER_DIR || '/mc-server';
function isAuthenticated(req, res, next) {
if (req.session.user) {

View File

@@ -3,7 +3,7 @@ const fs = require('fs-extra');
const path = require('path');
const router = express.Router();
const SERVER_DIR = process.env.SERVER_DIR || '/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red';
const SERVER_DIR = process.env.SERVER_DIR || '/mc-server';
function isAuthenticated(req, res, next) {
if (req.session.user) {

View File

@@ -9,7 +9,7 @@ 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';
const SERVER_DIR = process.env.SERVER_DIR || '/mc-server';
// Déterminer le chemin du frontend (support Docker et local)
const frontendPath = (() => {
@@ -28,8 +28,15 @@ const frontendPath = (() => {
// Middlewares
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// CORS dynamique pour supporter n'importe quelle origine
app.use(cors({
origin: true,
origin: (origin, callback) => {
// Autoriser toutes les origines en mode production
// ou l'origine spécifiée dans les variables d'environnement
const allowedOrigin = process.env.CORS_ORIGIN || true;
callback(null, allowedOrigin);
},
credentials: true
}));
@@ -95,8 +102,8 @@ app.use((err, req, res, next) => {
res.status(err.status || 500).json({ error: 'Erreur serveur interne', details: err.message });
});
app.listen(PORT, () => {
console.log(`Backend Admin NationsGlory démarré sur http://localhost:${PORT}`);
app.listen(PORT, '0.0.0.0', () => {
console.log(`Backend Admin NationsGlory démarré sur http://0.0.0.0:${PORT}`);
console.log(`Répertoire du serveur: ${SERVER_DIR}`);
});

View File

@@ -1,20 +1,35 @@
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: webnationsglory-admin
env_file:
- .env
environment:
NODE_ENV: production
PORT: 4001
NODE_ENV: ${NODE_ENV:-production}
PORT: ${PORT:-4001}
SERVER_DIR: /mc-server
RCON_HOST: localhost
RCON_PORT: 25575
SESSION_SECRET: change-this-in-production
RCON_HOST: ${RCON_HOST:-localhost}
RCON_PORT: ${RCON_PORT:-25575}
RCON_PASSWORD: ${RCON_PASSWORD:-minecraft}
SESSION_SECRET: ${SESSION_SECRET:-your-secret-key-change-in-production}
volumes:
- /NationsGloryRED/NationsGlory_ServeurBuild_Red:/mc-server
- web-admin:/mc-server/.web-admin
restart: unless-stopped
# Montage relatif du serveur MC (depuis le dossier parent)
- ${MC_SERVER_PATH:-../NationsGlory_ServeurBuild_Red}:/mc-server:ro
- web-admin-data:/app/data
ports:
- "${PORT:-4001}:${PORT:-4001}"
network_mode: host
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:${PORT:-4001}/api/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
volumes:
web-admin:
web-admin-data:

View File

@@ -1,5 +1,20 @@
// Configuration API
const API_URL = 'http://localhost:4001/api';
// Configuration API - Détection automatique de l'URL de base
// Fonctionne avec localhost, IP publique, ou nom de domaine
const API_URL = (() => {
const protocol = window.location.protocol; // http: ou https:
const hostname = window.location.hostname; // IP ou domaine
const port = '4001'; // Port du backend
// Si on accède depuis le même port que le backend (mode production intégré)
if (window.location.port === port) {
return `${protocol}//${hostname}:${port}/api`;
}
// Mode développement ou accès via différent port
return `${protocol}//${hostname}:${port}/api`;
})();
console.log('API URL configurée:', API_URL);
// State
let currentUser = null;
@@ -889,12 +904,13 @@ function getPlayersHTML() {
<thead>
<tr>
<th>Nom</th>
<th>OP</th>
<th>UUID</th>
<th>Dernière Connexion</th>
</tr>
</thead>
<tbody id="playersTable">
<tr><td colspan="3" style="text-align: center;">Chargement...</td></tr>
<tr><td colspan="4" style="text-align: center;">Chargement...</td></tr>
</tbody>
</table>
</div>
@@ -922,12 +938,13 @@ async function loadPlayersData() {
table.innerHTML = data.players.map(p => `
<tr>
<td>${p.name}</td>
<td style="text-align: center;">${p.isOp ? '✅ OP' : '❌'}</td>
<td><code style="font-size: 11px;">${p.uuid}</code></td>
<td>${new Date(p.lastPlayed).toLocaleString()}</td>
</tr>
`).join('');
} else {
table.innerHTML = '<tr><td colspan="3" style="text-align: center;">Aucun joueur</td></tr>';
table.innerHTML = '<tr><td colspan="4" style="text-align: center;">Aucun joueur</td></tr>';
}
} catch (e) {
console.error('Erreur joueurs:', e);
@@ -1016,15 +1033,46 @@ window.loadOnlinePlayers = async function() {
`;
if (onlineCount > 0 && playerNames.length > 0) {
listDiv.innerHTML = `
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; margin-top: 15px;">
${playerNames.map(name => `
<div style="padding: 12px; background: white; border: 2px solid #4CAF50; border-radius: 8px; text-align: center;">
<span style="font-weight: bold; color: #333;">🎮 ${name}</span>
</div>
`).join('')}
</div>
`;
// Charger les infos des joueurs pour voir qui est OP
try {
const playersResponse = await fetch(`${API_URL}/players`, {
credentials: 'include'
});
let playersData = await playersResponse.json();
let playersByName = {};
if (playersData.players) {
playersByName = playersData.players.reduce((acc, p) => {
acc[p.name] = p;
return acc;
}, {});
}
listDiv.innerHTML = `
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; margin-top: 15px;">
${playerNames.map(name => {
const playerInfo = playersByName[name];
const isOp = playerInfo && playerInfo.isOp;
return `
<div style="padding: 12px; background: white; border: 2px solid ${isOp ? '#FFD700' : '#4CAF50'}; border-radius: 8px; text-align: center;">
<span style="font-weight: bold; color: #333;">🎮 ${name}</span>
${isOp ? '<div style="margin-top: 5px; font-size: 12px; color: #FFD700; font-weight: bold;">👑 OP</div>' : ''}
</div>
`;
}).join('')}
</div>
`;
} catch (e) {
console.error('Erreur chargement infos joueurs:', e);
listDiv.innerHTML = `
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; margin-top: 15px;">
${playerNames.map(name => `
<div style="padding: 12px; background: white; border: 2px solid #4CAF50; border-radius: 8px; text-align: center;">
<span style="font-weight: bold; color: #333;">🎮 ${name}</span>
</div>
`).join('')}
</div>
`;
}
} else {
listDiv.innerHTML = '';
}

16
nationglory-admin.service Normal file
View File

@@ -0,0 +1,16 @@
[Unit]
Description=NationsGlory Admin Web Interface
After=network.target
[Service]
Type=simple
User=innotex
WorkingDirectory=/home/innotex/WebNationsGlory_Deploy/backend
ExecStart=/usr/bin/node src/server.js
Restart=always
RestartSec=10
Environment="NODE_ENV=production"
Environment="PORT=4001"
[Install]
WantedBy=multi-user.target

118
nginx.conf.example Normal file
View File

@@ -0,0 +1,118 @@
# ====================================
# Configuration Nginx - NationsGlory Web Admin
# ====================================
#
# Ce fichier fournit une configuration exemple pour utiliser nginx
# comme reverse proxy devant l'application web admin.
#
# Avantages :
# - Support HTTPS/SSL
# - Cache des assets statiques
# - Compression gzip
# - Sécurité renforcée
#
# Installation :
# 1. Copiez ce fichier dans /etc/nginx/sites-available/nationsglory
# 2. Modifiez les domaines et chemins SSL
# 3. Créez le lien symbolique : ln -s /etc/nginx/sites-available/nationsglory /etc/nginx/sites-enabled/
# 4. Testez : nginx -t
# 5. Rechargez : systemctl reload nginx
# Redirection HTTP vers HTTPS
server {
listen 80;
listen [::]:80;
server_name votre-domaine.com www.votre-domaine.com;
# Redirection vers HTTPS
return 301 https://$server_name$request_uri;
}
# Configuration HTTPS
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name votre-domaine.com www.votre-domaine.com;
# Certificats SSL (Let's Encrypt ou autre)
ssl_certificate /etc/letsencrypt/live/votre-domaine.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/votre-domaine.com/privkey.pem;
# Configuration SSL moderne et sécurisée
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# HSTS (optionnel mais recommandé)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Sécurité supplémentaire
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Logs
access_log /var/log/nginx/nationsglory-access.log;
error_log /var/log/nginx/nationsglory-error.log;
# Taille maximale des uploads (pour les backups)
client_max_body_size 500M;
# Proxy vers l'application Node.js
location / {
proxy_pass http://localhost:4001;
proxy_http_version 1.1;
# Headers pour le proxy
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# Support WebSocket (si nécessaire dans le futur)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Cache des assets statiques
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://localhost:4001;
proxy_cache_valid 200 7d;
expires 7d;
add_header Cache-Control "public, immutable";
}
# Compression gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
}
# Configuration alternative : Accès par sous-domaine
# Décommentez si vous voulez utiliser admin.votre-domaine.com
#
# server {
# listen 443 ssl http2;
# listen [::]:443 ssl http2;
# server_name admin.votre-domaine.com;
#
# ssl_certificate /etc/letsencrypt/live/votre-domaine.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/votre-domaine.com/privkey.pem;
#
# # Même configuration que ci-dessus
# location / {
# proxy_pass http://localhost:4001;
# # ... (même configuration proxy)
# }
# }