Skip to content
Closed
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f01d33a
Initial plan
Copilot Dec 23, 2025
b55e03f
Implement multi-user support with role-based access control
Copilot Dec 23, 2025
1b5027a
Fix linting issues in multi-user implementation
Copilot Dec 23, 2025
07213c4
Add comprehensive documentation for multi-user support
Copilot Dec 23, 2025
c0d2276
Simplify multi-user support: Remove role system, keep basic user mana…
Copilot Dec 23, 2025
13513b1
Delete db/knex_migrations/2025-12-23-0000-add-multi-user-support.js
KingIronMan2011 Dec 23, 2025
524bb20
Add comprehensive backend tests for user management functionality
Copilot Dec 24, 2025
7a989c3
Fix UI issues: Remove duplicate header and improve dark mode contrast
Copilot Dec 24, 2025
9b45c93
Remove user management tests to streamline codebase
KingIronMan2011 Dec 24, 2025
17a45b5
Merge branch 'copilot/add-multiple-user-support' of https://github.co…
KingIronMan2011 Dec 24, 2025
bfa6cd5
Fix dark mode table styling by following pattern from other components
Copilot Dec 24, 2025
991ebf2
Redesign Users UI to match API Keys page pattern with card-based layout
Copilot Dec 24, 2025
0013aa4
Fix button sizes by using auto width instead of fixed width
Copilot Dec 24, 2025
c0a4ff3
Auto-logout when user edits their own username
Copilot Dec 24, 2025
b1e4746
Fix user logout timing when editing own username and ensure user ID c…
KingIronMan2011 Dec 24, 2025
c458aa2
Refactor user deletion to permanently remove users instead of deactiv…
KingIronMan2011 Dec 24, 2025
b26484e
Merge pull request #1 from KingIronMan2011/copilot/add-multiple-user-…
KingIronMan2011 Dec 24, 2025
b03c5f7
Updated branch to include latest commits
KingIronMan2011 Dec 24, 2025
01135bc
Merge pull request #2 from louislam/master
KingIronMan2011 Dec 24, 2025
66515df
Linter fixing
KingIronMan2011 Dec 24, 2025
00f129a
Fixed formatting in en.json
KingIronMan2011 Dec 24, 2025
34d3dde
Initial plan
Copilot Dec 24, 2025
eb2f476
Add password validation, i18n support, delete warning, and e2e tests
Copilot Dec 24, 2025
ec93895
Fix linting error - remove trailing spaces
Copilot Dec 24, 2025
feacf46
Address code review: extract password validation, fix modal pattern, …
Copilot Dec 24, 2025
e8427ad
Address final code review: fix modal cleanup and test consistency
Copilot Dec 24, 2025
f76d3b2
Merge pull request #7 from KingIronMan2011/copilot/delete-user-cascad…
KingIronMan2011 Dec 24, 2025
77404e3
Initial plan
Copilot Dec 24, 2025
209f8ad
Fix E2E test selectors for toast and modal elements
Copilot Dec 24, 2025
3e34934
Merge pull request #8 from KingIronMan2011/copilot/format-code-and-ad…
KingIronMan2011 Dec 24, 2025
662e631
Update test/e2e/specs/user-management.spec.js
KingIronMan2011 Dec 24, 2025
cafa2e8
Update server/socket-handlers/user-management-socket-handler.js
KingIronMan2011 Dec 24, 2025
501d52e
Update test/e2e/specs/user-management.spec.js
KingIronMan2011 Dec 24, 2025
61682b0
Initial plan
Copilot Dec 24, 2025
c70e1f5
Add username validation and explicit user deletion handling
Copilot Dec 24, 2025
23440a1
Make user deletion warning message more user-friendly
Copilot Dec 24, 2025
e8ae5cf
Refactor username validation to shared utility module
Copilot Dec 24, 2025
e669522
Fix username trimming inconsistency in client-side validation
Copilot Dec 24, 2025
a022209
Merge pull request #9 from KingIronMan2011/copilot/fix-data-integrity…
KingIronMan2011 Dec 24, 2025
2ca97de
Initial plan
Copilot Dec 24, 2025
91f1fe9
Fix E2E test failures: self-deactivation and delete user warning
Copilot Dec 24, 2025
78e9b99
Fix delete user warning text to match test regex pattern
Copilot Dec 24, 2025
083b758
Improve code clarity in reserved username validation logic
Copilot Dec 24, 2025
d697a4f
Simplify reserved username validation condition
Copilot Dec 24, 2025
068015a
Merge pull request #10 from KingIronMan2011/copilot/fix-e2e-tests-use…
KingIronMan2011 Dec 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ const { dockerSocketHandler } = require("./socket-handlers/docker-socket-handler
const { maintenanceSocketHandler } = require("./socket-handlers/maintenance-socket-handler");
const { apiKeySocketHandler } = require("./socket-handlers/api-key-socket-handler");
const { generalSocketHandler } = require("./socket-handlers/general-socket-handler");
const { userManagementSocketHandler } = require("./socket-handlers/user-management-socket-handler");
const { Settings } = require("./settings");
const apicache = require("./modules/apicache");
const { resetChrome } = require("./monitor-types/real-browser-monitor-type");
Expand Down Expand Up @@ -1702,6 +1703,7 @@ let needSetup = false;
remoteBrowserSocketHandler(socket);
generalSocketHandler(socket, server);
chartSocketHandler(socket);
userManagementSocketHandler(socket, server);

log.debug("server", "added all socket handlers");

Expand Down
166 changes: 166 additions & 0 deletions server/socket-handlers/user-management-socket-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
const { checkLogin } = require("../util-server");
const { R } = require("redbean-node");
const passwordHash = require("../password-hash");
const { log } = require("../../src/util");

/**
* Handlers for user management
* @param {Socket} socket Socket.io instance
* @param {UptimeKumaServer} server Uptime Kuma server
* @returns {void}
*/
module.exports.userManagementSocketHandler = (socket, server) => {

// Get all users
socket.on("getUsers", async (callback) => {
try {
checkLogin(socket);
const users = await R.findAll("user");
const userList = users.map(user => ({
id: user.id,
username: user.username,
active: user.active,
timezone: user.timezone,
}));
callback({
ok: true,
users: userList,
});
} catch (e) {
log.error("user-management", e.message);
callback({
ok: false,
msg: e.message,
});
}
});
Comment on lines +29 to +50
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical security issue: The user management endpoints lack proper authorization checks. Currently, ANY logged-in user can view, create, modify, and delete ALL other users. This allows any user to:

  1. View all usernames in the system
  2. Create new users
  3. Change other users' passwords
  4. Deactivate or delete other users
  5. Escalate privileges by creating new accounts

The checkLogin function only verifies that a user is authenticated, not that they have admin/elevated privileges. You need to implement authorization to restrict user management operations to admin users only. Consider adding a role/permission system or at minimum, an is_admin flag in the user table, and check it before allowing these operations.

Copilot uses AI. Check for mistakes.

// Add a new user
socket.on("addUser", async (userData, callback) => {
try {
checkLogin(socket);

// Validate input
if (!userData.username || !userData.password) {
throw new Error("Username and password are required");
}

// Check if username already exists
const existingUser = await R.findOne("user", " username = ? ", [ userData.username.trim() ]);
if (existingUser) {
throw new Error("Username already exists");
}

// Create new user
const user = R.dispense("user");
user.username = userData.username.trim();
user.password = await passwordHash.generate(userData.password);
user.active = 1;

await R.store(user);

log.info("user-management", `User ${user.username} created by user ${socket.userID}`);

callback({
ok: true,
msg: "User created successfully",
userId: user.id,
});
} catch (e) {
log.error("user-management", e.message);
callback({
ok: false,
msg: e.message,
});
}
});

// Update user
socket.on("updateUser", async (userId, userData, callback) => {
try {
checkLogin(socket);

const user = await R.findOne("user", " id = ? ", [ userId ]);
if (!user) {
throw new Error("User not found");
}

// Check if user is editing their own username
const isEditingSelf = Number(userId) === Number(socket.userID);
const usernameChanged = userData.username && userData.username.trim() !== user.username;

// Update user fields
if (userData.username && userData.username.trim() !== user.username) {
// Check if new username already exists
const existingUser = await R.findOne("user", " username = ? AND id != ? ", [
userData.username.trim(),
userId
]);
if (existingUser) {
throw new Error("Username already exists");
}
user.username = userData.username.trim();
}

if (typeof userData.active !== "undefined") {
user.active = userData.active ? 1 : 0;
}

// Update password if provided
if (userData.password) {
user.password = await passwordHash.generate(userData.password);
}

await R.store(user);

log.info("user-management", `User ${user.username} updated by user ${socket.userID}`);

callback({
ok: true,
msg: "User updated successfully",
requiresLogout: isEditingSelf && usernameChanged,
});
} catch (e) {
log.error("user-management", e.message);
callback({
ok: false,
msg: e.message,
});
}
});

// Delete user
socket.on("deleteUser", async (userId, callback) => {
try {
checkLogin(socket);

const user = await R.findOne("user", " id = ? ", [ userId ]);
if (!user) {
throw new Error("User not found");
}

// Don't allow deleting yourself
if (Number(user.id) === Number(socket.userID)) {
throw new Error("Cannot delete your own account");
}

// Permanently remove the user from the database
await R.trash(user);

// Disconnect all socket connections for this user
server.disconnectAllSocketClients(userId);

log.info("user-management", `User ${user.username} deleted by user ${socket.userID}`);

callback({
ok: true,
msg: "User deleted successfully",
});
} catch (error) {
callback({
ok: false,
msg: error.message,
});
}
});
};
Loading
Loading