added security prevent no nsuper admins editing super admins

This commit is contained in:
2ManyProjects 2025-05-10 09:59:29 -05:00
parent b02bbb2086
commit 6312bd3da2
3 changed files with 100 additions and 37 deletions

View file

@ -15,10 +15,18 @@ module.exports = (pool, query) => {
try { try {
// Verify API key // Verify API key
const result = await query(
'SELECT id, email, first_name, last_name, is_admin FROM users WHERE api_key = $1', const result = await query(`
[apiKey] 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) { if (result.rows.length === 0) {
return res.status(401).json({ return res.status(401).json({

View file

@ -29,17 +29,22 @@ module.exports = (pool, query, authMiddleware) => {
const result = await query(` const result = await query(`
SELECT SELECT
id, u.id,
email, u.email,
first_name, u.first_name,
last_name, u.last_name,
is_admin, u.is_admin,
is_disabled, u.is_disabled,
internal_notes, u.internal_notes,
created_at, u.created_at,
last_login u.last_login,
FROM users CASE WHEN s.user_id IS NOT NULL THEN TRUE ELSE FALSE END AS is_super_admin
ORDER BY last_login DESC NULLS LAST 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); res.json(result.rows);
@ -63,17 +68,22 @@ module.exports = (pool, query, authMiddleware) => {
const result = await query(` const result = await query(`
SELECT SELECT
id, u.id,
email, u.email,
first_name, u.first_name,
last_name, u.last_name,
is_admin, u.is_admin,
is_disabled, u.is_disabled,
internal_notes, u.internal_notes,
created_at, u.created_at,
last_login u.last_login,
FROM users CASE WHEN s.user_id IS NOT NULL THEN TRUE ELSE FALSE END AS is_super_admin
WHERE id = $1 FROM
users u
LEFT JOIN
superadmins s ON u.id = s.user_id
WHERE
u.id = $1
`, [id]); `, [id]);
if (result.rows.length === 0) { if (result.rows.length === 0) {
@ -95,7 +105,7 @@ module.exports = (pool, query, authMiddleware) => {
const { id } = req.params; 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) { if (!req.user.is_admin) {
return res.status(403).json({ return res.status(403).json({
error: true, error: true,
@ -103,8 +113,25 @@ module.exports = (pool, query, authMiddleware) => {
}); });
} }
// Check if user exists if (is_admin !== undefined && typeof is_admin !== 'boolean') {
const userCheck = await query('SELECT * FROM users WHERE id = $1', [id]); return res.status(400).json({
error: true,
message: 'is_admin must be a boolean'
});
}
// 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) { if (userCheck.rows.length === 0) {
return res.status(404).json({ 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(` const result = await query(`
UPDATE users UPDATE users
SET SET
is_disabled = $1, is_disabled = $1,
internal_notes = $2, 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 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, is_disabled !== undefined ? is_disabled : targetUser.is_disabled,
internal_notes !== undefined ? internal_notes : userCheck.rows[0].internal_notes, internal_notes !== undefined ? internal_notes : targetUser.internal_notes,
is_admin !== undefined ? is_admin : userCheck.rows[0].is_admin, is_admin !== undefined ? is_admin : targetUser.is_admin,
id id
]); ]);
@ -134,10 +181,10 @@ module.exports = (pool, query, authMiddleware) => {
user: result.rows[0] user: result.rows[0]
}); });
} catch (error) { } catch (error) {
// Pass to error handler middleware
next(error); next(error);
} }
}); });
// Send email to user // Send email to user
router.post('/send-email', async (req, res, next) => { router.post('/send-email', async (req, res, next) => {
try { try {

View file

@ -227,6 +227,14 @@ const AdminCustomersPage = () => {
<Chip <Chip
size="small" size="small"
label="Admin" label="Admin"
color="secondary"
sx={{ ml: 1 }}
/>
)}
{user.is_super_admin && (
<Chip
size="small"
label="Super Admin"
color="primary" color="primary"
sx={{ ml: 1 }} sx={{ ml: 1 }}
/> />