import { CURRENT_ART9_MAIL_VERSION } from "../../utils/consent-texts"; import { writeConsentGrant, setMailConnectionConsent, getMailConnectionWithConsent, } from "../../db/consent"; /** * POST /api/mail-connections/consent * * Erteilt Art. 9-Einwilligung für eine oder mehrere bestehende MailConnections. * Schreibt pro Connection: * 1. Eintrag in consent_logs (append-only Beweislog, Art. 7 Abs. 1 DSGVO) * 2. consent_at + consent_version + consent_ip_address auf mail_connections * * Body: { mailConnectionId: string | string[], consentVersion: string } * * Response: * 200: { success: true, consentAt: ISO-string, updated: number } * 400: { error: 'invalid_body' } * 404: { error: 'connection_not_found' } — wenn eine ID nicht zum User gehört * 409: { error: 'consent_version_mismatch', expected: string } */ export default defineEventHandler(async (event) => { const user = await requireUser(event); const body = await readBody(event).catch(() => null); const rawId = body?.mailConnectionId as string | string[] | undefined; const consentVersion = body?.consentVersion as string | undefined; if (!rawId || !consentVersion) { throw createError({ statusCode: 400, data: { error: "invalid_body" }, }); } // Nur die aktuelle Version ist akzeptabel. if (consentVersion !== CURRENT_ART9_MAIL_VERSION) { throw createError({ statusCode: 409, data: { error: "consent_version_mismatch", expected: CURRENT_ART9_MAIL_VERSION, received: consentVersion, }, }); } // Normalisieren auf Array — erlaubt single-string und bulk-array const ids = Array.isArray(rawId) ? rawId : [rawId]; if (ids.length === 0) { throw createError({ statusCode: 400, data: { error: "invalid_body" }, }); } const now = new Date(); const ipAddress = getHeader(event, "x-forwarded-for")?.split(",")[0]?.trim() ?? getHeader(event, "x-real-ip") ?? null; const userAgent = getHeader(event, "user-agent") ?? null; // Alle Connections validieren (müssen dem User gehören) — dann in Serie verarbeiten. // Serie statt Promise.all: vermeidet Race-Conditions auf consent_logs primary key. for (const mailConnectionId of ids) { const connection = await getMailConnectionWithConsent( mailConnectionId, user.id, ); if (!connection) { throw createError({ statusCode: 404, data: { error: "connection_not_found", mailConnectionId }, }); } // 1. Append-only Audit-Log await writeConsentGrant({ userId: user.id, consentType: "art9-mail", consentVersion, consentAt: now, ipAddress, userAgent, mailConnectionId, }); // 2. MailConnection-Row updaten await setMailConnectionConsent({ connectionId: mailConnectionId, userId: user.id, consentAt: now, consentVersion, consentIpAddress: ipAddress, }); } return { success: true, consentAt: now.toISOString(), updated: ids.length }; });