From 744baec2ec4881c13eabb9e56cdcc76b359748d2 Mon Sep 17 00:00:00 2001 From: BOHA Date: Sat, 6 Jun 2026 02:40:41 +0200 Subject: [PATCH] feat(security): audit-log session termination (single + all-others) The two session-revocation endpoints wrote no audit entry. Added a 'session' EntityType (+ Czech label 'Relace') and logAudit on both deletes, so revoking sessions is recorded in the forensic trail. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/admin/lib/entityTypeLabels.ts | 1 + src/routes/admin/sessions.ts | 18 +++++++++++++++++- src/types/index.ts | 3 ++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/admin/lib/entityTypeLabels.ts b/src/admin/lib/entityTypeLabels.ts index 20b199d..3f615cb 100644 --- a/src/admin/lib/entityTypeLabels.ts +++ b/src/admin/lib/entityTypeLabels.ts @@ -36,4 +36,5 @@ export const ENTITY_TYPE_LABELS: Record = { work_plan_entry: "Plán prací – záznam", work_plan_override: "Plán prací – přepsání dne", plan_category: "Plán prací – kategorie", + session: "Relace", }; diff --git a/src/routes/admin/sessions.ts b/src/routes/admin/sessions.ts index 8adec12..1b10efc 100644 --- a/src/routes/admin/sessions.ts +++ b/src/routes/admin/sessions.ts @@ -3,6 +3,7 @@ import prisma from "../../config/database"; import { requireAuth } from "../../middleware/auth"; import { success, error, parseId } from "../../utils/response"; import { hashToken } from "../../services/auth"; +import { logAudit } from "../../services/audit"; /** Parse user-agent string into browser, OS, and device icon */ function parseUserAgent(ua: string | null): { @@ -94,6 +95,14 @@ export default async function sessionsRoutes( where: { id }, data: { replaced_at: new Date() }, }); + await logAudit({ + request, + authData, + action: "delete", + entityType: "session", + entityId: id, + description: `Ukončena relace #${id} (${session.ip_address ?? "?"})`, + }); return success(reply, null, 200, "Relace ukončena"); }, ); @@ -112,7 +121,7 @@ export default async function sessionsRoutes( return error(reply, "Nelze identifikovat aktuální relaci", 400); } - await prisma.refresh_tokens.updateMany({ + const terminated = await prisma.refresh_tokens.updateMany({ where: { user_id: authData.userId, replaced_at: null, @@ -120,6 +129,13 @@ export default async function sessionsRoutes( }, data: { replaced_at: new Date() }, }); + await logAudit({ + request, + authData, + action: "delete", + entityType: "session", + description: `Ukončeny všechny ostatní relace (${terminated.count})`, + }); return success(reply, null, 200, "Všechny ostatní relace ukončeny"); } diff --git a/src/types/index.ts b/src/types/index.ts index 9bb497d..1cfb252 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -146,4 +146,5 @@ export type EntityType = | "warehouse_receipt_attachment" | "work_plan_entry" | "work_plan_override" - | "plan_category"; + | "plan_category" + | "session";