@minecraft-docker/mcctl 1.13.0 → 1.14.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 +22 -6
- package/README.md +8 -0
- package/dist/commands/console/init.d.ts.map +1 -1
- package/dist/commands/console/init.js +51 -38
- package/dist/commands/console/init.js.map +1 -1
- package/dist/commands/console/service.d.ts.map +1 -1
- package/dist/commands/console/service.js +82 -19
- package/dist/commands/console/service.js.map +1 -1
- package/dist/commands/console/user.d.ts.map +1 -1
- package/dist/commands/console/user.js +344 -304
- package/dist/commands/console/user.js.map +1 -1
- package/dist/commands/create.d.ts +3 -0
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +16 -1
- package/dist/commands/create.js.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/adapters/ClackPromptAdapter.d.ts +4 -1
- package/dist/infrastructure/adapters/ClackPromptAdapter.d.ts.map +1 -1
- package/dist/infrastructure/adapters/ClackPromptAdapter.js +92 -9
- package/dist/infrastructure/adapters/ClackPromptAdapter.js.map +1 -1
- package/dist/infrastructure/di/container.d.ts +3 -1
- package/dist/infrastructure/di/container.d.ts.map +1 -1
- package/dist/infrastructure/di/container.js +9 -1
- package/dist/infrastructure/di/container.js.map +1 -1
- package/dist/lib/admin-config.js +1 -1
- package/dist/lib/console-db.d.ts +64 -0
- package/dist/lib/console-db.d.ts.map +1 -0
- package/dist/lib/console-db.js +240 -0
- package/dist/lib/console-db.js.map +1 -0
- package/package.json +5 -3
- package/scripts/create-server.sh +63 -3
- package/templates/.env.example +1 -1
|
@@ -1,337 +1,377 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import * as p from '@clack/prompts';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { Paths, colors, } from '@minecraft-docker/shared';
|
|
5
|
+
import { ConsoleDatabase } from '../../lib/console-db.js';
|
|
5
6
|
/**
|
|
6
|
-
* Get the
|
|
7
|
+
* Get the ConsoleDatabase instance
|
|
7
8
|
*/
|
|
8
|
-
function
|
|
9
|
+
function getConsoleDb() {
|
|
9
10
|
const paths = new Paths();
|
|
10
|
-
const
|
|
11
|
-
|
|
11
|
+
const dbPath = join(paths.root, 'data', 'mcctl.db');
|
|
12
|
+
const db = new ConsoleDatabase(dbPath);
|
|
13
|
+
db.ensureSchema();
|
|
14
|
+
return db;
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
14
17
|
* List all users
|
|
15
18
|
*/
|
|
16
19
|
async function listUsers(options) {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
const db = getConsoleDb();
|
|
21
|
+
try {
|
|
22
|
+
const users = db.findAllUsers();
|
|
23
|
+
if (options.json) {
|
|
24
|
+
console.log(JSON.stringify(users.map((u) => ({
|
|
25
|
+
id: u.id,
|
|
26
|
+
name: u.name,
|
|
27
|
+
email: u.email,
|
|
28
|
+
role: u.role,
|
|
29
|
+
emailVerified: u.emailVerified,
|
|
30
|
+
banned: u.banned,
|
|
31
|
+
createdAt: u.createdAt.toISOString(),
|
|
32
|
+
updatedAt: u.updatedAt.toISOString(),
|
|
33
|
+
})), null, 2));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (users.length === 0) {
|
|
37
|
+
console.log(colors.yellow('No users found.'));
|
|
38
|
+
console.log(`Run ${colors.cyan('mcctl console init')} to create an admin user.`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
console.log();
|
|
42
|
+
console.log(colors.bold('Users:'));
|
|
43
|
+
console.log();
|
|
44
|
+
// Table header
|
|
45
|
+
const header = `${colors.dim('Email'.padEnd(30))}${colors.dim('Name'.padEnd(20))}${colors.dim('Role'.padEnd(12))}${colors.dim('Created')}`;
|
|
46
|
+
console.log(header);
|
|
47
|
+
console.log(colors.dim('-'.repeat(75)));
|
|
48
|
+
// Table rows
|
|
49
|
+
for (const user of users) {
|
|
50
|
+
const roleColor = user.role === 'admin' ? colors.red : colors.blue;
|
|
51
|
+
const row = `${user.email.padEnd(30)}${user.name.padEnd(20)}${roleColor(user.role.padEnd(12))}${user.createdAt.toISOString().split('T')[0]}`;
|
|
52
|
+
console.log(row);
|
|
53
|
+
}
|
|
54
|
+
console.log();
|
|
55
|
+
console.log(`Total: ${colors.cyan(users.length.toString())} user(s)`);
|
|
27
56
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
console.log();
|
|
31
|
-
// Table header
|
|
32
|
-
const header = `${colors.dim('Username'.padEnd(20))}${colors.dim('Role'.padEnd(12))}${colors.dim('Created')}`;
|
|
33
|
-
console.log(header);
|
|
34
|
-
console.log(colors.dim('-'.repeat(50)));
|
|
35
|
-
// Table rows
|
|
36
|
-
for (const user of users) {
|
|
37
|
-
const roleColor = user.role.isAdmin ? colors.red : colors.blue;
|
|
38
|
-
const row = `${user.username.value.padEnd(20)}${roleColor(user.role.value.padEnd(12))}${user.createdAt.toISOString().split('T')[0]}`;
|
|
39
|
-
console.log(row);
|
|
57
|
+
finally {
|
|
58
|
+
db.close();
|
|
40
59
|
}
|
|
41
|
-
console.log();
|
|
42
|
-
console.log(`Total: ${colors.cyan(users.length.toString())} user(s)`);
|
|
43
60
|
}
|
|
44
61
|
/**
|
|
45
62
|
* Add a new user
|
|
46
63
|
*/
|
|
47
|
-
async function addUser(
|
|
48
|
-
const
|
|
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
|
-
|
|
64
|
+
async function addUser(emailArg, options) {
|
|
65
|
+
const db = getConsoleDb();
|
|
66
|
+
try {
|
|
67
|
+
let email;
|
|
68
|
+
let name;
|
|
69
|
+
let role;
|
|
70
|
+
let password;
|
|
71
|
+
if (emailArg && options.role && options.password) {
|
|
72
|
+
// CLI mode
|
|
73
|
+
email = emailArg;
|
|
74
|
+
name = emailArg.split('@')[0] ?? emailArg;
|
|
75
|
+
role = options.role.toLowerCase();
|
|
76
|
+
password = options.password;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// Interactive mode
|
|
80
|
+
p.intro(colors.cyan('Add New User'));
|
|
81
|
+
const result = await p.group({
|
|
82
|
+
email: () => p.text({
|
|
83
|
+
message: 'Email:',
|
|
84
|
+
placeholder: 'user@example.com',
|
|
85
|
+
validate: (value) => {
|
|
86
|
+
if (!value || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
|
87
|
+
return 'Valid email address is required';
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
},
|
|
91
|
+
}),
|
|
92
|
+
name: () => p.text({
|
|
93
|
+
message: 'Name:',
|
|
94
|
+
placeholder: 'User Name',
|
|
95
|
+
validate: (value) => {
|
|
96
|
+
if (!value || value.trim().length === 0) {
|
|
97
|
+
return 'Name is required';
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
},
|
|
101
|
+
}),
|
|
102
|
+
role: () => p.select({
|
|
103
|
+
message: 'Role:',
|
|
104
|
+
options: [
|
|
105
|
+
{ value: 'admin', label: 'Admin - Full access' },
|
|
106
|
+
{ value: 'user', label: 'User - Standard access' },
|
|
107
|
+
],
|
|
108
|
+
}),
|
|
109
|
+
password: () => p.password({
|
|
110
|
+
message: 'Password:',
|
|
111
|
+
validate: (value) => {
|
|
112
|
+
if (!value || value.length < 8) {
|
|
113
|
+
return 'Password must be at least 8 characters';
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
confirmPassword: () => p.password({
|
|
119
|
+
message: 'Confirm password:',
|
|
120
|
+
}),
|
|
121
|
+
}, {
|
|
122
|
+
onCancel: () => {
|
|
123
|
+
p.cancel('Operation cancelled.');
|
|
124
|
+
process.exit(0);
|
|
92
125
|
},
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
});
|
|
103
|
-
if (result.password !== result.confirmPassword) {
|
|
104
|
-
p.cancel('Passwords do not match.');
|
|
105
|
-
process.exit(1);
|
|
126
|
+
});
|
|
127
|
+
if (result.password !== result.confirmPassword) {
|
|
128
|
+
p.cancel('Passwords do not match.');
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
email = result.email;
|
|
132
|
+
name = result.name;
|
|
133
|
+
role = result.role;
|
|
134
|
+
password = result.password;
|
|
106
135
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
password = result.password;
|
|
110
|
-
}
|
|
111
|
-
// Check if user already exists
|
|
112
|
-
try {
|
|
113
|
-
const existingUser = await repo.findByUsername(Username.create(username));
|
|
136
|
+
// Check if user already exists
|
|
137
|
+
const existingUser = db.findUserByEmail(email);
|
|
114
138
|
if (existingUser) {
|
|
115
|
-
console.error(colors.red(`User '${
|
|
139
|
+
console.error(colors.red(`User '${email}' already exists.`));
|
|
116
140
|
process.exit(1);
|
|
117
141
|
}
|
|
142
|
+
// Create user
|
|
143
|
+
db.createUser({ email, name, password, role });
|
|
144
|
+
if (emailArg) {
|
|
145
|
+
console.log(JSON.stringify({ success: true, email }));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
p.outro(colors.green(`User '${email}' created successfully!`));
|
|
149
|
+
}
|
|
118
150
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
console.error(colors.red(`Invalid username: ${username}`));
|
|
122
|
-
process.exit(1);
|
|
123
|
-
}
|
|
124
|
-
// Create user
|
|
125
|
-
const passwordHash = await repo.hashPassword(password);
|
|
126
|
-
const user = User.create({
|
|
127
|
-
username: Username.create(username),
|
|
128
|
-
passwordHash,
|
|
129
|
-
role: Role.create(role),
|
|
130
|
-
});
|
|
131
|
-
await repo.save(user);
|
|
132
|
-
if (usernameArg) {
|
|
133
|
-
console.log(JSON.stringify({ success: true, username: user.username.value }));
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
p.outro(colors.green(`User '${username}' created successfully!`));
|
|
151
|
+
finally {
|
|
152
|
+
db.close();
|
|
137
153
|
}
|
|
138
154
|
}
|
|
139
155
|
/**
|
|
140
156
|
* Remove a user
|
|
141
157
|
*/
|
|
142
|
-
async function removeUser(
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
process.exit(1);
|
|
158
|
+
async function removeUser(emailArg, options) {
|
|
159
|
+
const db = getConsoleDb();
|
|
160
|
+
try {
|
|
161
|
+
let targetUser;
|
|
162
|
+
if (emailArg) {
|
|
163
|
+
const user = db.findUserByEmail(emailArg);
|
|
164
|
+
if (!user) {
|
|
165
|
+
console.error(colors.red(`User '${emailArg}' not found.`));
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
targetUser = user;
|
|
154
169
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
170
|
+
else {
|
|
171
|
+
// Interactive mode
|
|
172
|
+
const users = db.findAllUsers();
|
|
173
|
+
if (users.length === 0) {
|
|
174
|
+
console.error(colors.red('No users found.'));
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
const result = await p.select({
|
|
178
|
+
message: 'Select user to remove:',
|
|
179
|
+
options: users.map((u) => ({
|
|
180
|
+
value: u.email,
|
|
181
|
+
label: `${u.email} (${u.role})`,
|
|
182
|
+
})),
|
|
183
|
+
});
|
|
184
|
+
if (p.isCancel(result)) {
|
|
185
|
+
p.cancel('Operation cancelled.');
|
|
186
|
+
process.exit(0);
|
|
187
|
+
}
|
|
188
|
+
const user = db.findUserByEmail(result);
|
|
189
|
+
if (!user) {
|
|
190
|
+
console.error(colors.red('User not found.'));
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
targetUser = user;
|
|
165
194
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
// Check if this is the last admin
|
|
175
|
-
if (user.isAdmin) {
|
|
176
|
-
const allUsers = await repo.findAll();
|
|
177
|
-
const adminCount = allUsers.filter((u) => u.isAdmin).length;
|
|
178
|
-
if (adminCount <= 1) {
|
|
179
|
-
console.error(colors.red('Cannot delete the last admin user.'));
|
|
180
|
-
process.exit(1);
|
|
195
|
+
// Check if this is the last admin
|
|
196
|
+
if (targetUser.role === 'admin') {
|
|
197
|
+
const allUsers = db.findAllUsers();
|
|
198
|
+
const adminCount = allUsers.filter((u) => u.role === 'admin').length;
|
|
199
|
+
if (adminCount <= 1) {
|
|
200
|
+
console.error(colors.red('Cannot delete the last admin user.'));
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
181
203
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
204
|
+
// Confirm deletion
|
|
205
|
+
if (!options.force) {
|
|
206
|
+
const confirmed = await p.confirm({
|
|
207
|
+
message: `Are you sure you want to delete user '${targetUser.email}'?`,
|
|
208
|
+
});
|
|
209
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
210
|
+
p.cancel('Operation cancelled.');
|
|
211
|
+
process.exit(0);
|
|
212
|
+
}
|
|
191
213
|
}
|
|
214
|
+
db.deleteUser(targetUser.id);
|
|
215
|
+
console.log(colors.green(`User '${targetUser.email}' deleted successfully.`));
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
db.close();
|
|
192
219
|
}
|
|
193
|
-
await repo.delete(user.id);
|
|
194
|
-
console.log(colors.green(`User '${username}' deleted successfully.`));
|
|
195
220
|
}
|
|
196
221
|
/**
|
|
197
|
-
* Update a user
|
|
222
|
+
* Update a user's role
|
|
198
223
|
*/
|
|
199
|
-
async function updateUser(
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
process.exit(1);
|
|
224
|
+
async function updateUser(emailArg, options) {
|
|
225
|
+
const db = getConsoleDb();
|
|
226
|
+
try {
|
|
227
|
+
let targetUser;
|
|
228
|
+
if (emailArg) {
|
|
229
|
+
const user = db.findUserByEmail(emailArg);
|
|
230
|
+
if (!user) {
|
|
231
|
+
console.error(colors.red(`User '${emailArg}' not found.`));
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
targetUser = user;
|
|
211
235
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
236
|
+
else {
|
|
237
|
+
// Interactive mode
|
|
238
|
+
const users = db.findAllUsers();
|
|
239
|
+
if (users.length === 0) {
|
|
240
|
+
console.error(colors.red('No users found.'));
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
const result = await p.select({
|
|
244
|
+
message: 'Select user to update:',
|
|
245
|
+
options: users.map((u) => ({
|
|
246
|
+
value: u.email,
|
|
247
|
+
label: `${u.email} (${u.role})`,
|
|
248
|
+
})),
|
|
249
|
+
});
|
|
250
|
+
if (p.isCancel(result)) {
|
|
251
|
+
p.cancel('Operation cancelled.');
|
|
252
|
+
process.exit(0);
|
|
253
|
+
}
|
|
254
|
+
const user = db.findUserByEmail(result);
|
|
255
|
+
if (!user) {
|
|
256
|
+
console.error(colors.red('User not found.'));
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
targetUser = user;
|
|
222
260
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (!user) {
|
|
228
|
-
console.error(colors.red(`User '${username}' not found.`));
|
|
229
|
-
process.exit(1);
|
|
230
|
-
}
|
|
231
|
-
// Update role
|
|
232
|
-
let newRole;
|
|
233
|
-
if (options.role) {
|
|
234
|
-
newRole = options.role.toLowerCase();
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
const result = await p.select({
|
|
238
|
-
message: 'New role:',
|
|
239
|
-
initialValue: user.role.value,
|
|
240
|
-
options: [
|
|
241
|
-
{ value: RoleEnum.ADMIN, label: 'Admin - Full access' },
|
|
242
|
-
{ value: RoleEnum.VIEWER, label: 'Viewer - Read-only access' },
|
|
243
|
-
],
|
|
244
|
-
});
|
|
245
|
-
if (p.isCancel(result)) {
|
|
246
|
-
p.cancel('Operation cancelled.');
|
|
247
|
-
process.exit(0);
|
|
261
|
+
// Update role
|
|
262
|
+
let newRole;
|
|
263
|
+
if (options.role) {
|
|
264
|
+
newRole = options.role.toLowerCase();
|
|
248
265
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
266
|
+
else {
|
|
267
|
+
const result = await p.select({
|
|
268
|
+
message: 'New role:',
|
|
269
|
+
initialValue: targetUser.role,
|
|
270
|
+
options: [
|
|
271
|
+
{ value: 'admin', label: 'Admin - Full access' },
|
|
272
|
+
{ value: 'user', label: 'User - Standard access' },
|
|
273
|
+
],
|
|
274
|
+
});
|
|
275
|
+
if (p.isCancel(result)) {
|
|
276
|
+
p.cancel('Operation cancelled.');
|
|
277
|
+
process.exit(0);
|
|
278
|
+
}
|
|
279
|
+
newRole = result;
|
|
258
280
|
}
|
|
281
|
+
// Check if this would remove the last admin
|
|
282
|
+
if (targetUser.role === 'admin' && newRole !== 'admin') {
|
|
283
|
+
const allUsers = db.findAllUsers();
|
|
284
|
+
const adminCount = allUsers.filter((u) => u.role === 'admin').length;
|
|
285
|
+
if (adminCount <= 1) {
|
|
286
|
+
console.error(colors.red('Cannot change role of the last admin user.'));
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
db.updateUserRole(targetUser.id, newRole);
|
|
291
|
+
console.log(colors.green(`User '${targetUser.email}' updated to role '${newRole}'.`));
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
db.close();
|
|
259
295
|
}
|
|
260
|
-
user.updateRole(Role.create(newRole));
|
|
261
|
-
await repo.save(user);
|
|
262
|
-
console.log(colors.green(`User '${username}' updated to role '${newRole}'.`));
|
|
263
296
|
}
|
|
264
297
|
/**
|
|
265
298
|
* Reset a user's password
|
|
266
299
|
*/
|
|
267
|
-
async function resetPassword(
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
process.exit(1);
|
|
279
|
-
}
|
|
280
|
-
const result = await p.select({
|
|
281
|
-
message: 'Select user to reset password:',
|
|
282
|
-
options: users.map((u) => ({
|
|
283
|
-
value: u.username.value,
|
|
284
|
-
label: `${u.username.value} (${u.role.value})`,
|
|
285
|
-
})),
|
|
286
|
-
});
|
|
287
|
-
if (p.isCancel(result)) {
|
|
288
|
-
p.cancel('Operation cancelled.');
|
|
289
|
-
process.exit(0);
|
|
300
|
+
async function resetPassword(emailArg, options) {
|
|
301
|
+
const db = getConsoleDb();
|
|
302
|
+
try {
|
|
303
|
+
let targetUser;
|
|
304
|
+
if (emailArg) {
|
|
305
|
+
const user = db.findUserByEmail(emailArg);
|
|
306
|
+
if (!user) {
|
|
307
|
+
console.error(colors.red(`User '${emailArg}' not found.`));
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
targetUser = user;
|
|
290
311
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
password: () => p.password({
|
|
307
|
-
message: 'New password:',
|
|
308
|
-
validate: (value) => {
|
|
309
|
-
if (!value || value.length < 8) {
|
|
310
|
-
return 'Password must be at least 8 characters';
|
|
311
|
-
}
|
|
312
|
-
return undefined;
|
|
313
|
-
},
|
|
314
|
-
}),
|
|
315
|
-
confirmPassword: () => p.password({
|
|
316
|
-
message: 'Confirm password:',
|
|
317
|
-
}),
|
|
318
|
-
}, {
|
|
319
|
-
onCancel: () => {
|
|
312
|
+
else {
|
|
313
|
+
// Interactive mode
|
|
314
|
+
const users = db.findAllUsers();
|
|
315
|
+
if (users.length === 0) {
|
|
316
|
+
console.error(colors.red('No users found.'));
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
const result = await p.select({
|
|
320
|
+
message: 'Select user to reset password:',
|
|
321
|
+
options: users.map((u) => ({
|
|
322
|
+
value: u.email,
|
|
323
|
+
label: `${u.email} (${u.role})`,
|
|
324
|
+
})),
|
|
325
|
+
});
|
|
326
|
+
if (p.isCancel(result)) {
|
|
320
327
|
p.cancel('Operation cancelled.');
|
|
321
328
|
process.exit(0);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
329
|
+
}
|
|
330
|
+
const user = db.findUserByEmail(result);
|
|
331
|
+
if (!user) {
|
|
332
|
+
console.error(colors.red('User not found.'));
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
targetUser = user;
|
|
327
336
|
}
|
|
328
|
-
|
|
337
|
+
// Get new password
|
|
338
|
+
let newPassword;
|
|
339
|
+
if (options.password) {
|
|
340
|
+
newPassword = options.password;
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
const result = await p.group({
|
|
344
|
+
password: () => p.password({
|
|
345
|
+
message: 'New password:',
|
|
346
|
+
validate: (value) => {
|
|
347
|
+
if (!value || value.length < 8) {
|
|
348
|
+
return 'Password must be at least 8 characters';
|
|
349
|
+
}
|
|
350
|
+
return undefined;
|
|
351
|
+
},
|
|
352
|
+
}),
|
|
353
|
+
confirmPassword: () => p.password({
|
|
354
|
+
message: 'Confirm password:',
|
|
355
|
+
}),
|
|
356
|
+
}, {
|
|
357
|
+
onCancel: () => {
|
|
358
|
+
p.cancel('Operation cancelled.');
|
|
359
|
+
process.exit(0);
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
if (result.password !== result.confirmPassword) {
|
|
363
|
+
p.cancel('Passwords do not match.');
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
newPassword = result.password;
|
|
367
|
+
}
|
|
368
|
+
// Update password
|
|
369
|
+
db.updatePassword(targetUser.id, newPassword);
|
|
370
|
+
console.log(colors.green(`Password for '${targetUser.email}' has been reset.`));
|
|
371
|
+
}
|
|
372
|
+
finally {
|
|
373
|
+
db.close();
|
|
329
374
|
}
|
|
330
|
-
// Update password
|
|
331
|
-
const passwordHash = await repo.hashPassword(newPassword);
|
|
332
|
-
user.updatePasswordHash(passwordHash);
|
|
333
|
-
await repo.save(user);
|
|
334
|
-
console.log(colors.green(`Password for '${username}' has been reset.`));
|
|
335
375
|
}
|
|
336
376
|
/**
|
|
337
377
|
* Create console user command
|
|
@@ -344,14 +384,14 @@ Examples:
|
|
|
344
384
|
$ mcctl console user list List all users
|
|
345
385
|
$ mcctl console user list --json List users in JSON format
|
|
346
386
|
$ mcctl console user add Add user (interactive)
|
|
347
|
-
$ mcctl console user add
|
|
387
|
+
$ mcctl console user add user@example.com --role admin --password "secret"
|
|
348
388
|
$ mcctl console user remove Remove user (interactive)
|
|
349
|
-
$ mcctl console user remove
|
|
350
|
-
$ mcctl console user remove
|
|
389
|
+
$ mcctl console user remove user@example.com Remove user directly
|
|
390
|
+
$ mcctl console user remove user@example.com --force Remove without confirmation
|
|
351
391
|
$ mcctl console user update Update user (interactive)
|
|
352
|
-
$ mcctl console user update
|
|
392
|
+
$ mcctl console user update user@example.com --role admin
|
|
353
393
|
$ mcctl console user reset-password Reset password (interactive)
|
|
354
|
-
$ mcctl console user reset-password
|
|
394
|
+
$ mcctl console user reset-password user@example.com
|
|
355
395
|
`);
|
|
356
396
|
// list subcommand
|
|
357
397
|
cmd
|
|
@@ -369,13 +409,13 @@ Examples:
|
|
|
369
409
|
});
|
|
370
410
|
// add subcommand
|
|
371
411
|
cmd
|
|
372
|
-
.command('add [
|
|
412
|
+
.command('add [email]')
|
|
373
413
|
.description('Add a new user')
|
|
374
|
-
.option('--role <role>', 'User role (admin,
|
|
414
|
+
.option('--role <role>', 'User role (admin, user)')
|
|
375
415
|
.option('--password <password>', 'User password')
|
|
376
|
-
.action(async (
|
|
416
|
+
.action(async (email, options) => {
|
|
377
417
|
try {
|
|
378
|
-
await addUser(
|
|
418
|
+
await addUser(email, options);
|
|
379
419
|
}
|
|
380
420
|
catch (error) {
|
|
381
421
|
console.error(colors.red(`Error: ${error.message}`));
|
|
@@ -384,12 +424,12 @@ Examples:
|
|
|
384
424
|
});
|
|
385
425
|
// remove subcommand
|
|
386
426
|
cmd
|
|
387
|
-
.command('remove [
|
|
427
|
+
.command('remove [email]')
|
|
388
428
|
.description('Remove a user')
|
|
389
429
|
.option('--force', 'Skip confirmation')
|
|
390
|
-
.action(async (
|
|
430
|
+
.action(async (email, options) => {
|
|
391
431
|
try {
|
|
392
|
-
await removeUser(
|
|
432
|
+
await removeUser(email, options);
|
|
393
433
|
}
|
|
394
434
|
catch (error) {
|
|
395
435
|
console.error(colors.red(`Error: ${error.message}`));
|
|
@@ -398,12 +438,12 @@ Examples:
|
|
|
398
438
|
});
|
|
399
439
|
// update subcommand
|
|
400
440
|
cmd
|
|
401
|
-
.command('update [
|
|
441
|
+
.command('update [email]')
|
|
402
442
|
.description('Update a user')
|
|
403
|
-
.option('--role <role>', 'New role (admin,
|
|
404
|
-
.action(async (
|
|
443
|
+
.option('--role <role>', 'New role (admin, user)')
|
|
444
|
+
.action(async (email, options) => {
|
|
405
445
|
try {
|
|
406
|
-
await updateUser(
|
|
446
|
+
await updateUser(email, options);
|
|
407
447
|
}
|
|
408
448
|
catch (error) {
|
|
409
449
|
console.error(colors.red(`Error: ${error.message}`));
|
|
@@ -412,12 +452,12 @@ Examples:
|
|
|
412
452
|
});
|
|
413
453
|
// reset-password subcommand
|
|
414
454
|
cmd
|
|
415
|
-
.command('reset-password [
|
|
455
|
+
.command('reset-password [email]')
|
|
416
456
|
.description('Reset a user password')
|
|
417
457
|
.option('--password <password>', 'New password (for non-interactive use)')
|
|
418
|
-
.action(async (
|
|
458
|
+
.action(async (email, options) => {
|
|
419
459
|
try {
|
|
420
|
-
await resetPassword(
|
|
460
|
+
await resetPassword(email, options);
|
|
421
461
|
}
|
|
422
462
|
catch (error) {
|
|
423
463
|
console.error(colors.red(`Error: ${error.message}`));
|