vako 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/README.md +1944 -0
- package/bin/commands/quick-setup.js +111 -0
- package/bin/commands/setup-executor.js +203 -0
- package/bin/commands/setup.js +737 -0
- package/bin/create-veko-app.js +75 -0
- package/bin/veko-update.js +205 -0
- package/bin/veko.js +188 -0
- package/error/error.ejs +382 -0
- package/index.js +36 -0
- package/lib/adapters/nextjs-adapter.js +241 -0
- package/lib/app.js +749 -0
- package/lib/core/auth-manager.js +1353 -0
- package/lib/core/auto-updater.js +1118 -0
- package/lib/core/logger.js +97 -0
- package/lib/core/module-installer.js +86 -0
- package/lib/dev/dev-server.js +292 -0
- package/lib/layout/layout-manager.js +834 -0
- package/lib/plugin-manager.js +1795 -0
- package/lib/routing/route-manager.js +1000 -0
- package/package.json +231 -0
- package/templates/public/css/style.css +2 -0
- package/templates/public/js/main.js +1 -0
- package/tsconfig.json +50 -0
- package/types/index.d.ts +238 -0
package/lib/app.js
ADDED
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const helmet = require('helmet'); // Sécurité headers
|
|
5
|
+
const rateLimit = require('express-rate-limit'); // Protection DDoS
|
|
6
|
+
const validator = require('validator'); // Validation d'entrées
|
|
7
|
+
|
|
8
|
+
const ModuleInstaller = require('./core/module-installer');
|
|
9
|
+
const Logger = require('./core/logger');
|
|
10
|
+
const LayoutManager = require('./layout/layout-manager');
|
|
11
|
+
const RouteManager = require('./routing/route-manager');
|
|
12
|
+
const DevServer = require('./dev/dev-server');
|
|
13
|
+
const PluginManager = require('./plugin-manager');
|
|
14
|
+
const AuthManager = require('./core/auth-manager');
|
|
15
|
+
|
|
16
|
+
// Vérification de l'existence de l'auto-updater de manière sécurisée
|
|
17
|
+
let AutoUpdater = null;
|
|
18
|
+
try {
|
|
19
|
+
AutoUpdater = require('./core/auto-updater');
|
|
20
|
+
} catch (error) {
|
|
21
|
+
// L'auto-updater n'est pas disponible, mais l'application peut continuer
|
|
22
|
+
console.warn('Auto-updater non disponible:', error.message);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class App {
|
|
26
|
+
constructor(options = {}) {
|
|
27
|
+
// Validation des options d'entrée
|
|
28
|
+
this.validateOptions(options);
|
|
29
|
+
|
|
30
|
+
// Configuration par défaut
|
|
31
|
+
this.options = {
|
|
32
|
+
port: this.sanitizePort(options.port) || 3000,
|
|
33
|
+
wsPort: this.sanitizePort(options.wsPort) || 3008,
|
|
34
|
+
viewsDir: this.sanitizePath(options.viewsDir) || 'views',
|
|
35
|
+
staticDir: this.sanitizePath(options.staticDir) || 'public',
|
|
36
|
+
routesDir: this.sanitizePath(options.routesDir) || 'routes',
|
|
37
|
+
isDev: Boolean(options.isDev),
|
|
38
|
+
watchDirs: this.sanitizePaths(options.watchDirs) || ['views', 'routes', 'public'],
|
|
39
|
+
errorLog: this.sanitizePath(options.errorLog) || 'error.log',
|
|
40
|
+
showStack: process.env.NODE_ENV !== 'production' && Boolean(options.showStack),
|
|
41
|
+
autoInstall: Boolean(options.autoInstall ?? true),
|
|
42
|
+
// Configuration sécurisée par défaut
|
|
43
|
+
security: {
|
|
44
|
+
helmet: true,
|
|
45
|
+
rateLimit: {
|
|
46
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
47
|
+
max: 100, // limit each IP to 100 requests per windowMs
|
|
48
|
+
message: 'Trop de requêtes, veuillez réessayer plus tard.'
|
|
49
|
+
},
|
|
50
|
+
cors: {
|
|
51
|
+
origin: process.env.ALLOWED_ORIGINS?.split(',') || false,
|
|
52
|
+
credentials: true
|
|
53
|
+
},
|
|
54
|
+
...options.security
|
|
55
|
+
},
|
|
56
|
+
layouts: {
|
|
57
|
+
enabled: true,
|
|
58
|
+
layoutsDir: this.sanitizePath(options.layouts?.layoutsDir) || 'views/layouts',
|
|
59
|
+
defaultLayout: this.sanitizeString(options.layouts?.defaultLayout) || 'main',
|
|
60
|
+
extension: this.sanitizeString(options.layouts?.extension) || '.ejs',
|
|
61
|
+
sections: this.sanitizeArray(options.layouts?.sections) || ['head', 'header', 'content', 'footer', 'scripts'],
|
|
62
|
+
cache: process.env.NODE_ENV === 'production',
|
|
63
|
+
...options.layouts
|
|
64
|
+
},
|
|
65
|
+
plugins: {
|
|
66
|
+
enabled: Boolean(options.plugins?.enabled ?? true),
|
|
67
|
+
autoLoad: Boolean(options.plugins?.autoLoad ?? true),
|
|
68
|
+
pluginsDir: this.sanitizePath(options.plugins?.pluginsDir) || 'plugins',
|
|
69
|
+
whitelist: options.plugins?.whitelist || [], // Plugins autorisés
|
|
70
|
+
...options.plugins
|
|
71
|
+
},
|
|
72
|
+
prefetch: {
|
|
73
|
+
enabled: Boolean(options.prefetch?.enabled ?? true),
|
|
74
|
+
maxConcurrent: Math.min(Math.max(1, options.prefetch?.maxConcurrent || 3), 10),
|
|
75
|
+
notifyUser: Boolean(options.prefetch?.notifyUser ?? true),
|
|
76
|
+
cacheRoutes: Boolean(options.prefetch?.cacheRoutes ?? true),
|
|
77
|
+
prefetchDelay: Math.max(100, options.prefetch?.prefetchDelay || 1000),
|
|
78
|
+
...options.prefetch
|
|
79
|
+
},
|
|
80
|
+
// Configuration de l'auto-updater
|
|
81
|
+
autoUpdater: {
|
|
82
|
+
enabled: Boolean(options.autoUpdater?.enabled ?? true) && AutoUpdater !== null,
|
|
83
|
+
checkOnStart: Boolean(options.autoUpdater?.checkOnStart ?? true),
|
|
84
|
+
autoUpdate: Boolean(options.autoUpdater?.autoUpdate ?? false),
|
|
85
|
+
updateChannel: options.autoUpdater?.updateChannel || 'stable',
|
|
86
|
+
securityUpdates: Boolean(options.autoUpdater?.securityUpdates ?? true),
|
|
87
|
+
showNotifications: Boolean(options.autoUpdater?.showNotifications ?? true),
|
|
88
|
+
backupCount: Math.max(1, options.autoUpdater?.backupCount || 5),
|
|
89
|
+
checkInterval: Math.max(300000, options.autoUpdater?.checkInterval || 3600000), // min 5 min
|
|
90
|
+
...options.autoUpdater
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
this.app = express();
|
|
95
|
+
this.express = this.app;
|
|
96
|
+
|
|
97
|
+
// Initialize components
|
|
98
|
+
this.logger = new Logger();
|
|
99
|
+
this.layoutManager = new LayoutManager(this, this.options.layouts);
|
|
100
|
+
this.routeManager = new RouteManager(this, this.options);
|
|
101
|
+
|
|
102
|
+
// Système d'authentification
|
|
103
|
+
this.auth = new AuthManager(this);
|
|
104
|
+
|
|
105
|
+
// Système d'auto-updater (si disponible)
|
|
106
|
+
if (this.options.autoUpdater.enabled && AutoUpdater) {
|
|
107
|
+
this.autoUpdater = AutoUpdater;
|
|
108
|
+
this.autoUpdaterActive = false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (this.options.isDev) {
|
|
112
|
+
this.devServer = new DevServer(this, this.options);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (this.options.plugins.enabled) {
|
|
116
|
+
this.plugins = new PluginManager(this, this.options.plugins);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.init();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async ensureModules() {
|
|
123
|
+
if (this.options.autoInstall !== false) {
|
|
124
|
+
try {
|
|
125
|
+
await ModuleInstaller.checkAndInstall();
|
|
126
|
+
ModuleInstaller.createPackageJsonIfNeeded();
|
|
127
|
+
} catch (error) {
|
|
128
|
+
this.logger.log('error', 'Erreur lors de la vérification des modules', error.message);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async installModule(moduleName, version = 'latest') {
|
|
134
|
+
return await ModuleInstaller.installModule(moduleName, version);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
log(type, message, details = '') {
|
|
138
|
+
this.logger.log(type, message, details);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 🚀 Initialisation de l'auto-updater non bloquante avec meilleure gestion des erreurs
|
|
142
|
+
async initAutoUpdater() {
|
|
143
|
+
if (!this.options.autoUpdater.enabled || !this.autoUpdater) return;
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
this.log('info', 'Initialisation de l\'auto-updater', '🔄');
|
|
147
|
+
|
|
148
|
+
// Tester si l'auto-updater a les méthodes nécessaires
|
|
149
|
+
if (typeof this.autoUpdater.init !== 'function') {
|
|
150
|
+
throw new Error('Module auto-updater invalide ou incomplet');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Configure l'auto-updater avec les options de l'app
|
|
154
|
+
this.autoUpdater.config = {
|
|
155
|
+
...this.autoUpdater.defaultConfig || {},
|
|
156
|
+
autoCheck: this.options.autoUpdater.checkOnStart,
|
|
157
|
+
autoUpdate: this.options.autoUpdater.autoUpdate,
|
|
158
|
+
updateChannel: this.options.autoUpdater.updateChannel,
|
|
159
|
+
securityCheck: this.options.autoUpdater.securityUpdates,
|
|
160
|
+
notifications: this.options.autoUpdater.showNotifications,
|
|
161
|
+
backupCount: this.options.autoUpdater.backupCount,
|
|
162
|
+
checkInterval: this.options.autoUpdater.checkInterval
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Initialiser l'auto-updater de manière non bloquante
|
|
166
|
+
this.autoUpdater.init().then(() => {
|
|
167
|
+
this.autoUpdaterActive = true;
|
|
168
|
+
this.log('success', 'Auto-updater initialisé', '✅');
|
|
169
|
+
|
|
170
|
+
// Vérification initiale si demandée, mais sans bloquer
|
|
171
|
+
if (this.options.autoUpdater.checkOnStart) {
|
|
172
|
+
// Utiliser setTimeout pour garantir que la vérification n'est pas bloquante
|
|
173
|
+
setTimeout(() => {
|
|
174
|
+
this.checkForUpdates(true).catch(err => {
|
|
175
|
+
this.log('error', 'Erreur lors de la vérification initiale', err.message);
|
|
176
|
+
});
|
|
177
|
+
}, 2000); // Délai pour permettre au serveur de démarrer d'abord
|
|
178
|
+
}
|
|
179
|
+
}).catch(error => {
|
|
180
|
+
this.log('error', 'Erreur lors de l\'initialisation de l\'auto-updater', error.message);
|
|
181
|
+
this.autoUpdaterActive = false;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
} catch (error) {
|
|
185
|
+
// Capturer les erreurs mais ne pas bloquer l'application
|
|
186
|
+
this.log('error', 'Erreur lors de la configuration de l\'auto-updater', error.message);
|
|
187
|
+
this.autoUpdaterActive = false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 🔍 Vérification des mises à jour avec gestion d'erreurs améliorée
|
|
192
|
+
async checkForUpdates(silent = false) {
|
|
193
|
+
if (!this.autoUpdaterActive) return null;
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const updateInfo = await Promise.race([
|
|
197
|
+
this.autoUpdater.checkForUpdates(silent),
|
|
198
|
+
// Timeout après 5 secondes pour ne pas bloquer
|
|
199
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout lors de la vérification')), 5000))
|
|
200
|
+
]);
|
|
201
|
+
|
|
202
|
+
if (updateInfo.hasUpdate && !silent) {
|
|
203
|
+
this.log('warning', 'Mise à jour disponible', `${updateInfo.currentVersion} → ${updateInfo.latestVersion}`);
|
|
204
|
+
|
|
205
|
+
// Notification pour les mises à jour de sécurité
|
|
206
|
+
if (updateInfo.security) {
|
|
207
|
+
this.log('error', 'MISE À JOUR DE SÉCURITÉ CRITIQUE', '🔒 Mise à jour fortement recommandée');
|
|
208
|
+
|
|
209
|
+
// Mise à jour automatique pour les correctifs de sécurité si activée
|
|
210
|
+
if (this.options.autoUpdater.securityUpdates && this.options.autoUpdater.autoUpdate) {
|
|
211
|
+
this.log('info', 'Mise à jour de sécurité automatique', '🚀 Démarrage...');
|
|
212
|
+
this.performUpdate(updateInfo).catch(err => {
|
|
213
|
+
this.log('error', 'Échec de la mise à jour de sécurité', err.message);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Mise à jour automatique normale si activée
|
|
219
|
+
if (this.options.autoUpdater.autoUpdate && !updateInfo.security) {
|
|
220
|
+
this.log('info', 'Mise à jour automatique', '🚀 Démarrage...');
|
|
221
|
+
this.performUpdate(updateInfo).catch(err => {
|
|
222
|
+
this.log('error', 'Échec de la mise à jour automatique', err.message);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
} else if (updateInfo.needsInstall && !silent) {
|
|
226
|
+
this.log('warning', 'Veko non installé correctement', '⚠️ Réinstallation requise');
|
|
227
|
+
} else if (!updateInfo.hasUpdate && !silent) {
|
|
228
|
+
this.log('success', 'Veko à jour', `✅ Version ${updateInfo.currentVersion || 'inconnue'}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return updateInfo;
|
|
232
|
+
|
|
233
|
+
} catch (error) {
|
|
234
|
+
// Log l'erreur mais continue l'exécution
|
|
235
|
+
this.log('error', 'Erreur lors de la vérification des mises à jour', error.message);
|
|
236
|
+
return { hasUpdate: false, error: error.message };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// 🚀 Exécution de la mise à jour
|
|
241
|
+
async performUpdate(updateInfo) {
|
|
242
|
+
if (!this.autoUpdaterActive) {
|
|
243
|
+
throw new Error('Auto-updater non actif');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
this.log('info', 'Début de la mise à jour', `🚀 ${updateInfo.latestVersion}`);
|
|
248
|
+
|
|
249
|
+
// Hook avant mise à jour
|
|
250
|
+
if (this.plugins) {
|
|
251
|
+
await this.plugins.executeHook('app:before-update', this, updateInfo);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const success = await this.autoUpdater.performUpdate(updateInfo);
|
|
255
|
+
|
|
256
|
+
if (success) {
|
|
257
|
+
this.log('success', 'Mise à jour terminée', '✅ Redémarrage requis');
|
|
258
|
+
|
|
259
|
+
// Hook après mise à jour réussie
|
|
260
|
+
if (this.plugins) {
|
|
261
|
+
await this.plugins.executeHook('app:after-update', this, updateInfo);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Notification optionnelle de redémarrage
|
|
265
|
+
if (this.options.autoUpdater.showNotifications) {
|
|
266
|
+
console.log('\n' + '═'.repeat(60));
|
|
267
|
+
console.log('\x1b[32m\x1b[1m🎉 MISE À JOUR VEKO RÉUSSIE!\x1b[0m');
|
|
268
|
+
console.log('\x1b[33m⚠️ Redémarrez l\'application pour appliquer les changements\x1b[0m');
|
|
269
|
+
console.log('═'.repeat(60) + '\n');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return true;
|
|
273
|
+
} else {
|
|
274
|
+
this.log('error', 'Échec de la mise à jour', '❌');
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
} catch (error) {
|
|
279
|
+
this.log('error', 'Erreur durant la mise à jour', error.message);
|
|
280
|
+
|
|
281
|
+
// Hook en cas d'erreur
|
|
282
|
+
if (this.plugins) {
|
|
283
|
+
await this.plugins.executeHook('app:update-error', this, error);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// 🔄 Rollback vers une version précédente
|
|
291
|
+
async rollbackUpdate(backupPath = null) {
|
|
292
|
+
if (!this.autoUpdaterActive) {
|
|
293
|
+
throw new Error('Auto-updater non actif');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
this.log('info', 'Début du rollback', '🔄');
|
|
298
|
+
|
|
299
|
+
const success = await this.autoUpdater.rollback(backupPath);
|
|
300
|
+
|
|
301
|
+
if (success) {
|
|
302
|
+
this.log('success', 'Rollback terminé', '✅');
|
|
303
|
+
return true;
|
|
304
|
+
} else {
|
|
305
|
+
this.log('error', 'Échec du rollback', '❌');
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
} catch (error) {
|
|
310
|
+
this.log('error', 'Erreur durant le rollback', error.message);
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// 📊 Informations sur l'auto-updater
|
|
316
|
+
getAutoUpdaterInfo() {
|
|
317
|
+
if (!this.autoUpdaterActive) {
|
|
318
|
+
return { active: false, message: 'Auto-updater désactivé' };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
active: true,
|
|
323
|
+
currentVersion: this.autoUpdater.getCurrentVersion(),
|
|
324
|
+
config: this.autoUpdater.config,
|
|
325
|
+
stats: this.autoUpdater.stats
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// 📋 Route d'administration pour l'auto-updater
|
|
330
|
+
setupAutoUpdaterRoutes() {
|
|
331
|
+
if (!this.autoUpdaterActive) return;
|
|
332
|
+
|
|
333
|
+
// Route pour vérifier les mises à jour
|
|
334
|
+
this.app.get('/_veko/updates/check', async (req, res) => {
|
|
335
|
+
try {
|
|
336
|
+
const updateInfo = await this.checkForUpdates(true);
|
|
337
|
+
res.json(updateInfo);
|
|
338
|
+
} catch (error) {
|
|
339
|
+
res.status(500).json({ error: error.message });
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Route pour déclencher une mise à jour
|
|
344
|
+
this.app.post('/_veko/updates/perform', async (req, res) => {
|
|
345
|
+
try {
|
|
346
|
+
const updateInfo = await this.checkForUpdates(true);
|
|
347
|
+
if (updateInfo.hasUpdate) {
|
|
348
|
+
const success = await this.performUpdate(updateInfo);
|
|
349
|
+
res.json({ success, updateInfo });
|
|
350
|
+
} else {
|
|
351
|
+
res.json({ success: false, message: 'Aucune mise à jour disponible' });
|
|
352
|
+
}
|
|
353
|
+
} catch (error) {
|
|
354
|
+
res.status(500).json({ error: error.message });
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// Route pour les statistiques
|
|
359
|
+
this.app.get('/_veko/updates/stats', (req, res) => {
|
|
360
|
+
res.json(this.getAutoUpdaterInfo());
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// Route pour effectuer un rollback
|
|
364
|
+
this.app.post('/_veko/updates/rollback', async (req, res) => {
|
|
365
|
+
try {
|
|
366
|
+
const { backupPath } = req.body;
|
|
367
|
+
const success = await this.rollbackUpdate(backupPath);
|
|
368
|
+
res.json({ success });
|
|
369
|
+
} catch (error) {
|
|
370
|
+
res.status(500).json({ error: error.message });
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
this.log('info', 'Routes auto-updater configurées', '🔗 /_veko/updates/*');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async init() {
|
|
378
|
+
this.setupExpress();
|
|
379
|
+
|
|
380
|
+
// Initialisation asynchrone et non bloquante de l'auto-updater
|
|
381
|
+
this.initAutoUpdater().catch(err => {
|
|
382
|
+
// L'erreur est déjà enregistrée dans la méthode initAutoUpdater
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
if (this.plugins) {
|
|
386
|
+
this.plugins.executeHook('app:init', this);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (this.options.isDev) {
|
|
390
|
+
this.devServer.setup();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Configuration des routes d'administration seulement si l'auto-updater est activé
|
|
394
|
+
if (this.autoUpdaterActive) {
|
|
395
|
+
this.setupAutoUpdaterRoutes();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
setupExpress() {
|
|
400
|
+
// Configuration sécurisée des headers
|
|
401
|
+
if (this.options.security.helmet) {
|
|
402
|
+
this.app.use(helmet({
|
|
403
|
+
contentSecurityPolicy: {
|
|
404
|
+
directives: {
|
|
405
|
+
defaultSrc: ["'self'"],
|
|
406
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
407
|
+
scriptSrc: ["'self'"],
|
|
408
|
+
imgSrc: ["'self'", "data:", "https:"],
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
hsts: process.env.NODE_ENV === 'production'
|
|
412
|
+
}));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Rate limiting
|
|
416
|
+
if (this.options.security.rateLimit) {
|
|
417
|
+
const limiter = rateLimit(this.options.security.rateLimit);
|
|
418
|
+
this.app.use(limiter);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
this.app.set('view engine', 'ejs');
|
|
422
|
+
this.app.set('views', [
|
|
423
|
+
path.join(process.cwd(), this.options.viewsDir),
|
|
424
|
+
path.join(process.cwd(), this.options.layouts.layoutsDir),
|
|
425
|
+
path.join(__dirname, '..', 'views'),
|
|
426
|
+
path.join(__dirname, '..', 'error')
|
|
427
|
+
]);
|
|
428
|
+
|
|
429
|
+
// Configuration sécurisée du parsing
|
|
430
|
+
this.app.use(express.json({
|
|
431
|
+
limit: '10mb',
|
|
432
|
+
verify: (req, res, buf) => {
|
|
433
|
+
// Vérification de la taille et du contenu
|
|
434
|
+
if (buf.length > 10485760) { // 10MB
|
|
435
|
+
throw new Error('Payload trop volumineux');
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}));
|
|
439
|
+
|
|
440
|
+
this.app.use(express.urlencoded({
|
|
441
|
+
extended: true,
|
|
442
|
+
limit: '10mb',
|
|
443
|
+
parameterLimit: 100
|
|
444
|
+
}));
|
|
445
|
+
|
|
446
|
+
// Serveur de fichiers statiques sécurisé
|
|
447
|
+
const staticDir = this.options.staticDir;
|
|
448
|
+
this.app.use(express.static(path.join(process.cwd(), staticDir), {
|
|
449
|
+
dotfiles: 'deny',
|
|
450
|
+
index: false,
|
|
451
|
+
maxAge: process.env.NODE_ENV === 'production' ? '1d' : 0
|
|
452
|
+
}));
|
|
453
|
+
|
|
454
|
+
// Middleware de sécurité personnalisé
|
|
455
|
+
this.app.use(this.securityMiddleware());
|
|
456
|
+
|
|
457
|
+
if (this.options.layouts?.enabled) {
|
|
458
|
+
this.app.use(this.layoutManager.middleware());
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (this.options.isDev) {
|
|
462
|
+
this.app.use(this.devServer.middleware());
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
this.logger.log('success', 'Express configuration initialized', '⚡ Ready to start');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Middleware de sécurité personnalisé
|
|
469
|
+
securityMiddleware() {
|
|
470
|
+
return (req, res, next) => {
|
|
471
|
+
// Protection XSS
|
|
472
|
+
res.setHeader('X-XSS-Protection', '1; mode=block');
|
|
473
|
+
|
|
474
|
+
// Masquer les informations du serveur
|
|
475
|
+
res.removeHeader('X-Powered-By');
|
|
476
|
+
|
|
477
|
+
// Validation des headers
|
|
478
|
+
const suspiciousHeaders = ['x-forwarded-host', 'x-real-ip'];
|
|
479
|
+
for (const header of suspiciousHeaders) {
|
|
480
|
+
if (req.headers[header] && !this.isValidHeader(req.headers[header])) {
|
|
481
|
+
return res.status(400).json({ error: 'En-tête suspect détecté' });
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
next();
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
isValidHeader(value) {
|
|
490
|
+
// Validation basique des headers
|
|
491
|
+
return typeof value === 'string' &&
|
|
492
|
+
value.length < 1000 &&
|
|
493
|
+
!/[<>\"']/.test(value);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Active le système d'authentification
|
|
498
|
+
* @param {Object} config - Configuration de l'authentification
|
|
499
|
+
*/
|
|
500
|
+
async enableAuth(config = {}) {
|
|
501
|
+
await this.auth.init(config);
|
|
502
|
+
return this;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Vérifie si l'authentification est activée
|
|
507
|
+
*/
|
|
508
|
+
isAuthEnabled() {
|
|
509
|
+
return this.auth.isEnabled;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Middleware pour protéger une route
|
|
514
|
+
*/
|
|
515
|
+
requireAuth() {
|
|
516
|
+
if (!this.auth.isEnabled) {
|
|
517
|
+
throw new Error('Le système d\'authentification n\'est pas activé');
|
|
518
|
+
}
|
|
519
|
+
return this.auth.requireAuth.bind(this.auth);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Middleware pour protéger une route avec un rôle spécifique
|
|
524
|
+
*/
|
|
525
|
+
requireRole(role) {
|
|
526
|
+
if (!this.auth.isEnabled) {
|
|
527
|
+
throw new Error('Le système d\'authentification n\'est pas activé');
|
|
528
|
+
}
|
|
529
|
+
return this.auth.requireRole(role);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Delegate route methods to RouteManager
|
|
533
|
+
createRoute(method, path, handler, options = {}) {
|
|
534
|
+
return this.routeManager.createRoute(method, path, handler, options);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
deleteRoute(method, path) {
|
|
538
|
+
return this.routeManager.deleteRoute(method, path);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
updateRoute(method, path, newHandler) {
|
|
542
|
+
return this.routeManager.updateRoute(method, path, newHandler);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
loadRoutes(routesDir = this.options.routesDir) {
|
|
546
|
+
return this.routeManager.loadRoutes(routesDir);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
listRoutes() {
|
|
550
|
+
return this.routeManager.listRoutes();
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Delegate layout methods to LayoutManager
|
|
554
|
+
createLayout(layoutName, content = null) {
|
|
555
|
+
return this.layoutManager.createLayout(layoutName, content);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
deleteLayout(layoutName) {
|
|
559
|
+
return this.layoutManager.deleteLayout(layoutName);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
listLayouts() {
|
|
563
|
+
return this.layoutManager.listLayouts();
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
use(middleware) {
|
|
567
|
+
this.app.use(middleware);
|
|
568
|
+
return this;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
listen(port = this.options.port, callback) {
|
|
572
|
+
return this.app.listen(port, async () => {
|
|
573
|
+
console.log('\n' + '═'.repeat(60));
|
|
574
|
+
console.log(`\x1b[35m\x1b[1m
|
|
575
|
+
╔══════════════════════════════════════════════════════╗
|
|
576
|
+
║ 🚀 VEKO.JS 🚀 ║
|
|
577
|
+
╚══════════════════════════════════════════════════════╝\x1b[0m`);
|
|
578
|
+
|
|
579
|
+
this.logger.log('server', 'Server started successfully', `🌐 http://localhost:${port}`);
|
|
580
|
+
|
|
581
|
+
// Affichage des informations auto-updater seulement si actif
|
|
582
|
+
if (this.autoUpdaterActive) {
|
|
583
|
+
try {
|
|
584
|
+
const autoUpdaterInfo = this.getAutoUpdaterInfo();
|
|
585
|
+
if (autoUpdaterInfo.currentVersion) {
|
|
586
|
+
this.logger.log('info', 'Auto-updater active', `🔄 Version Veko: ${autoUpdaterInfo.currentVersion}`);
|
|
587
|
+
this.logger.log('info', 'Canal de mise à jour', `📢 ${autoUpdaterInfo.config.updateChannel}`);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Programme les vérifications automatiques de manière sécurisée
|
|
591
|
+
if (this.autoUpdater.config && this.autoUpdater.config.autoCheck) {
|
|
592
|
+
this.scheduleAutoUpdates();
|
|
593
|
+
}
|
|
594
|
+
} catch (err) {
|
|
595
|
+
this.logger.log('warn', 'Auto-updater disponible mais pas complètement initialisé', '⚠️');
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (this.options.isDev) {
|
|
600
|
+
this.logger.log('dev', 'Development mode active', `🔥 Smart hot reload on port ${this.options.wsPort}`);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (this.plugins) {
|
|
604
|
+
const stats = this.plugins.getStats();
|
|
605
|
+
this.logger.log('info', 'Plugin system', `🔌 ${stats.active}/${stats.total} plugins active`);
|
|
606
|
+
await this.plugins.executeHook('app:start', this, port);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
console.log('═'.repeat(60) + '\n');
|
|
610
|
+
|
|
611
|
+
if (callback && typeof callback === 'function') {
|
|
612
|
+
callback();
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// ⏰ Programmation des vérifications automatiques avec protection
|
|
618
|
+
scheduleAutoUpdates() {
|
|
619
|
+
if (!this.autoUpdaterActive) return;
|
|
620
|
+
|
|
621
|
+
try {
|
|
622
|
+
const interval = this.autoUpdater.config.checkInterval || 3600000;
|
|
623
|
+
|
|
624
|
+
setInterval(() => {
|
|
625
|
+
this.checkForUpdates(true).catch(error => {
|
|
626
|
+
// Capture les erreurs sans bloquer le timer
|
|
627
|
+
this.log('error', 'Erreur vérification automatique', error.message);
|
|
628
|
+
});
|
|
629
|
+
}, interval);
|
|
630
|
+
|
|
631
|
+
this.log('info', 'Vérifications automatiques programmées', `⏰ Toutes les ${Math.round(interval / 60000)} minutes`);
|
|
632
|
+
} catch (error) {
|
|
633
|
+
this.log('error', 'Erreur lors de la programmation des vérifications', error.message);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
startDev(port = this.options.port) {
|
|
638
|
+
this.options.isDev = true;
|
|
639
|
+
if (!this.devServer) {
|
|
640
|
+
this.devServer = new DevServer(this, this.options);
|
|
641
|
+
this.devServer.setup();
|
|
642
|
+
}
|
|
643
|
+
this.loadRoutes();
|
|
644
|
+
return this.listen(port);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
async stop() {
|
|
648
|
+
if (this.plugins) {
|
|
649
|
+
this.plugins.executeHook('app:stop', this);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (this.devServer) {
|
|
653
|
+
this.devServer.stop();
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Fermer l'authentification si activée
|
|
657
|
+
if (this.auth.isEnabled) {
|
|
658
|
+
await this.auth.destroy();
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Nettoyage de l'auto-updater de manière sécurisée
|
|
662
|
+
if (this.autoUpdaterActive && this.autoUpdater && typeof this.autoUpdater.closeReadline === 'function') {
|
|
663
|
+
try {
|
|
664
|
+
this.autoUpdater.closeReadline();
|
|
665
|
+
this.log('info', 'Auto-updater arrêté', '🔄');
|
|
666
|
+
} catch (err) {
|
|
667
|
+
this.log('error', 'Erreur lors de l\'arrêt de l\'auto-updater', err.message);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
this.logger.log('server', 'Server stopped', '🛑 Goodbye!');
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Méthodes de validation et sanitisation
|
|
675
|
+
validateOptions(options) {
|
|
676
|
+
if (typeof options !== 'object' || options === null) {
|
|
677
|
+
throw new Error('Les options doivent être un objet');
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Validation du port
|
|
681
|
+
if (options.port !== undefined && !this.isValidPort(options.port)) {
|
|
682
|
+
throw new Error('Le port doit être un nombre entre 1 et 65535');
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Validation du port WebSocket
|
|
686
|
+
if (options.wsPort !== undefined && !this.isValidPort(options.wsPort)) {
|
|
687
|
+
throw new Error('Le port WebSocket doit être un nombre entre 1 et 65535');
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Validation des chemins
|
|
691
|
+
const pathOptions = ['viewsDir', 'staticDir', 'routesDir', 'errorLog'];
|
|
692
|
+
for (const pathOption of pathOptions) {
|
|
693
|
+
if (options[pathOption] !== undefined && !this.isValidPath(options[pathOption])) {
|
|
694
|
+
throw new Error(`${pathOption} doit être un chemin valide`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Validation des tableaux
|
|
699
|
+
if (options.watchDirs !== undefined && !Array.isArray(options.watchDirs)) {
|
|
700
|
+
throw new Error('watchDirs doit être un tableau');
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
isValidPort(port) {
|
|
705
|
+
const portNumber = parseInt(port, 10);
|
|
706
|
+
return !isNaN(portNumber) && portNumber >= 1 && portNumber <= 65535;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
isValidPath(path) {
|
|
710
|
+
if (typeof path !== 'string') return false;
|
|
711
|
+
// Empêcher les chemins dangereux
|
|
712
|
+
const dangerousPatterns = ['../', '..\\', '<', '>', '|', '?', '*'];
|
|
713
|
+
return !dangerousPatterns.some(pattern => path.includes(pattern)) && path.length > 0;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
sanitizePort(port) {
|
|
717
|
+
if (port === undefined || port === null) return null;
|
|
718
|
+
const portNumber = parseInt(port, 10);
|
|
719
|
+
return this.isValidPort(portNumber) ? portNumber : null;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
sanitizePath(path) {
|
|
723
|
+
if (typeof path !== 'string') return null;
|
|
724
|
+
// Nettoyer et valider le chemin
|
|
725
|
+
const cleanPath = validator.escape(path.trim());
|
|
726
|
+
return this.isValidPath(cleanPath) ? cleanPath : null;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
sanitizePaths(paths) {
|
|
730
|
+
if (!Array.isArray(paths)) return null;
|
|
731
|
+
return paths
|
|
732
|
+
.map(path => this.sanitizePath(path))
|
|
733
|
+
.filter(path => path !== null);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
sanitizeString(str) {
|
|
737
|
+
if (typeof str !== 'string') return null;
|
|
738
|
+
return validator.escape(str.trim());
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
sanitizeArray(arr) {
|
|
742
|
+
if (!Array.isArray(arr)) return null;
|
|
743
|
+
return arr
|
|
744
|
+
.filter(item => typeof item === 'string')
|
|
745
|
+
.map(item => validator.escape(item.trim()));
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
module.exports = App;
|