57 lines
3.0 KiB
JavaScript
57 lines
3.0 KiB
JavaScript
import { parsePortalSession } from './session.js'
|
|
import { VIEW_EXPIRED_MESSAGE, endKundenansicht, isExpired, isKundenansicht, logOnce } from '../services/kundenansicht.js'
|
|
|
|
/**
|
|
* Waechter der Kundenansicht vor allen Kunden-Routen (Spec 2026-10-01, Abschnitt 3):
|
|
* 1. abgelaufen (2 h) -> 401 view_expired, Cookies weg; ebenso alte Ansichten ohne viewId ("Als Kunde ansehen" aus
|
|
* dem frueheren Admin-Panel)
|
|
* 2. fremde Kennung im Header X-Kundenansicht -> 409 view_replaced (Tab einer anderen Kundenansicht)
|
|
* 3. Lesen ist erlaubt; Schreiben nur mit passender Kennung und nur, was auf der Erlaubnisliste steht.
|
|
* Neue Schreib-Routen sind damit in der Kundenansicht gesperrt, bis sie bewusst auf die Liste kommen.
|
|
*/
|
|
export const KUNDENANSICHT_ALLOWED = [
|
|
['POST', /^\/api\/news\/?$/],
|
|
['POST', /^\/api\/news\/image\/?$/],
|
|
['PATCH', /^\/api\/news\/[^/]+\/?$/],
|
|
['DELETE', /^\/api\/news\/[^/]+\/?$/],
|
|
['POST', /^\/api\/funktionen\/analyse\/heatmap-link\/?$/],
|
|
['POST', /^\/api\/auth\/kundenansicht\/ende\/?$/],
|
|
['POST', /^\/api\/auth\/logout\/?$/],
|
|
['POST', /^\/api\/auth\/login\/?$/],
|
|
]
|
|
|
|
// Eigene Pruefung (Ticketsystem-JWT) oder Ablauf/Wechsel selbst behandelt (me, Einloesen)
|
|
const PASS_PREFIXES = ['/api/team/', '/api/admin/']
|
|
const PASS_EXACT = ['/api/auth/me', '/api/auth/kundenansicht']
|
|
const READ_METHODS = ['GET', 'HEAD', 'OPTIONS']
|
|
|
|
const replaced = (res, session) =>
|
|
res.status(409).json({ code: 'view_replaced', kunde: session.name || '', error: 'Diese Ansicht wurde durch eine andere Kundenansicht ersetzt.' })
|
|
|
|
export async function kundenansichtGuard(req, res, next) {
|
|
// Express routet ohne Ruecksicht auf Gross-/Kleinschreibung (/API/news trifft /api/news): hier genauso pruefen
|
|
const pathname = String(req.path || '').toLowerCase()
|
|
if (!pathname.startsWith('/api/')) return next()
|
|
if (PASS_PREFIXES.some((p) => pathname.startsWith(p)) || PASS_EXACT.includes(pathname.replace(/\/$/, ''))) return next()
|
|
const session = parsePortalSession(req)
|
|
if (!session) return next()
|
|
|
|
const current = isKundenansicht(session)
|
|
// Abgelaufen oder alte Ansicht ohne Kennung (frueheres Admin-Panel): beenden, nichts mehr lesen oder schreiben
|
|
if (session.viewAs && (!current || isExpired(session))) {
|
|
endKundenansicht(res)
|
|
if (current) await logOnce(session, 'abgelaufen')
|
|
return res.status(401).json({ code: 'view_expired', error: VIEW_EXPIRED_MESSAGE })
|
|
}
|
|
// Tab einer Kundenansicht, deren Cookie inzwischen zu etwas anderem gehoert (andere Kundenansicht,
|
|
// Kunden-Login): keine fremden Daten in diesen Tab
|
|
const header = req.get('X-Kundenansicht')
|
|
if (header && header !== session.viewId) return replaced(res, session)
|
|
if (!session.viewAs) return next()
|
|
if (READ_METHODS.includes(req.method)) return next()
|
|
if (!header) return replaced(res, session)
|
|
|
|
if (KUNDENANSICHT_ALLOWED.some(([method, re]) => method === req.method && re.test(pathname))) return next()
|
|
return res.status(403).json({ code: 'view_blocked', error: 'In der Kundenansicht nicht möglich.' })
|
|
}
|