6a670df1c4
Bug 1 — Support-Anfrage: ausgewaehlter Vertrag nicht erkennbar
Im Kundenportal beim Erstellen einer Support-Anfrage war der
Selected-State des Vertrags nur ein dezenter blau-grauer
Hintergrund + Border-Farbwechsel. Auf hellem Bildschirm / nicht-
perfekter Lichtsituation kaum zu sehen.
Fix: kraefigere Markierung mit linkem 4px-Akzent-Bar
(border-l-blue-600), kraefigerem Background (bg-blue-100),
Checkmark-Icon rechtsbuendig und blauer Titel-Text.
Bug 2 — Email-Sync im Portal: "Keine Berechtigung"
POST /api/stressfrei-emails/:id/sync hatte
requirePermission('customers:update') – die Portal-Kunden nicht
haben (nur customers:read fuer eigene Daten). Sie konnten ihr
eigenes Postfach nicht synchronisieren.
Fix: Perm-Middleware aus der Route raus, Mitarbeiter-Check +
Owner-Check in den Controller verlegt:
- isCustomerPortal: nur Owner-Check (canAccessStressfreiEmail)
- Mitarbeiter: muss customers:update haben
Trennung der Threat-Modelle – Portal-User darf sein Postfach
syncen, sonst aber nichts triggern; Mitarbeiter brauchen weiter
die Update-Perm.
Live-verifiziert:
- Portal-User 1 syncs eigenes Konto → Auth passiert (400 wegen
fehlender IMAP-Config in dev-DB, NICHT 403)
- Portal-User 1 syncs Customer-3-Konto → 403 "Kein Zugriff"
- Mitarbeiter ohne customers:update → weiter 403
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
296 lines
8.0 KiB
TypeScript
296 lines
8.0 KiB
TypeScript
// ==================== CACHED EMAIL ROUTES ====================
|
||
|
||
import { Router } from 'express';
|
||
import * as cachedEmailController from '../controllers/cachedEmail.controller.js';
|
||
import { authenticate, requirePermission } from '../middleware/auth.js';
|
||
|
||
const router = Router();
|
||
|
||
// ==================== E-MAIL LISTEN ====================
|
||
|
||
// E-Mails für Kunden (mit optionalem Account-Filter)
|
||
// GET /api/customers/:customerId/emails?accountId=1&limit=50&offset=0
|
||
router.get(
|
||
'/customers/:customerId/emails',
|
||
authenticate,
|
||
requirePermission('customers:read'),
|
||
cachedEmailController.getEmailsForCustomer
|
||
);
|
||
|
||
// E-Mails für Vertrag
|
||
// GET /api/contracts/:contractId/emails?limit=50&offset=0
|
||
router.get(
|
||
'/contracts/:contractId/emails',
|
||
authenticate,
|
||
requirePermission('contracts:read'),
|
||
cachedEmailController.getEmailsForContract
|
||
);
|
||
|
||
// Ordner-Anzahlen für Vertrag (zugeordnete E-Mails)
|
||
// GET /api/contracts/:contractId/emails/folder-counts
|
||
router.get(
|
||
'/contracts/:contractId/emails/folder-counts',
|
||
authenticate,
|
||
requirePermission('contracts:read'),
|
||
cachedEmailController.getContractFolderCounts
|
||
);
|
||
|
||
// Mailbox-Konten eines Kunden (für Dropdown)
|
||
// GET /api/customers/:customerId/mailbox-accounts
|
||
router.get(
|
||
'/customers/:customerId/mailbox-accounts',
|
||
authenticate,
|
||
requirePermission('customers:read'),
|
||
cachedEmailController.getMailboxAccounts
|
||
);
|
||
|
||
// Ungelesene E-Mails zählen
|
||
// GET /api/emails/unread-count?customerId=1 oder ?contractId=1
|
||
router.get(
|
||
'/emails/unread-count',
|
||
authenticate,
|
||
requirePermission('customers:read'),
|
||
cachedEmailController.getUnreadCount
|
||
);
|
||
|
||
// ==================== EINZELNE E-MAIL ====================
|
||
|
||
// Einzelne E-Mail abrufen (mit Body, markiert als gelesen)
|
||
// GET /api/emails/:id
|
||
router.get(
|
||
'/emails/:id',
|
||
authenticate,
|
||
requirePermission('customers:read'),
|
||
cachedEmailController.getEmail
|
||
);
|
||
|
||
// E-Mail in Papierkorb verschieben (nur User mit emails:delete Permission)
|
||
// DELETE /api/emails/:id
|
||
router.delete(
|
||
'/emails/:id',
|
||
authenticate,
|
||
requirePermission('emails:delete'),
|
||
cachedEmailController.deleteEmail
|
||
);
|
||
|
||
// ==================== PAPIERKORB ====================
|
||
|
||
// Papierkorb-E-Mails für Kunden abrufen
|
||
// GET /api/customers/:customerId/emails/trash
|
||
router.get(
|
||
'/customers/:customerId/emails/trash',
|
||
authenticate,
|
||
requirePermission('emails:delete'),
|
||
cachedEmailController.getTrashEmails
|
||
);
|
||
|
||
// Papierkorb-Anzahl für Kunden
|
||
// GET /api/customers/:customerId/emails/trash/count
|
||
router.get(
|
||
'/customers/:customerId/emails/trash/count',
|
||
authenticate,
|
||
requirePermission('emails:delete'),
|
||
cachedEmailController.getTrashCount
|
||
);
|
||
|
||
// E-Mail aus Papierkorb wiederherstellen
|
||
// POST /api/emails/:id/restore
|
||
router.post(
|
||
'/emails/:id/restore',
|
||
authenticate,
|
||
requirePermission('emails:delete'),
|
||
cachedEmailController.restoreEmail
|
||
);
|
||
|
||
// E-Mail endgültig löschen (nur aus Papierkorb)
|
||
// DELETE /api/emails/:id/permanent
|
||
router.delete(
|
||
'/emails/:id/permanent',
|
||
authenticate,
|
||
requirePermission('emails:delete'),
|
||
cachedEmailController.permanentDeleteEmail
|
||
);
|
||
|
||
// E-Mail-Thread abrufen
|
||
// GET /api/emails/:id/thread
|
||
router.get(
|
||
'/emails/:id/thread',
|
||
authenticate,
|
||
requirePermission('customers:read'),
|
||
cachedEmailController.getThread
|
||
);
|
||
|
||
// Als gelesen/ungelesen markieren
|
||
// PATCH /api/emails/:id/read
|
||
router.patch(
|
||
'/emails/:id/read',
|
||
authenticate,
|
||
requirePermission('customers:update'),
|
||
cachedEmailController.markAsRead
|
||
);
|
||
|
||
// Stern umschalten
|
||
// POST /api/emails/:id/star
|
||
router.post(
|
||
'/emails/:id/star',
|
||
authenticate,
|
||
requirePermission('customers:update'),
|
||
cachedEmailController.toggleStar
|
||
);
|
||
|
||
// ==================== ANHÄNGE ====================
|
||
|
||
// Anhang-Liste einer E-Mail
|
||
// GET /api/emails/:emailId/attachments
|
||
router.get(
|
||
'/emails/:emailId/attachments',
|
||
authenticate,
|
||
requirePermission('customers:read'),
|
||
cachedEmailController.getAttachments
|
||
);
|
||
|
||
// Einzelnen Anhang herunterladen
|
||
// GET /api/emails/:emailId/attachments/:filename
|
||
router.get(
|
||
'/emails/:emailId/attachments/:filename',
|
||
authenticate,
|
||
requirePermission('customers:read'),
|
||
cachedEmailController.downloadAttachment
|
||
);
|
||
|
||
// Verfügbare Dokumenten-Ziele für Anhänge (zum Speichern)
|
||
// GET /api/emails/:id/attachment-targets
|
||
router.get(
|
||
'/emails/:id/attachment-targets',
|
||
authenticate,
|
||
requirePermission('customers:read'),
|
||
cachedEmailController.getAttachmentTargets
|
||
);
|
||
|
||
// Anhang in Dokumentenfeld speichern
|
||
// POST /api/emails/:id/attachments/:filename/save-to { entityType, entityId?, targetKey }
|
||
router.post(
|
||
'/emails/:id/attachments/:filename/save-to',
|
||
authenticate,
|
||
requirePermission('customers:update'),
|
||
cachedEmailController.saveAttachmentTo
|
||
);
|
||
|
||
// E-Mail als PDF exportieren und speichern
|
||
// POST /api/emails/:id/save-as-pdf { entityType, entityId?, targetKey }
|
||
router.post(
|
||
'/emails/:id/save-as-pdf',
|
||
authenticate,
|
||
requirePermission('customers:update'),
|
||
cachedEmailController.saveEmailAsPdf
|
||
);
|
||
|
||
// E-Mail als PDF exportieren und als Rechnung speichern
|
||
// POST /api/emails/:id/save-as-invoice { invoiceDate, invoiceType, notes? }
|
||
router.post(
|
||
'/emails/:id/save-as-invoice',
|
||
authenticate,
|
||
requirePermission('contracts:update'),
|
||
cachedEmailController.saveEmailAsInvoice
|
||
);
|
||
|
||
// Anhang als Rechnung speichern
|
||
// POST /api/emails/:id/attachments/:filename/save-as-invoice { invoiceDate, invoiceType, notes? }
|
||
router.post(
|
||
'/emails/:id/attachments/:filename/save-as-invoice',
|
||
authenticate,
|
||
requirePermission('contracts:update'),
|
||
cachedEmailController.saveAttachmentAsInvoice
|
||
);
|
||
|
||
// Anhang als Vertragsdokument speichern
|
||
// POST /api/emails/:id/attachments/:filename/save-as-contract-document { documentType, notes? }
|
||
router.post(
|
||
'/emails/:id/attachments/:filename/save-as-contract-document',
|
||
authenticate,
|
||
requirePermission('contracts:update'),
|
||
cachedEmailController.saveAttachmentAsContractDocument
|
||
);
|
||
|
||
// ==================== VERTRAGSZUORDNUNG ====================
|
||
|
||
// E-Mail Vertrag zuordnen
|
||
// POST /api/emails/:id/assign { contractId: number }
|
||
router.post(
|
||
'/emails/:id/assign',
|
||
authenticate,
|
||
requirePermission('contracts:update'),
|
||
cachedEmailController.assignToContract
|
||
);
|
||
|
||
// Zuordnung aufheben
|
||
// DELETE /api/emails/:id/assign
|
||
router.delete(
|
||
'/emails/:id/assign',
|
||
authenticate,
|
||
requirePermission('contracts:update'),
|
||
cachedEmailController.unassignFromContract
|
||
);
|
||
|
||
// ==================== STRESSFREI-EMAIL OPERATIONEN ====================
|
||
|
||
// E-Mails für ein Konto synchronisieren
|
||
// POST /api/stressfrei-emails/:id/sync?full=true
|
||
//
|
||
// KEIN `requirePermission('customers:update')` hier: Portal-Kunden
|
||
// dürfen ihr EIGENES Postfach synchronisieren – sie haben aber nur
|
||
// `customers:read`. Der Mitarbeiter-Perm-Check und der Owner-Check
|
||
// laufen im Controller. (Pentest 2026-05-30 follow-up.)
|
||
router.post(
|
||
'/stressfrei-emails/:id/sync',
|
||
authenticate,
|
||
cachedEmailController.syncAccount
|
||
);
|
||
|
||
// E-Mail senden
|
||
// POST /api/stressfrei-emails/:id/send { to, cc, subject, text, html, inReplyTo, references }
|
||
router.post(
|
||
'/stressfrei-emails/:id/send',
|
||
authenticate,
|
||
requirePermission('customers:update'),
|
||
cachedEmailController.sendEmailFromAccount
|
||
);
|
||
|
||
// Mailbox nachträglich aktivieren
|
||
// POST /api/stressfrei-emails/:id/enable-mailbox
|
||
router.post(
|
||
'/stressfrei-emails/:id/enable-mailbox',
|
||
authenticate,
|
||
requirePermission('customers:update'),
|
||
cachedEmailController.enableMailbox
|
||
);
|
||
|
||
// Mailbox-Status mit Provider synchronisieren
|
||
// POST /api/stressfrei-emails/:id/sync-mailbox-status
|
||
router.post(
|
||
'/stressfrei-emails/:id/sync-mailbox-status',
|
||
authenticate,
|
||
requirePermission('customers:read'),
|
||
cachedEmailController.syncMailboxStatus
|
||
);
|
||
|
||
// Mailbox-Zugangsdaten abrufen (IMAP/SMTP)
|
||
// GET /api/stressfrei-emails/:id/credentials
|
||
router.get(
|
||
'/stressfrei-emails/:id/credentials',
|
||
authenticate,
|
||
requirePermission('customers:read'),
|
||
cachedEmailController.getMailboxCredentials
|
||
);
|
||
|
||
// Ordner-Anzahlen für ein Konto (INBOX, SENT, ungelesen)
|
||
// GET /api/stressfrei-emails/:id/folder-counts
|
||
router.get(
|
||
'/stressfrei-emails/:id/folder-counts',
|
||
authenticate,
|
||
requirePermission('customers:read'),
|
||
cachedEmailController.getFolderCounts
|
||
);
|
||
|
||
export default router;
|