feat: globales HTTP-Access-Log und Exception-Logging
Backend Image / build-and-push (push) Successful in 2m50s
Frontend Image / build-and-push (push) Successful in 2m22s

Bisher waren 4xx-Fehler (falsches Passwort, Validierungsfehler,
fehlende Berechtigung) im Log komplett unsichtbar - Nest loggt
standardmaessig nur unbehandelte 5xx-Fehler. Ein Login, das aus
Infra-Gruenden fehlschlaegt (siehe TLS/Cloudflare-Vorfall), war dadurch
allein aus den Logs nicht diagnostizierbar.

- AllExceptionsFilter (global): loggt jede Exception mit Methode/Pfad/
  Status (4xx als warn, 5xx als error mit Stacktrace). Client-Antwort
  bleibt unveraendert - HttpExceptions liefern weiter exakt ihren
  bisherigen Body, unbehandelte Fehler weiter nur eine generische
  500-Meldung ohne interne Details.
- requestLoggerMiddleware (global): eine Zeile pro Request (Methode,
  Pfad, Status, Dauer), damit auch erfolgreiche Requests im Log
  auftauchen - vorher gab's dafuer gar keine Spur.
- AuthService.login()/verifyMfaAndLogin(): konkrete Ursache pro
  Fehlschlag (unbekannter User, deaktiviert, falsches Passwort,
  falscher MFA-Code) zusaetzlich zum bestehenden Audit-Log-Eintrag.

Nirgends werden Passwoerter, Tokens oder Request-Bodies geloggt - nur
Methode, Pfad, Status, Dauer und die (bereits client-sichere)
Fehlermeldung.

Verifiziert: tsc/eslint clean, 304/304 Tests (7 neu), Build clean.
This commit is contained in:
Nick Szaunig committed 2026-08-27 07:29:51 +02:00
1 parent 05876838b0
commit ce3a0c44ac
7 files changed
+269 -5

No files matched your search

@@ -0,0 +1,80 @@
import { Logger } from '@nestjs/common';
import type { NextFunction, Request, Response } from 'express';
import { requestLoggerMiddleware } from './request-logger.middleware';
describe('requestLoggerMiddleware', () => {
afterEach(() => {
jest.restoreAllMocks();
});
function fakeRequest(): Request {
return { method: 'GET', originalUrl: '/api/v1/dashboard' } as Request;
}
function fakeResponse(statusCode: number): {
res: Response;
finish: () => void;
} {
let finishHandler: () => void = () => undefined;
const res = {
statusCode,
on: (event: string, handler: () => void) => {
if (event === 'finish') {
finishHandler = handler;
}
},
} as unknown as Response;
return { res, finish: () => finishHandler() };
}
it('calls next() immediately without waiting for the response', () => {
const next = jest.fn() as NextFunction;
const { res } = fakeResponse(200);
requestLoggerMiddleware(fakeRequest(), res, next);
expect(next).toHaveBeenCalled();
});
it('logs at log level for a 2xx response', () => {
const logSpy = jest
.spyOn(Logger.prototype, 'log')
.mockImplementation(() => undefined);
const { res, finish } = fakeResponse(200);
requestLoggerMiddleware(fakeRequest(), res, jest.fn());
finish();
expect(logSpy).toHaveBeenCalledWith(
expect.stringContaining('GET /api/v1/dashboard 200'),
);
});
it('logs at warn level for a 4xx response', () => {
const warnSpy = jest
.spyOn(Logger.prototype, 'warn')
.mockImplementation(() => undefined);
const { res, finish } = fakeResponse(401);
requestLoggerMiddleware(fakeRequest(), res, jest.fn());
finish();
expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining('GET /api/v1/dashboard 401'),
);
});
it('logs at error level for a 5xx response', () => {
const errorSpy = jest
.spyOn(Logger.prototype, 'error')
.mockImplementation(() => undefined);
const { res, finish } = fakeResponse(500);
requestLoggerMiddleware(fakeRequest(), res, jest.fn());
finish();
expect(errorSpy).toHaveBeenCalledWith(
expect.stringContaining('GET /api/v1/dashboard 500'),
);
});
});
@@ -0,0 +1,31 @@
import { Logger } from '@nestjs/common';
import type { NextFunction, Request, Response } from 'express';
const logger = new Logger('HTTP');
/**
* One line per request in stdout/kubectl logs: method, path, status, and
* timing - never the body/headers, so passwords and tokens never end up in
* logs. Without this, a normal (non-error) request leaves zero trace,
* which makes "did the request even arrive?" the hardest question to
* answer when something looks broken from outside the cluster.
*/
export function requestLoggerMiddleware(
req: Request,
res: Response,
next: NextFunction,
): void {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
const line = `${req.method} ${req.originalUrl} ${res.statusCode} ${duration}ms`;
if (res.statusCode >= 500) {
logger.error(line);
} else if (res.statusCode >= 400) {
logger.warn(line);
} else {
logger.log(line);
}
});
next();
}