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 {
|
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({
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
@ -93,18 +103,35 @@ module.exports = (pool, query, authMiddleware) => {
|
||||||
router.patch('/:id', async (req, res, next) => {
|
router.patch('/:id', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
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,
|
||||||
message: 'Admin access required'
|
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
|
// 4. Check if user exists and get their details (including superadmin status)
|
||||||
const userCheck = await query('SELECT * FROM users WHERE id = $1', [id]);
|
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 {
|
||||||
|
|
|
||||||
|
|
@ -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 }}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue