From ab15c2367be5af44f0da6b76f5ed5c37cd265c4b Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Fri, 13 Feb 2026 13:43:23 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(gdpr):=20add=20DSGVO=20improve?= =?UTF-8?q?ments=20for=20self-service=20data=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add account deletion confirmation email - Extend data export with sessions, security events, transactions - Add DSGVO info banner with privacy policy link - Add data retention periods section - Add cookie info (no tracking cookies) Co-Authored-By: Claude Opus 4.5 --- .../(app)/settings/my-data/+page.svelte | 76 ++++++++++++ .../src/admin/user-data.service.ts | 112 ++++++++++++++++++ .../mana-core-auth/src/email/email.service.ts | 50 ++++++++ services/mana-core-auth/src/me/me.service.ts | 77 ++++++++++-- 4 files changed, 306 insertions(+), 9 deletions(-) diff --git a/apps/manacore/apps/web/src/routes/(app)/settings/my-data/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/settings/my-data/+page.svelte index a5459cbc1..5aaf63013 100644 --- a/apps/manacore/apps/web/src/routes/(app)/settings/my-data/+page.svelte +++ b/apps/manacore/apps/web/src/routes/(app)/settings/my-data/+page.svelte @@ -149,6 +149,42 @@ {/if} + +
+
+ + + +
+

+ Hier siehst du alle Daten, die wir uber dich speichern. Mehr Informationen findest du in + unserer + + Datenschutzerklarung + . Wir verwenden keine Tracking-Cookies – unsere Analyse erfolgt vollstandig + anonym via Umami. +

