blockmine 1.12.1 → 1.13.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 CHANGED
@@ -1,6 +1,13 @@
1
1
  # История версий
2
2
 
3
3
 
4
+ ## [1.13.0](https://github.com/blockmineJS/blockmine/compare/v1.12.0...v1.13.0) (2025-07-18)
5
+
6
+
7
+ ### ✨ Новые возможности
8
+
9
+ * возможность сбрасывать пароль рут аккаунта ([e23181d](https://github.com/blockmineJS/blockmine/commit/e23181d29dbe730ff882d654b8ec0e80a1f007bc))
10
+
4
11
  ## [1.12.0](https://github.com/blockmineJS/blockmine/compare/v1.11.5...v1.12.0) (2025-07-18)
5
12
 
6
13
 
@@ -1,201 +1,219 @@
1
- generator client {
2
- provider = "prisma-client-js"
3
- }
4
-
5
- datasource db {
6
- provider = "sqlite"
7
- url = "file:C:\\Users\\user\\.blockmine\\blockmine.db"
8
- }
9
-
10
- model Server {
11
- id Int @id @default(autoincrement())
12
- name String @unique
13
- host String
14
- port Int @default(25565)
15
- version String
16
- bots Bot[]
17
- }
18
-
19
- model Bot {
20
- id Int @id @default(autoincrement())
21
- username String @unique
22
- password String?
23
- prefix String? @default("@")
24
- note String?
25
- serverId Int
26
- proxyHost String?
27
- proxyPort Int?
28
- proxyUsername String?
29
- proxyPassword String?
30
- createdAt DateTime @default(now())
31
- updatedAt DateTime @updatedAt
32
- owners String? @default("")
33
- server Server @relation(fields: [serverId], references: [id])
34
- commands Command[]
35
- eventGraphs EventGraph[]
36
- groups Group[]
37
- installedPlugins InstalledPlugin[]
38
- permissions Permission[]
39
- pluginData PluginDataStore[]
40
- users User[]
41
- }
42
-
43
- model InstalledPlugin {
44
- id Int @id @default(autoincrement())
45
- botId Int
46
- name String
47
- version String
48
- description String?
49
- sourceType String
50
- sourceUri String?
51
- path String
52
- isEnabled Boolean @default(true)
53
- manifest String?
54
- settings String? @default("{}")
55
- createdAt DateTime @default(now())
56
- bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
57
-
58
- @@unique([botId, name])
59
- }
60
-
61
- model Command {
62
- id Int @id @default(autoincrement())
63
- botId Int
64
- name String
65
- isEnabled Boolean @default(true)
66
- cooldown Int @default(0)
67
- aliases String @default("[]")
68
- description String?
69
- owner String?
70
- permissionId Int?
71
- allowedChatTypes String @default("[\"chat\", \"private\"]")
72
- isVisual Boolean @default(false)
73
- argumentsJson String? @default("[]")
74
- graphJson String? @default("null")
75
- permission Permission? @relation(fields: [permissionId], references: [id])
76
- bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
77
-
78
- @@unique([botId, name])
79
- }
80
-
81
- model EventGraph {
82
- id Int @id @default(autoincrement())
83
- botId Int
84
- name String
85
- isEnabled Boolean @default(true)
86
- graphJson String? @default("null")
87
- createdAt DateTime @default(now())
88
- updatedAt DateTime @updatedAt
89
- variables String? @default("[]")
90
- bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
91
- triggers EventTrigger[]
92
-
93
- @@unique([botId, name])
94
- }
95
-
96
- model EventTrigger {
97
- id Int @id @default(autoincrement())
98
- graphId Int
99
- eventType String
100
- graph EventGraph @relation(fields: [graphId], references: [id], onDelete: Cascade)
101
-
102
- @@unique([graphId, eventType])
103
- }
104
-
105
- model User {
106
- id Int @id @default(autoincrement())
107
- username String
108
- isBlacklisted Boolean @default(false)
109
- botId Int
110
- bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
111
- groups UserGroup[]
112
-
113
- @@unique([botId, username])
114
- }
115
-
116
- model Group {
117
- id Int @id @default(autoincrement())
118
- name String
119
- owner String @default("system")
120
- botId Int
121
- bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
122
- permissions GroupPermission[]
123
- users UserGroup[]
124
-
125
- @@unique([botId, name])
126
- }
127
-
128
- model Permission {
129
- id Int @id @default(autoincrement())
130
- name String
131
- description String?
132
- owner String @default("system")
133
- botId Int
134
- commands Command[]
135
- groups GroupPermission[]
136
- bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
137
-
138
- @@unique([botId, name])
139
- }
140
-
141
- model UserGroup {
142
- userId Int
143
- groupId Int
144
- group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
145
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
146
-
147
- @@id([userId, groupId])
148
- }
149
-
150
- model GroupPermission {
151
- groupId Int
152
- permissionId Int
153
- permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
154
- group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
155
-
156
- @@id([groupId, permissionId])
157
- }
158
-
159
- model ScheduledTask {
160
- id Int @id @default(autoincrement())
161
- name String
162
- cronPattern String?
163
- action String
164
- targetBotIds String
165
- payload String? @default("{}")
166
- isEnabled Boolean @default(true)
167
- runOnStartup Boolean @default(false)
168
- lastRun DateTime?
169
- createdAt DateTime @default(now())
170
- updatedAt DateTime @updatedAt
171
- }
172
-
173
- model PanelUser {
174
- id Int @id @default(autoincrement())
175
- uuid String @unique @default(uuid())
176
- username String @unique
177
- passwordHash String
178
- roleId Int
179
- createdAt DateTime @default(now())
180
- role PanelRole @relation(fields: [roleId], references: [id])
181
- }
182
-
183
- model PanelRole {
184
- id Int @id @default(autoincrement())
185
- name String @unique
186
- permissions String @default("[]")
187
- users PanelUser[]
188
- }
189
-
190
- model PluginDataStore {
191
- id Int @id @default(autoincrement())
192
- pluginName String
193
- botId Int
194
- key String
195
- value String
196
- createdAt DateTime @default(now())
197
- updatedAt DateTime @updatedAt
198
- bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
199
-
200
- @@unique([pluginName, botId, key])
201
- }
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "sqlite"
7
+ url = env("DATABASE_URL")
8
+ }
9
+
10
+ model Server {
11
+ id Int @id @default(autoincrement())
12
+ name String @unique
13
+ host String
14
+ port Int @default(25565)
15
+ version String
16
+ bots Bot[]
17
+ }
18
+
19
+ model Bot {
20
+ id Int @id @default(autoincrement())
21
+ username String @unique
22
+ password String?
23
+ prefix String? @default("@")
24
+ note String?
25
+ owners String? @default("")
26
+
27
+ server Server @relation(fields: [serverId], references: [id])
28
+ serverId Int
29
+
30
+ proxyHost String?
31
+ proxyPort Int?
32
+ proxyUsername String?
33
+ proxyPassword String?
34
+
35
+ installedPlugins InstalledPlugin[]
36
+ users User[]
37
+ groups Group[]
38
+ permissions Permission[]
39
+ commands Command[]
40
+ eventGraphs EventGraph[]
41
+ pluginData PluginDataStore[]
42
+
43
+ createdAt DateTime @default(now())
44
+ updatedAt DateTime @updatedAt
45
+ }
46
+
47
+
48
+ model InstalledPlugin {
49
+ id Int @id @default(autoincrement())
50
+ botId Int
51
+ bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
52
+ name String
53
+ version String
54
+ description String?
55
+ sourceType String
56
+ sourceUri String?
57
+ path String
58
+ isEnabled Boolean @default(true)
59
+
60
+ manifest String?
61
+ settings String? @default("{}")
62
+
63
+ createdAt DateTime @default(now())
64
+
65
+ @@unique([botId, name])
66
+ }
67
+
68
+ model Command {
69
+ id Int @id @default(autoincrement())
70
+ botId Int
71
+ bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
72
+ name String
73
+ isEnabled Boolean @default(true)
74
+ cooldown Int @default(0)
75
+ aliases String @default("[]")
76
+ description String?
77
+ owner String?
78
+ permissionId Int?
79
+ permission Permission? @relation(fields: [permissionId], references: [id], onDelete: SetNull)
80
+ allowedChatTypes String @default("[\"chat\", \"private\"]")
81
+
82
+ isVisual Boolean @default(false)
83
+ argumentsJson String? @default("[]")
84
+ graphJson String? @default("null")
85
+
86
+ @@unique([botId, name])
87
+ }
88
+
89
+ model EventGraph {
90
+ id Int @id @default(autoincrement())
91
+ botId Int
92
+ bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
93
+ name String
94
+ isEnabled Boolean @default(true)
95
+ graphJson String? @default("null")
96
+ variables String? @default("[]")
97
+
98
+ triggers EventTrigger[]
99
+
100
+ createdAt DateTime @default(now())
101
+ updatedAt DateTime @updatedAt
102
+
103
+ @@unique([botId, name])
104
+ }
105
+
106
+ model EventTrigger {
107
+ id Int @id @default(autoincrement())
108
+ graphId Int
109
+ graph EventGraph @relation(fields: [graphId], references: [id], onDelete: Cascade)
110
+ eventType String // e.g., "entitySpawn", "chat"
111
+
112
+ @@unique([graphId, eventType])
113
+ }
114
+
115
+ model User {
116
+ id Int @id @default(autoincrement())
117
+ username String
118
+ isBlacklisted Boolean @default(false)
119
+ botId Int
120
+ bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
121
+ groups UserGroup[]
122
+
123
+ @@unique([botId, username])
124
+ }
125
+
126
+ model Group {
127
+ id Int @id @default(autoincrement())
128
+ name String
129
+ owner String @default("system")
130
+ botId Int
131
+ bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
132
+ permissions GroupPermission[]
133
+ users UserGroup[]
134
+
135
+ @@unique([botId, name])
136
+ }
137
+
138
+ model Permission {
139
+ id Int @id @default(autoincrement())
140
+ name String
141
+ description String?
142
+ owner String @default("system")
143
+ botId Int
144
+ bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
145
+ groups GroupPermission[]
146
+ commands Command[]
147
+
148
+ @@unique([botId, name])
149
+ }
150
+
151
+
152
+ model UserGroup {
153
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
154
+ userId Int
155
+ group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
156
+ groupId Int
157
+
158
+ @@id([userId, groupId])
159
+ }
160
+
161
+ model GroupPermission {
162
+ group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
163
+ groupId Int
164
+ permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
165
+ permissionId Int
166
+
167
+ @@id([groupId, permissionId])
168
+ }
169
+
170
+
171
+
172
+ model ScheduledTask {
173
+ id Int @id @default(autoincrement())
174
+ name String
175
+ cronPattern String?
176
+ action String
177
+ targetBotIds String // джсон массив ID ботов или "ALL"
178
+ payload String? @default("{}") // джсон для доп данных, например, команды
179
+ isEnabled Boolean @default(true)
180
+ runOnStartup Boolean @default(false)
181
+ lastRun DateTime?
182
+ createdAt DateTime @default(now())
183
+ updatedAt DateTime @updatedAt
184
+ }
185
+
186
+ model PanelUser {
187
+ id Int @id @default(autoincrement())
188
+ uuid String @unique @default(uuid())
189
+ username String @unique
190
+ passwordHash String
191
+ role PanelRole @relation(fields: [roleId], references: [id])
192
+ roleId Int
193
+ createdAt DateTime @default(now())
194
+ }
195
+
196
+ // Роли пользователей (Admin, Moderator, Viewer)
197
+ model PanelRole {
198
+ id Int @id @default(autoincrement())
199
+ name String @unique
200
+
201
+ // Храним права как джсон строку. SQLite не поддерживает массивы.
202
+ // Пример: '[\"bot:create\", \"bot:delete\", \"user:manage\"]'
203
+ permissions String @default("[]")
204
+ users PanelUser[]
205
+ }
206
+
207
+ model PluginDataStore {
208
+ id Int @id @default(autoincrement())
209
+ pluginName String
210
+ botId Int
211
+ key String
212
+ value String // в виде json
213
+ createdAt DateTime @default(now())
214
+ updatedAt DateTime @updatedAt
215
+
216
+ bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
217
+
218
+ @@unique([pluginName, botId, key])
219
+ }
@@ -1,9 +1,12 @@
1
1
  const express = require('express');
2
2
  const bcrypt = require('bcryptjs');
3
3
  const jwt = require('jsonwebtoken');
4
+ const crypto = require('crypto');
4
5
  const { PrismaClient } = require('@prisma/client');
5
6
  const config = require('../../config');
6
7
  const { authenticate, authorize } = require('../middleware/auth');
8
+ const path = require('path');
9
+ const os = require('os');
7
10
 
8
11
  const router = express.Router();
9
12
  const prisma = new PrismaClient();
@@ -11,6 +14,8 @@ const prisma = new PrismaClient();
11
14
  const JWT_SECRET = config.security.jwtSecret;
12
15
  const JWT_EXPIRES_IN = '7d';
13
16
 
17
+ const activeResetTokens = new Map();
18
+
14
19
  /**
15
20
  * @route GET /api/auth/status
16
21
  * @desc Проверяет, была ли произведена первоначальная настройка (создан админ)
@@ -26,6 +31,16 @@ router.get('/status', async (req, res) => {
26
31
  }
27
32
  });
28
33
 
34
+ /**
35
+ * @route GET /api/auth/config-path
36
+ * @desc Получить путь к конфигурационному файлу
37
+ * @access Public
38
+ */
39
+ router.get('/config-path', (req, res) => {
40
+ const configPath = path.join(os.homedir(), '.blockmine', 'config.json');
41
+ res.json({ configPath });
42
+ });
43
+
29
44
  /**
30
45
  * @route POST /api/auth/setup
31
46
  * @desc Создает первого пользователя с ролью администратора
@@ -100,6 +115,131 @@ router.post('/setup', async (req, res) => {
100
115
  }
101
116
  });
102
117
 
118
+ /**
119
+ * @route POST /api/auth/recovery/verify
120
+ * @desc Проверка кода восстановления
121
+ * @access Public
122
+ */
123
+ router.post('/recovery/verify', async (req, res) => {
124
+ try {
125
+ const { recoveryCode } = req.body;
126
+
127
+ if (!recoveryCode) {
128
+ return res.status(400).json({ error: 'Код восстановления обязателен' });
129
+ }
130
+
131
+ if (recoveryCode !== config.security.adminRecoveryCode) {
132
+ await new Promise(resolve => setTimeout(resolve, 1000));
133
+ return res.status(401).json({ error: 'Неверный код восстановления' });
134
+ }
135
+
136
+ const rootUser = await prisma.panelUser.findFirst({
137
+ orderBy: { id: 'asc' },
138
+ include: { role: true }
139
+ });
140
+
141
+ if (!rootUser) {
142
+ return res.status(404).json({ error: 'В системе нет ни одного пользователя. Выполните первоначальную настройку.' });
143
+ }
144
+
145
+ const tokenId = crypto.randomBytes(16).toString('hex');
146
+ const resetToken = jwt.sign(
147
+ {
148
+ userId: rootUser.id,
149
+ type: 'password-reset',
150
+ tokenId: tokenId,
151
+ timestamp: Date.now()
152
+ },
153
+ JWT_SECRET,
154
+ { expiresIn: '5m' }
155
+ );
156
+
157
+ activeResetTokens.set(tokenId, {
158
+ userId: rootUser.id,
159
+ createdAt: Date.now(),
160
+ used: false
161
+ });
162
+
163
+ setTimeout(() => {
164
+ activeResetTokens.delete(tokenId);
165
+ }, 5 * 60 * 1000);
166
+
167
+ res.json({
168
+ success: true,
169
+ username: rootUser.username,
170
+ resetToken
171
+ });
172
+
173
+ } catch (error) {
174
+ console.error('[Recovery Verify Error]', error);
175
+ res.status(500).json({ error: 'Ошибка при проверке кода восстановления' });
176
+ }
177
+ });
178
+
179
+ /**
180
+ * @route POST /api/auth/recovery/reset
181
+ * @desc Сброс пароля с использованием токена
182
+ * @access Public (с валидным токеном сброса)
183
+ */
184
+ router.post('/recovery/reset', async (req, res) => {
185
+ try {
186
+ const { resetToken, newPassword } = req.body;
187
+
188
+ if (!resetToken || !newPassword) {
189
+ return res.status(400).json({ error: 'Токен и новый пароль обязательны' });
190
+ }
191
+
192
+ if (newPassword.length < 4) {
193
+ return res.status(400).json({ error: 'Пароль должен быть не менее 4 символов' });
194
+ }
195
+
196
+ let decoded;
197
+ try {
198
+ decoded = jwt.verify(resetToken, JWT_SECRET);
199
+ if (decoded.type !== 'password-reset') {
200
+ throw new Error('Invalid token type');
201
+ }
202
+
203
+ const tokenInfo = activeResetTokens.get(decoded.tokenId);
204
+ if (!tokenInfo) {
205
+ throw new Error('Token not found in active tokens');
206
+ }
207
+
208
+ if (tokenInfo.used) {
209
+ throw new Error('Token already used');
210
+ }
211
+
212
+ if (tokenInfo.userId !== decoded.userId) {
213
+ throw new Error('Token userId mismatch');
214
+ }
215
+
216
+ } catch (err) {
217
+ return res.status(401).json({ error: 'Недействительный или истекший токен' });
218
+ }
219
+
220
+ const hashedPassword = await bcrypt.hash(newPassword, 12);
221
+ const updatedUser = await prisma.panelUser.update({
222
+ where: { id: decoded.userId },
223
+ data: { passwordHash: hashedPassword },
224
+ select: { username: true }
225
+ });
226
+
227
+ const tokenInfo = activeResetTokens.get(decoded.tokenId);
228
+ tokenInfo.used = true;
229
+
230
+ activeResetTokens.delete(decoded.tokenId);
231
+
232
+ res.json({
233
+ message: 'Пароль успешно сброшен',
234
+ username: updatedUser.username
235
+ });
236
+
237
+ } catch (error) {
238
+ console.error('[Recovery Reset Error]', error);
239
+ res.status(500).json({ error: 'Ошибка при сбросе пароля' });
240
+ }
241
+ });
242
+
103
243
  /**
104
244
  * @route POST /api/auth/login
105
245
  * @desc Аутентифицирует пользователя и возвращает токен