Files
Webklar-Kundenbereich/server/services/kundenansicht.js
T

94 lines
3.2 KiB
JavaScript

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 stammen aus "Als Kunde ansehen" im alten Admin-Panel.
*/
export const KUNDENANSICHT_TTL_MS = 2 * 60 * 60 * 1000
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 || '',
}
setPortalSession(res, session, { maxAgeMs: KUNDENANSICHT_TTL_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,
})
}