feat: add super-admin role with admin impersonation support
Add a new super-admin role that can impersonate other admins. Regular admins retain all existing permissions but cannot impersonate other admins or promote users to super-admin. Backend changes: - Add isSuperAdmin field to users table with default false - Add isSuperAdmin() check function to auth service - Update JWT tokens to include isSuperAdmin claim - Allow super-admins to impersonate other admins - Add security rules for super-admin role changes Frontend changes: - Display "Super Admin" badge with gradient styling - Add "Super Admin" option to role change modal - Enable impersonate button for super-admins targeting admins - Add "Super Admins Only" filter option Security rules: - Only super-admins can promote/demote super-admins - Regular admins cannot promote users to super-admin - Super-admins cannot demote themselves - Cannot demote the last super-admin Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -225,6 +225,7 @@ const app = new Elysia()
|
||||
email: payload.email,
|
||||
callsign: payload.callsign,
|
||||
isAdmin: payload.isAdmin,
|
||||
isSuperAdmin: payload.isSuperAdmin,
|
||||
impersonatedBy: payload.impersonatedBy, // Admin ID if impersonating
|
||||
},
|
||||
isImpersonation,
|
||||
@@ -360,6 +361,8 @@ const app = new Elysia()
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
callsign: user.callsign,
|
||||
isAdmin: user.isAdmin,
|
||||
isSuperAdmin: user.isSuperAdmin,
|
||||
exp,
|
||||
});
|
||||
|
||||
@@ -429,6 +432,7 @@ const app = new Elysia()
|
||||
email: user.email,
|
||||
callsign: user.callsign,
|
||||
isAdmin: user.isAdmin,
|
||||
isSuperAdmin: user.isSuperAdmin,
|
||||
exp,
|
||||
});
|
||||
|
||||
@@ -1209,7 +1213,7 @@ const app = new Elysia()
|
||||
|
||||
/**
|
||||
* POST /api/admin/users/:userId/role
|
||||
* Update user admin status (admin only)
|
||||
* Update user role (admin only)
|
||||
*/
|
||||
.post('/api/admin/users/:userId/role', async ({ user, params, body, set }) => {
|
||||
if (!user || !user.isAdmin) {
|
||||
@@ -1223,21 +1227,27 @@ const app = new Elysia()
|
||||
return { success: false, error: 'Invalid user ID' };
|
||||
}
|
||||
|
||||
const { isAdmin } = body;
|
||||
const { role } = body;
|
||||
|
||||
if (typeof isAdmin !== 'boolean') {
|
||||
if (typeof role !== 'string') {
|
||||
set.status = 400;
|
||||
return { success: false, error: 'isAdmin (boolean) is required' };
|
||||
return { success: false, error: 'role (string) is required' };
|
||||
}
|
||||
|
||||
const validRoles = ['user', 'admin', 'super-admin'];
|
||||
if (!validRoles.includes(role)) {
|
||||
set.status = 400;
|
||||
return { success: false, error: `Invalid role. Must be one of: ${validRoles.join(', ')}` };
|
||||
}
|
||||
|
||||
try {
|
||||
await changeUserRole(user.id, targetUserId, isAdmin);
|
||||
await changeUserRole(user.id, targetUserId, role);
|
||||
return {
|
||||
success: true,
|
||||
message: 'User admin status updated successfully',
|
||||
message: 'User role updated successfully',
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Error updating user admin status', { error: error.message, userId: user.id });
|
||||
logger.error('Error updating user role', { error: error.message, userId: user.id });
|
||||
set.status = 400;
|
||||
return {
|
||||
success: false,
|
||||
@@ -1304,6 +1314,7 @@ const app = new Elysia()
|
||||
email: targetUser.email,
|
||||
callsign: targetUser.callsign,
|
||||
isAdmin: targetUser.isAdmin,
|
||||
isSuperAdmin: targetUser.isSuperAdmin,
|
||||
impersonatedBy: user.id, // Admin ID who started impersonation
|
||||
exp,
|
||||
});
|
||||
@@ -1364,6 +1375,7 @@ const app = new Elysia()
|
||||
email: adminUser.email,
|
||||
callsign: adminUser.callsign,
|
||||
isAdmin: adminUser.isAdmin,
|
||||
isSuperAdmin: adminUser.isSuperAdmin,
|
||||
exp,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user