-
-
Notifications
You must be signed in to change notification settings - Fork 7.3k
[Feature] Allow multiple users #6521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
KingIronMan2011
wants to merge
45
commits into
louislam:master
from
KingIronMan2011:feature/allow-multiple-users
Closed
Changes from 20 commits
Commits
Show all changes
45 commits
Select commit
Hold shift + click to select a range
f01d33a
Initial plan
Copilot b55e03f
Implement multi-user support with role-based access control
Copilot 1b5027a
Fix linting issues in multi-user implementation
Copilot 07213c4
Add comprehensive documentation for multi-user support
Copilot c0d2276
Simplify multi-user support: Remove role system, keep basic user mana…
Copilot 13513b1
Delete db/knex_migrations/2025-12-23-0000-add-multi-user-support.js
KingIronMan2011 524bb20
Add comprehensive backend tests for user management functionality
Copilot 7a989c3
Fix UI issues: Remove duplicate header and improve dark mode contrast
Copilot 9b45c93
Remove user management tests to streamline codebase
KingIronMan2011 17a45b5
Merge branch 'copilot/add-multiple-user-support' of https://github.co…
KingIronMan2011 bfa6cd5
Fix dark mode table styling by following pattern from other components
Copilot 991ebf2
Redesign Users UI to match API Keys page pattern with card-based layout
Copilot 0013aa4
Fix button sizes by using auto width instead of fixed width
Copilot c0a4ff3
Auto-logout when user edits their own username
Copilot b1e4746
Fix user logout timing when editing own username and ensure user ID c…
KingIronMan2011 c458aa2
Refactor user deletion to permanently remove users instead of deactiv…
KingIronMan2011 b26484e
Merge pull request #1 from KingIronMan2011/copilot/add-multiple-user-…
KingIronMan2011 b03c5f7
Updated branch to include latest commits
KingIronMan2011 01135bc
Merge pull request #2 from louislam/master
KingIronMan2011 66515df
Linter fixing
KingIronMan2011 00f129a
Fixed formatting in en.json
KingIronMan2011 34d3dde
Initial plan
Copilot eb2f476
Add password validation, i18n support, delete warning, and e2e tests
Copilot ec93895
Fix linting error - remove trailing spaces
Copilot feacf46
Address code review: extract password validation, fix modal pattern, …
Copilot e8427ad
Address final code review: fix modal cleanup and test consistency
Copilot f76d3b2
Merge pull request #7 from KingIronMan2011/copilot/delete-user-cascad…
KingIronMan2011 77404e3
Initial plan
Copilot 209f8ad
Fix E2E test selectors for toast and modal elements
Copilot 3e34934
Merge pull request #8 from KingIronMan2011/copilot/format-code-and-ad…
KingIronMan2011 662e631
Update test/e2e/specs/user-management.spec.js
KingIronMan2011 cafa2e8
Update server/socket-handlers/user-management-socket-handler.js
KingIronMan2011 501d52e
Update test/e2e/specs/user-management.spec.js
KingIronMan2011 61682b0
Initial plan
Copilot c70e1f5
Add username validation and explicit user deletion handling
Copilot 23440a1
Make user deletion warning message more user-friendly
Copilot e8ae5cf
Refactor username validation to shared utility module
Copilot e669522
Fix username trimming inconsistency in client-side validation
Copilot a022209
Merge pull request #9 from KingIronMan2011/copilot/fix-data-integrity…
KingIronMan2011 2ca97de
Initial plan
Copilot 91f1fe9
Fix E2E test failures: self-deactivation and delete user warning
Copilot 78e9b99
Fix delete user warning text to match test regex pattern
Copilot 083b758
Improve code clarity in reserved username validation logic
Copilot d697a4f
Simplify reserved username validation condition
Copilot 068015a
Merge pull request #10 from KingIronMan2011/copilot/fix-e2e-tests-use…
KingIronMan2011 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
166 changes: 166 additions & 0 deletions
166
server/socket-handlers/user-management-socket-handler.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | ||
| }); | ||
| } | ||
| }); | ||
|
|
||
| // 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"); | ||
| } | ||
KingIronMan2011 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Create new user | ||
| const user = R.dispense("user"); | ||
| user.username = userData.username.trim(); | ||
| user.password = await passwordHash.generate(userData.password); | ||
KingIronMan2011 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| user.active = 1; | ||
KingIronMan2011 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| await R.store(user); | ||
|
|
||
| log.info("user-management", `User ${user.username} created by user ${socket.userID}`); | ||
|
|
||
| callback({ | ||
| ok: true, | ||
| msg: "User created successfully", | ||
KingIronMan2011 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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") { | ||
KingIronMan2011 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| user.active = userData.active ? 1 : 0; | ||
| } | ||
|
|
||
| // Update password if provided | ||
| if (userData.password) { | ||
| user.password = await passwordHash.generate(userData.password); | ||
| } | ||
KingIronMan2011 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| await R.store(user); | ||
|
|
||
| log.info("user-management", `User ${user.username} updated by user ${socket.userID}`); | ||
|
|
||
| callback({ | ||
| ok: true, | ||
| msg: "User updated successfully", | ||
KingIronMan2011 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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"); | ||
KingIronMan2011 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| // Permanently remove the user from the database | ||
| await R.trash(user); | ||
KingIronMan2011 marked this conversation as resolved.
Show resolved
Hide resolved
KingIronMan2011 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // 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", | ||
KingIronMan2011 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }); | ||
| } catch (error) { | ||
| callback({ | ||
| ok: false, | ||
| msg: error.message, | ||
KingIronMan2011 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
KingIronMan2011 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }); | ||
| } | ||
| }); | ||
| }; | ||
KingIronMan2011 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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:
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.