blockmine 1.12.1 → 1.13.1
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 +14 -0
- package/backend/prisma/schema.prisma +219 -201
- package/backend/src/api/routes/auth.js +140 -0
- package/backend/src/core/BotManager.js +1 -1
- package/backend/src/core/UserService.js +8 -4
- package/frontend/dist/assets/{index-BzDKhCKk.js → index-D4biDGgF.js} +1682 -1681
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
# История версий
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
### [1.13.1](https://github.com/blockmineJS/blockmine/compare/v1.13.0...v1.13.1) (2025-07-18)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
### 🐛 Исправления
|
|
8
|
+
|
|
9
|
+
* исправлены ошибки в обработке групп и прав пользователей, обновлены иконки у плагинов ([bb4f098](https://github.com/blockmineJS/blockmine/commit/bb4f098736b3249d19e5fc83bb7baadab9e6ce9f))
|
|
10
|
+
|
|
11
|
+
## [1.13.0](https://github.com/blockmineJS/blockmine/compare/v1.12.0...v1.13.0) (2025-07-18)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### ✨ Новые возможности
|
|
15
|
+
|
|
16
|
+
* возможность сбрасывать пароль рут аккаунта ([e23181d](https://github.com/blockmineJS/blockmine/commit/e23181d29dbe730ff882d654b8ec0e80a1f007bc))
|
|
17
|
+
|
|
4
18
|
## [1.12.0](https://github.com/blockmineJS/blockmine/compare/v1.11.5...v1.12.0) (2025-07-18)
|
|
5
19
|
|
|
6
20
|
|
|
@@ -1,201 +1,219 @@
|
|
|
1
|
-
generator client {
|
|
2
|
-
provider = "prisma-client-js"
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
datasource db {
|
|
6
|
-
provider = "sqlite"
|
|
7
|
-
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
|
|
21
|
-
username
|
|
22
|
-
password
|
|
23
|
-
prefix
|
|
24
|
-
note
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
permissions Permission[]
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
botId
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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 Аутентифицирует пользователя и возвращает токен
|
|
@@ -460,7 +460,7 @@ class BotManager {
|
|
|
460
460
|
case 'removePermission':
|
|
461
461
|
break;
|
|
462
462
|
case 'getGroups':
|
|
463
|
-
result = user.groups;
|
|
463
|
+
result = user.groups ? user.groups.map(g => g.group.name) : [];
|
|
464
464
|
break;
|
|
465
465
|
case 'getPermissions':
|
|
466
466
|
result = Array.from(user.permissionsSet);
|
|
@@ -169,13 +169,17 @@ class User {
|
|
|
169
169
|
|
|
170
170
|
if (!defaultGroup) {
|
|
171
171
|
console.warn(`[UserService] Дефолтная группа 'User' не найдена для бота ID ${botId}. Пользователь будет создан без группы.`);
|
|
172
|
-
userInstance = await prisma.user.
|
|
173
|
-
|
|
172
|
+
userInstance = await prisma.user.upsert({
|
|
173
|
+
where: { botId_username: { botId, username: lowerUsername } },
|
|
174
|
+
update: {},
|
|
175
|
+
create: { username: lowerUsername, botId },
|
|
174
176
|
include: { groups: { include: { group: { include: { permissions: { include: { permission: true } } } } } } },
|
|
175
177
|
});
|
|
176
178
|
} else {
|
|
177
|
-
userInstance = await prisma.user.
|
|
178
|
-
|
|
179
|
+
userInstance = await prisma.user.upsert({
|
|
180
|
+
where: { botId_username: { botId, username: lowerUsername } },
|
|
181
|
+
update: {},
|
|
182
|
+
create: {
|
|
179
183
|
username: lowerUsername,
|
|
180
184
|
botId,
|
|
181
185
|
groups: { create: { groupId: defaultGroup.id } }
|