fix: admin action log and impersonation improvements
- Fix admin action log not displaying entries (use raw sqlite for self-join) - Add global impersonation banner to all pages during impersonation - Fix timestamp display in action log (convert Unix seconds to milliseconds) - Add loginWithToken method to auth store for direct token authentication - Fix /api/auth/me to include impersonatedBy field from JWT - Remove duplicate impersonation code from admin page Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
import {
|
||||
getSystemStats,
|
||||
getUserStats,
|
||||
getAdminActions,
|
||||
impersonateUser,
|
||||
verifyImpersonation,
|
||||
stopImpersonation,
|
||||
@@ -434,9 +435,15 @@ const app = new Elysia()
|
||||
return { success: false, error: 'User not found' };
|
||||
}
|
||||
|
||||
// Include impersonatedBy from JWT if present (not stored in database)
|
||||
const responseUser = {
|
||||
...userData,
|
||||
impersonatedBy: user.impersonatedBy,
|
||||
};
|
||||
|
||||
return {
|
||||
success: true,
|
||||
user: userData,
|
||||
user: responseUser,
|
||||
};
|
||||
})
|
||||
|
||||
|
||||
@@ -34,31 +34,35 @@ export async function logAdminAction(adminId, actionType, targetUserId = null, d
|
||||
* @returns {Promise<Array>} Array of admin actions
|
||||
*/
|
||||
export async function getAdminActions(adminId = null, { limit = 50, offset = 0 } = {}) {
|
||||
let query = db
|
||||
.select({
|
||||
id: adminActions.id,
|
||||
adminId: adminActions.adminId,
|
||||
adminEmail: users.email,
|
||||
adminCallsign: users.callsign,
|
||||
actionType: adminActions.actionType,
|
||||
targetUserId: adminActions.targetUserId,
|
||||
targetEmail: sql`target_users.email`.as('targetEmail'),
|
||||
targetCallsign: sql`target_users.callsign`.as('targetCallsign'),
|
||||
details: adminActions.details,
|
||||
createdAt: adminActions.createdAt,
|
||||
})
|
||||
.from(adminActions)
|
||||
.leftJoin(users, eq(adminActions.adminId, users.id))
|
||||
.leftJoin(sql`${users} as target_users`, eq(adminActions.targetUserId, sql.raw('target_users.id')))
|
||||
.orderBy(desc(adminActions.createdAt))
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
// Use raw SQL for the self-join (admin users and target users from same users table)
|
||||
// Using bun:sqlite prepared statements for raw SQL
|
||||
let query = `
|
||||
SELECT
|
||||
aa.id as id,
|
||||
aa.admin_id as adminId,
|
||||
admin_user.email as adminEmail,
|
||||
admin_user.callsign as adminCallsign,
|
||||
aa.action_type as actionType,
|
||||
aa.target_user_id as targetUserId,
|
||||
target_user.email as targetEmail,
|
||||
target_user.callsign as targetCallsign,
|
||||
aa.details as details,
|
||||
aa.created_at as createdAt
|
||||
FROM admin_actions aa
|
||||
LEFT JOIN users admin_user ON admin_user.id = aa.admin_id
|
||||
LEFT JOIN users target_user ON target_user.id = aa.target_user_id
|
||||
`;
|
||||
|
||||
if (adminId) {
|
||||
query = query.where(eq(adminActions.adminId, adminId));
|
||||
const params = [];
|
||||
if (adminId !== null) {
|
||||
query += ` WHERE aa.admin_id = ?`;
|
||||
params.push(adminId);
|
||||
}
|
||||
|
||||
return await query;
|
||||
query += ` ORDER BY aa.created_at DESC LIMIT ? OFFSET ?`;
|
||||
params.push(limit, offset);
|
||||
|
||||
return sqlite.prepare(query).all(...params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,24 +241,26 @@ export async function stopImpersonation(adminId, targetUserId) {
|
||||
* @returns {Promise<Array>} Array of recent impersonation actions
|
||||
*/
|
||||
export async function getImpersonationStatus(adminId, { limit = 10 } = {}) {
|
||||
const impersonations = await db
|
||||
.select({
|
||||
id: adminActions.id,
|
||||
actionType: adminActions.actionType,
|
||||
targetUserId: adminActions.targetUserId,
|
||||
targetEmail: sql`target_users.email`,
|
||||
targetCallsign: sql`target_users.callsign`,
|
||||
details: adminActions.details,
|
||||
createdAt: adminActions.createdAt,
|
||||
})
|
||||
.from(adminActions)
|
||||
.leftJoin(sql`${users} as target_users`, eq(adminActions.targetUserId, sql.raw('target_users.id')))
|
||||
.where(eq(adminActions.adminId, adminId))
|
||||
.where(sql`${adminActions.actionType} LIKE 'impersonate%'`)
|
||||
.orderBy(desc(adminActions.createdAt))
|
||||
.limit(limit);
|
||||
// Use raw SQL for the self-join to avoid Drizzle alias issues
|
||||
// Using bun:sqlite prepared statements for raw SQL
|
||||
const query = `
|
||||
SELECT
|
||||
aa.id as id,
|
||||
aa.action_type as actionType,
|
||||
aa.target_user_id as targetUserId,
|
||||
u.email as targetEmail,
|
||||
u.callsign as targetCallsign,
|
||||
aa.details as details,
|
||||
aa.created_at as createdAt
|
||||
FROM admin_actions aa
|
||||
LEFT JOIN users u ON u.id = aa.target_user_id
|
||||
WHERE aa.admin_id = ?
|
||||
AND aa.action_type LIKE 'impersonate%'
|
||||
ORDER BY aa.created_at DESC
|
||||
LIMIT ?
|
||||
`;
|
||||
|
||||
return impersonations;
|
||||
return sqlite.prepare(query).all(adminId, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user