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:
2026-01-21 18:26:20 +01:00
parent 7c209e3270
commit bdd8aa497d
5 changed files with 171 additions and 130 deletions

View File

@@ -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);
}
/**