Audit-Log: stiller Token-Refresh entrauscht (eigene Action TOKEN_REFRESH)
POST /auth/refresh wurde als CREATE / "Anmeldung erstellt" / CRITICAL / anonymous geloggt und sah damit wie eine anonyme Login-Flut aus. Es ist aber der regulaere Silent-Refresh des Frontend-Interceptors (Access-Token lebt nur im Speicher -> nach Reload/401 einmaliger Cookie-Refresh). - Neuer AuditAction-Wert TOKEN_REFRESH (Migration 20260818120000, idempotentes MODIFY COLUMN) - determineAction() mappt /auth/refresh -> TOKEN_REFRESH, Label "Sitzung verlaengert (Token erneuert)", Sensitivitaet explizit LOW (statt Default Authentication -> CRITICAL) - LOGIN/LOGOUT/LOGIN_FAILED bleiben unveraendert CRITICAL - Frontend: Filter-Option + dezente Badge-Farbe + Typ-Union - anonymous bewusst beibehalten (Endpoint ohne authenticate-Middleware) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
-- Neuer AuditAction-Wert TOKEN_REFRESH: trennt stille Access-Token-Refreshes
|
||||||
|
-- vom interaktiven Login-Rauschen (eigene Action + LOW-Sensitivität).
|
||||||
|
-- MODIFY COLUMN ist idempotent (setzt die Enum-Definition, mehrfach ausführbar).
|
||||||
|
ALTER TABLE `AuditLog`
|
||||||
|
MODIFY COLUMN `action` ENUM(
|
||||||
|
'CREATE','READ','UPDATE','DELETE','EXPORT','ANONYMIZE',
|
||||||
|
'LOGIN','LOGOUT','LOGIN_FAILED','TOKEN_REFRESH'
|
||||||
|
) NOT NULL;
|
||||||
@@ -1231,6 +1231,7 @@ enum AuditAction {
|
|||||||
LOGIN
|
LOGIN
|
||||||
LOGOUT
|
LOGOUT
|
||||||
LOGIN_FAILED
|
LOGIN_FAILED
|
||||||
|
TOKEN_REFRESH // stiller Access-Token-Refresh (Cookie), kein interaktiver Login
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AuditSensitivity {
|
enum AuditSensitivity {
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ function determineAction(method: string, path: string, success: boolean): AuditA
|
|||||||
if (path.includes('/auth/logout')) {
|
if (path.includes('/auth/logout')) {
|
||||||
return 'LOGOUT';
|
return 'LOGOUT';
|
||||||
}
|
}
|
||||||
|
// Stiller Token-Refresh (Cookie) – kein interaktiver Login, eigene Action
|
||||||
|
if (path.includes('/auth/refresh')) {
|
||||||
|
return 'TOKEN_REFRESH';
|
||||||
|
}
|
||||||
|
|
||||||
// Standard CRUD-Aktionen
|
// Standard CRUD-Aktionen
|
||||||
switch (method.toUpperCase()) {
|
switch (method.toUpperCase()) {
|
||||||
@@ -157,6 +161,7 @@ const ACTION_LABELS: Record<string, string> = {
|
|||||||
LOGIN: 'angemeldet',
|
LOGIN: 'angemeldet',
|
||||||
LOGOUT: 'abgemeldet',
|
LOGOUT: 'abgemeldet',
|
||||||
LOGIN_FAILED: 'Anmeldung fehlgeschlagen',
|
LOGIN_FAILED: 'Anmeldung fehlgeschlagen',
|
||||||
|
TOKEN_REFRESH: 'Sitzung verlängert',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -198,6 +203,7 @@ function generateHumanLabel(
|
|||||||
: `Anmeldung fehlgeschlagen für ${email}`;
|
: `Anmeldung fehlgeschlagen für ${email}`;
|
||||||
}
|
}
|
||||||
if (path.includes('/auth/logout')) return 'Benutzer hat sich abgemeldet';
|
if (path.includes('/auth/logout')) return 'Benutzer hat sich abgemeldet';
|
||||||
|
if (path.includes('/auth/refresh')) return 'Sitzung verlängert (Token erneuert)';
|
||||||
|
|
||||||
// Kunden-Operationen
|
// Kunden-Operationen
|
||||||
if (resourceType === 'Customer') {
|
if (resourceType === 'Customer') {
|
||||||
@@ -436,6 +442,9 @@ export function auditMiddleware(req: AuthRequest, res: Response, next: NextFunct
|
|||||||
customerId: req.user?.customerId,
|
customerId: req.user?.customerId,
|
||||||
isCustomerPortal: req.user?.isCustomerPortal,
|
isCustomerPortal: req.user?.isCustomerPortal,
|
||||||
action,
|
action,
|
||||||
|
// Stiller Token-Refresh ist Routine → LOW statt CRITICAL (sonst Log-Flut).
|
||||||
|
// Andere Auth-Events behalten ihre Default-Sensitivität (Authentication → CRITICAL).
|
||||||
|
sensitivity: action === 'TOKEN_REFRESH' ? 'LOW' : undefined,
|
||||||
resourceType: mapping.type,
|
resourceType: mapping.type,
|
||||||
resourceId,
|
resourceId,
|
||||||
resourceLabel,
|
resourceLabel,
|
||||||
|
|||||||
@@ -97,6 +97,22 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
|||||||
|
|
||||||
## ✅ Erledigt
|
## ✅ Erledigt
|
||||||
|
|
||||||
|
- [x] **🔇 Audit-Log: stiller Token-Refresh entrauscht (`TOKEN_REFRESH`)** (2026-08-18)
|
||||||
|
- Automatische `POST /auth/refresh`-Aufrufe (Interceptor bei 401 / nach
|
||||||
|
Seiten-Reload, da Access-Token nur im Speicher) wurden als `CREATE` /
|
||||||
|
„Anmeldung erstellt" / **CRITICAL** / User `anonymous` geloggt → sah aus wie
|
||||||
|
anonyme Login-Flut, war aber die eigene Session.
|
||||||
|
- Neuer `AuditAction`-Wert **`TOKEN_REFRESH`** (Enum-Migration
|
||||||
|
`20260818120000_audit_token_refresh_action`, idempotentes `MODIFY COLUMN`).
|
||||||
|
`determineAction()` erkennt `/auth/refresh` → eigene Action; Label
|
||||||
|
„Sitzung verlängert (Token erneuert)"; Sensitivität in der Middleware
|
||||||
|
explizit auf **LOW** (statt Default `Authentication → CRITICAL`).
|
||||||
|
- Frontend `AuditLogs.tsx`: Filter-Option „Sitzung verlängert" + dezente
|
||||||
|
Badge-Farbe (slate); Typ-Union ergänzt. `anonymous` bleibt (Endpoint läuft
|
||||||
|
ohne `authenticate`-Middleware, authentifiziert per Cookie im Service) –
|
||||||
|
bewusst nicht geändert (Option 1).
|
||||||
|
- Verifiziert: Migration auf Dev-DB aktiv, `tsc` + `vite build` grün.
|
||||||
|
|
||||||
- [x] **🔒 Mass-Assignment-Schutz: Nested-Vertragsdetails (Pentest R162-01)** (2026-08-18)
|
- [x] **🔒 Mass-Assignment-Schutz: Nested-Vertragsdetails (Pentest R162-01)** (2026-08-18)
|
||||||
- `createContract`/`updateContract` spreadeten `energyDetails`/`tvDetails`/
|
- `createContract`/`updateContract` spreadeten `energyDetails`/`tvDetails`/
|
||||||
`carInsuranceDetails`/`mobileDetails` (via `...mobileData`) roh an Prisma →
|
`carInsuranceDetails`/`mobileDetails` (via `...mobileData`) roh an Prisma →
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const ACTION_OPTIONS = [
|
|||||||
{ value: 'LOGIN', label: 'Login' },
|
{ value: 'LOGIN', label: 'Login' },
|
||||||
{ value: 'LOGOUT', label: 'Logout' },
|
{ value: 'LOGOUT', label: 'Logout' },
|
||||||
{ value: 'LOGIN_FAILED', label: 'Login fehlgeschlagen' },
|
{ value: 'LOGIN_FAILED', label: 'Login fehlgeschlagen' },
|
||||||
|
{ value: 'TOKEN_REFRESH', label: 'Sitzung verlängert' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const SENSITIVITY_OPTIONS = [
|
const SENSITIVITY_OPTIONS = [
|
||||||
@@ -68,6 +69,7 @@ function getActionColor(action: AuditAction): string {
|
|||||||
case 'LOGIN': return 'bg-teal-100 text-teal-800';
|
case 'LOGIN': return 'bg-teal-100 text-teal-800';
|
||||||
case 'LOGOUT': return 'bg-gray-100 text-gray-800';
|
case 'LOGOUT': return 'bg-gray-100 text-gray-800';
|
||||||
case 'LOGIN_FAILED': return 'bg-red-200 text-red-900';
|
case 'LOGIN_FAILED': return 'bg-red-200 text-red-900';
|
||||||
|
case 'TOKEN_REFRESH': return 'bg-slate-50 text-slate-500';
|
||||||
default: return 'bg-gray-100 text-gray-800';
|
default: return 'bg-gray-100 text-gray-800';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -877,7 +877,7 @@ export interface CockpitResult {
|
|||||||
|
|
||||||
// ==================== AUDIT-LOGGING & DSGVO ====================
|
// ==================== AUDIT-LOGGING & DSGVO ====================
|
||||||
|
|
||||||
export type AuditAction = 'CREATE' | 'READ' | 'UPDATE' | 'DELETE' | 'EXPORT' | 'ANONYMIZE' | 'LOGIN' | 'LOGOUT' | 'LOGIN_FAILED';
|
export type AuditAction = 'CREATE' | 'READ' | 'UPDATE' | 'DELETE' | 'EXPORT' | 'ANONYMIZE' | 'LOGIN' | 'LOGOUT' | 'LOGIN_FAILED' | 'TOKEN_REFRESH';
|
||||||
export type AuditSensitivity = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
export type AuditSensitivity = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
||||||
export type ConsentType = 'DATA_PROCESSING' | 'MARKETING_EMAIL' | 'MARKETING_PHONE' | 'DATA_SHARING_PARTNER';
|
export type ConsentType = 'DATA_PROCESSING' | 'MARKETING_EMAIL' | 'MARKETING_PHONE' | 'DATA_SHARING_PARTNER';
|
||||||
export type ConsentStatus = 'GRANTED' | 'WITHDRAWN' | 'PENDING';
|
export type ConsentStatus = 'GRANTED' | 'WITHDRAWN' | 'PENDING';
|
||||||
|
|||||||
Reference in New Issue
Block a user