Security-Hardening Runde 3: JWT, trust-proxy, weitere IDORs, Attachment-Härtung

- JWT-Algorithmus fest auf HS256 (Defense-in-Depth gegen alg-confusion)
- app.set('trust proxy', 1) – Rate-Limiter wirkt jetzt auch hinter Reverse-Proxy
- IDOR-Fix: Invoice-ECD-Endpoints + PDF-Template-Generierung (canAccessContract/ECD)
- Email-Anhang-Download: Content-Type-Safelist, SVG nie inline, nosniff, Filename-CRLF-Sanitize
- Provider/Tariff-GET-Routen: requirePermission('providers:read') (Portal-Kunden raus)
- SMTP-Header-Injection zentral in sendEmail blockiert (schützt alle Caller)
- bcrypt-Cost 10 → 12 (OWASP 2026)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 09:38:25 +02:00
parent 301aafffd1
commit 8aead8c2f6
11 changed files with 131 additions and 21 deletions
+11 -6
View File
@@ -2,14 +2,15 @@ import { Request, Response } from 'express';
import * as invoiceService from '../services/invoice.service.js';
import { logChange } from '../services/audit.service.js';
import { ApiResponse, AuthRequest } from '../types/index.js';
import { canAccessContract } from '../utils/accessControl.js';
import { canAccessContract, canAccessEnergyContractDetails } from '../utils/accessControl.js';
/**
* Alle Rechnungen für ein EnergyContractDetails abrufen
*/
export async function getInvoices(req: Request, res: Response): Promise<void> {
export async function getInvoices(req: AuthRequest, res: Response): Promise<void> {
try {
const ecdId = parseInt(req.params.ecdId);
if (!(await canAccessEnergyContractDetails(req, res, ecdId))) return;
const invoices = await invoiceService.getInvoices(ecdId);
res.json({ success: true, data: invoices } as ApiResponse);
} catch (error) {
@@ -24,10 +25,11 @@ export async function getInvoices(req: Request, res: Response): Promise<void> {
/**
* Einzelne Rechnung abrufen
*/
export async function getInvoice(req: Request, res: Response): Promise<void> {
export async function getInvoice(req: AuthRequest, res: Response): Promise<void> {
try {
const ecdId = parseInt(req.params.ecdId);
const invoiceId = parseInt(req.params.invoiceId);
if (!(await canAccessEnergyContractDetails(req, res, ecdId))) return;
const invoice = await invoiceService.getInvoice(ecdId, invoiceId);
if (!invoice) {
@@ -51,9 +53,10 @@ export async function getInvoice(req: Request, res: Response): Promise<void> {
/**
* Neue Rechnung hinzufügen
*/
export async function addInvoice(req: Request, res: Response): Promise<void> {
export async function addInvoice(req: AuthRequest, res: Response): Promise<void> {
try {
const ecdId = parseInt(req.params.ecdId);
if (!(await canAccessEnergyContractDetails(req, res, ecdId))) return;
const { invoiceDate, invoiceType, documentPath, notes } = req.body;
if (!invoiceDate || !invoiceType) {
@@ -90,10 +93,11 @@ export async function addInvoice(req: Request, res: Response): Promise<void> {
/**
* Rechnung aktualisieren
*/
export async function updateInvoice(req: Request, res: Response): Promise<void> {
export async function updateInvoice(req: AuthRequest, res: Response): Promise<void> {
try {
const ecdId = parseInt(req.params.ecdId);
const invoiceId = parseInt(req.params.invoiceId);
if (!(await canAccessEnergyContractDetails(req, res, ecdId))) return;
const { invoiceDate, invoiceType, documentPath, notes } = req.body;
const invoice = await invoiceService.updateInvoice(ecdId, invoiceId, {
@@ -122,10 +126,11 @@ export async function updateInvoice(req: Request, res: Response): Promise<void>
/**
* Rechnung löschen
*/
export async function deleteInvoice(req: Request, res: Response): Promise<void> {
export async function deleteInvoice(req: AuthRequest, res: Response): Promise<void> {
try {
const ecdId = parseInt(req.params.ecdId);
const invoiceId = parseInt(req.params.invoiceId);
if (!(await canAccessEnergyContractDetails(req, res, ecdId))) return;
await invoiceService.deleteInvoice(ecdId, invoiceId);