diff --git a/backend/src/middleware/auth.js b/backend/src/middleware/auth.js index 2b75427..4f22243 100644 --- a/backend/src/middleware/auth.js +++ b/backend/src/middleware/auth.js @@ -15,10 +15,18 @@ module.exports = (pool, query) => { try { // Verify API key - const result = await query( - 'SELECT id, email, first_name, last_name, is_admin FROM users WHERE api_key = $1', - [apiKey] - ); + + const result = await query(` + SELECT + u.*, + CASE WHEN s.user_id IS NOT NULL THEN TRUE ELSE FALSE END AS is_super_admin + FROM + users u + LEFT JOIN + superadmins s ON u.id = s.user_id + WHERE + u.api_key = $1 + `, [apiKey]); if (result.rows.length === 0) { return res.status(401).json({ diff --git a/backend/src/routes/userAdmin.js b/backend/src/routes/userAdmin.js index 3735d5b..c6e211f 100644 --- a/backend/src/routes/userAdmin.js +++ b/backend/src/routes/userAdmin.js @@ -29,17 +29,22 @@ module.exports = (pool, query, authMiddleware) => { const result = await query(` SELECT - id, - email, - first_name, - last_name, - is_admin, - is_disabled, - internal_notes, - created_at, - last_login - FROM users - ORDER BY last_login DESC NULLS LAST + u.id, + u.email, + u.first_name, + u.last_name, + u.is_admin, + u.is_disabled, + u.internal_notes, + u.created_at, + u.last_login, + CASE WHEN s.user_id IS NOT NULL THEN TRUE ELSE FALSE END AS is_super_admin + FROM + users u + LEFT JOIN + superadmins s ON u.id = s.user_id + ORDER BY + u.last_login DESC NULLS LAST `); res.json(result.rows); @@ -63,17 +68,22 @@ module.exports = (pool, query, authMiddleware) => { const result = await query(` SELECT - id, - email, - first_name, - last_name, - is_admin, - is_disabled, - internal_notes, - created_at, - last_login - FROM users - WHERE id = $1 + u.id, + u.email, + u.first_name, + u.last_name, + u.is_admin, + u.is_disabled, + u.internal_notes, + u.created_at, + u.last_login, + CASE WHEN s.user_id IS NOT NULL THEN TRUE ELSE FALSE END AS is_super_admin + FROM + users u + LEFT JOIN + superadmins s ON u.id = s.user_id + WHERE + u.id = $1 `, [id]); if (result.rows.length === 0) { @@ -93,18 +103,35 @@ module.exports = (pool, query, authMiddleware) => { router.patch('/:id', async (req, res, next) => { try { const { id } = req.params; - const { is_disabled, internal_notes, is_admin} = req.body; - + const { is_disabled, internal_notes, is_admin } = req.body; + // Verify admin status from middleware if (!req.user.is_admin) { return res.status(403).json({ error: true, message: 'Admin access required' }); + } + + if (is_admin !== undefined && typeof is_admin !== 'boolean') { + return res.status(400).json({ + error: true, + message: 'is_admin must be a boolean' + }); } - // Check if user exists - const userCheck = await query('SELECT * FROM users WHERE id = $1', [id]); + // 4. Check if user exists and get their details (including superadmin status) + const userCheck = await query(` + SELECT + u.*, + CASE WHEN s.user_id IS NOT NULL THEN TRUE ELSE FALSE END AS is_super_admin + FROM + users u + LEFT JOIN + superadmins s ON u.id = s.user_id + WHERE + u.id = $1 + `, [id]); if (userCheck.rows.length === 0) { return res.status(404).json({ @@ -113,19 +140,39 @@ module.exports = (pool, query, authMiddleware) => { }); } - // Update only allowed fields + const targetUser = userCheck.rows[0]; + + // - If target user is a superadmin, only allow self-editing + if (targetUser.is_super_admin && req.user.id !== targetUser.id) { + return res.status(403).json({ + error: true, + message: 'Superadmin accounts can only be edited by themselves' + }); + } + const result = await query(` UPDATE users SET is_disabled = $1, internal_notes = $2, - is_admin = $3 + is_admin = CASE + WHEN EXISTS (SELECT 1 FROM superadmins WHERE user_id = $4) THEN TRUE + ELSE $3 + END WHERE id = $4 - RETURNING id, email, first_name, last_name, is_admin, is_disabled, internal_notes + RETURNING + id, + email, + first_name, + last_name, + is_admin, + is_disabled, + internal_notes, + (SELECT CASE WHEN COUNT(*) > 0 THEN TRUE ELSE FALSE END FROM superadmins WHERE user_id = $4) AS is_super_admin `, [ - is_disabled !== undefined ? is_disabled : userCheck.rows[0].is_disabled, - internal_notes !== undefined ? internal_notes : userCheck.rows[0].internal_notes, - is_admin !== undefined ? is_admin : userCheck.rows[0].is_admin, + is_disabled !== undefined ? is_disabled : targetUser.is_disabled, + internal_notes !== undefined ? internal_notes : targetUser.internal_notes, + is_admin !== undefined ? is_admin : targetUser.is_admin, id ]); @@ -134,10 +181,10 @@ module.exports = (pool, query, authMiddleware) => { user: result.rows[0] }); } catch (error) { + // Pass to error handler middleware next(error); } }); - // Send email to user router.post('/send-email', async (req, res, next) => { try { diff --git a/frontend/src/pages/Admin/CustomersPage.jsx b/frontend/src/pages/Admin/CustomersPage.jsx index 88f4a38..67b666a 100644 --- a/frontend/src/pages/Admin/CustomersPage.jsx +++ b/frontend/src/pages/Admin/CustomersPage.jsx @@ -227,6 +227,14 @@ const AdminCustomersPage = () => { + )} + {user.is_super_admin && ( +