+
+
+
+ {#if loading}
{#each Array(4) as _} @@ -333,6 +369,46 @@
+ + +
+

+ + + + Aufbewahrungsfristen +

+

So lange speichern wir deine Daten:

+
+
+ Benutzerkonto & Profil + Bis zur Loschung +
+
+ Sessions & Login-Historie + 90 Tage nach Ablauf +
+
+ Credit-Transaktionen + 10 Jahre (gesetzlich) +
+
+ Security-Logs + 1 Jahr +
+
+ Projektdaten (Chat, Todo, etc.) + Bis zur Loschung +
+
+
+
+
diff --git a/services/mana-core-auth/src/admin/user-data.service.ts b/services/mana-core-auth/src/admin/user-data.service.ts index 4fdbae95f..973cfd7b4 100644 --- a/services/mana-core-auth/src/admin/user-data.service.ts +++ b/services/mana-core-auth/src/admin/user-data.service.ts @@ -338,6 +338,118 @@ export class UserDataService { }; } + /** + * Get full export data including sessions, security events, and transactions + */ + async getFullExportData(userId: string) { + const summary = await this.getUserDataSummary(userId); + + // Get additional details for export + const [sessions, securityEvents, transactions] = await Promise.all([ + this.getSessionHistory(userId), + this.getSecurityEvents(userId), + this.getTransactionHistory(userId), + ]); + + return { + ...summary, + exportedAt: new Date().toISOString(), + exportVersion: '2.0', + sessions: { + active: sessions.filter((s) => !s.revokedAt && new Date(s.expiresAt) > new Date()), + history: sessions, + }, + securityEvents, + creditTransactions: transactions, + }; + } + + /** + * Get session history for a user + */ + private async getSessionHistory(userId: string) { + const db = this.getDatabase(); + + return db + .select({ + id: schema.sessions.id, + createdAt: schema.sessions.createdAt, + expiresAt: schema.sessions.expiresAt, + lastActivityAt: schema.sessions.lastActivityAt, + ipAddress: schema.sessions.ipAddress, + userAgent: schema.sessions.userAgent, + deviceName: schema.sessions.deviceName, + revokedAt: schema.sessions.revokedAt, + }) + .from(schema.sessions) + .where(eq(schema.sessions.userId, userId)) + .orderBy(desc(schema.sessions.createdAt)) + .limit(100); + } + + /** + * Get security events for a user + */ + private async getSecurityEvents(userId: string) { + const db = this.getDatabase(); + + return db + .select({ + id: schema.securityEvents.id, + eventType: schema.securityEvents.eventType, + ipAddress: schema.securityEvents.ipAddress, + userAgent: schema.securityEvents.userAgent, + metadata: schema.securityEvents.metadata, + createdAt: schema.securityEvents.createdAt, + }) + .from(schema.securityEvents) + .where(eq(schema.securityEvents.userId, userId)) + .orderBy(desc(schema.securityEvents.createdAt)) + .limit(100); + } + + /** + * Get transaction history for a user + */ + private async getTransactionHistory(userId: string) { + const db = this.getDatabase(); + + return db + .select({ + id: schema.transactions.id, + type: schema.transactions.type, + status: schema.transactions.status, + amount: schema.transactions.amount, + balanceBefore: schema.transactions.balanceBefore, + balanceAfter: schema.transactions.balanceAfter, + appId: schema.transactions.appId, + description: schema.transactions.description, + createdAt: schema.transactions.createdAt, + completedAt: schema.transactions.completedAt, + }) + .from(schema.transactions) + .where(eq(schema.transactions.userId, userId)) + .orderBy(desc(schema.transactions.createdAt)); + } + + /** + * Get user data for email (before deletion) + */ + async getUserForEmail(userId: string) { + const db = this.getDatabase(); + + const user = await db + .select({ + email: schema.users.email, + name: schema.users.name, + }) + .from(schema.users) + .where(eq(schema.users.id, userId)) + .limit(1); + + return user[0] || null; + } + /** * Query a single backend for user data */ diff --git a/services/mana-core-auth/src/email/email.service.ts b/services/mana-core-auth/src/email/email.service.ts index 7b4887c7d..34df867d2 100644 --- a/services/mana-core-auth/src/email/email.service.ts +++ b/services/mana-core-auth/src/email/email.service.ts @@ -224,6 +224,56 @@ export async function sendVerificationEmail( }); } +/** + * Send account deletion confirmation email + */ +export async function sendAccountDeletionEmail(email: string, userName?: string): Promise { + const name = userName || email.split('@')[0]; + + return sendEmail({ + to: email, + subject: 'Dein ManaCore-Konto wurde gelöscht', + html: ` + + + + + + + +
+

ManaCore

+
+ +

Hallo ${name},

+ +

dein ManaCore-Konto und alle damit verbundenen Daten wurden erfolgreich gelöscht.

+ +
+

Folgende Daten wurden entfernt:

+
    +
  • Benutzerprofil und Anmeldedaten
  • +
  • Alle Sessions und verknüpften Accounts
  • +
  • Credits und Transaktionshistorie
  • +
  • Daten in allen verbundenen Apps
  • +
+
+ +

Diese Aktion ist unwiderruflich. Falls du ManaCore erneut nutzen möchtest, kannst du jederzeit ein neues Konto erstellen.

+ +

Bei Fragen erreichst du uns unter support@mana.how.

+ +
+ +

+ Diese E-Mail wurde automatisch von ManaCore gesendet. +

+ + +`, + }); +} + /** * Send welcome/verification email */ diff --git a/services/mana-core-auth/src/me/me.service.ts b/services/mana-core-auth/src/me/me.service.ts index 49da2699e..9125f2428 100644 --- a/services/mana-core-auth/src/me/me.service.ts +++ b/services/mana-core-auth/src/me/me.service.ts @@ -1,6 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { UserDataService } from '../admin/user-data.service'; import type { UserDataSummary, DeleteUserDataResponse } from '../admin/dto/user-data.dto'; +import { sendAccountDeletionEmail } from '../email/email.service'; /** * Self-service data management for authenticated users. @@ -22,24 +23,38 @@ export class MeService { /** * Export the authenticated user's data as a complete JSON object + * Includes sessions, security events, and credit transactions for GDPR compliance */ - async exportMyData(userId: string): Promise { + async exportMyData(userId: string): Promise { this.logger.log(`User ${userId} exporting own data`); - const summary = await this.userDataService.getUserDataSummary(userId); - - return { - exportedAt: new Date().toISOString(), - exportVersion: '1.0', - data: summary, - }; + return this.userDataService.getFullExportData(userId); } /** * Delete all data for the authenticated user (GDPR right to be forgotten) + * Sends confirmation email after successful deletion */ async deleteMyData(userId: string): Promise { this.logger.log(`User ${userId} requesting deletion of own data`); - return this.userDataService.deleteUserData(userId); + + // Get user data BEFORE deletion for sending confirmation email + const user = await this.userDataService.getUserForEmail(userId); + + // Perform deletion + const result = await this.userDataService.deleteUserData(userId); + + // Send confirmation email if deletion was successful + if (result.success && user?.email) { + try { + await sendAccountDeletionEmail(user.email, user.name || undefined); + this.logger.log(`Account deletion confirmation email sent to ${user.email}`); + } catch (error) { + // Log but don't fail the deletion if email fails + this.logger.error(`Failed to send deletion confirmation email to ${user.email}`, error); + } + } + + return result; } } @@ -48,3 +63,47 @@ export interface UserDataExport { exportVersion: string; data: UserDataSummary; } + +export interface SessionExport { + id: string; + createdAt: Date; + expiresAt: Date; + lastActivityAt: Date | null; + ipAddress: string | null; + userAgent: string | null; + deviceName: string | null; + revokedAt: Date | null; +} + +export interface SecurityEventExport { + id: string; + eventType: string; + ipAddress: string | null; + userAgent: string | null; + metadata: unknown; + createdAt: Date; +} + +export interface TransactionExport { + id: string; + type: string; + status: string; + amount: number; + balanceBefore: number; + balanceAfter: number; + appId: string; + description: string; + createdAt: Date; + completedAt: Date | null; +} + +export interface FullUserDataExport extends UserDataSummary { + exportedAt: string; + exportVersion: string; + sessions: { + active: SessionExport[]; + history: SessionExport[]; + }; + securityEvents: SecurityEventExport[]; + creditTransactions: TransactionExport[]; +}