added security prevent no nsuper admins editing super admins
This commit is contained in:
parent
b02bbb2086
commit
6312bd3da2
3 changed files with 100 additions and 37 deletions
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Reference in a new issue