From 7938c50762284d56b9bcc0a32b1e38b1d043db37 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 1 Sep 2025 18:46:59 -0400 Subject: [PATCH] Add rocket chat user id field and show messages from linked user id on non-admin accounts --- inventory-server/auth/routes.js | 35 ++-- inventory-server/auth/server.js | 3 +- .../src/components/settings/UserForm.tsx | 115 +++++++++++++- inventory/src/contexts/AuthContext.tsx | 2 + inventory/src/pages/Chat.tsx | 150 +++++++++++++----- 5 files changed, 256 insertions(+), 49 deletions(-) diff --git a/inventory-server/auth/routes.js b/inventory-server/auth/routes.js index 3d7ef62..ecec280 100644 --- a/inventory-server/auth/routes.js +++ b/inventory-server/auth/routes.js @@ -34,10 +34,12 @@ const authenticate = async (req, res, next) => { // Get user from database const result = await pool.query( - 'SELECT id, username, is_admin FROM users WHERE id = $1', + 'SELECT id, username, email, is_admin, rocket_chat_user_id FROM users WHERE id = $1', [decoded.userId] ); + console.log('Database query result for user', decoded.userId, ':', result.rows[0]); + if (result.rows.length === 0) { return res.status(401).json({ error: 'User not found' }); } @@ -58,7 +60,7 @@ router.post('/login', async (req, res) => { // Get user from database const result = await pool.query( - 'SELECT id, username, password, is_admin, is_active FROM users WHERE username = $1', + 'SELECT id, username, password, is_admin, is_active, rocket_chat_user_id FROM users WHERE username = $1', [username] ); @@ -101,6 +103,7 @@ router.post('/login', async (req, res) => { id: user.id, username: user.username, is_admin: user.is_admin, + rocket_chat_user_id: user.rocket_chat_user_id, permissions } }); @@ -119,8 +122,13 @@ router.get('/me', authenticate, async (req, res) => { res.json({ id: req.user.id, username: req.user.username, + email: req.user.email, is_admin: req.user.is_admin, - permissions + rocket_chat_user_id: req.user.rocket_chat_user_id, + permissions, + // Debug info + _debug_raw_user: req.user, + _server_identifier: "INVENTORY_AUTH_SERVER_MODIFIED" }); } catch (error) { console.error('Error getting current user:', error); @@ -132,7 +140,7 @@ router.get('/me', authenticate, async (req, res) => { router.get('/users', authenticate, requirePermission('view:users'), async (req, res) => { try { const result = await pool.query(` - SELECT id, username, email, is_admin, is_active, created_at, last_login + SELECT id, username, email, is_admin, is_active, rocket_chat_user_id, created_at, last_login FROM users ORDER BY username `); @@ -151,7 +159,7 @@ router.get('/users/:id', authenticate, requirePermission('view:users'), async (r // Get user details const userResult = await pool.query(` - SELECT id, username, email, is_admin, is_active, created_at, last_login + SELECT id, username, email, is_admin, is_active, rocket_chat_user_id, created_at, last_login FROM users WHERE id = $1 `, [userId]); @@ -187,13 +195,14 @@ router.post('/users', authenticate, requirePermission('create:users'), async (re const client = await pool.connect(); try { - const { username, email, password, is_admin, is_active, permissions } = req.body; + const { username, email, password, is_admin, is_active, rocket_chat_user_id, permissions } = req.body; console.log("Create user request:", { username, email, is_admin, is_active, + rocket_chat_user_id, permissions: permissions || [] }); @@ -221,10 +230,10 @@ router.post('/users', authenticate, requirePermission('create:users'), async (re // Insert new user const userResult = await client.query(` - INSERT INTO users (username, email, password, is_admin, is_active, created_at) - VALUES ($1, $2, $3, $4, $5, CURRENT_TIMESTAMP) + INSERT INTO users (username, email, password, is_admin, is_active, rocket_chat_user_id, created_at) + VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP) RETURNING id - `, [username, email || null, hashedPassword, !!is_admin, is_active !== false]); + `, [username, email || null, hashedPassword, !!is_admin, is_active !== false, rocket_chat_user_id || null]); const userId = userResult.rows[0].id; @@ -299,7 +308,7 @@ router.put('/users/:id', authenticate, requirePermission('edit:users'), async (r try { const userId = req.params.id; - const { username, email, password, is_admin, is_active, permissions } = req.body; + const { username, email, password, is_admin, is_active, rocket_chat_user_id, permissions } = req.body; console.log("Update user request:", { userId, @@ -307,6 +316,7 @@ router.put('/users/:id', authenticate, requirePermission('edit:users'), async (r email, is_admin, is_active, + rocket_chat_user_id, permissions: permissions || [] }); @@ -348,6 +358,11 @@ router.put('/users/:id', authenticate, requirePermission('edit:users'), async (r updateValues.push(!!is_active); } + if (rocket_chat_user_id !== undefined) { + updateFields.push(`rocket_chat_user_id = $${paramIndex++}`); + updateValues.push(rocket_chat_user_id || null); + } + // Update password if provided if (password) { const saltRounds = 10; diff --git a/inventory-server/auth/server.js b/inventory-server/auth/server.js index d77cf72..d2c048b 100644 --- a/inventory-server/auth/server.js +++ b/inventory-server/auth/server.js @@ -108,7 +108,7 @@ app.get('/me', async (req, res) => { // Get user details from database const userResult = await pool.query( - 'SELECT id, username, email, is_admin, is_active FROM users WHERE id = $1', + 'SELECT id, username, email, is_admin, rocket_chat_user_id, is_active FROM users WHERE id = $1', [decoded.userId] ); @@ -135,6 +135,7 @@ app.get('/me', async (req, res) => { id: user.id, username: user.username, email: user.email, + rocket_chat_user_id: user.rocket_chat_user_id, is_admin: user.is_admin, permissions: permissions }); diff --git a/inventory/src/components/settings/UserForm.tsx b/inventory/src/components/settings/UserForm.tsx index b6f8207..ec949fd 100644 --- a/inventory/src/components/settings/UserForm.tsx +++ b/inventory/src/components/settings/UserForm.tsx @@ -14,8 +14,11 @@ import { import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { PermissionSelector } from "./PermissionSelector"; import { Alert, AlertDescription } from "@/components/ui/alert"; +import config from "@/config"; interface Permission { id: number; @@ -25,12 +28,22 @@ interface Permission { category?: string; } +interface RocketChatUser { + id: number; + username: string; + name: string; + type: string; + active: boolean; + mongo_id?: string; +} + interface User { id: number; username: string; email?: string; is_admin: boolean; is_active: boolean; + rocket_chat_user_id?: string; permissions?: Permission[]; } @@ -53,6 +66,7 @@ const userFormSchema = z.object({ password: z.string().min(6, { message: "Password must be at least 6 characters" }).optional().or(z.literal("")), is_admin: z.boolean().default(false), is_active: z.boolean().default(true), + rocket_chat_user_id: z.string().optional(), }); type FormValues = z.infer; @@ -80,12 +94,15 @@ interface UserSaveData { password?: string; is_admin: boolean; is_active: boolean; + rocket_chat_user_id?: string; permissions: Permission[]; } export function UserForm({ user, permissions, onSave, onCancel }: UserFormProps) { const [selectedPermissions, setSelectedPermissions] = useState([]); const [formError, setFormError] = useState(null); + const [rocketChatUsers, setRocketChatUsers] = useState([]); + const [loadingRocketChatUsers, setLoadingRocketChatUsers] = useState(true); // Initialize the form with React Hook Form const form = useForm({ @@ -96,10 +113,33 @@ export function UserForm({ user, permissions, onSave, onCancel }: UserFormProps) password: "", // Don't pre-fill password is_admin: user?.is_admin || false, is_active: user?.is_active !== false, + rocket_chat_user_id: user?.rocket_chat_user_id || "none", }, }); - // Initialize selected permissions + // Fetch Rocket Chat users + useEffect(() => { + const fetchRocketChatUsers = async () => { + try { + const response = await fetch(`${config.chatUrl}/users`); + const data = await response.json(); + + if (data.status === 'success') { + setRocketChatUsers(data.users); + } else { + console.error('Failed to fetch Rocket Chat users:', data.error); + } + } catch (error) { + console.error('Error fetching Rocket Chat users:', error); + } finally { + setLoadingRocketChatUsers(false); + } + }; + + fetchRocketChatUsers(); + }, []); + + // Initialize selected permissions and form values useEffect(() => { console.log("User permissions:", user?.permissions); @@ -112,7 +152,19 @@ export function UserForm({ user, permissions, onSave, onCancel }: UserFormProps) console.log("No permissions found or empty permissions array"); setSelectedPermissions([]); } - }, [user]); + + // Update form values when user data changes + if (user) { + form.reset({ + username: user.username || "", + email: user.email || "", + password: "", // Don't pre-fill password + is_admin: user.is_admin || false, + is_active: user.is_active !== false, + rocket_chat_user_id: user.rocket_chat_user_id || "none", + }); + } + }, [user, form]); // Handle form submission const onSubmit = (data: FormValues) => { @@ -130,6 +182,7 @@ export function UserForm({ user, permissions, onSave, onCancel }: UserFormProps) const userData: UserSaveData = { ...data, id: user?.id, // Include ID if editing existing user + rocket_chat_user_id: data.is_admin ? undefined : (data.rocket_chat_user_id === "none" ? undefined : data.rocket_chat_user_id), permissions: [] // Initialize with empty array }; @@ -220,6 +273,64 @@ export function UserForm({ user, permissions, onSave, onCancel }: UserFormProps) )} /> + ( + + Rocket Chat User + + {form.watch("is_admin") ? ( +
+ + Admin users have access to all chat rooms by default + +
+ ) : ( + + )} +
+ + {form.watch("is_admin") + ? "Admin users can view all chat rooms regardless of this setting" + : "Connect this user to a Rocket Chat account to control their chat access" + } + + +
+ )} + /> + ([]); const [selectedUserId, setSelectedUserId] = useState(''); const [selectedRoomId, setSelectedRoomId] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [userRocketChatId, setUserRocketChatId] = useState(null); // Global search state const [globalSearchQuery, setGlobalSearchQuery] = useState(''); @@ -51,6 +54,12 @@ export function Chat() { const [showSearchResults, setShowSearchResults] = useState(false); const [searching, setSearching] = useState(false); + useEffect(() => { + if (currentUser) { + setUserRocketChatId(currentUser.rocket_chat_user_id || null); + } + }, [currentUser]); + useEffect(() => { const fetchUsers = async () => { try { @@ -59,6 +68,25 @@ export function Chat() { if (data.status === 'success') { setUsers(data.users); + + // Auto-select user based on permissions + if (currentUser && !currentUser.is_admin && userRocketChatId) { + console.log('Attempting to auto-select user:', { + currentUser: currentUser.username, + userRocketChatId, + userRocketChatIdType: typeof userRocketChatId, + availableUsers: data.users.map((u: User) => ({ id: u.id, idType: typeof u.id })) + }); + + // Non-admin users should only see their connected rocket chat user + const userRocketChatUser = data.users.find((user: User) => user.id.toString() === userRocketChatId?.toString()); + console.log('Found matching user:', userRocketChatUser); + + if (userRocketChatUser) { + setSelectedUserId(userRocketChatUser.id.toString()); + console.log('Auto-selected user ID:', userRocketChatUser.id.toString()); + } + } } else { throw new Error(data.error || 'Failed to fetch users'); } @@ -70,14 +98,19 @@ export function Chat() { } }; - fetchUsers(); - }, []); + if (currentUser) { + fetchUsers(); + } + }, [currentUser, userRocketChatId]); const handleUserChange = (userId: string) => { - setSelectedUserId(userId); - setSelectedRoomId(null); // Reset room selection when user changes - setGlobalSearchQuery(''); // Clear search when user changes - setShowSearchResults(false); + // Only allow admins to change users, or if the user is selecting their own connected account + if (currentUser?.is_admin || userId === userRocketChatId) { + setSelectedUserId(userId); + setSelectedRoomId(null); // Reset room selection when user changes + setGlobalSearchQuery(''); // Clear search when user changes + setShowSearchResults(false); + } }; const handleRoomSelect = (roomId: string) => { @@ -181,32 +214,66 @@ export function Chat() { )} - +{currentUser?.is_admin ? ( + + ) : ( +
+ {selectedUserId && users.length > 0 ? ( + <> + {(() => { + const selectedUser = users.find(u => u.id.toString() === selectedUserId); + return selectedUser ? ( + <> + + + + {(selectedUser.name || selectedUser.username).charAt(0).toUpperCase()} + + + + Viewing as: {selectedUser.name || selectedUser.username} + + + ) : ( + No connected Rocket Chat user + ); + })()} + + ) : ( + + {userRocketChatId ? 'Loading...' : 'No connected Rocket Chat user'} + + )} +
+ )} @@ -233,9 +300,20 @@ export function Chat() { ) : ( -

- Select a user to view their chat rooms and messages. -

+ {currentUser?.is_admin ? ( +

+ Select a user to view their chat rooms and messages. +

+ ) : ( +
+

+ No Rocket Chat user connected to your account. +

+

+ Please contact your administrator to connect your account with a Rocket Chat user. +

+
+ )}
)}