Add account deletion feature and privacy section
Implement a new endpoint for account deletion in AuthController, allowing users to permanently delete their accounts and associated data. Update AuthService to handle account deletion logic, including confirmation checks and data removal from the database. Enhance frontend with new views and components for account deletion and privacy information, including links in the side menu and profile view. Update mobile app to support account deletion and privacy sections, improving user experience and compliance with data protection standards.
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 176 KiB |
@@ -78,6 +78,8 @@ const pageTitle = computed(() => {
|
||||
'settings-timewish': 'Zeitwünsche',
|
||||
'settings-invite': 'Einladen',
|
||||
'settings-permissions': 'Berechtigungen',
|
||||
'privacy': 'Datenschutz',
|
||||
'account-deletion': 'Account löschen',
|
||||
'export': 'Export',
|
||||
'entries': 'Einträge',
|
||||
'stats': 'Statistiken'
|
||||
@@ -280,4 +282,3 @@ const pageTitle = computed(() => {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -82,7 +82,9 @@ const SECTIONS_USER = [
|
||||
{ label: 'Paßwort ändern', to: '/settings/password' },
|
||||
{ label: 'Zeitwünsche', to: '/settings/timewish' },
|
||||
{ label: 'Zugriffe verwalten', to: '/settings/permissions' },
|
||||
{ label: 'Einladen', to: '/settings/invite' }
|
||||
{ label: 'Einladen', to: '/settings/invite' },
|
||||
{ label: 'Account löschen', to: '/account-loeschen' },
|
||||
{ label: 'Datenschutz', to: '/datenschutz' }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -189,5 +191,3 @@ const toggleSection = (title) => {
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import Register from '../views/Register.vue'
|
||||
import PasswordForgot from '../views/PasswordForgot.vue'
|
||||
import PasswordReset from '../views/PasswordReset.vue'
|
||||
import OAuthCallback from '../views/OAuthCallback.vue'
|
||||
import AccountDeletion from '../views/AccountDeletion.vue'
|
||||
import Privacy from '../views/Privacy.vue'
|
||||
import WeekOverview from '../views/WeekOverview.vue'
|
||||
import Timefix from '../views/Timefix.vue'
|
||||
import Vacation from '../views/Vacation.vue'
|
||||
@@ -57,6 +59,16 @@ const router = createRouter({
|
||||
name: 'oauth-callback',
|
||||
component: OAuthCallback
|
||||
},
|
||||
{
|
||||
path: '/datenschutz',
|
||||
name: 'privacy',
|
||||
component: Privacy
|
||||
},
|
||||
{
|
||||
path: '/account-loeschen',
|
||||
name: 'account-deletion',
|
||||
component: AccountDeletion
|
||||
},
|
||||
|
||||
// Geschützte Routes
|
||||
{
|
||||
@@ -192,4 +204,3 @@ router.beforeEach(async (to, from, next) => {
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
|
||||
123
frontend/src/views/AccountDeletion.vue
Normal file
123
frontend/src/views/AccountDeletion.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div class="legal-page">
|
||||
<div class="legal-card">
|
||||
<AppBrand class="legal-brand" />
|
||||
<h1>Account und Daten löschen</h1>
|
||||
<p class="updated">Stand: 15.05.2026</p>
|
||||
|
||||
<section>
|
||||
<h2>Wie kann ich meinen Account selbst löschen?</h2>
|
||||
<p>
|
||||
Melden Sie sich in der Web-UI an und öffnen Sie
|
||||
<router-link to="/settings/profile">Persönliches</router-link>.
|
||||
Dort befindet sich der Bereich <strong>Account löschen</strong>. Nach Eingabe
|
||||
von <strong>ACCOUNT LÖSCHEN</strong> und Bestätigung wird der Account dauerhaft
|
||||
gelöscht.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Kann ich die Löschung auch ohne App beantragen?</h2>
|
||||
<p>
|
||||
Ja. Senden Sie eine Löschanfrage per E-Mail an
|
||||
<a href="mailto:kontakt@tsschulz.de?subject=TimeClock%20Account%20loeschen">kontakt@tsschulz.de</a>.
|
||||
Bitte nennen Sie die E-Mail-Adresse des TimeClock-Accounts. Zur Vermeidung
|
||||
unberechtigter Löschungen kann eine Bestätigung der Account-Inhaberschaft
|
||||
erforderlich sein.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Welche Daten werden gelöscht?</h2>
|
||||
<p>
|
||||
Gelöscht werden Accountdaten, Anmeldedaten, Google-Verknüpfungen,
|
||||
Zeitbuchungen, Zeitkorrekturen, Urlaubs- und Krankmeldungen,
|
||||
Zeitwünsche, Wochenarbeitszeiten und persönliche Einstellungen.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Welche Daten können vorübergehend verbleiben?</h2>
|
||||
<p>
|
||||
Server-Logs und Backups können technisch bedingt bis zum Ablauf der
|
||||
regulären Löschzyklen weiterbestehen. Daten können außerdem aufbewahrt
|
||||
werden, soweit gesetzliche Pflichten oder die Geltendmachung, Ausübung
|
||||
oder Verteidigung von Rechtsansprüchen dies erfordern.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Wie lange dauert die Bearbeitung?</h2>
|
||||
<p>
|
||||
Eine Löschung über die Web-UI erfolgt unmittelbar. Löschanfragen per E-Mail
|
||||
werden ohne unangemessene Verzögerung bearbeitet. Nutzer erhalten eine
|
||||
Rückmeldung, falls weitere Informationen zur Identitätsprüfung erforderlich sind.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Wo finde ich die Datenschutzerklärung?</h2>
|
||||
<p>
|
||||
Die Datenschutzerklärung ist unter
|
||||
<router-link to="/datenschutz">https://stechuhr3.tsschulz.de/datenschutz</router-link>
|
||||
abrufbar.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AppBrand from '../components/AppBrand.vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.legal-page {
|
||||
min-height: 100vh;
|
||||
background: #f7f7f7;
|
||||
padding: 32px 16px;
|
||||
}
|
||||
|
||||
.legal-card {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.legal-brand {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 0 6px;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.updated {
|
||||
margin: 0 0 24px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 18px;
|
||||
margin: 0 0 8px;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #2f8f3a;
|
||||
}
|
||||
</style>
|
||||
@@ -101,6 +101,10 @@
|
||||
<router-link to="/password-forgot" class="link">Passwort vergessen</router-link>
|
||||
|
|
||||
<router-link to="/register" class="link">Registrieren</router-link>
|
||||
|
|
||||
<router-link to="/account-loeschen" class="link">Account löschen</router-link>
|
||||
|
|
||||
<router-link to="/datenschutz" class="link">Datenschutz</router-link>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
209
frontend/src/views/Privacy.vue
Normal file
209
frontend/src/views/Privacy.vue
Normal file
@@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<div class="legal-page">
|
||||
<div class="legal-card">
|
||||
<AppBrand class="legal-brand" />
|
||||
<h1>Datenschutzerklärung</h1>
|
||||
<p class="updated">Stand: 15.05.2026</p>
|
||||
<p class="notice">
|
||||
Hinweis vor Veröffentlichung: Die ladungsfähige Anschrift des Verantwortlichen
|
||||
muss vor Einreichung im Play Store ergänzt werden.
|
||||
</p>
|
||||
|
||||
<section>
|
||||
<h2>Wer ist verantwortlich?</h2>
|
||||
<p>
|
||||
Verantwortlich für die Datenverarbeitung in TimeClock ist Torsten Schulz.
|
||||
Anschrift: <strong>BITTE VOR VERÖFFENTLICHUNG ERGÄNZEN</strong>.
|
||||
Kontakt: <a href="mailto:kontakt@tsschulz.de">kontakt@tsschulz.de</a>.
|
||||
</p>
|
||||
<p>
|
||||
Ein Datenschutzbeauftragter ist nicht benannt, sofern keine gesetzliche
|
||||
Pflicht zur Benennung besteht.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Wofür wird TimeClock genutzt?</h2>
|
||||
<p>
|
||||
TimeClock ist eine Zeiterfassungsanwendung. Die App verarbeitet Daten zur
|
||||
Anmeldung, Zeiterfassung, Korrektur, Auswertung und Verwaltung persönlicher
|
||||
Arbeitszeiten.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Welche Daten werden verarbeitet?</h2>
|
||||
<ul>
|
||||
<li>Accountdaten: Name, E-Mail-Adresse, Passwort-Hash, Login-Status</li>
|
||||
<li>Zeiterfassungsdaten: Kommen, Gehen, Pausen, Korrekturen, Zeitstempel</li>
|
||||
<li>Abwesenheitsdaten: Urlaub, Krankheit, Feiertage, Arbeitszeitwünsche</li>
|
||||
<li>Einstellungen: Bundesland, Soll-Arbeitszeit, Anzeigeoptionen, Berechtigungen</li>
|
||||
<li>Technische Daten: IP-Adresse, Zugriffszeitpunkt, Geräte-/Browserinformationen, Server-Logs</li>
|
||||
<li>Bei optionaler Google-Anmeldung: Google-Konto-ID, E-Mail-Adresse und Anzeigename</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Auf welchen Rechtsgrundlagen erfolgt die Verarbeitung?</h2>
|
||||
<ul>
|
||||
<li>Art. 6 Abs. 1 lit. b DSGVO für Account, Login und die Bereitstellung der Zeiterfassung</li>
|
||||
<li>Art. 6 Abs. 1 lit. c DSGVO, soweit gesetzliche Aufbewahrungs- oder Nachweispflichten bestehen</li>
|
||||
<li>Art. 6 Abs. 1 lit. f DSGVO für Sicherheit, Fehleranalyse, Missbrauchsschutz und Server-Logs</li>
|
||||
<li>Art. 6 Abs. 1 lit. a DSGVO für optionale Funktionen, soweit eine Einwilligung erforderlich ist</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Wird Google-Anmeldung verwendet?</h2>
|
||||
<p>
|
||||
Ja, optional. Nutzer können sich mit Google anmelden oder ein bestehendes
|
||||
Konto mit Google verknüpfen. Dabei werden nur die für die Anmeldung
|
||||
erforderlichen Profilinformationen verwendet. Das Google-Passwort wird
|
||||
nicht an TimeClock übermittelt oder gespeichert.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Wer erhält Daten?</h2>
|
||||
<p>
|
||||
Personenbezogene Daten werden nicht verkauft. Eine Weitergabe erfolgt nur,
|
||||
soweit sie für Betrieb, Authentifizierung, Hosting, E-Mail-Versand,
|
||||
Fehleranalyse oder gesetzliche Pflichten erforderlich ist. Eingesetzte
|
||||
Kategorien von Empfängern sind Hosting-/Serveranbieter, E-Mail-Dienstleister
|
||||
und Google als OAuth-Anbieter, wenn Google-Anmeldung genutzt wird.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Werden Daten in Drittländer übertragen?</h2>
|
||||
<p>
|
||||
Beim Einsatz von Google-Anmeldung kann eine Verarbeitung durch Google auch
|
||||
außerhalb der EU/des EWR stattfinden. Für den regulären Betrieb der
|
||||
TimeClock-Datenbank ist kein Verkauf oder keine werbliche Weitergabe von
|
||||
Nutzerdaten vorgesehen.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Wie lange werden Daten gespeichert?</h2>
|
||||
<p>
|
||||
Account-, Zeit- und Einstellungsdaten werden grundsätzlich gespeichert,
|
||||
solange der Account besteht. Nach Account-Löschung werden die aktiven
|
||||
Accountdaten, Anmeldedaten, Google-Verknüpfungen, Zeitbuchungen,
|
||||
Korrekturen, Urlaubs- und Krankmeldungen sowie persönliche Einstellungen
|
||||
gelöscht. Server-Logs und Backups können technisch bedingt für einen
|
||||
begrenzten Zeitraum weiterbestehen und werden im Rahmen der regulären
|
||||
Löschzyklen entfernt. Daten können länger aufbewahrt werden, wenn gesetzliche
|
||||
Pflichten oder die Geltendmachung, Ausübung oder Verteidigung von
|
||||
Rechtsansprüchen dies erfordern.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Wie kann der Account gelöscht werden?</h2>
|
||||
<p>
|
||||
Eingeloggte Nutzer können ihren Account in der Web-UI unter
|
||||
<strong>Persönliches</strong> löschen. Zusätzlich steht eine öffentliche
|
||||
Löschanfrage-Seite zur Verfügung:
|
||||
<router-link to="/account-loeschen">Account und Daten löschen</router-link>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Welche Rechte haben Nutzer?</h2>
|
||||
<p>
|
||||
Nutzer können Auskunft, Berichtigung, Löschung, Einschränkung der
|
||||
Verarbeitung und Datenübertragbarkeit verlangen sowie einer Verarbeitung
|
||||
widersprechen. Außerdem besteht ein Beschwerderecht bei einer zuständigen
|
||||
Datenschutzaufsichtsbehörde. Anfragen können an
|
||||
<a href="mailto:kontakt@tsschulz.de">kontakt@tsschulz.de</a> gerichtet werden.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Wie werden Daten geschützt?</h2>
|
||||
<p>
|
||||
Die Übertragung erfolgt über HTTPS. Passwörter werden nicht im Klartext
|
||||
gespeichert. Zugriffstokens und OAuth-Verknüpfungen werden serverseitig
|
||||
verwaltet. Zugriffe werden auf das erforderliche Maß beschränkt.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Kann sich diese Erklärung ändern?</h2>
|
||||
<p>
|
||||
Ja. Diese Datenschutzerklärung kann angepasst werden, wenn sich Funktionen,
|
||||
technische Abläufe oder rechtliche Anforderungen ändern.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AppBrand from '../components/AppBrand.vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.legal-page {
|
||||
min-height: 100vh;
|
||||
background: #f7f7f7;
|
||||
padding: 32px 16px;
|
||||
}
|
||||
|
||||
.legal-card {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.legal-brand {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 0 6px;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.updated {
|
||||
margin: 0 0 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.notice {
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #f0ad4e;
|
||||
background: #fcf8e3;
|
||||
border-radius: 4px;
|
||||
color: #6f4e00;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 18px;
|
||||
margin: 0 0 8px;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
p,
|
||||
li {
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #2f8f3a;
|
||||
}
|
||||
</style>
|
||||
@@ -106,6 +106,31 @@
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<section class="danger-zone">
|
||||
<h2>Account löschen</h2>
|
||||
<p>
|
||||
Löscht den Account, die Anmeldung, Google-Verknüpfungen, Zeitbuchungen,
|
||||
Korrekturen, Urlaubs- und Krankmeldungen sowie persönliche Einstellungen dauerhaft.
|
||||
</p>
|
||||
<label for="deleteConfirmation">
|
||||
Zur Bestätigung bitte <strong>ACCOUNT LÖSCHEN</strong> eingeben
|
||||
</label>
|
||||
<input
|
||||
id="deleteConfirmation"
|
||||
v-model="deleteConfirmation"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-danger"
|
||||
:disabled="deleting || deleteConfirmation !== deletePhrase"
|
||||
@click="deleteAccount"
|
||||
>
|
||||
{{ deleting ? 'Account wird gelöscht...' : 'Account dauerhaft löschen' }}
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Modal-Komponente -->
|
||||
@@ -138,6 +163,9 @@ const authStore = useAuthStore()
|
||||
const availableStates = ref([])
|
||||
const identities = ref([])
|
||||
const loading = ref(false)
|
||||
const deleting = ref(false)
|
||||
const deletePhrase = 'ACCOUNT LÖSCHEN'
|
||||
const deleteConfirmation = ref('')
|
||||
|
||||
const { showModal, modalConfig, alert, confirm, onConfirm, onCancel } = useModal()
|
||||
|
||||
@@ -302,6 +330,35 @@ async function unlinkGoogle() {
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteAccount() {
|
||||
const confirmed = await confirm(
|
||||
'Der Account und alle personenbezogenen Daten werden dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.',
|
||||
'Account dauerhaft löschen'
|
||||
)
|
||||
if (!confirmed) return
|
||||
|
||||
try {
|
||||
deleting.value = true
|
||||
const response = await fetch(`${API_URL}/auth/account`, {
|
||||
method: 'DELETE',
|
||||
headers: authStore.getAuthHeaders(),
|
||||
body: JSON.stringify({ confirmation: deleteConfirmation.value })
|
||||
})
|
||||
const result = await response.json()
|
||||
if (!response.ok || !result.success) {
|
||||
throw new Error(result.error || 'Account konnte nicht gelöscht werden')
|
||||
}
|
||||
|
||||
await alert('Account wurde gelöscht', 'Erfolg')
|
||||
authStore.clearAuth()
|
||||
router.push('/login')
|
||||
} catch (error) {
|
||||
await alert(`Fehler: ${error.message}`, 'Fehler')
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Initiales Laden
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
@@ -408,6 +465,41 @@ onMounted(async () => {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.danger-zone {
|
||||
margin-top: 32px;
|
||||
padding-top: 24px;
|
||||
border-top: 1px solid #f0c2c2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.danger-zone h2 {
|
||||
margin: 0;
|
||||
color: #a94442;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.danger-zone p {
|
||||
margin: 0;
|
||||
color: #555;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.danger-zone label {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.danger-zone input {
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
@@ -434,4 +526,13 @@ onMounted(async () => {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(76, 175, 80, 0.4);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #d9534f;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover:not(:disabled) {
|
||||
background: #c9302c;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -83,6 +83,8 @@
|
||||
<div class="buttons">
|
||||
Bereits registriert?
|
||||
<router-link to="/login" class="link">Jetzt einloggen</router-link>
|
||||
|
|
||||
<router-link to="/datenschutz" class="link">Datenschutz</router-link>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user