Striche erfassen
+Teilnehmer und Admins behalten Verbrauch, Einzahlungen und Salden im Blick.
+diff --git a/app/saas-mail.php b/app/saas-mail.php new file mode 100644 index 0000000..7421bb6 --- /dev/null +++ b/app/saas-mail.php @@ -0,0 +1,151 @@ +", + "Reply-To: {$from}", + 'Content-Type: text/plain; charset=UTF-8', + ]); +} + +function saas_log_mail(string $to, string $subject, string $body): array +{ + $logDir = (string)app_env('APP_MAIL_LOG_DIR', APP_ROOT . '/var/mail'); + if (!is_dir($logDir)) { + @mkdir($logDir, 0700, true); + } + + if (!is_dir($logDir) || !is_writable($logDir)) { + return [ + 'ok' => false, + 'transport' => 'log', + 'error' => 'mail log directory is not writable', + ]; + } + + $file = rtrim($logDir, '/') . '/mail_' . date('Ymd_His') . '_' . bin2hex(random_bytes(6)) . '.txt'; + $content = "To: {$to}\nSubject: {$subject}\n\n{$body}\n"; + + if (file_put_contents($file, $content) === false) { + return [ + 'ok' => false, + 'transport' => 'log', + 'error' => 'mail log write failed', + ]; + } + + return [ + 'ok' => true, + 'transport' => 'log', + 'path' => $file, + ]; +} + +function saas_send_mail(string $to, string $subject, string $body): array +{ + $to = trim($to); + if (!filter_var($to, FILTER_VALIDATE_EMAIL)) { + return [ + 'ok' => false, + 'transport' => saas_mail_transport(), + 'error' => 'invalid recipient', + ]; + } + + if (saas_mail_transport() === 'log') { + return saas_log_mail($to, $subject, $body); + } + + $sent = mail($to, $subject, $body, saas_mail_headers()); + + return [ + 'ok' => $sent, + 'transport' => 'mail', + 'error' => $sent ? null : 'mail transport failed', + ]; +} + +function saas_send_password_reset_mail(string $to, string $token): array +{ + $link = saas_app_url('passwort-zuruecksetzen.php?token=' . urlencode($token)); + $body = implode("\n", [ + 'Hallo,', + '', + 'fuer dein Kaffeeliste-Konto wurde ein Passwort-Reset angefordert.', + 'Oeffne diesen Link, um ein neues Passwort zu setzen:', + '', + $link, + '', + 'Der Link ist zeitlich begrenzt und kann nur einmal verwendet werden.', + 'Falls du den Reset nicht angefordert hast, kannst du diese Nachricht ignorieren.', + '', + 'Deine Kaffeeliste', + ]); + + return saas_send_mail($to, 'Kaffeeliste Passwort zuruecksetzen', $body); +} + +function saas_send_email_verification_mail(string $to, string $token): array +{ + $link = saas_app_url('email-verifizieren.php?token=' . urlencode($token)); + $body = implode("\n", [ + 'Hallo,', + '', + 'bitte bestaetige deine E-Mail-Adresse fuer Kaffeeliste.', + 'Oeffne dazu diesen Link:', + '', + $link, + '', + 'Der Link ist zeitlich begrenzt und kann nur einmal verwendet werden.', + '', + 'Deine Kaffeeliste', + ]); + + return saas_send_mail($to, 'Kaffeeliste E-Mail bestaetigen', $body); +} diff --git a/assets/images/landing-hero.png b/assets/images/landing-hero.png new file mode 100644 index 0000000..27de416 Binary files /dev/null and b/assets/images/landing-hero.png differ diff --git a/docs/dev-mysql.md b/docs/dev-mysql.md index 7cd3f13..24d7383 100644 --- a/docs/dev-mysql.md +++ b/docs/dev-mysql.md @@ -26,6 +26,12 @@ export DEV_AUTH_EMAIL="admin@test.local" export DEV_AUTH_NAME="Test Admin" # Optional: Standard ist var/sessions im Repository. export APP_SESSION_PATH="/path/to/writable/sessions" +# Optional fuer Passwort-Reset und E-Mail-Verifikation. +export APP_BASE_URL="http://127.0.0.1:8080" +export APP_MAIL_TRANSPORT="log" +export APP_MAIL_LOG_DIR="/path/to/writable/mail-log" +export APP_MAIL_FROM="noreply@example.test" +export APP_MAIL_FROM_NAME="Kaffeeliste" ``` ## Testdatenbank initialisieren @@ -63,6 +69,10 @@ der Port ueber die Ports-Ansicht weitergeleitet werden. ## Hinweise - Der Dev-Login umgeht AD/LDAP und nutzt `DEV_AUTH_EMAIL`. +- Der Dev-Mailversand nutzt standardmaessig `APP_MAIL_TRANSPORT=log` und legt + Nachrichten unter `var/mail` ab. Fuer Produktion kann zunaechst + `APP_MAIL_TRANSPORT=mail` mit passendem `APP_BASE_URL` und Absender gesetzt + werden; eine SMTP-/Provider-Anbindung bleibt ein spaeterer Betriebsausbau. - Die Tabellen werden ueber `database/migrations/` angelegt. - `database/mysql-dev-schema.sql` bleibt als historische Dev-Schema-Baseline erhalten; der aktive Weg ist `scripts/migrate.php`. diff --git a/docs/m2-technical-foundation.md b/docs/m2-technical-foundation.md index 4aeacd2..4529656 100644 --- a/docs/m2-technical-foundation.md +++ b/docs/m2-technical-foundation.md @@ -1,6 +1,6 @@ # M2 Technisches Fundament -Stand: 2026-07-12 +Stand: 2026-07-13 M2 fuehrt die technischen Grundbausteine fuer den SaaS-Umbau ein, ohne das Legacy-Verhalten oder das bestehende Design zu veraendern. @@ -140,7 +140,7 @@ Die Skripte erwarten die bekannten Dev-Umgebungsvariablen `DB_HOST`, `DB_NAME`, eine PayPal-Alias-Dublette und hinterlaesst keine Datei in `var/uploads`. - CSV-Upload negative Tests: Nicht-CSV-Dateien werden abgewiesen. - Golden Master weiterhin gruen mit 104 Assertions. -- HTTP-Smoke weiterhin gruen mit 21 sicheren Seiten inklusive Login, +- HTTP-Smoke weiterhin gruen mit 22 sicheren Seiten inklusive Landingpage, Login, Registrierung, Passwort-Reset, E-Mail-Verifikation und geschuetzter Mandant-Einstellungen sowie geschuetzter Mandantenauswahl. diff --git a/docs/m3-saas-basis-vorbereitung.md b/docs/m3-saas-basis-vorbereitung.md index 835472a..c2db179 100644 --- a/docs/m3-saas-basis-vorbereitung.md +++ b/docs/m3-saas-basis-vorbereitung.md @@ -1,6 +1,6 @@ # M3 SaaS-Basis Vorbereitung -Stand: 2026-07-12 +Stand: 2026-07-13 M3 fuehrt Mandanten, Benutzer, Mitgliedschaften und Grundeinstellungen additiv ein. Die bestehende Legacy-App bleibt dabei lauffaehig und wird noch nicht auf @@ -53,6 +53,7 @@ scripts/check-m3-auth-flow.php scripts/check-m3-settings-flow.php scripts/check-m3-password-email-flow.php scripts/check-m3-tenant-resolution-flow.php +scripts/check-m3-mail-flow.php ``` Wichtige Regeln: @@ -122,6 +123,7 @@ Umgesetzte Dateien: ```text app/database.php app/saas-auth.php +app/saas-mail.php login.php register.php konto.php @@ -131,6 +133,8 @@ passwort-vergessen.php passwort-zuruecksetzen.php email-verifikation-senden.php email-verifizieren.php +landing.php +assets/images/landing-hero.png ``` Umgesetzter Umfang: @@ -151,13 +155,33 @@ Umgesetzter Umfang: - Passwort-Reset erzeugt Single-Use-Tokens und speichert nur Token-Hashes. - E-Mail-Verifikation erzeugt Single-Use-Tokens und setzt `users.email_verified_at`. -- Bis zum produktiven Mailversand werden Links nur im Dev-Modus angezeigt - beziehungsweise in CLI-Checks direkt verwendet. +- Reset- und Verifikationslinks werden ueber `app/saas-mail.php` versendet. + Der Dev-Standard schreibt Mails nach `var/mail`; produktiv kann auf PHP + `mail()` umgestellt werden. +- Dev-Links werden weiterhin nur im Dev-Modus angezeigt, damit lokale Checks + ohne echten Postausgang reproduzierbar bleiben. Noch offen im M3-Auth-Scope: - Weitergehende Rollenmatrix fuer spaetere SaaS-Seiten. -- Produktiver Mailversand fuer Reset- und Verifikationslinks. +- Produktive SMTP-/Provider-Anbindung inklusive Bounce-/Fehlerprotokoll. + +## Public-Landingpage + +Umgesetzte Dateien: + +```text +landing.php +assets/images/landing-hero.png +``` + +Umgesetzter Umfang: + +- Oeffentliche Landingpage ohne Legacy-DB-Zugriff. +- Hero mit generiertem Bild-Asset, bestehender Typografie und gruenem Akzent. +- CTA zu Login und Registrierung. +- Kurzabschnitt zu Stricherfassung, Mandantenfaehigkeit und Webspace-Betrieb. +- Die geschuetzte App-Sidebar bleibt von der Public-Seite getrennt. ## Tenant-Aufloesung @@ -212,7 +236,10 @@ Umgesetzter Umfang: - Owner-/Admin-Grundeinstellungen koennen aktualisiert werden: erfuellt. - Passwort-Reset und E-Mail-Verifikation funktionieren mit Single-Use-Tokens: erfuellt. +- Reset- und Verifikationslinks werden im Dev-/Testmodus als Mail-Log erzeugt: + erfuellt. - Mandantenauswahl funktioniert fuer User mit mehreren Mandanten: erfuellt. +- Oeffentliche Landingpage ist per HTTP-Smoke erreichbar: erfuellt. - Golden-Master und HTTP-Smoke bleiben gruen: erfuellt. ## Risiken @@ -243,5 +270,7 @@ Umgesetzter Umfang: 14. Mandantenauswahl und zentrale Session-Aufloesung bauen: erledigt. 15. Tenant-Resolution-Kontrollskript schreiben: erledigt. 16. Golden-Master und HTTP-Smoke ausfuehren: erledigt. -17. Produktiven Mailversand oder Landingpage vorbereiten: +17. Mail-Transport-Abstraktion und Mail-Flow-Kontrollskript bauen: erledigt. +18. Public-Landingpage mit erstem Hero-Asset vorbereiten: erledigt. +19. M3-Abschlusscheck dokumentieren und M4-Datenmigration starten: naechster Schritt. diff --git a/docs/saas-umstrukturierungsplan.md b/docs/saas-umstrukturierungsplan.md index b84dfca..10dcf67 100644 --- a/docs/saas-umstrukturierungsplan.md +++ b/docs/saas-umstrukturierungsplan.md @@ -1,6 +1,6 @@ # SaaS-Umstrukturierungsplan Kaffeeliste -Stand: 2026-07-11 +Stand: 2026-07-13 Dieses Dokument beschreibt den geplanten Umbau der bestehenden Kaffeelisten-App zu einer mehrkundenfaehigen SaaS-Anwendung mit oeffentlicher Landingpage, @@ -283,11 +283,11 @@ Uebersicht: | M0 | Baseline | Bestand, Sicherheit und Designreferenz sind dokumentiert | | M1 | Golden Master | Legacy-Ergebnisse sind als Vergleichsbasis eingefroren; HTTP-Smoke prueft sichere Seiten | | M2 | Technisches Fundament | Abgeschlossen: Migrationen, Bootstrap, Session, CSRF-Helper und Legacy-Schreibseitenschutz stehen | -| M3 | SaaS-Basis | Tenants, User, Registrierung, Login und Rollen funktionieren | +| M3 | SaaS-Basis | Tenants, User, Registrierung, Login, Rollen, Mail-Links und zentrale Mandantenauswahl funktionieren | | M4 | Datenmigration | Legacy-Daten sind tenant-sicher im Zielmodell abgebildet | | M5 | App-Kern | Dashboard, Striche, Einzahlungen, Mitglieder und Liste laufen | | M6 | Betriebsflows | Import, Export, Mail und Jahresprozesse sind auditierbar | -| M7 | Landingpage | Werbliche Seite und Registrierung sind oeffentlich nutzbar | +| M7 | Landingpage | Erste werbliche Seite ist oeffentlich nutzbar; spaetere Ausbaustufen folgen | | M8 | Haertung | Betrieb, Datenschutz, Monitoring und Isolation sind geprueft | | M9 | Cutover | Produktivumstellung ist vorbereitet und Legacy ist read-only | @@ -421,16 +421,19 @@ Schritte: Flow erledigt. - Login und Logout bauen: erster Flow erledigt. - Passwort-Reset und E-Mail-Verifikation bauen: Dev-Flow mit Single-Use-Tokens - erledigt. + und Mail-Transport-Abstraktion erledigt. - Tenant-Aufloesung definieren: zentrale App-Domain mit Session-Kontext und Mandantenauswahl erledigt; feste Domains vorbereitet, keine Wildcards. - Rollenpruefung zentralisieren: erster Owner/Admin-Check erledigt. - Erste Admin-/Owner-Seite fuer Grundeinstellungen: erledigt. +- Erste Public-Landingpage mit CTA zu Login und Registrierung: erledigt. Ergebnis: - Ein neuer Kunde kann sich registrieren und in seine eigene App gelangen. - Rollen und Tenant-Kontext sind serverseitig verfuegbar. +- Reset- und Verifikationslinks koennen im Dev-Modus geloggt und produktiv ueber + einen konfigurierten Mail-Transport versendet werden. Abhaengigkeiten: @@ -515,10 +518,11 @@ zu verwischen. Schritte: -- Public-Layout mit bestehender Typografie und gruenem Akzent bauen. -- Landingpage-Inhalte erstellen. +- Public-Layout mit bestehender Typografie und gruenem Akzent bauen. Erster + Stand als `landing.php` erledigt. +- Landingpage-Inhalte erstellen. Erster Stand erledigt. - Demo-Screenshot oder Demo-Ansicht einbinden. -- CTA zu Registrierung und Login. +- CTA zu Registrierung und Login. Erledigt. - FAQ-Auszug strukturieren. - Keine App-Sidebar im Public-Bereich. @@ -588,12 +592,12 @@ umgebaut werden. MVP-Paket: 1. Tenant, User, Login, Registrierung. -2. Teilnehmer, Settings, Ledger. -3. Dashboard, Striche, Einzahlungen, Gesamtuebersicht. -4. Mitgliederverwaltung. -5. Hinweise. -6. CSV-Import, Export. -7. Landingpage. +2. Mail-Links, zentrale Mandantenauswahl und erste Landingpage. +3. Teilnehmer, Settings, Ledger. +4. Dashboard, Striche, Einzahlungen, Gesamtuebersicht. +5. Mitgliederverwaltung. +6. Hinweise. +7. CSV-Import, Export. Spaeter: diff --git a/email-verifikation-senden.php b/email-verifikation-senden.php index 0472cdb..45504a4 100644 --- a/email-verifikation-senden.php +++ b/email-verifikation-senden.php @@ -1,6 +1,7 @@ + +
+Die digitale Kaffeekasse fuer Teams, Bueros und Vereine: Striche, Einzahlungen und offene Betraege bleiben nachvollziehbar an einem Ort.
+Teilnehmer und Admins behalten Verbrauch, Einzahlungen und Salden im Blick.
+Jeder Kunde arbeitet in seinem eigenen Bereich mit eigenen Einstellungen.
+Der Start erfolgt ueber eine zentrale App-Adresse mit Login und Mandantenauswahl.
+