Impressum, Datenschutzerklärung und DSGVO-Löschfunktion

- /impressum (§ 5 DDG) und /datenschutz als öffentliche Seiten im
  Brand-Look; Betreiber-Angaben (Name, Anschrift, E-Mail, Zusatz)
  liegen als Settings und sind bewusst Owner-only — Team-Mitglieder
  mit settings-Scope kommen an die private Anschrift nicht heran
- Die Datenschutzerklärung beschreibt die tatsächliche Verarbeitung
  dieser Instanz: Server-Logs, Session-Cookies (einwilligungsfrei nach
  § 25 Abs. 2 TDDDG, kein Tracking), Discord-Login, Level, Wünsche,
  Geburtstage, Playtester/Alpha-Keys, Bewerbungen, Tickets/Modmail,
  Erinnerungen, Moderation und Audit-Log
- DSGVO Art. 17: DELETE /api/profile löscht XP, Geburtstag,
  Playtester-Eintrag, Abstimmungen, Erinnerungen, Rollen-Sicherung,
  Giveaway-Teilnahmen und gibt den Alpha-Key wieder frei; Button im
  Profil unter „Meine Daten", Löschung landet im Audit-Log
- Footer verlinkt beide Rechtstexte, neues GET /api/legal (öffentlich)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 13:19:33 +02:00
co-authored by Claude Fable 5
parent 676fc45bb1
commit 8853bce2ec
10 changed files with 464 additions and 4 deletions
+197
View File
@@ -0,0 +1,197 @@
// Datenschutzerklärung — beschreibt die tatsächliche Verarbeitung dieser Instanz
// (Discord-Login, Community-Funktionen, Moderation). Betreiber-Angaben aus den Settings.
import { useEffect, useState } from 'react';
import { apiGet } from '../api.js';
export default function Datenschutz() {
const [data, setData] = useState(null);
useEffect(() => {
apiGet('/api/legal').then(setData).catch(() => setData({ legal: {} }));
window.scrollTo(0, 0);
}, []);
const legal = data?.legal ?? {};
const brand = data?.brand ?? 'Wir';
return (
<>
<section className="page-header">
<div className="page-bg-text">LEGAL</div>
<div className="page-header-grid" aria-hidden="true" />
<div className="page-header-content">
<div className="page-tag">// DSGVO</div>
<h1 className="page-title">Datenschutz</h1>
<p className="page-subtitle">
Welche Daten hier verarbeitet werden, warum und wie du sie wieder loswirst.
</p>
</div>
</section>
<section className="content legal-text">
<h2 className="settings-title">// 1. Verantwortlicher</h2>
<p>
Verantwortlich für die Datenverarbeitung auf dieser Webseite im Sinne der
Datenschutz-Grundverordnung (DSGVO) ist:<br /><br />
{legal.name || '—'}<br />
{(legal.address || '').split('\n').map((line, i) => (
<span key={i}>{line}<br /></span>
))}
{legal.email && <>E-Mail: <a href={`mailto:${legal.email}`}>{legal.email}</a></>}
</p>
<h2 className="settings-title">// 2. Aufruf der Webseite (Server-Logs)</h2>
<p>
Beim Aufruf dieser Seite verarbeitet der Server technische Zugriffsdaten:
IP-Adresse, Zeitpunkt der Anfrage, aufgerufene Adresse, HTTP-Statuscode und
die Browserkennung. Diese Daten sind technisch erforderlich, um die Seite
auszuliefern und Störungen sowie Angriffe erkennen zu können. Rechtsgrundlage
ist Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse am sicheren und
stabilen Betrieb). Die Logdaten werden nicht mit anderen Datenquellen
zusammengeführt und nach kurzer Zeit im Rahmen der üblichen Rotation gelöscht.
</p>
<h2 className="settings-title">// 3. Cookies</h2>
<p>
Diese Seite verwendet ausschließlich technisch notwendige Cookies. Konkret
wird nach einem Login ein signiertes Sitzungs-Cookie gesetzt, damit du
eingeloggt bleibst; ein weiteres kurzlebiges Cookie sichert den Login-Vorgang
gegen Missbrauch ab (CSRF-Schutz). Diese Cookies sind für den von dir
gewünschten Dienst unbedingt erforderlich und daher nach § 25 Abs. 2 Nr. 2
TDDDG einwilligungsfrei. Es findet <strong>kein Tracking</strong>, keine
Reichweitenmessung und keine Werbung statt. Die verwendeten Schriftarten
werden lokal von diesem Server ausgeliefert es entsteht dabei keine
Verbindung zu Dritten.
</p>
<h2 className="settings-title">// 4. Login mit Discord</h2>
<p>
Für den Mitglieder-Bereich kannst du dich freiwillig mit deinem
Discord-Konto anmelden (OAuth2, Berechtigung <code>identify</code>). Dabei
erhalten wir von Discord deine Benutzer-ID, deinen Benutzernamen und dein
Profilbild. Diese Angaben werden in dem oben genannten Sitzungs-Cookie
gespeichert; ein Zugriffstoken speichern wir nicht. Zusätzlich prüfen wir
beim Login, ob du Mitglied unseres Discord-Servers bist, da der Zugang
Mitgliedern vorbehalten ist. Rechtsgrundlage ist Art. 6 Abs. 1 lit. a DSGVO
(Einwilligung durch die Anmeldung) bzw. lit. b DSGVO für die Bereitstellung
des Mitglieder-Bereichs. Du kannst dich jederzeit über den Logout abmelden;
das Cookie wird dann gelöscht.
</p>
<p>
Anbieter ist Discord Netherlands BV bzw. Discord Inc., 444 De Haro Street,
San Francisco, CA 94107, USA. Beim Login und beim Anzeigen von Profilbildern
(<code>cdn.discordapp.com</code>) werden Daten an Discord übertragen und
gegebenenfalls in den USA verarbeitet. Details findest du in der
Datenschutzerklärung von Discord.
</p>
<h2 className="settings-title">// 5. Community-Funktionen</h2>
<p>
{brand} betreibt einen eigenen Discord-Bot, der zur Verwaltung der Community
Daten speichert. Verarbeitet werden je nachdem, welche Funktionen du nutzt:
</p>
<ul className="legal-list">
<li>
<strong>Level-System:</strong> Benutzer-ID, Anzeigename und gesammelte
Erfahrungspunkte, um eine Bestenliste anzuzeigen.
</li>
<li>
<strong>Feature-Wünsche &amp; Abstimmungen:</strong> dein Anzeigename und der
Text deines Vorschlags, bei Abstimmungen deine Benutzer-ID (damit jede
Person nur einmal abstimmt).
</li>
<li>
<strong>Geburtstage:</strong> Tag und Monat deines Geburtstags nur wenn du
sie selbst per Befehl einträgst, jederzeit selbst wieder entfernbar.
</li>
<li>
<strong>Playtester &amp; Zugangsschlüssel:</strong> Benutzer-ID, Anzeigename und
Zeitpunkt der Anmeldung sowie ein dir zugeteilter Alpha-Key.
</li>
<li>
<strong>Bewerbungen:</strong> deine Antworten auf die Formularfragen sowie das
Ergebnis der Prüfung.
</li>
<li>
<strong>Support &amp; Kontakt:</strong> Inhalte von Tickets und Direktnachrichten
an den Bot, damit das Team antworten kann; beim Schließen eines Tickets
wird auf Wunsch ein Gesprächsverlauf erstellt.
</li>
<li>
<strong>Erinnerungen:</strong> Text und Zeitpunkt, bis die Erinnerung
zugestellt wurde.
</li>
<li>
<strong>Veröffentlichte Beiträge:</strong> Entwicklungsberichte und
Community-Screenshots werden mit Anzeigenamen archiviert und auf dieser
Webseite dargestellt.
</li>
</ul>
<p>
Rechtsgrundlage ist Art. 6 Abs. 1 lit. a DSGVO (Einwilligung) für alle
freiwilligen Angaben und Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse
am Betrieb einer funktionierenden Community) für den technischen Betrieb.
</p>
<h2 className="settings-title">// 6. Moderation und Protokolle</h2>
<p>
Zur Durchsetzung der Serverregeln werden Verwarnungen mit Benutzer-ID, Grund
und Zeitpunkt gespeichert. Änderungen im Administrationsbereich der Webseite
werden in einem Protokoll festgehalten (wer wann welche Einstellung geändert
hat), um Missbrauch nachvollziehen zu können. Rechtsgrundlage ist Art. 6
Abs. 1 lit. f DSGVO.
</p>
<h2 className="settings-title">// 7. Speicherdauer</h2>
<p>
Wir speichern personenbezogene Daten so lange, wie du Mitglied der Community
bist oder die jeweilige Funktion nutzt. Verlässt du den Discord-Server, kannst
du die Löschung deiner Daten verlangen (siehe Punkt 9). Sitzungs-Cookies
laufen spätestens nach sieben Tagen ab. Von der Datenbank werden verschlüsselte
Sicherungskopien erstellt, die nach spätestens 14 Tagen automatisch gelöscht
werden.
</p>
<h2 className="settings-title">// 8. Weitergabe an Dritte</h2>
<p>
Eine Weitergabe deiner Daten zu Werbezwecken findet nicht statt. Ein Verkauf
von Daten erfolgt nicht. Übermittlungen erfolgen nur an Discord, soweit dies
für die Nutzung des Discord-Servers und des Logins technisch notwendig ist.
</p>
<h2 className="settings-title">// 9. Deine Rechte</h2>
<p>
Du hast jederzeit das Recht auf Auskunft über die zu deiner Person
gespeicherten Daten (Art. 15 DSGVO), auf Berichtigung (Art. 16), auf Löschung
(Art. 17), auf Einschränkung der Verarbeitung (Art. 18), auf
Datenübertragbarkeit (Art. 20) sowie auf Widerspruch gegen die Verarbeitung
(Art. 21). Eine erteilte Einwilligung kannst du jederzeit mit Wirkung für die
Zukunft widerrufen.
</p>
<p>
<strong>Selbst löschen:</strong> Eingeloggte Mitglieder können ihre gespeicherten
Daten direkt auf der Profilseite löschen dort findest du unten den Bereich
Meine Daten löschen". Beiträge, die im Discord stehen (etwa Screenshots oder
Nachrichten), löschst du bitte dort; melde dich sonst gerne bei uns.
</p>
<p>
Für Anfragen genügt eine formlose Nachricht an
{' '}{legal.email ? <a href={`mailto:${legal.email}`}>{legal.email}</a> : 'die im Impressum genannte Adresse'}.
</p>
<h2 className="settings-title">// 10. Beschwerderecht</h2>
<p>
Wenn du der Ansicht bist, dass die Verarbeitung deiner Daten gegen die DSGVO
verstößt, kannst du dich bei einer Datenschutz-Aufsichtsbehörde beschweren —
etwa bei der für deinen Wohnsitz zuständigen Landesdatenschutzbehörde.
</p>
<h2 className="settings-title">// 11. Änderungen</h2>
<p>
Wir passen diese Datenschutzerklärung an, wenn sich die angebotenen Funktionen
ändern. Es gilt jeweils die hier veröffentlichte Fassung.
</p>
</section>
</>
);
}
+97
View File
@@ -0,0 +1,97 @@
// Impressum nach § 5 DDG — die Betreiber-Angaben kommen aus den Settings
// (System-Tab, nur Owner), damit hier nichts hartkodiert im Repo landet.
import { useEffect, useState } from 'react';
import { apiGet } from '../api.js';
export default function Impressum() {
const [data, setData] = useState(null);
useEffect(() => {
apiGet('/api/legal').then(setData).catch(() => setData({ legal: {} }));
window.scrollTo(0, 0);
}, []);
const legal = data?.legal ?? {};
const incomplete = !legal.name || !legal.address || !legal.email;
return (
<>
<section className="page-header">
<div className="page-bg-text">LEGAL</div>
<div className="page-header-grid" aria-hidden="true" />
<div className="page-header-content">
<div className="page-tag">// Pflichtangaben</div>
<h1 className="page-title">Impressum</h1>
<p className="page-subtitle">Angaben gemäß § 5 Digitale-Dienste-Gesetz (DDG).</p>
</div>
</section>
<section className="content legal-text">
{incomplete && (
<p className="notice">
Die Impressums-Angaben sind noch nicht vollständig hinterlegt
(Setup System Impressum).
</p>
)}
<h2 className="settings-title">// Diensteanbieter</h2>
<p>
{legal.name || '—'}<br />
{(legal.address || '').split('\n').map((line, i) => (
<span key={i}>{line}<br /></span>
))}
</p>
<h2 className="settings-title">// Kontakt</h2>
<p>
E-Mail: {legal.email ? <a href={`mailto:${legal.email}`}>{legal.email}</a> : '—'}
</p>
{legal.extra && (
<>
<h2 className="settings-title">// Weitere Angaben</h2>
<p>{legal.extra.split('\n').map((line, i) => <span key={i}>{line}<br /></span>)}</p>
</>
)}
<h2 className="settings-title">// Verantwortlich für den Inhalt</h2>
<p>
Verantwortlich für journalistisch-redaktionelle Inhalte nach § 18 Abs. 2 MStV:
{' '}{legal.name || '—'}{legal.address ? `, ${legal.address.replace(/\n/g, ', ')}` : ''}
</p>
<h2 className="settings-title">// Haftung für Inhalte</h2>
<p>
Als Diensteanbieter sind wir für eigene Inhalte auf diesen Seiten nach den
allgemeinen Gesetzen verantwortlich. Wir sind jedoch nicht verpflichtet,
übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach
Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen.
Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen
nach den allgemeinen Gesetzen bleiben hiervon unberührt. Eine diesbezügliche
Haftung ist jedoch erst ab dem Zeitpunkt der Kenntnis einer konkreten
Rechtsverletzung möglich. Bei Bekanntwerden entsprechender Rechtsverletzungen
entfernen wir diese Inhalte umgehend.
</p>
<h2 className="settings-title">// Haftung für Links</h2>
<p>
Unser Angebot enthält Links zu externen Webseiten Dritter, auf deren Inhalte
wir keinen Einfluss haben. Deshalb können wir für diese fremden Inhalte auch
keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der
jeweilige Anbieter oder Betreiber der Seiten verantwortlich. Bei Bekanntwerden
von Rechtsverletzungen entfernen wir derartige Links umgehend.
</p>
<h2 className="settings-title">// Urheberrecht</h2>
<p>
Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten
unterliegen dem deutschen Urheberrecht. Beiträge Dritter insbesondere
Screenshots aus der Community-Galerie sind als solche gekennzeichnet.
Sollten Sie auf eine Urheberrechtsverletzung aufmerksam werden, bitten wir um
einen entsprechenden Hinweis; bei Bekanntwerden entfernen wir die Inhalte
umgehend.
</p>
</section>
</>
);
}
+36 -1
View File
@@ -1,7 +1,8 @@
// Member-Profil: Rang & XP, Discord-Rollen, Alpha-Key, Playtester-Status
// + Rollen-Selfservice (Rollen-Menüs direkt im Browser togglen).
import { useEffect, useState } from 'react';
import { apiGet, apiPost } from '../api.js';
import { Link } from 'react-router-dom';
import { apiGet, apiPost, apiDelete } from '../api.js';
function fmtDate(iso) {
if (!iso) return '—';
@@ -15,6 +16,7 @@ export default function Profil({ me }) {
const [error, setError] = useState(null);
const [busy, setBusy] = useState('');
const [copied, setCopied] = useState(false);
const [wipeMsg, setWipeMsg] = useState('');
useEffect(() => {
apiGet('/api/profile').then(setProfile).catch((e) => setError(e?.message ?? 'Fehler'));
@@ -40,6 +42,23 @@ export default function Profil({ me }) {
setBusy('');
}
async function wipeData() {
if (!window.confirm(
'Wirklich alle gespeicherten Daten löschen?\n\n' +
'Betrifft: XP/Level, Geburtstag, Playtester-Eintrag, Abstimmungen, ' +
'Erinnerungen, Rollen-Sicherung und deinen Alpha-Key.\n\n' +
'Das lässt sich nicht rückgängig machen.'
)) return;
try {
const res = await apiDelete('/api/profile');
const total = Object.values(res.deleted).reduce((a, b) => a + b, 0);
setWipeMsg(`${total} Einträge gelöscht.`);
apiGet('/api/profile').then(setProfile).catch(() => {});
} catch {
setWipeMsg('⚠️ Löschen fehlgeschlagen — melde dich bitte beim Team.');
}
}
async function copyKey() {
try {
await navigator.clipboard.writeText(profile.alphaKey);
@@ -156,6 +175,22 @@ export default function Profil({ me }) {
))}
</div>
)}
{/* DSGVO Art. 17: eigene Daten löschen */}
<div className="roadmap-group danger-zone">
<h2 className="settings-title">// Meine Daten</h2>
<p className="field-hint">
Du kannst jederzeit alle Daten löschen, die hier zu dir gespeichert
sind: XP/Level, Geburtstag, Playtester-Eintrag, Abstimmungen,
Erinnerungen, Rollen-Sicherung und einen zugeteilten Alpha-Key.
Was du im Discord gepostet hast, löschst du bitte dort. Details im{' '}
<Link to="/datenschutz" style={{ textDecoration: 'underline' }}>Datenschutz</Link>.
</p>
<div className="field-row" style={{ marginTop: '.8rem' }}>
<button className="btn btn-danger" onClick={wipeData}>Meine Daten löschen</button>
</div>
{wipeMsg && <p className="notice" style={{ padding: '.6rem 0 0' }}>{wipeMsg}</p>}
</div>
</>
)}
</section>
+25
View File
@@ -1219,6 +1219,31 @@ export default function Settings({ me }) {
</div>
<div className="settings-section">
<h2 className="settings-title">// Impressum &amp; Datenschutz</h2>
<p className="field-hint" style={{ marginBottom: '.9rem' }}>
Erscheint auf <code>/impressum</code> und <code>/datenschutz</code>. Für § 5 DDG
ist eine ladungsfähige Anschrift nötig (kein Postfach) nur der Owner sieht
und ändert diese Felder.
</p>
<div className="field">
<label>Name / Verantwortlicher</label>
<input type="text" placeholder="Vorname Nachname" value={form.legal_name ?? ''} onChange={(e) => setForm({ ...form, legal_name: e.target.value })} />
</div>
<div className="field">
<label>Anschrift <span className="field-hint">mehrzeilig: Straße, PLZ Ort, Land</span></label>
<textarea rows={3} placeholder={'Musterstraße 1\n12345 Musterstadt\nDeutschland'} value={form.legal_address ?? ''} onChange={(e) => setForm({ ...form, legal_address: e.target.value })} />
</div>
<div className="field">
<label>Kontakt-E-Mail <span className="field-hint">Pflichtangabe muss erreichbar sein</span></label>
<input type="email" placeholder="kontakt@d4rkst3r.de" value={form.legal_email ?? ''} onChange={(e) => setForm({ ...form, legal_email: e.target.value })} />
</div>
<div className="field">
<label>Weitere Angaben <span className="field-hint">optional: USt-ID, Telefon, Handelsregister </span></label>
<textarea rows={2} placeholder="USt-IdNr.: DE123456789" value={form.legal_extra ?? ''} onChange={(e) => setForm({ ...form, legal_extra: e.target.value })} />
</div>
</div>
<div className="settings-section">
<h2 className="settings-title">// Web-Login (Member)</h2>
<div className="field">