feat(legal): Zustimmungs-Nachweise mit IP, Gerät/Browser & Hash (v1.7.8)

Belastbarer Einwilligungsnachweis:
- Schema/Migration: legal_acceptances + ipAddress, userAgent (contentHash bestand bereits)
- Erfassung bei Registrierung und jeder Zustimmung (/api/legal/accept)
- Admin /admin/legal: neue Spalten IP + Gerät/Browser + Hash; CSV-Export um ipAddress,
  userAgent, contentHash erweitert
- Datenschutzerklärung + Dateninventar: IP/User-Agent als dokumentierter Beweiszweck ergänzt

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Pepe Ziberi
2026-07-23 17:55:57 +02:00
parent 5cdf0e63c9
commit fa1e911ecd
10 changed files with 27 additions and 8 deletions

View File

@@ -29,7 +29,7 @@ export async function GET(req: NextRequest) {
where: userFilter,
orderBy: { acceptedAt: 'desc' },
take: 2000,
select: { id: true, userId: true, organizationId: true, documentType: true, documentVersion: true, context: true, acceptedAt: true },
select: { id: true, userId: true, organizationId: true, documentType: true, documentVersion: true, context: true, acceptedAt: true, contentHash: true, ipAddress: true, userAgent: true },
})
// Nur die notwendigen Nachweisdaten anreichern (E-Mail/Name) — keine überflüssigen Personendaten.
@@ -43,11 +43,11 @@ export async function GET(req: NextRequest) {
}))
if (format === 'csv') {
const header = ['acceptedAt', 'email', 'name', 'documentType', 'documentVersion', 'context', 'organizationId']
const header = ['acceptedAt', 'email', 'name', 'documentType', 'documentVersion', 'context', 'ipAddress', 'userAgent', 'contentHash', 'organizationId']
const esc = (v: any) => `"${String(v ?? '').replace(/"/g, '""')}"`
const lines = [header.join(',')]
for (const r of rows) {
lines.push([new Date(r.acceptedAt).toISOString(), r.email, r.name, r.documentType, r.documentVersion, r.context, r.organizationId || ''].map(esc).join(','))
lines.push([new Date(r.acceptedAt).toISOString(), r.email, r.name, r.documentType, r.documentVersion, r.context, r.ipAddress || '', r.userAgent || '', r.contentHash || '', r.organizationId || ''].map(esc).join(','))
}
return new NextResponse(lines.join('\r\n'), {
headers: {