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
+28 -8
View File
@@ -200,14 +200,16 @@ export const ui = {
},
account: {
eyebrow: "Mein Konto", title: "Anmelden oder als Gast bestellen",
todoNote: "Phase 1 Demo-Anmeldung ohne echte Passwortprüfung: Jede E-Mail-Adresse funktioniert, das Passwort-Feld ist reine Vorschau. Die Bestellungen im Konto sind Beispieldaten. Eine echte Anmeldung mit Datenbank kommt in Phase 2.",
todoNote: "Anmeldung mit Google/PayPal ist technisch fertig und echt (eigenes Kundenkonto, sichere Sitzung) — bis Google/PayPal-Zugangsdaten hinterlegt sind, meldet der Button einen klaren Hinweis statt eine Anmeldung vorzutäuschen. Die E-Mail/Passwort-Anmeldung darunter ist weiterhin eine Vorschau ohne echte Passwortprüfung.",
loginTitle: "Anmelden", email: "E-Mail", password: "Passwort", loginButton: "Anmelden", forgotPassword: "Passwort vergessen?",
newTitle: "Neu hier?", newText: "Lege ein Konto an, um Bestellhistorie, Wunschliste und Sendungsverfolgung an einem Ort zu haben oder bestelle einfach als Gast, ganz ohne Konto.",
guestButton: "Als Gast weiter einkaufen",
loginError: "Bitte gib eine E-Mail-Adresse ein.",
socialHeading: "Schneller anmelden mit",
socialGoogle: "Mit Google anmelden", socialPaypal: "Mit PayPal anmelden",
socialComingSoon: "Kommt in Phase 2: Sobald die echte Konto-Anbindung steht, kannst du dich hier direkt anmelden — ganz ohne neues Passwort.",
loginErrorUnconfigured: "Diese Anmeldeart ist noch nicht eingerichtet. Bitte melde dich per E-Mail an oder versuche es später erneut.",
loginErrorDenied: "Anmeldung abgebrochen.",
loginErrorGeneric: "Anmeldung fehlgeschlagen. Bitte versuche es erneut.",
orDivider: "oder mit E-Mail",
showPassword: "Passwort anzeigen", hidePassword: "Passwort verbergen",
rememberMe: "Angemeldet bleiben",
@@ -220,6 +222,9 @@ export const ui = {
previewNote: "Phase 1: Deine Anmeldung, dein Profil und deine Rezensionen werden schon jetzt echt auf diesem Gerät gespeichert. Nur die Bestellungen unten sind Beispieldaten zur Gestaltung — echte Bestellungen kommen in Phase 2 mit Zahlungsanbindung und Datenbank (Cloudflare Worker + D1).",
notLoggedIn: "Du bist nicht angemeldet — du wirst zur Anmeldung weitergeleitet …",
logout: "Abmelden",
exportDataButton: "📥 Meine Daten herunterladen", deleteAccountButton: "🗑️ Konto löschen",
deleteAccountConfirm: "Dein Konto wird unwiderruflich gelöscht (nicht deine bereits aufgegebenen Bestellungen, die aus gesetzlichen Gründen aufbewahrt werden müssen). Fortfahren?",
deleteAccountError: "Konto konnte nicht gelöscht werden. Bitte versuche es erneut.",
navLabel: "Kontobereich-Navigation",
statOrders: "Bestellungen", statWishlist: "Wunschliste", statOpen: "Offene Sendungen", statData: "Meine Daten", statLoyalty: "Treuebonus",
datenHeaderBtn: "👤 Meine Daten",
@@ -502,14 +507,16 @@ export const ui = {
},
account: {
eyebrow: "My Account", title: "Sign in or check out as a guest",
todoNote: "Phase 1 demo sign-in without real password checking: any email address works, the password field is a preview only. Orders shown in the account are example data. A real login with a database is coming in Phase 2.",
todoNote: "Sign-in with Google/PayPal is technically finished and real (your own customer account, secure session) — until Google/PayPal credentials are set up, the button shows a clear notice instead of faking a sign-in. The email/password sign-in below remains a preview without real password checking.",
loginTitle: "Sign in", email: "Email", password: "Password", loginButton: "Sign in", forgotPassword: "Forgot password?",
newTitle: "New here?", newText: "Create an account to keep order history, wishlist and tracking in one place or simply check out as a guest, no account needed.",
guestButton: "Continue shopping as guest",
loginError: "Please enter an email address.",
socialHeading: "Sign in faster with",
socialGoogle: "Sign in with Google", socialPaypal: "Sign in with PayPal",
socialComingSoon: "Coming in Phase 2: once the real account backend is live, you'll be able to sign in here directly — no new password needed.",
loginErrorUnconfigured: "This sign-in method isn't set up yet. Please sign in by email or try again later.",
loginErrorDenied: "Sign-in cancelled.",
loginErrorGeneric: "Sign-in failed. Please try again.",
orDivider: "or with email",
showPassword: "Show password", hidePassword: "Hide password",
rememberMe: "Keep me signed in",
@@ -522,6 +529,9 @@ export const ui = {
previewNote: "Phase 1: your sign-in, your profile and your reviews are already genuinely saved on this device. Only the orders below are example data for design purposes — real orders arrive in Phase 2 with payment integration and a database (Cloudflare Worker + D1).",
notLoggedIn: "You're not signed in — redirecting you to sign in …",
logout: "Sign out",
exportDataButton: "📥 Download my data", deleteAccountButton: "🗑️ Delete account",
deleteAccountConfirm: "Your account will be deleted permanently (not your past orders, which must be kept for legal reasons). Continue?",
deleteAccountError: "Account could not be deleted. Please try again.",
navLabel: "Account section navigation",
statOrders: "Orders", statWishlist: "Wishlist", statOpen: "Open shipments", statData: "My details", statLoyalty: "Loyalty reward",
datenHeaderBtn: "👤 My details",
@@ -804,14 +814,16 @@ export const ui = {
},
account: {
eyebrow: "Mis Konto", title: "Aamälde oder als Gast bstelle",
todoNote: "Phase 1 Demo-Aamäldig ohni echti Passwortprüefig: jedi E-Mail-Adrässe funktioniert, s'Passwort-Fäld isch nume Vorschau. D'Bstellige im Konto sind Bispieldate. E echti Aamäldig mit Datebank chunnt i Phase 2.",
todoNote: "Aamäldig mit Google/PayPal isch technisch fertig und echt (eigets Kundekonto, sicheri Sitzig) — bis Google/PayPal-Zuegangsdate hinterlegt sind, zeigt dr Knopf en klare Hiwys statt e Aamäldig vorztüsche. D'E-Mail-/Passwort-Aamäldig drunder blibt wyterhin nume Vorschau ohni echti Passwortprüefig.",
loginTitle: "Aamälde", email: "E-Mail", password: "Passwort", loginButton: "Aamälde", forgotPassword: "Passwort vergässe?",
newTitle: "Neu da?", newText: "Leg es Konto a, für Bstellhistorie, Wunschlischte und Sändigsverfolgig a einem Ort z'ha oder bstell eifach als Gast, ganz ohni Konto.",
guestButton: "Als Gast wyter yichaufe",
loginError: "Bitte gib e E-Mail-Adrässe i.",
socialHeading: "Schnäller aamälde mit",
socialGoogle: "Mit Google aamälde", socialPaypal: "Mit PayPal aamälde",
socialComingSoon: "Chunnt i Phase 2: Sobald d'echti Konto-Aabindig staht, chasch di da direkt aamälde — ganz ohni nöis Passwort.",
loginErrorUnconfigured: "Die Aamäldeart isch no nid iigrichtet. Bitte mäld di per E-Mail a oder versuech's spöter nochmal.",
loginErrorDenied: "Aamäldig abbroche.",
loginErrorGeneric: "Aamäldig fählgschlage. Bitte versuech's nochmal.",
orDivider: "oder mit E-Mail",
showPassword: "Passwort azeige", hidePassword: "Passwort verstecke",
rememberMe: "Aagmäldet bliebe",
@@ -823,6 +835,9 @@ export const ui = {
eyebrow: "Mis Konto", greeting: (name: string) => `Hoi ${name} 🩵`, lead: "Schön, bisch wieder da. Da findsch alli dini Bstellige, dini Wunschlischte und dini Date uf ein Blick.",
previewNote: "Phase 1: dini Aamäldig, dis Profil und dini Bewärtige werde scho jetzt echt uf däm Gerät gspeicheret. Nume d'Bstellige unde sind Bispieldate für d'Gstaltig — echti Bstellige chöme i Phase 2 mit Zahligsaabindig und Datebank (Cloudflare Worker + D1).",
notLoggedIn: "Du bisch nid aagmäldet — du wirsch zur Aamäldig wytergleitet …",
exportDataButton: "📥 Mini Date abelade", deleteAccountButton: "🗑️ Konto lösche",
deleteAccountConfirm: "Dis Konto wird unwiderruflich glöscht (nid dini scho ufgäbene Bstellige, wo us gsetzlichne Gründ ufbewahrt werde müesse). Wyterfahre?",
deleteAccountError: "Konto het nid chöne glöscht werde. Bitte versuech's nomol.",
logout: "Abmälde",
navLabel: "Kontobereich-Navigation",
statOrders: "Bstellige", statWishlist: "Wunschlischte", statOpen: "Offni Sändige", statData: "Mini Date", statLoyalty: "Treuebonus",
@@ -1106,14 +1121,16 @@ export const ui = {
},
account: {
eyebrow: "Mon compte", title: "Se connecter ou commander en tant qu'invité",
todoNote: "Phase 1 connexion de démonstration sans vérification réelle du mot de passe : n'importe quelle adresse e-mail fonctionne, le champ mot de passe n'est qu'un aperçu. Les commandes affichées dans le compte sont des exemples. Une vraie connexion avec base de données arrivera en phase 2.",
todoNote: "La connexion avec Google/PayPal est techniquement terminée et réelle (votre propre compte client, session sécurisée) — tant que les identifiants Google/PayPal ne sont pas configurés, le bouton affiche un message clair plutôt que de simuler une connexion. La connexion par e-mail/mot de passe ci-dessous reste un aperçu sans vérification réelle du mot de passe.",
loginTitle: "Se connecter", email: "E-mail", password: "Mot de passe", loginButton: "Se connecter", forgotPassword: "Mot de passe oublié ?",
newTitle: "Nouveau ici ?", newText: "Créez un compte pour retrouver l'historique des commandes, la liste de souhaits et le suivi au même endroit ou commandez simplement en tant qu'invité, sans compte.",
guestButton: "Continuer mes achats en tant qu'invité",
loginError: "Veuillez saisir une adresse e-mail.",
socialHeading: "Connexion plus rapide avec",
socialGoogle: "Se connecter avec Google", socialPaypal: "Se connecter avec PayPal",
socialComingSoon: "Disponible en phase 2 : dès que la vraie connexion au compte sera en place, vous pourrez vous connecter directement ici — sans nouveau mot de passe.",
loginErrorUnconfigured: "Ce mode de connexion n'est pas encore configuré. Veuillez vous connecter par e-mail ou réessayer plus tard.",
loginErrorDenied: "Connexion annulée.",
loginErrorGeneric: "Échec de la connexion. Veuillez réessayer.",
orDivider: "ou avec e-mail",
showPassword: "Afficher le mot de passe", hidePassword: "Masquer le mot de passe",
rememberMe: "Rester connecté(e)",
@@ -1125,6 +1142,9 @@ export const ui = {
eyebrow: "Mon compte", greeting: (name: string) => `Bonjour ${name} 🩵`, lead: "Ravie de vous revoir. Retrouvez ici toutes vos commandes, votre liste de souhaits et vos données en un coup d'œil.",
previewNote: "Phase 1 : votre connexion, votre profil et vos avis sont déjà réellement enregistrés sur cet appareil. Seules les commandes ci-dessous sont des exemples destinés à la conception — les vraies commandes arriveront en phase 2 avec le paiement et une base de données (Cloudflare Worker + D1).",
notLoggedIn: "Vous n'êtes pas connecté(e) — redirection vers la connexion …",
exportDataButton: "📥 Télécharger mes données", deleteAccountButton: "🗑️ Supprimer le compte",
deleteAccountConfirm: "Votre compte sera supprimé définitivement (pas vos commandes déjà passées, qui doivent être conservées pour des raisons légales). Continuer ?",
deleteAccountError: "Le compte n'a pas pu être supprimé. Veuillez réessayer.",
logout: "Se déconnecter",
navLabel: "Navigation de l'espace client",
statOrders: "Commandes", statWishlist: "Liste de souhaits", statOpen: "Envois en cours", statData: "Mes données", statLoyalty: "Récompense",