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) <noreply@anthropic.com>
This commit is contained in:
@@ -36,4 +36,5 @@ export const ENTITY_TYPE_LABELS: Record<string, string> = {
|
|||||||
work_plan_entry: "Plán prací – záznam",
|
work_plan_entry: "Plán prací – záznam",
|
||||||
work_plan_override: "Plán prací – přepsání dne",
|
work_plan_override: "Plán prací – přepsání dne",
|
||||||
plan_category: "Plán prací – kategorie",
|
plan_category: "Plán prací – kategorie",
|
||||||
|
session: "Relace",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import prisma from "../../config/database";
|
|||||||
import { requireAuth } from "../../middleware/auth";
|
import { requireAuth } from "../../middleware/auth";
|
||||||
import { success, error, parseId } from "../../utils/response";
|
import { success, error, parseId } from "../../utils/response";
|
||||||
import { hashToken } from "../../services/auth";
|
import { hashToken } from "../../services/auth";
|
||||||
|
import { logAudit } from "../../services/audit";
|
||||||
|
|
||||||
/** Parse user-agent string into browser, OS, and device icon */
|
/** Parse user-agent string into browser, OS, and device icon */
|
||||||
function parseUserAgent(ua: string | null): {
|
function parseUserAgent(ua: string | null): {
|
||||||
@@ -94,6 +95,14 @@ export default async function sessionsRoutes(
|
|||||||
where: { id },
|
where: { id },
|
||||||
data: { replaced_at: new Date() },
|
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");
|
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);
|
return error(reply, "Nelze identifikovat aktuální relaci", 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.refresh_tokens.updateMany({
|
const terminated = await prisma.refresh_tokens.updateMany({
|
||||||
where: {
|
where: {
|
||||||
user_id: authData.userId,
|
user_id: authData.userId,
|
||||||
replaced_at: null,
|
replaced_at: null,
|
||||||
@@ -120,6 +129,13 @@ export default async function sessionsRoutes(
|
|||||||
},
|
},
|
||||||
data: { replaced_at: new Date() },
|
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");
|
return success(reply, null, 200, "Všechny ostatní relace ukončeny");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -146,4 +146,5 @@ export type EntityType =
|
|||||||
| "warehouse_receipt_attachment"
|
| "warehouse_receipt_attachment"
|
||||||
| "work_plan_entry"
|
| "work_plan_entry"
|
||||||
| "work_plan_override"
|
| "work_plan_override"
|
||||||
| "plan_category";
|
| "plan_category"
|
||||||
|
| "session";
|
||||||
|
|||||||
Reference in New Issue
Block a user