mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
feat(admin): users management with filters + force-logout action (T2.7 deferred)
Folds in the v1.4.6 deferred backlog item: * `<UserManagementSection>` now exposes role filters (All / Admins / Users — "suspended" is documented as a no-op since the User model lacks a `suspended` field; mapped to passthrough for forward compatibility). * New per-row "Force logout" action surfaces a confirmation dialog, then POSTs to the new endpoint below. Shows a toast with the number of sessions revoked. * `POST /api/admin/users/[id]/force-logout` — admin-only kill switch that deletes every Session row for the target user. Audit-logged as `admin.user.force-logout`. Existing role-toggle, edit, and reset-password actions are untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
69c5225cea
commit
5957e1827e
2 files changed
+203
-13
No files matched your search
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* POST /api/admin/users/[id]/force-logout — admin-only kill-switch.
|
||||
*
|
||||
* Deletes every active session for the target user. The next request
|
||||
* from any of their browser/native clients will see the cookie/bearer
|
||||
* rejected and bounce to /auth/login. API tokens are NOT touched here
|
||||
* (admins manage those from the API-Tokens section); this endpoint is
|
||||
* specifically for "I see this user's session active in audit-log,
|
||||
* boot them now".
|
||||
*/
|
||||
import { prisma } from "@/lib/db";
|
||||
import { apiHandler, requireAdmin } from "@/lib/api-handler";
|
||||
import { auditLog } from "@/lib/auth/audit";
|
||||
import { apiSuccess, apiError, getClientIp } from "@/lib/api-response";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export const POST = apiHandler(
|
||||
async (
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) => {
|
||||
const { user: admin } = await requireAdmin();
|
||||
const { id } = await params;
|
||||
|
||||
annotate({
|
||||
action: {
|
||||
name: "admin.users.force-logout",
|
||||
entity_type: "user",
|
||||
entity_id: id,
|
||||
},
|
||||
});
|
||||
|
||||
const target = await prisma.user.findUnique({
|
||||
where: { id },
|
||||
select: { id: true, username: true },
|
||||
});
|
||||
if (!target) return apiError("User not found", 404);
|
||||
|
||||
// Hard-delete all sessions; the cascading cookie + bearer guards in
|
||||
// `requireAuth()` reject anything missing from this table.
|
||||
const result = await prisma.session.deleteMany({
|
||||
where: { userId: id },
|
||||
});
|
||||
|
||||
await auditLog("admin.user.force-logout", {
|
||||
userId: admin.id,
|
||||
ipAddress: getClientIp(request),
|
||||
details: { targetUserId: id, sessionsRevoked: result.count },
|
||||
});
|
||||
|
||||
annotate({ meta: { sessions_revoked: result.count } });
|
||||
|
||||
return apiSuccess({ sessionsRevoked: result.count });
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user