feat(users): implement server actions and audit logging

This commit is contained in:
CycroftX
2026-02-09 21:25:42 +05:30
parent 7ff360c2b5
commit 7a8c441b34
19 changed files with 5188 additions and 71 deletions

View File

@@ -0,0 +1,146 @@
'use server';
import { z } from 'zod';
import { logAdminAction } from '@/lib/audit-logger';
// --- Validation Schemas ---
const suspendSchema = z.object({
userId: z.string(),
reason: z.string().min(10, "Reason must be at least 10 characters"),
duration: z.enum(['24h', '7d', '30d', 'indefinite']),
});
const banSchema = z.object({
userId: z.string(),
reason: z.string().min(10),
});
const ticketSchema = z.object({
userId: z.string(),
summary: z.string().min(5),
});
// --- Authorization Helper ---
async function verifyAdmin() {
// TODO: Integrate with Auth.js session check
// const session = await auth();
// if (session?.user?.role !== 'admin') throw new Error("Unauthorized");
// Mock successful admin check
return { id: 'admin-1', role: 'admin' };
}
// --- Server Actions ---
export async function resetPassword(userId: string) {
try {
const admin = await verifyAdmin();
// Simulate DB call
await new Promise(resolve => setTimeout(resolve, 800));
await logAdminAction({
actorId: admin.id,
action: 'RESET_PASSWORD',
targetId: userId,
});
return { success: true, message: "Recovery email sent to user." };
} catch (error) {
return { success: false, message: "Failed to reset password." };
}
}
export async function impersonateUser(userId: string) {
try {
const admin = await verifyAdmin();
await logAdminAction({
actorId: admin.id,
action: 'IMPERSONATE_USER',
targetId: userId,
});
// In a real app, you'd set a cookie here
// cookies().set('impersonate_id', userId);
return { success: true, redirectUrl: '/dashboard' };
} catch (error) {
return { success: false, message: "Failed to start impersonation session." };
}
}
export async function terminateSessions(userId: string) {
try {
const admin = await verifyAdmin();
// Simulate DB call to delete sessions
await new Promise(resolve => setTimeout(resolve, 500));
await logAdminAction({
actorId: admin.id,
action: 'TERMINATE_SESSIONS',
targetId: userId,
});
return { success: true, message: "All active sessions terminated." };
} catch (error) {
return { success: false, message: "Failed to terminate sessions." };
}
}
export async function toggleSuspension(formData: FormData) {
try {
const admin = await verifyAdmin();
const rawData = {
userId: formData.get('userId'),
reason: formData.get('reason'),
duration: formData.get('duration'),
};
const validated = suspendSchema.safeParse(rawData);
if (!validated.success) {
return { success: false, message: validated.error.errors[0].message };
}
// Simulate DB update
await new Promise(resolve => setTimeout(resolve, 1000));
await logAdminAction({
actorId: admin.id,
action: 'SUSPEND_USER',
targetId: validated.data.userId,
details: { reason: validated.data.reason, duration: validated.data.duration },
});
return { success: true, message: `User suspended for ${validated.data.duration}.` };
} catch (error) {
return { success: false, message: "Failed to suspend user." };
}
}
export async function createSupportTicket(userId: string, summary: string) {
try {
const admin = await verifyAdmin();
const validated = ticketSchema.safeParse({ userId, summary });
if (!validated.success) return { success: false, message: "Invalid input" };
// Simulate DB insert
await new Promise(resolve => setTimeout(resolve, 600));
await logAdminAction({
actorId: admin.id,
action: 'CREATE_TICKET',
targetId: userId,
details: { summary },
});
return { success: true, message: "Support ticket #T-1234 created." };
} catch (error) {
return { success: false, message: "Failed to create ticket." };
}
}