blockmine 1.3.22 → 1.4.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/backend/cli.js +5 -7
- package/backend/package.json +2 -1
- package/backend/prisma/migrations/20250617133815_add_panel_users_and_roles/migration.sql +26 -0
- package/backend/prisma/schema.prisma +21 -1
- package/backend/src/api/middleware/auth.js +59 -0
- package/backend/src/api/routes/auth.js +445 -0
- package/backend/src/api/routes/bots.js +55 -34
- package/backend/src/api/routes/panel.js +67 -0
- package/backend/src/api/routes/permissions.js +9 -7
- package/backend/src/api/routes/plugins.js +6 -5
- package/backend/src/api/routes/servers.js +6 -6
- package/backend/src/api/routes/tasks.js +8 -5
- package/backend/src/config.js +78 -0
- package/backend/src/core/BotManager.js +17 -15
- package/backend/src/core/utils/crypto.js +49 -0
- package/backend/src/real-time/socketHandler.js +15 -7
- package/backend/src/server.js +44 -21
- package/frontend/dist/assets/index-BJe5Bwf5.js +8179 -0
- package/frontend/dist/assets/index-BKT73TwN.css +1 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-BP4_jhRx.css +0 -1
- package/frontend/dist/assets/index-Bz8G6akb.js +0 -8179
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
|
-
const router = express.Router();
|
|
3
2
|
const { PrismaClient } = require('@prisma/client');
|
|
4
3
|
const path = require('path');
|
|
5
4
|
const fs = require('fs/promises');
|
|
@@ -7,7 +6,8 @@ const BotManager = require('../../core/BotManager');
|
|
|
7
6
|
const PluginManager = require('../../core/PluginManager');
|
|
8
7
|
const UserService = require('../../core/UserService');
|
|
9
8
|
const commandManager = require('../../core/system/CommandManager');
|
|
10
|
-
|
|
9
|
+
const { authenticate, authorize } = require('../middleware/auth');
|
|
10
|
+
const { encrypt } = require('../../core/utils/crypto');
|
|
11
11
|
|
|
12
12
|
const multer = require('multer');
|
|
13
13
|
const archiver = require('archiver');
|
|
@@ -16,6 +16,12 @@ const AdmZip = require('adm-zip');
|
|
|
16
16
|
const prisma = new PrismaClient();
|
|
17
17
|
const upload = multer({ storage: multer.memoryStorage() });
|
|
18
18
|
|
|
19
|
+
|
|
20
|
+
const router = express.Router();
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
router.use(authenticate);
|
|
24
|
+
|
|
19
25
|
async function setupDefaultPermissionsForBot(botId, prismaClient = prisma) {
|
|
20
26
|
const initialData = {
|
|
21
27
|
groups: ["User", "Admin"],
|
|
@@ -53,7 +59,7 @@ async function setupDefaultPermissionsForBot(botId, prismaClient = prisma) {
|
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
|
|
56
|
-
router.get('/', async (req, res) => {
|
|
62
|
+
router.get('/', authorize('bot:list'), async (req, res) => {
|
|
57
63
|
try {
|
|
58
64
|
const bots = await prisma.bot.findMany({ include: { server: true }, orderBy: { createdAt: 'asc' } });
|
|
59
65
|
res.json(bots);
|
|
@@ -63,19 +69,28 @@ router.get('/', async (req, res) => {
|
|
|
63
69
|
}
|
|
64
70
|
});
|
|
65
71
|
|
|
66
|
-
router.get('/state', (req, res) => {
|
|
72
|
+
router.get('/state', authorize('bot:list'), (req, res) => {
|
|
67
73
|
try {
|
|
68
74
|
const state = BotManager.getFullState();
|
|
69
75
|
res.json(state);
|
|
70
76
|
} catch (error) { res.status(500).json({ error: 'Не удалось получить состояние ботов' }); }
|
|
71
77
|
});
|
|
72
78
|
|
|
73
|
-
router.post('/', async (req, res) => {
|
|
79
|
+
router.post('/', authorize('bot:create'), async (req, res) => {
|
|
74
80
|
try {
|
|
75
81
|
const { username, password, prefix, serverId, note } = req.body;
|
|
76
82
|
if (!username || !serverId) return res.status(400).json({ error: 'Имя и сервер обязательны' });
|
|
83
|
+
|
|
84
|
+
const data = {
|
|
85
|
+
username,
|
|
86
|
+
prefix,
|
|
87
|
+
note,
|
|
88
|
+
serverId: parseInt(serverId, 10),
|
|
89
|
+
password: password ? encrypt(password) : null
|
|
90
|
+
};
|
|
91
|
+
|
|
77
92
|
const newBot = await prisma.bot.create({
|
|
78
|
-
data:
|
|
93
|
+
data: data,
|
|
79
94
|
include: { server: true }
|
|
80
95
|
});
|
|
81
96
|
await setupDefaultPermissionsForBot(newBot.id);
|
|
@@ -87,7 +102,7 @@ router.post('/', async (req, res) => {
|
|
|
87
102
|
}
|
|
88
103
|
});
|
|
89
104
|
|
|
90
|
-
router.put('/:id', async (req, res) => {
|
|
105
|
+
router.put('/:id', authorize('bot:update'), async (req, res) => {
|
|
91
106
|
try {
|
|
92
107
|
const botId = parseInt(req.params.id, 10);
|
|
93
108
|
|
|
@@ -106,8 +121,12 @@ router.put('/:id', async (req, res) => {
|
|
|
106
121
|
};
|
|
107
122
|
|
|
108
123
|
if (password) {
|
|
109
|
-
dataToUpdate.password = password;
|
|
124
|
+
dataToUpdate.password = encrypt(password);
|
|
110
125
|
}
|
|
126
|
+
if (proxyPassword) {
|
|
127
|
+
dataToUpdate.proxyPassword = encrypt(proxyPassword);
|
|
128
|
+
}
|
|
129
|
+
|
|
111
130
|
if (proxyPassword) {
|
|
112
131
|
dataToUpdate.proxyPassword = proxyPassword;
|
|
113
132
|
}
|
|
@@ -135,7 +154,7 @@ router.put('/:id', async (req, res) => {
|
|
|
135
154
|
}
|
|
136
155
|
});
|
|
137
156
|
|
|
138
|
-
router.delete('/:id', async (req, res) => {
|
|
157
|
+
router.delete('/:id', authorize('bot:delete'), async (req, res) => {
|
|
139
158
|
try {
|
|
140
159
|
const botId = parseInt(req.params.id, 10);
|
|
141
160
|
if (BotManager.bots.has(botId)) return res.status(400).json({ error: 'Нельзя удалить запущенного бота' });
|
|
@@ -145,7 +164,7 @@ router.delete('/:id', async (req, res) => {
|
|
|
145
164
|
});
|
|
146
165
|
|
|
147
166
|
|
|
148
|
-
router.post('/:id/start', async (req, res) => {
|
|
167
|
+
router.post('/:id/start', authorize('bot:start_stop'), async (req, res) => {
|
|
149
168
|
try {
|
|
150
169
|
const botId = parseInt(req.params.id, 10);
|
|
151
170
|
const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true } });
|
|
@@ -155,13 +174,13 @@ router.post('/:id/start', async (req, res) => {
|
|
|
155
174
|
} catch (error) { res.status(500).json({ error: 'Ошибка при запуске бота: ' + error.message }); }
|
|
156
175
|
});
|
|
157
176
|
|
|
158
|
-
router.post('/:id/stop', (req, res) => {
|
|
177
|
+
router.post('/:id/stop', authorize('bot:start_stop'), (req, res) => {
|
|
159
178
|
const botId = parseInt(req.params.id, 10);
|
|
160
179
|
const result = BotManager.stopBot(botId);
|
|
161
180
|
res.json(result);
|
|
162
181
|
});
|
|
163
182
|
|
|
164
|
-
router.post('/:id/chat', (req, res) => {
|
|
183
|
+
router.post('/:id/chat', authorize('bot:interact'), (req, res) => {
|
|
165
184
|
try {
|
|
166
185
|
const botId = parseInt(req.params.id, 10);
|
|
167
186
|
const { message } = req.body;
|
|
@@ -172,7 +191,7 @@ router.post('/:id/chat', (req, res) => {
|
|
|
172
191
|
} catch (error) { res.status(500).json({ error: 'Внутренняя ошибка сервера: ' + error.message }); }
|
|
173
192
|
});
|
|
174
193
|
|
|
175
|
-
router.get('/:id/export', async (req, res) => {
|
|
194
|
+
router.get('/:id/export', authorize('bot:export'), async (req, res) => {
|
|
176
195
|
try {
|
|
177
196
|
const botId = parseInt(req.params.id, 10);
|
|
178
197
|
const {
|
|
@@ -231,7 +250,7 @@ router.get('/:id/export', async (req, res) => {
|
|
|
231
250
|
await fs.access(plugin.path);
|
|
232
251
|
archive.directory(plugin.path, `plugins/${pluginFolderName}`);
|
|
233
252
|
} catch (error) {
|
|
234
|
-
console.warn(`[Export]
|
|
253
|
+
console.warn(`[Export] Директория плагина ${plugin.name} (${plugin.path}) не найдена, пропускаем.`);
|
|
235
254
|
}
|
|
236
255
|
}
|
|
237
256
|
}
|
|
@@ -239,13 +258,15 @@ router.get('/:id/export', async (req, res) => {
|
|
|
239
258
|
await archive.finalize();
|
|
240
259
|
|
|
241
260
|
} catch (error) {
|
|
242
|
-
console.error("
|
|
243
|
-
res.status(500).json({ error: 'Не удалось экспортировать
|
|
261
|
+
console.error("Bot export error:", error);
|
|
262
|
+
res.status(500).json({ error: 'Не удалось экспортировать бота.' });
|
|
244
263
|
}
|
|
245
264
|
});
|
|
246
265
|
|
|
247
|
-
router.post('/import', upload.single('
|
|
248
|
-
if (!req.file)
|
|
266
|
+
router.post('/import', upload.single('file'), authorize('bot:import'), async (req, res) => {
|
|
267
|
+
if (!req.file) {
|
|
268
|
+
return res.status(400).send('Файл не загружен.');
|
|
269
|
+
}
|
|
249
270
|
if (path.extname(req.file.originalname).toLowerCase() !== '.zip') return res.status(400).json({ error: 'Неверный формат файла. Ожидался ZIP-архив.' });
|
|
250
271
|
|
|
251
272
|
const zip = new AdmZip(req.file.buffer);
|
|
@@ -358,7 +379,7 @@ router.post('/import', upload.single('botFile'), async (req, res) => {
|
|
|
358
379
|
});
|
|
359
380
|
|
|
360
381
|
|
|
361
|
-
router.get('/:botId/plugins', async (req, res) => {
|
|
382
|
+
router.get('/:botId/plugins', authorize('plugin:list'), async (req, res) => {
|
|
362
383
|
try {
|
|
363
384
|
const botId = parseInt(req.params.botId);
|
|
364
385
|
const plugins = await prisma.installedPlugin.findMany({ where: { botId } });
|
|
@@ -366,7 +387,7 @@ router.get('/:botId/plugins', async (req, res) => {
|
|
|
366
387
|
} catch (error) { res.status(500).json({ error: 'Не удалось получить плагины бота' }); }
|
|
367
388
|
});
|
|
368
389
|
|
|
369
|
-
router.post('/:botId/plugins/install/github', async (req, res) => {
|
|
390
|
+
router.post('/:botId/plugins/install/github', authorize('plugin:install'), async (req, res) => {
|
|
370
391
|
try {
|
|
371
392
|
const botId = parseInt(req.params.botId);
|
|
372
393
|
const { repoUrl } = req.body;
|
|
@@ -375,7 +396,7 @@ router.post('/:botId/plugins/install/github', async (req, res) => {
|
|
|
375
396
|
} catch (error) { res.status(500).json({ error: error.message }); }
|
|
376
397
|
});
|
|
377
398
|
|
|
378
|
-
router.post('/:botId/plugins/register/local', async (req, res) => {
|
|
399
|
+
router.post('/:botId/plugins/register/local', authorize('plugin:install'), async (req, res) => {
|
|
379
400
|
try {
|
|
380
401
|
const botId = parseInt(req.params.botId);
|
|
381
402
|
const { path } = req.body;
|
|
@@ -384,7 +405,7 @@ router.post('/:botId/plugins/register/local', async (req, res) => {
|
|
|
384
405
|
} catch (error) { res.status(500).json({ error: error.message }); }
|
|
385
406
|
});
|
|
386
407
|
|
|
387
|
-
router.delete('/:botId/plugins/:pluginId', async (req, res) => {
|
|
408
|
+
router.delete('/:botId/plugins/:pluginId', authorize('plugin:delete'), async (req, res) => {
|
|
388
409
|
try {
|
|
389
410
|
const pluginId = parseInt(req.params.pluginId);
|
|
390
411
|
await PluginManager.deletePlugin(pluginId);
|
|
@@ -392,7 +413,7 @@ router.delete('/:botId/plugins/:pluginId', async (req, res) => {
|
|
|
392
413
|
} catch (error) { res.status(500).json({ error: error.message }); }
|
|
393
414
|
});
|
|
394
415
|
|
|
395
|
-
router.get('/:botId/plugins/:pluginId/settings', async (req, res) => {
|
|
416
|
+
router.get('/:botId/plugins/:pluginId/settings', authorize('plugin:settings:view'), async (req, res) => {
|
|
396
417
|
try {
|
|
397
418
|
const pluginId = parseInt(req.params.pluginId);
|
|
398
419
|
const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
|
|
@@ -425,7 +446,7 @@ router.get('/:botId/plugins/:pluginId/settings', async (req, res) => {
|
|
|
425
446
|
}
|
|
426
447
|
});
|
|
427
448
|
|
|
428
|
-
router.put('/:botId/plugins/:pluginId', async (req, res) => {
|
|
449
|
+
router.put('/:botId/plugins/:pluginId', authorize('plugin:settings:edit'), async (req, res) => {
|
|
429
450
|
try {
|
|
430
451
|
const pluginId = parseInt(req.params.pluginId);
|
|
431
452
|
const { isEnabled, settings } = req.body;
|
|
@@ -439,7 +460,7 @@ router.put('/:botId/plugins/:pluginId', async (req, res) => {
|
|
|
439
460
|
});
|
|
440
461
|
|
|
441
462
|
|
|
442
|
-
router.get('/:botId/management-data', async (req, res) => {
|
|
463
|
+
router.get('/:botId/management-data', authorize('management:view'), async (req, res) => {
|
|
443
464
|
try {
|
|
444
465
|
const botId = parseInt(req.params.botId);
|
|
445
466
|
|
|
@@ -511,7 +532,7 @@ router.get('/:botId/management-data', async (req, res) => {
|
|
|
511
532
|
}
|
|
512
533
|
});
|
|
513
534
|
|
|
514
|
-
router.put('/:botId/commands/:commandId', async (req, res) => {
|
|
535
|
+
router.put('/:botId/commands/:commandId', authorize('management:edit'), async (req, res) => {
|
|
515
536
|
try {
|
|
516
537
|
const commandId = parseInt(req.params.commandId, 10);
|
|
517
538
|
const botId = parseInt(req.params.botId, 10);
|
|
@@ -546,7 +567,7 @@ router.put('/:botId/commands/:commandId', async (req, res) => {
|
|
|
546
567
|
}
|
|
547
568
|
});
|
|
548
569
|
|
|
549
|
-
router.post('/:botId/groups', async (req, res) => {
|
|
570
|
+
router.post('/:botId/groups', authorize('management:edit'), async (req, res) => {
|
|
550
571
|
try {
|
|
551
572
|
const botId = parseInt(req.params.botId);
|
|
552
573
|
const { name, permissionIds } = req.body;
|
|
@@ -571,7 +592,7 @@ router.post('/:botId/groups', async (req, res) => {
|
|
|
571
592
|
}
|
|
572
593
|
});
|
|
573
594
|
|
|
574
|
-
router.put('/:botId/groups/:groupId', async (req, res) => {
|
|
595
|
+
router.put('/:botId/groups/:groupId', authorize('management:edit'), async (req, res) => {
|
|
575
596
|
try {
|
|
576
597
|
const botId = parseInt(req.params.botId, 10);
|
|
577
598
|
const groupId = parseInt(req.params.groupId);
|
|
@@ -606,7 +627,7 @@ router.put('/:botId/groups/:groupId', async (req, res) => {
|
|
|
606
627
|
}
|
|
607
628
|
});
|
|
608
629
|
|
|
609
|
-
router.delete('/:botId/groups/:groupId', async (req, res) => {
|
|
630
|
+
router.delete('/:botId/groups/:groupId', authorize('management:edit'), async (req, res) => {
|
|
610
631
|
try {
|
|
611
632
|
const botId = parseInt(req.params.botId, 10);
|
|
612
633
|
const groupId = parseInt(req.params.groupId);
|
|
@@ -622,7 +643,7 @@ router.delete('/:botId/groups/:groupId', async (req, res) => {
|
|
|
622
643
|
} catch (error) { res.status(500).json({ error: 'Не удалось удалить группу.' }); }
|
|
623
644
|
});
|
|
624
645
|
|
|
625
|
-
router.post('/:botId/permissions', async (req, res) => {
|
|
646
|
+
router.post('/:botId/permissions', authorize('management:edit'), async (req, res) => {
|
|
626
647
|
try {
|
|
627
648
|
const botId = parseInt(req.params.botId);
|
|
628
649
|
const { name, description } = req.body;
|
|
@@ -640,7 +661,7 @@ router.post('/:botId/permissions', async (req, res) => {
|
|
|
640
661
|
}
|
|
641
662
|
});
|
|
642
663
|
|
|
643
|
-
router.put('/:botId/users/:userId', async (req, res) => {
|
|
664
|
+
router.put('/:botId/users/:userId', authorize('management:edit'), async (req, res) => {
|
|
644
665
|
try {
|
|
645
666
|
const botId = parseInt(req.params.botId, 10);
|
|
646
667
|
const userId = parseInt(req.params.userId, 10);
|
|
@@ -677,7 +698,7 @@ router.put('/:botId/users/:userId', async (req, res) => {
|
|
|
677
698
|
});
|
|
678
699
|
|
|
679
700
|
|
|
680
|
-
router.post('/start-all', async (req, res) => {
|
|
701
|
+
router.post('/start-all', authorize('bot:start_stop'), async (req, res) => {
|
|
681
702
|
try {
|
|
682
703
|
console.log('[API] Получен запрос на запуск всех ботов.');
|
|
683
704
|
const allBots = await prisma.bot.findMany({ include: { server: true } });
|
|
@@ -695,7 +716,7 @@ router.post('/start-all', async (req, res) => {
|
|
|
695
716
|
}
|
|
696
717
|
});
|
|
697
718
|
|
|
698
|
-
router.post('/stop-all', (req, res) => {
|
|
719
|
+
router.post('/stop-all', authorize('bot:start_stop'), (req, res) => {
|
|
699
720
|
try {
|
|
700
721
|
console.log('[API] Получен запрос на остановку всех ботов.');
|
|
701
722
|
const botIds = Array.from(BotManager.bots.keys());
|
|
@@ -712,7 +733,7 @@ router.post('/stop-all', (req, res) => {
|
|
|
712
733
|
});
|
|
713
734
|
|
|
714
735
|
|
|
715
|
-
router.get('/:id/settings/all', async (req, res) => {
|
|
736
|
+
router.get('/:id/settings/all', authorize('bot:update'), async (req, res) => {
|
|
716
737
|
try {
|
|
717
738
|
const botId = parseInt(req.params.id, 10);
|
|
718
739
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const fs = require('fs/promises');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const { authenticate, authorize } = require('../middleware/auth');
|
|
6
|
+
const config = require('../../config');
|
|
7
|
+
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
|
|
10
|
+
const DATA_DIR = path.join(os.homedir(), '.blockmine');
|
|
11
|
+
const CONFIG_PATH = path.join(DATA_DIR, 'config.json');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @route GET /api/panel/settings
|
|
15
|
+
* @desc Получить текущие глобальные настройки
|
|
16
|
+
* @access Private (Admin only)
|
|
17
|
+
*/
|
|
18
|
+
router.get('/settings', authenticate, authorize('panel:settings:view'), (req, res) => {
|
|
19
|
+
const { server, telemetry } = config;
|
|
20
|
+
res.json({
|
|
21
|
+
server: {
|
|
22
|
+
allowExternalAccess: server.allowExternalAccess
|
|
23
|
+
},
|
|
24
|
+
telemetry: {
|
|
25
|
+
enabled: telemetry?.enabled ?? true
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @route PUT /api/panel/settings
|
|
32
|
+
* @desc Обновить глобальные настройки
|
|
33
|
+
* @access Private (Admin only)
|
|
34
|
+
*/
|
|
35
|
+
router.put('/settings', authenticate, authorize('panel:settings:edit'), async (req, res) => {
|
|
36
|
+
const { allowExternalAccess, telemetryEnabled } = req.body;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const currentConfig = JSON.parse(await fs.readFile(CONFIG_PATH, 'utf-8'));
|
|
40
|
+
|
|
41
|
+
if (typeof allowExternalAccess === 'boolean') {
|
|
42
|
+
currentConfig.server.allowExternalAccess = allowExternalAccess;
|
|
43
|
+
currentConfig.server.host = allowExternalAccess ? '0.0.0.0' : '127.0.0.1';
|
|
44
|
+
console.log(`[Config Update] Внешний доступ ${allowExternalAccess ? 'ВКЛЮЧЕН' : 'ВЫКЛЮЧЕН'}. Хост изменен на ${currentConfig.server.host}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (typeof telemetryEnabled === 'boolean') {
|
|
48
|
+
if (!currentConfig.telemetry) {
|
|
49
|
+
currentConfig.telemetry = {};
|
|
50
|
+
}
|
|
51
|
+
currentConfig.telemetry.enabled = telemetryEnabled;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await fs.writeFile(CONFIG_PATH, JSON.stringify(currentConfig, null, 2), 'utf-8');
|
|
55
|
+
|
|
56
|
+
res.json({
|
|
57
|
+
message: 'Настройки сохранены. Для применения требуется перезапуск панели.',
|
|
58
|
+
requiresRestart: true
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error("Ошибка обновления файла конфигурации:", error);
|
|
63
|
+
res.status(500).json({ error: 'Не удалось сохранить настройки.' });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
module.exports = router;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const router = express.Router();
|
|
3
3
|
const { PrismaClient } = require('@prisma/client');
|
|
4
|
+
const { authenticate, authorize } = require('../middleware/auth');
|
|
4
5
|
const prisma = new PrismaClient();
|
|
5
6
|
|
|
7
|
+
router.use(authenticate);
|
|
6
8
|
|
|
7
|
-
router.get('/groups', async (req, res) => {
|
|
9
|
+
router.get('/groups', authorize('panel:role:view'), async (req, res) => {
|
|
8
10
|
const groups = await prisma.group.findMany({ include: { permissions: { include: { permission: true } } } });
|
|
9
11
|
res.json(groups);
|
|
10
12
|
});
|
|
11
13
|
|
|
12
|
-
router.post('/groups', async (req, res) => {
|
|
14
|
+
router.post('/groups', authorize('panel:role:edit'), async (req, res) => {
|
|
13
15
|
const { name, permissionIds } = req.body;
|
|
14
16
|
const newGroup = await prisma.group.create({
|
|
15
17
|
data: {
|
|
@@ -24,7 +26,7 @@ router.post('/groups', async (req, res) => {
|
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
|
|
27
|
-
router.put('/groups/:id', async (req, res) => {
|
|
29
|
+
router.put('/groups/:id', authorize('panel:role:edit'), async (req, res) => {
|
|
28
30
|
const groupId = parseInt(req.params.id);
|
|
29
31
|
const { name, permissionIds } = req.body;
|
|
30
32
|
try {
|
|
@@ -40,20 +42,20 @@ router.put('/groups/:id', async (req, res) => {
|
|
|
40
42
|
res.status(200).send();
|
|
41
43
|
} catch (error) { res.status(500).json({ error: 'Не удалось обновить группу' }); }
|
|
42
44
|
});
|
|
43
|
-
router.delete('/groups/:id', async (req, res) => {
|
|
45
|
+
router.delete('/groups/:id', authorize('panel:role:edit'), async (req, res) => {
|
|
44
46
|
try {
|
|
45
47
|
await prisma.group.delete({ where: { id: parseInt(req.params.id) } });
|
|
46
48
|
res.status(204).send();
|
|
47
49
|
} catch (error) { res.status(500).json({ error: 'Не удалось удалить группу' }); }
|
|
48
50
|
});
|
|
49
51
|
|
|
50
|
-
router.get('/all', async (req, res) => {
|
|
52
|
+
router.get('/all', authorize('panel:role:view'), async (req, res) => {
|
|
51
53
|
const permissions = await prisma.permission.findMany({ orderBy: { name: 'asc' } });
|
|
52
54
|
res.json(permissions);
|
|
53
55
|
});
|
|
54
56
|
|
|
55
57
|
|
|
56
|
-
router.get('/users/:username', async (req, res) => {
|
|
58
|
+
router.get('/users/:username', authorize('panel:user:view'), async (req, res) => {
|
|
57
59
|
const user = await prisma.user.findUnique({
|
|
58
60
|
where: { username: req.params.username },
|
|
59
61
|
include: { groups: { include: { group: true } } }
|
|
@@ -62,7 +64,7 @@ router.get('/users/:username', async (req, res) => {
|
|
|
62
64
|
res.json(user);
|
|
63
65
|
});
|
|
64
66
|
|
|
65
|
-
router.post('/users/:username/groups', async (req, res) => {
|
|
67
|
+
router.post('/users/:username/groups', authorize('panel:user:edit'), async (req, res) => {
|
|
66
68
|
const { groupId } = req.body;
|
|
67
69
|
const user = await prisma.user.upsert({
|
|
68
70
|
where: { username: req.params.username },
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
|
|
2
1
|
const express = require('express');
|
|
2
|
+
const { authenticate, authorize } = require('../middleware/auth');
|
|
3
3
|
const router = express.Router();
|
|
4
4
|
const PluginManager = require('../../core/PluginManager');
|
|
5
5
|
|
|
6
6
|
const OFFICIAL_CATALOG_URL = "https://raw.githubusercontent.com/blockmineJS/official-plugins-list/main/index.json";
|
|
7
7
|
|
|
8
|
+
router.use(authenticate);
|
|
8
9
|
|
|
9
10
|
const getCacheBustedUrl = (url) => `${url}?t=${new Date().getTime()}`;
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
router.get('/catalog', async (req, res) => {
|
|
13
|
+
router.get('/catalog', authorize('plugin:browse'), async (req, res) => {
|
|
13
14
|
try {
|
|
14
15
|
const response = await fetch(getCacheBustedUrl(OFFICIAL_CATALOG_URL));
|
|
15
16
|
|
|
@@ -26,7 +27,7 @@ router.get('/catalog', async (req, res) => {
|
|
|
26
27
|
}
|
|
27
28
|
});
|
|
28
29
|
|
|
29
|
-
router.post('/check-updates/:botId', async (req, res) => {
|
|
30
|
+
router.post('/check-updates/:botId', authorize('plugin:update'), async (req, res) => {
|
|
30
31
|
try {
|
|
31
32
|
const botId = parseInt(req.params.botId);
|
|
32
33
|
|
|
@@ -42,7 +43,7 @@ router.post('/check-updates/:botId', async (req, res) => {
|
|
|
42
43
|
}
|
|
43
44
|
});
|
|
44
45
|
|
|
45
|
-
router.post('/update/:pluginId', async (req, res) => {
|
|
46
|
+
router.post('/update/:pluginId', authorize('plugin:update'), async (req, res) => {
|
|
46
47
|
try {
|
|
47
48
|
const pluginId = parseInt(req.params.pluginId);
|
|
48
49
|
const updatedPlugin = await PluginManager.updatePlugin(pluginId);
|
|
@@ -52,7 +53,7 @@ router.post('/update/:pluginId', async (req, res) => {
|
|
|
52
53
|
}
|
|
53
54
|
});
|
|
54
55
|
|
|
55
|
-
router.get('/catalog/:name', async (req, res) => {
|
|
56
|
+
router.get('/catalog/:name', authorize('plugin:browse'), async (req, res) => {
|
|
56
57
|
try {
|
|
57
58
|
const pluginName = req.params.name;
|
|
58
59
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const router = express.Router();
|
|
3
3
|
const { PrismaClient } = require('@prisma/client');
|
|
4
|
+
const { authenticate, authorize } = require('../middleware/auth');
|
|
4
5
|
const prisma = new PrismaClient();
|
|
5
6
|
|
|
6
|
-
router.
|
|
7
|
+
router.use(authenticate);
|
|
8
|
+
|
|
9
|
+
router.get('/', authorize('server:list'), async (req, res) => {
|
|
7
10
|
try {
|
|
8
11
|
const servers = await prisma.server.findMany({ orderBy: { name: 'asc' } });
|
|
9
12
|
res.json(servers);
|
|
@@ -13,7 +16,7 @@ router.get('/', async (req, res) => {
|
|
|
13
16
|
}
|
|
14
17
|
});
|
|
15
18
|
|
|
16
|
-
router.post('/', async (req, res) => {
|
|
19
|
+
router.post('/', authorize('server:create'), async (req, res) => {
|
|
17
20
|
try {
|
|
18
21
|
const { name, host, port, version } = req.body;
|
|
19
22
|
if (!name || !host || !version) {
|
|
@@ -29,10 +32,7 @@ router.post('/', async (req, res) => {
|
|
|
29
32
|
}
|
|
30
33
|
});
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
router.delete('/:id', async (req, res) => {
|
|
35
|
+
router.delete('/:id', authorize('server:delete'), async (req, res) => {
|
|
36
36
|
try {
|
|
37
37
|
const serverId = parseInt(req.params.id, 10);
|
|
38
38
|
|
|
@@ -2,17 +2,20 @@ const express = require('express');
|
|
|
2
2
|
const router = express.Router();
|
|
3
3
|
const { PrismaClient } = require('@prisma/client');
|
|
4
4
|
const TaskScheduler = require('../../core/TaskScheduler');
|
|
5
|
+
const { authenticate, authorize } = require('../middleware/auth');
|
|
5
6
|
|
|
6
7
|
const { CronExpressionParser } = require('cron-parser');
|
|
7
8
|
|
|
8
9
|
const prisma = new PrismaClient();
|
|
9
10
|
|
|
11
|
+
router.use(authenticate);
|
|
12
|
+
|
|
10
13
|
const normalizeCronPattern = (pattern) => {
|
|
11
14
|
if (typeof pattern !== 'string') return '* * * * *';
|
|
12
15
|
return pattern.replace(/\*\/1/g, '*').trim();
|
|
13
16
|
};
|
|
14
17
|
|
|
15
|
-
router.get('/', async (req, res) => {
|
|
18
|
+
router.get('/', authorize('task:list'), async (req, res) => {
|
|
16
19
|
try {
|
|
17
20
|
const tasks = await prisma.scheduledTask.findMany({ orderBy: { createdAt: 'desc' } });
|
|
18
21
|
const normalizedTasks = tasks.map(task => ({
|
|
@@ -26,7 +29,7 @@ router.get('/', async (req, res) => {
|
|
|
26
29
|
}
|
|
27
30
|
});
|
|
28
31
|
|
|
29
|
-
router.post('/', async (req, res) => {
|
|
32
|
+
router.post('/', authorize('task:create'), async (req, res) => {
|
|
30
33
|
try {
|
|
31
34
|
const taskData = { ...req.body, cronPattern: normalizeCronPattern(req.body.cronPattern) };
|
|
32
35
|
const newTask = await prisma.scheduledTask.create({ data: taskData });
|
|
@@ -38,7 +41,7 @@ router.post('/', async (req, res) => {
|
|
|
38
41
|
}
|
|
39
42
|
});
|
|
40
43
|
|
|
41
|
-
router.put('/:id', async (req, res) => {
|
|
44
|
+
router.put('/:id', authorize('task:edit'), async (req, res) => {
|
|
42
45
|
const taskId = parseInt(req.params.id, 10);
|
|
43
46
|
try {
|
|
44
47
|
const { id, createdAt, updatedAt, lastRun, ...dataToUpdate } = req.body;
|
|
@@ -59,7 +62,7 @@ router.put('/:id', async (req, res) => {
|
|
|
59
62
|
}
|
|
60
63
|
});
|
|
61
64
|
|
|
62
|
-
router.delete('/:id', async (req, res) => {
|
|
65
|
+
router.delete('/:id', authorize('task:delete'), async (req, res) => {
|
|
63
66
|
const taskId = parseInt(req.params.id, 10);
|
|
64
67
|
try {
|
|
65
68
|
await prisma.scheduledTask.delete({ where: { id: taskId } });
|
|
@@ -72,7 +75,7 @@ router.delete('/:id', async (req, res) => {
|
|
|
72
75
|
});
|
|
73
76
|
|
|
74
77
|
|
|
75
|
-
router.post('/describe', (req, res) => {
|
|
78
|
+
router.post('/describe', authorize('task:list'), (req, res) => {
|
|
76
79
|
const { pattern } = req.body;
|
|
77
80
|
|
|
78
81
|
if (!pattern) {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
|
|
6
|
+
const DATA_DIR = path.join(os.homedir(), '.blockmine');
|
|
7
|
+
const CONFIG_PATH = path.join(DATA_DIR, 'config.json');
|
|
8
|
+
|
|
9
|
+
let config = null;
|
|
10
|
+
|
|
11
|
+
function generateInitialConfig() {
|
|
12
|
+
console.log('[Config] Файл конфигурации не найден. Генерируем новый...');
|
|
13
|
+
|
|
14
|
+
const isLinux = process.platform === 'linux';
|
|
15
|
+
|
|
16
|
+
const newConfig = {
|
|
17
|
+
server: {
|
|
18
|
+
host: isLinux ? '0.0.0.0' : '127.0.0.1',
|
|
19
|
+
port: 3001,
|
|
20
|
+
allowExternalAccess: isLinux,
|
|
21
|
+
},
|
|
22
|
+
security: {
|
|
23
|
+
jwtSecret: crypto.randomBytes(64).toString('hex'),
|
|
24
|
+
encryptionKey: crypto.randomBytes(32).toString('hex'),
|
|
25
|
+
adminRecoveryCode: `bmr-${crypto.randomBytes(12).toString('hex')}`
|
|
26
|
+
},
|
|
27
|
+
telemetry: {
|
|
28
|
+
enabled: true
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
if (!fs.existsSync(DATA_DIR)) {
|
|
33
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(newConfig, null, 2), 'utf-8');
|
|
37
|
+
|
|
38
|
+
console.log('================================================================');
|
|
39
|
+
console.log('ВАЖНО: Конфигурация сгенерирована!');
|
|
40
|
+
console.log(`Файл сохранен в: ${CONFIG_PATH}`);
|
|
41
|
+
|
|
42
|
+
if (isLinux) {
|
|
43
|
+
console.log('\n[Linux] Обнаружена система Linux. Внешний доступ к панели включен по умолчанию.');
|
|
44
|
+
console.log('Просмотр панели будет доступен с внешнего IP адреса вашего сервера.');
|
|
45
|
+
console.log(`Чтобы отключить это, измените "allowExternalAccess" на false в файле конфигурации:`);
|
|
46
|
+
console.log(CONFIG_PATH);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log('\nПожалуйста, сохраните этот код восстановления в безопасном месте.');
|
|
50
|
+
console.log('Он понадобится для сброса пароля администратора.');
|
|
51
|
+
console.log(`\n Код восстановления: ${newConfig.security.adminRecoveryCode}\n`);
|
|
52
|
+
console.log('================================================================');
|
|
53
|
+
|
|
54
|
+
return newConfig;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function loadConfig() {
|
|
58
|
+
if (config) {
|
|
59
|
+
return config;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
64
|
+
const fileContent = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
|
65
|
+
config = JSON.parse(fileContent);
|
|
66
|
+
console.log(`[Config] Конфигурация успешно загружена из ${CONFIG_PATH}`);
|
|
67
|
+
} else {
|
|
68
|
+
config = generateInitialConfig();
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('[Config] КРИТИЧЕСКАЯ ОШИБКА: Не удалось загрузить или создать файл конфигурации.', error);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return config;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = loadConfig();
|