Teil 2 des Compliance-Updates — revisionssichere Zustimmungsprotokollierung: - Prisma: Modelle LegalDocument (versioniert, contentHash, isActive, unique[type,version]) und LegalAcceptance (userId, organizationId?, documentType/-Version, contentHash, context, unique gegen Duplikate, User-Relation) - migrate.js: idempotente Tabellen + Indizes + Seeding/Aktivierung der 5 Rechtsdokumente (eine aktive Version je Typ; contentHash aus bewusster Versionsangabe) - src/lib/legal.ts: getActiveDocuments, getPendingAcceptances, recordAcceptances (skipDuplicates, nie überschreiben), Pflichtsets (alle: TERMS+PRIVACY; Org-Admin zusätzlich ORGANIZATION_DECLARATION) - API: GET /api/legal/status (offene Zustimmungen), POST /api/legal/accept (erfassen, CSRF-Check) - Registrierung: zwei getrennte, nicht vorausgewählte Zustimmungen (Nutzungsbedingungen akzeptieren + Datenschutz-Kenntnisnahme), serverseitig via zod erzwungen, Version protokolliert Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
52 lines
2.3 KiB
TypeScript
52 lines
2.3 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { getSession } from '@/lib/auth'
|
|
import { recordAcceptances, getPendingAcceptances, type LegalAcceptanceContext, type LegalDocumentType } from '@/lib/legal'
|
|
|
|
const VALID_TYPES: LegalDocumentType[] = ['TERMS', 'PRIVACY', 'ORGANIZATION_DECLARATION', 'RESPONSIBLE_USE', 'DATA_PROCESSING_AGREEMENT']
|
|
const VALID_CONTEXTS: LegalAcceptanceContext[] = ['REGISTRATION', 'LOGIN_RECONSENT', 'ORGANIZATION_CREATION', 'ADMIN_ROLE_ACCEPTANCE', 'DONATION']
|
|
|
|
// POST: erfasst Zustimmungen des angemeldeten Benutzers revisionssicher.
|
|
// body: { types: LegalDocumentType[], context: LegalAcceptanceContext }
|
|
export async function POST(req: NextRequest) {
|
|
try {
|
|
const user = await getSession()
|
|
if (!user) return NextResponse.json({ error: 'Nicht autorisiert' }, { status: 401 })
|
|
|
|
// CSRF-Schutz: nur same-origin JSON-Requests (Cookie ist sameSite=lax).
|
|
const contentType = req.headers.get('content-type') || ''
|
|
if (!contentType.includes('application/json')) {
|
|
return NextResponse.json({ error: 'Ungültiger Request' }, { status: 400 })
|
|
}
|
|
|
|
const body = await req.json().catch(() => ({}))
|
|
const types: LegalDocumentType[] = Array.isArray(body.types)
|
|
? body.types.filter((t: any) => VALID_TYPES.includes(t))
|
|
: []
|
|
const context: LegalAcceptanceContext = VALID_CONTEXTS.includes(body.context) ? body.context : 'LOGIN_RECONSENT'
|
|
|
|
if (types.length === 0) {
|
|
return NextResponse.json({ error: 'Keine gültigen Dokumenttypen' }, { status: 400 })
|
|
}
|
|
|
|
// Organisationsbezogene Zustimmung (Org-Bestätigung) an den Tenant binden.
|
|
const orgBound = context === 'ORGANIZATION_CREATION' || context === 'ADMIN_ROLE_ACCEPTANCE' || types.includes('ORGANIZATION_DECLARATION')
|
|
const organizationId = orgBound ? (user.tenantId ?? null) : null
|
|
|
|
const count = await recordAcceptances({
|
|
userId: user.id,
|
|
organizationId,
|
|
context,
|
|
types,
|
|
locale: 'de-CH',
|
|
})
|
|
|
|
const isOrgAdmin = user.role === 'TENANT_ADMIN'
|
|
const pending = await getPendingAcceptances(user.id, isOrgAdmin)
|
|
|
|
return NextResponse.json({ success: true, recorded: count, needsAcceptance: pending.length > 0, pending })
|
|
} catch (error) {
|
|
console.error('Error recording legal acceptance:', error)
|
|
return NextResponse.json({ error: 'Serverfehler' }, { status: 500 })
|
|
}
|
|
}
|