Echtes Google-/PayPal-Login: OAuth 2.0 + PKCE, eigene Kundendatenbank, DSGVO-Selbstbedienung

Kundenkonten sind jetzt genauso echt wie die PayPal-Zahlung: server-geprüftes OAuth 2.0 mit
PKCE für Google und "Log in with PayPal" (functions/_shared/oauth.js, oauth-handlers.js),
neue D1-Tabelle "customers" (bewusst ohne Passwort-Feld), eigene von der Zugangscode-Schranke
getrennte Sitzungs-Logik (customer-auth.js). Echte DSGVO-Rechte direkt im Kontobereich:
Daten herunterladen (Art. 15/20) und Konto unwiderruflich löschen (Art. 17), Bestellungen
bleiben aus gesetzlichen Gründen erhalten. Datenschutzerklärung entsprechend ergänzt.

Ohne echte Google-/PayPal-Zugangsdaten zeigt der Login-Button ehrlich einen
"noch nicht eingerichtet"-Hinweis statt eine Anmeldung vorzutäuschen.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
qcigano
2026-08-04 01:45:55 +02:00
co-authored by Claude Sonnet 5
parent e80aba5ca6
commit bf0d169015
24 changed files with 985 additions and 127 deletions
+29
View File
@@ -0,0 +1,29 @@
/* =====================================================================
POST /api/account/delete — löscht das echte Kundenkonto unwiderruflich (DSGVO Art. 17,
"Recht auf Löschung"). Bewusst als echtes Selbstbedienungs-Werkzeug gebaut, nicht nur als
"schreib uns eine E-Mail"-Hinweis — genau das, was rechtlich am saubersten ist und was
VanVan vor Anfragen/Beschwerden schützt, die sonst manuell bearbeitet werden müssten.
Löscht NUR den customers-Datensatz (Login-Identität) — bereits aufgegebene Bestellungen
(orders-Tabelle) bleiben bestehen, weil dafür eine gesetzliche Aufbewahrungspflicht gilt
(handels-/steuerrechtliche Aufbewahrungsfristen, § 257 HGB / § 147 AO) — das Konto ist davon
rechtlich unabhängig. ===================================================================== */
import { json } from "../../_shared/http.js";
import { getCustomer, clearCustomerCookieHeader } from "../../_shared/customer-auth.js";
export async function onRequestPost(context) {
const { request, env } = context;
const customer = await getCustomer(request, env);
if (!customer) return json(401, { ok: false, error: "Nicht angemeldet." });
try {
await env.DB.prepare(`DELETE FROM customers WHERE id = ?`).bind(customer.id).run();
} catch {
return json(500, { ok: false, error: "Konto konnte nicht gelöscht werden. Bitte versuche es erneut." });
}
const headers = new Headers({ "Content-Type": "application/json" });
headers.append("Set-Cookie", clearCustomerCookieHeader());
return new Response(JSON.stringify({ ok: true }), { status: 200, headers });
}
+38
View File
@@ -0,0 +1,38 @@
/* =====================================================================
GET /api/account/export — liefert alle über dieses Konto gespeicherten Daten als JSON-Datei
zum Download (DSGVO Art. 15 "Auskunftsrecht" + Art. 20 "Recht auf Datenübertragbarkeit").
Echtes Selbstbedienungs-Werkzeug statt eines manuellen Prozesses. ===================================================================== */
import { getCustomer } from "../../_shared/customer-auth.js";
export async function onRequestGet(context) {
const { request, env } = context;
const customer = await getCustomer(request, env);
if (!customer) {
return new Response(JSON.stringify({ ok: false, error: "Nicht angemeldet." }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}
const daten = {
konto: {
email: customer.email,
name: customer.name,
anmeldeart: customer.provider,
erstelltAm: customer.created_at,
},
hinweis:
"Dies sind alle Daten, die Van's DIY & Bastelbedarf zu deinem Kundenkonto speichert. " +
"Bereits aufgegebene Bestellungen werden getrennt davon aus gesetzlichen Aufbewahrungsgründen " +
"weiter gespeichert (siehe Datenschutzerklärung, Abschnitt Bestellabwicklung).",
};
return new Response(JSON.stringify(daten, null, 2), {
status: 200,
headers: {
"Content-Type": "application/json",
"Content-Disposition": 'attachment; filename="meine-daten.json"',
},
});
}
+13
View File
@@ -0,0 +1,13 @@
/* =====================================================================
POST /api/account/logout — löscht die echte Kunden-Sitzung (Cookie). Die lokale Demo-
Kontoabmeldung (localStorage, siehe scripts/account.ts logout()) läuft weiterhin zusätzlich
im Frontend, damit die Kopfzeile sofort reagiert. ===================================================================== */
import { json } from "../../_shared/http.js";
import { clearCustomerCookieHeader } from "../../_shared/customer-auth.js";
export async function onRequestPost() {
const headers = new Headers({ "Content-Type": "application/json" });
headers.append("Set-Cookie", clearCustomerCookieHeader());
return new Response(JSON.stringify({ ok: true }), { status: 200, headers });
}
+20
View File
@@ -0,0 +1,20 @@
/* =====================================================================
GET /api/account/me — liefert den eingeloggten Kundenkonto-Datensatz (oder ok:false, wenn
keine gültige Sitzung vorliegt). Wird vom Frontend (siehe scripts/account.ts-Brücke in
konto/index.astro + konto/angemeldet.astro) genutzt, um die echte Server-Sitzung mit dem
bisherigen, rein lokalen Demo-Kontosystem abzugleichen — bewusst ok:false statt 401, weil
diese Route aktiv zum "unauffällig nachschauen, ob schon eine Sitzung besteht" gedacht ist,
nicht zur Absicherung einer geschützten Aktion. ===================================================================== */
import { json } from "../../_shared/http.js";
import { getCustomer } from "../../_shared/customer-auth.js";
export async function onRequestGet(context) {
const { request, env } = context;
const customer = await getCustomer(request, env);
if (!customer) return json(200, { ok: false });
return json(200, {
ok: true,
customer: { email: customer.email, name: customer.name, provider: customer.provider, seit: customer.created_at },
});
}
+5
View File
@@ -0,0 +1,5 @@
import { verarbeiteOAuthCallback } from "../../../_shared/oauth-handlers.js";
export function onRequestGet(context) {
return verarbeiteOAuthCallback(context, "google");
}
+5
View File
@@ -0,0 +1,5 @@
import { starteOAuth } from "../../../_shared/oauth-handlers.js";
export function onRequestGet(context) {
return starteOAuth(context, "google");
}
+5
View File
@@ -0,0 +1,5 @@
import { verarbeiteOAuthCallback } from "../../../_shared/oauth-handlers.js";
export function onRequestGet(context) {
return verarbeiteOAuthCallback(context, "paypal");
}
+5
View File
@@ -0,0 +1,5 @@
import { starteOAuth } from "../../../_shared/oauth-handlers.js";
export function onRequestGet(context) {
return starteOAuth(context, "paypal");
}