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 {
// 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({

View file

@ -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) {
@ -95,7 +105,7 @@ module.exports = (pool, query, authMiddleware) => {
const { id } = req.params;
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,
@ -103,8 +113,25 @@ module.exports = (pool, query, authMiddleware) => {
});
}
// Check if user exists
const userCheck = await query('SELECT * FROM users WHERE id = $1', [id]);
if (is_admin !== undefined && typeof is_admin !== 'boolean') {
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) {
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 {

View file

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