import { randomBytes } from 'node:crypto' import { clearPortalSession, setPortalSession } from '../middleware/session.js' import { logViewAction } from './customerViewLog.js' import { PREVIEW_COOKIE_NAME, clearPreviewSession, createPreviewSessionToken, previewSessionCookieOptions, } from './previewSession.js' /** * Kundenansicht aus dem Ticketsystem (Spec 2026-10-01, Abschnitt 3): ein Mitarbeiter oeffnet per Einmal-Link das * Portal eines Kunden. Die Sitzung traegt eine zufaellige Kennung (viewId) gegen Verwechslung zweier Tabs und * endet nach 2 Stunden. Sitzungen ohne viewId ("Als Kunde ansehen" im frueheren Admin-Panel) gelten als beendet * (kundenansichtGuard, /api/auth/me). */ export const KUNDENANSICHT_TTL_MS = 2 * 60 * 60 * 1000 const COOKIE_GRACE_MS = 60 * 60 * 1000 export const VIEW_EXPIRED_MESSAGE = 'Die Kundenansicht ist abgelaufen. Bitte im Ticketsystem erneut öffnen.' export const isKundenansicht = (session) => Boolean(session?.viewAs && session.viewId) export const isExpired = (session, now = Date.now()) => now >= Number(session?.viewExpiresAt || 0) /** Portal-Sitzung und Preview-Zugang fuer genau diesen Kunden setzen; liefert die Sitzung. */ export async function startKundenansicht(res, { staff, customer, now = Date.now() }) { const session = { role: 'admin', viewAs: true, viewId: randomBytes(16).toString('hex'), viewExpiresAt: now + KUNDENANSICHT_TTL_MS, isAdmin: Boolean(staff.isAdmin), appwriteUserId: staff.appwriteUserId, adminName: staff.name || 'Mitarbeiter', adminEmail: staff.email || '', customerId: customer.$id, name: customer.name || '', email: customer.email || '', } // Das Cookie lebt eine Stunde laenger als die Ansicht: so meldet der Server nach 2 Stunden "abgelaufen" (Hinweis im // Tab, Protokolleintrag), statt dass der Browser das Cookie still verwirft setPortalSession(res, session, { maxAgeMs: KUNDENANSICHT_TTL_MS + COOKIE_GRACE_MS }) await setKundenansichtPreview(res, session, KUNDENANSICHT_TTL_MS / 1000) return session } /** Preview-Cookie wie beim Kunden selbst (nur seine Projekte), hoechstens bis zum Ende der Kundenansicht. */ export async function setKundenansichtPreview(res, session, maxAgeSec) { const token = await createPreviewSessionToken( { userId: session.appwriteUserId, customerId: session.customerId, email: session.email || '', name: session.name || '', role: 'customer', labels: [], authSource: 'kundenansicht', }, { maxAgeSec } ) return res.cookie(PREVIEW_COOKIE_NAME, token, previewSessionCookieOptions({ maxAgeSec })) } export function endKundenansicht(res) { clearPortalSession(res) clearPreviewSession(res) } /** Aenderung in der Kundenansicht protokollieren; ausserhalb einer Kundenansicht (Kunde selbst) passiert nichts. */ export async function logIfKundenansicht(req, action, detail = '') { const session = req.session if (!isKundenansicht(session)) return await logViewAction({ customerId: session.customerId, staffUserId: session.appwriteUserId, staffName: session.adminName, action, detail, }) } // "beendet"/"abgelaufen" je Ansicht nur einmal protokollieren (parallele Anfragen eines Tabs) const LOGGED_TTL_MS = 3 * 60 * 60 * 1000 const logged = new Map() export async function logOnce(session, action) { const now = Date.now() for (const [key, at] of logged) if (now - at > LOGGED_TTL_MS) logged.delete(key) const key = `${session.viewId}:${action}` if (logged.has(key)) return logged.set(key, now) await logViewAction({ customerId: session.customerId, staffUserId: session.appwriteUserId, staffName: session.adminName, action, }) }