hello.karameht.com — Devlog #3: Wer liest das eigentlich?
Wenn Nachrichten in einer DB liegen, brauche ich einen Weg sie zu lesen. Drei Auth-Optionen, eine Tendenz, ein Schwur: Auth-Code schreibe ich nicht selber. Plus: was das Dashboard kann und was bewusst draußen bleibt. Teil 3 von 4.
In Teil 2 liegen die Nachrichten sauber in einer SQLite-DB, Cron schickt sie aus der Queue raus, das Postfach am anderen Ende ist Proton mit E2E. Eigentlich könnte ich hier aufhören — User schreibt, DB speichert, Mail kommt bei mir an, ich antworte aus Proton. Fertig.
Aber: was, wenn ich nachschauen will, ob alles glatt läuft? Was, wenn eine Nachricht nie im Postfach ankommt — wo prüfe ich, ob sie zumindest in der DB lag?
Heißt: Admin-Dashboard als Fallback. Nicht als Hauptarbeitsplatz — der ist Proton. Sondern als Audit-View und Notfall-Werkzeug. Read-only meistens, mit ein paar Aktionen für Hygiene. HeidiSQL und andere Tools sind mir bekannt — aber vielleicht will ich mal auch ein Dashboard sehen statt Tables und Terminal.
Trotzdem: ein Dashboard heißt eine Route, die ich vor der Welt schützen muss. Heißt Auth. Heißt: die nächste Entscheidung.
Drei Wege liegen auf dem Tisch
Option 1 — Selber schreiben
Eine Login-Seite, PHP-Session, eine Liste-Ansicht, eine Detail-Ansicht, ein Logout. Maximale Kontrolle, kein Drittsystem, alles im Repo, alles nachvollziehbar.
Nachteil: ich schreibe Auth-Code. Und Auth-Code schreiben ist eine eigene Disziplin mit eigenem OWASP-Eintrag, eigener Klasse von Bugs, eigenen Pentest-Reports. Session-Fixation, Timing-Attacks bei Passwortvergleich, Brute-Force-Schutz, Password-Reset-Flow (falls ich das jemals brauche), CSRF auf Login-Form, Logout-CSRF, „Remember me”-Cookie-Hygiene. Jeder Punkt davon hat eine Subtilität, die ich übersehen kann.
Plus: ich bin einer. Ein einziger Login. Den ganzen Apparat aufzuziehen für genau einen User ist Kokolores.
Option 2 — .htaccess + Basic Auth
Bewährt seit Apache existiert, single-User reicht völlig, kein Code, kein Session-Management. Plus: IP-Whitelist direkt in .htaccess als zweite Schicht.
AuthType Basic
AuthName "hello-admin"
AuthUserFile /pfad/außerhalb/htpasswd
Require valid-user
# Zweite Schicht: nur von meinen IPs
Require ip 1.2.3.4
Require ip 5.6.7.0/24
Nachteil: kein 2FA out-of-the-box, Browser-Popup statt eigene UI, Passwort wandert pro Request im Authorization-Header (über HTTPS, aber immerhin). Cosmetisch: das Browser-Login-Popup sieht aus wie 2005.
Gewinn: Null Auth-Code in meinem Repo. Null Session-Bugs. Null Logout-Flow. Bei IP-Whitelist plus Basic Auth: zwei unabhängige Schichten, die beide gebrochen werden müssten.
Option 3 — Externes IdP (managed oder self-hosted)
Auth als eigenständige Schicht statt Apache-Regel. Zwei Unterklassen:
- Self-hosted IdP: Keycloak, Authentik, Pocket-ID auf eigenem Server. Volle Kontrolle, alles selber updaten, alles selber härten. Sinnvoll wenn man eh schon Kubernetes-Cluster betreibt — für ein PHP-Hobby auf all-inkl Overengineering.
- Managed IdP: Infomaniak Auth (Schweiz), Auth0, FusionAuth-Cloud. OAuth2/OIDC-Provider als Service. Integration via Standard-Protokoll, der Anbieter macht den Rest.
Was Option 3 mitbringt, das Option 1 und 2 nicht haben: WebAuthn / Passkeys / 2FA out-of-the-box, Threat-Detection auf der Auth-Schicht, Session-Management standardkonform, SSO über mehrere Domains sobald mehrere kommen.
Trade-off bei managed: Lock-in zum Anbieter — aber zweigeteilt zu betrachten. Code-Lock-in entfällt bei OAuth2/OIDC: Standard-Protokoll, Library-austauschbar, jeder Provider implementiert dieselbe Spec. Config-Lock-in bleibt: Redirect-URIs, Client-Secret-Rotation, Scope-Mapping, User-ID-Auflösung — alles provider-spezifisch. Migration ist möglich, aber sie kostet Konfigurations-Arbeit, nicht nur Code-Tausch. Mein Move dagegen: alles Provider-Spezifische landet in einer config/oidc.php — Migration wird Datei-Tausch, nicht Code-Diff.
Meine Tendenz: Option 3 — konkret Infomaniak Auth (im Test)
.htaccess + Basic Auth scheidet aus. Erst dachte ich, das wäre der KISS-Move. Aber: kein 2FA, kein Passkey, Browser-Popup-Ästhetik von 2005, Passwort wandert pro Request im Authorization-Header. Für ein System, das ich live im Devlog dokumentiere, ist das nicht das Signal, das ich senden will.
Selber schreiben (Option 1) bleibt für mich trotzdem raus. Auth-Code falsch zu schreiben ist eine eigene Disziplin — und das Risiko ist zu hoch für den Nutzen.
Aber jedem, der lernen will, empfehle ich genau das einmal selber zu bauen. In verschiedenen Sprachen — PHP, Go, Rust, Python. In verschiedenen Spielarten — Session-Cookies, JWT, OAuth-Client gegen einen fremden IdP, sogar einen eigenen OIDC-Provider. Man lernt dabei mehr über Security in zwei Wochen als in einem Tutorial-Marathon: warum === beim Passwort-Vergleich angreifbar ist, warum Session-Fixation existiert, warum CSRF erfunden wurde, warum „Remember Me”-Cookies ein eigener Bug-Magnet sind.
Nur eben: das Ergebnis bleibt in der Schublade. Produktion will erprobte Frameworks und IdPs, nicht meinen Erstversuch.
Bleibt Option 3, konkret als Test: Infomaniak Auth. OAuth2/OIDC-Provider, Schweizer Datenschutz (passt zum Proton-Stack aus Teil 2), 2FA und Passkey out-of-the-box, managed — also kein eigener Keycloak-Server, den ich nebenbei pflegen müsste. Free-Tier zum Ausprobieren ist da.
Plus: Infomaniak ist sowieso schon Teil meiner restlichen Infrastruktur. Heißt: kein Schritt in unbekanntes Gebiet, sondern ein bekannter Anbieter, der jetzt eine zusätzliche Rolle übernimmt. Eine Chance geben: definitiv.
Plan: aufsetzen, PHP-Integration via einem schlanken OIDC-Client (kein dickes SDK), Login-Flow im Dashboard testen. Provider-spezifische Werte landen in config/oidc.php — wenn’s nicht überzeugt, ist Wechsel ein Config-Tausch und nicht ein Refactor.
Aber: das hier ist Stand der Überlegung, nicht Stand der Implementierung. Folge-Devlogs werden zeigen, ob Infomaniak Auth liefert, was es verspricht. Wenn nicht, gibt’s ein offenes Update.
Was ich trotzdem selber schreibe: das Dashboard selbst. Vanilla PHP, vanilla HTML, output escaped, ein Form pro Aktion. Kein React. Kein Tailwind. Wirklich nicht.
Was kann das Dashboard?
Erstmal das Minimum, das die Fallback-Rolle erfüllt:
- Liste: alle Nachrichten chronologisch absteigend, Status-Filter (
pending,sent,failed,read,archived) - Detail: eine Nachricht voll lesen, IP-Hash + User-Agent + Zeitstempel sichtbar zur Forensik
- Status setzen:
read,archived, oder hartdelete(mit Bestätigung) - Export: CSV-Dump für DSGVO-Auskunft, ein Klick
Was nicht dazugehört: ein Reply-Composer. Antworten passiert in Proton (siehe nächste Sektion), nicht im Dashboard. Spart mir Code, spart mir eine zweite UI-Schicht, spart mir den ganzen outbound_messages-Workflow, den ich vorher mal andiskutiert hatte. YAGNI an der richtigen Stelle.
Mehr nicht. Wirklich nicht. Kein Notification-System, kein Dashboard-Widget, keine Charts, kein „Top Subjects der Woche”. Wenn ich später wirklich etwas vermisse, baue ich es dann.
Reply läuft aus Proton, nicht aus dem Dashboard
Subtile Architektur-Entscheidung mit großen Folgen: ich antworte direkt aus meinem Proton-Mailclient. Nicht aus dem Dashboard.
Warum nicht aus dem Dashboard? Weil es drei Sachen einsparen würde, die nichts bringen:
- Eine zweite Tabelle (
outbound_messages) plus zweiter Cron-Worker für den Versand - Eine Reply-UI im Dashboard mit Textarea, Send-Button, Vorschau, Anhang-Handling
- Threading-Header (
In-Reply-To,References) selber setzen statt Proton machen lassen
Statt das alles selber zu bauen: Proton kann das alles besser. Threading, IMAP-Search, Anhänge, Drafts, sogar PGP wenn ich will. Warum sollte ich das nachbauen?
Folge: meine Antwort geht aus Proton raus, der User antwortet ggf. zurück nach Proton (Threading bleibt erhalten, Proton macht das automatisch). Die DB sieht den initialen Kontakt. Die Konversation danach läuft außerhalb der DB.
„DB ist Wahrheit” gilt damit nur für den Eingangs-Kontakt — und das reicht. Für DSGVO-Auskunft („was haben Sie von mir gespeichert?”) ist die initiale Form-Submission die Datenmenge, alles danach ist normaler Mail-Verkehr unter Proton-AVV.
Mein Aufräum-Workflow
Die Sache wird nur dann nicht zur DB-Halde, wenn ich auch aufräume. Mein Vorgehen, das ich wöchentlich durchziehe:
- Schauen: Postfach durch (Proton), Dashboard durch (Audit-Filter
pendingundfailedzuerst — gibt es da etwas Hängendes?) - Pro Nachricht entscheiden:
- Kann ich antworten? → erst antworten, dann löschen. Nicht umgekehrt.
- Will ich nicht antworten? → checken warum. Spam? Bot? Trolling? Unklar? Notiz fürs Pattern, dann löschen.
- Status pflegen: was beantwortet ist, ist beantwortet. DB-Status auf
readoder direktdelete(mit Bestätigung). - Retention läuft sowieso: nach 90 Tagen ist alles weg, mein Aufräumen ist nur die wöchentliche Disziplin obendrauf.
Auf dem Papier klar. In der Realität fällt Aufräumen aber immer dann hinten runter, wenn es nicht festgehalten ist — und die schwächste Stelle in jedem System ist nicht der Code, sondern die Routine drumherum.
Damit das auch wirklich passiert: Kalender-Eintrag mit Serie. Wöchentlich, fester Slot, mit Erinnerung. Disziplin braucht Tool-Support, nicht nur Willenskraft — sonst rutscht das Aufräumen unter „mache ich später” und ist nach drei Wochen Backlog. Der Termin steht im Kalender, nicht im Kopf.
Die wichtige Regel ist Schritt 2 — antworten kommt vor löschen. Wer sich die Zeit nimmt zu schreiben, kriegt eine Antwort. Auch wenn die Antwort kurz ist. Auch wenn sie ablehnend ist. Aber sie kriegt eine Antwort.
Das ist keine Tool-Regel, das ist Haltung. Und sie steht hier explizit drin, damit ich mich selber dran halte — wenn ich es vergesse, kann mich jemand auf diesen Absatz verweisen.
Was nicht ins Dashboard kommt
Ein paar Reflexe, die ich bewusst aussperre, damit die Disziplin hält:
- Inline-Analytics — kein „diese Nachricht wurde 3× gelesen”-Counter. Brauche ich nicht, dient nur dem Reflex.
- Auto-Tagging via NLP — kein Sentiment-Score, kein Topic-Classifier. Wenn ich Pattern erkennen will, mache ich das im Kopf.
- Trackbare Lese-Bestätigung an User — keine 1×1-Pixel-Mails, kein Read-Receipt. Wer mir schreibt, kriegt eine echte Antwort, keine automatische Empfangs-Mail mit Tracker.
- Mass-Mail / Newsletter — wenn ich mal sowas brauche, ist das ein separates Tool, kein Dashboard-Feature.
- AI-Assist — keine GPT-Antwortvorschläge, keine „Mehmet, das ist die übliche Anfrage, willst du Antwort A oder B?”-Vorschläge. Wer mir schreibt, kriegt eine Antwort von mir. Sonst kann ich auch gleich Mailgun nehmen.
Hinterfragen ist kein Lifestyle, das ist Default-Hygiene. Auch — und vielleicht besonders — beim eigenen Tool.
Was hier offen bleibt
- 2FA + Passkey — kommt mit Infomaniak Auth automatisch. Test wird zeigen, wie schmerzfrei der Flow auf Mobile aussieht und ob ich auf Passwort + Passkey gehe oder nur Passkey.
- Session-Timeout im Dashboard — wird OIDC-Token-Lifetime sein, bei Infomaniak Auth konfigurierbar. Aktuell tendiere ich zu kurzen Tokens + Browser-Schließen = abgemeldet. Reicht.
- Multi-Device-Zugriff — wenn ich vom Handy lesen will, brauche ich Mobile-CSS. Reines Dashboard, vanilla CSS, max 600px Layout, fertig.
- Was bei Infomaniak-Ausfall? — wenn der IdP down ist, komme ich nicht ins Dashboard. Für ein Fallback-Tool akzeptabel. Notfall-Pfad: SSH zum Server, direkt SQLite-Query.
- Account-Recovery — Infomaniak hat einen offiziellen Recovery-Flow. Kein selbstgebastelter Reset-Mechanismus mit Mail-Token nötig — auch hier YAGNI durch Outsourcing.
complexity(auth) ∝ users
In #4 das, was alles drumherum nicht passieren darf: DSGVO-Faktencheck mit Artikel-Nummern, Security-Härtung von CSRF bis Backup, Code-Prinzipien und ein konkreter Cheat-Sheet zum Selbst-Bauen.
Single-User darf single-Login haben. Wer Keycloak für sein Hobby aufsetzt, hat mein vollstes Mitgefühl. Das Werkzeug muss zur Werkbank passen, nicht umgekehrt.
Euer Mehmet