Commit Graph
13 Commits
Author SHA1 Message Date
florian.wilde 7d5a4f3f29 docs: README mit Deployment-, Bedienungs- und Sicherheitsteil
Zwei Deployment-Wege (Shared Hosting per FTP in zwei Varianten sowie
Docker), vollstaendige Konfigurationsreferenz, Bedienungsanleitung und
eine Anleitung, was fuer eine weitere Domain beim Provider zu tun ist -
inklusive Fehlersuche.
2026-08-03 18:11:40 +02:00
florian.wilde f4aece9557 build: Docker-Setup und Apache-Konfiguration
Apache mit mod_php statt PHP-FPM plus Nginx: dadurch verhalten sich
Docker und klassisches Shared Hosting gleich, es gilt dieselbe
.htaccess. MariaDB liegt hinter einem Compose-Profil.

Die Entwicklungspakete bleiben im Image installiert - beim Aufraeumen
mit apt-get purge --auto-remove verschwindet sonst auch die von imagick
benoetigte Laufzeitbibliothek.

Die .htaccess im Stammverzeichnis wird nur gebraucht, wenn der Webroot
nicht auf public/ gelegt werden kann; sie sperrt dann alle uebrigen
Verzeichnisse.
2026-08-03 18:11:40 +02:00
florian.wilde 8a7939bee2 test: Testsuite fuer Kernlogik, QR-Codes, Domains und Sicherheit
232 Tests, unter anderem:
- Slug-Erzeugung, Kollisionsbehandlung und Validierung
- URL-Validierung inklusive IDN, Prozentkodierung und abgelehnter
  Protokolle; Punycode gegengeprueft gegen idn_to_ascii
- QR-Erzeugung in allen Groessen, mit Logo und Level-H-Automatik
- Weiterleitung gegen eine echte SQLite-Datenbank samt Domain-
  Aufloesung ueber den Host-Header
- Update einer bestehenden Installation auf mehrere Domains
- Angriffsseitig: gefaelschte Proxy-Header, Host-Header-Injektion,
  SVG mit Skripten, XXE und externen Referenzen
2026-08-03 18:11:40 +02:00
florian.wilde 73a9bb09b1 feat(app): Verdrahtung, Routing und Security-Header
Setzt CSP, X-Frame-Options, Referrer-Policy, COOP/CORP und
Permissions-Policy; ueber HTTPS zusaetzlich HSTS und
upgrade-insecure-requests. Letzteres bewusst nur bei HTTPS, weil es
eine reine HTTP-Installation sonst unbrauchbar macht.

X-Forwarded-For und X-Forwarded-Proto werden nur hinter einem in
TRUSTED_PROXIES eingetragenen Proxy ausgewertet - sonst liesse sich die
Login-Sperre mit einem erfundenen Header umgehen. Ebenso fliesst der
Host-Header nur geprueft in erzeugte URLs ein.
2026-08-03 18:11:40 +02:00
florian.wilde fabaef1f2b feat(ui): Serverseitig gerenderte Oberflaeche ohne Build-Schritt
Eigenes CSS statt Framework, Vanilla-JavaScript, heller und dunkler
Modus. Ohne JavaScript bleibt alles bedienbar; das Liniendiagramm
faellt auf eine selbst gezeichnete Balkendarstellung zurueck, wenn das
CDN nicht erreichbar ist.

Chart.js ist auf eine Version festgelegt und per Subresource Integrity
an seinen Hash gebunden.

Die Domain-Seite enthaelt eine Schritt-fuer-Schritt-Anleitung fuer DNS,
Hosting-Panel und Zertifikat samt Fehlersuche.
2026-08-03 18:11:40 +02:00
florian.wilde 7238e7a937 feat(domains): Pruefung von DNS, Zuordnung und Zertifikat
Eine reine DNS-Pruefung sagt nichts darueber aus, ob der Webserver die
Domain auch dem richtigen Verzeichnis zuordnet - genau das ist der
haeufigste Fehler. Die Pruefung ruft die Domain deshalb von aussen auf
einen signierten Endpunkt auf. Kommt der Aufruf mit gueltigem Token an,
ist die ganze Kette bewiesen, denn nur diese Installation kann ein
solches Token erzeugen.

Erkannt und benannt werden: fehlender DNS-Eintrag, Domain-Weiterleitung
des Registrars, falsches Verzeichnis, ungueltiges Zertifikat.

Link-lokale Adressen werden nicht abgerufen - dort liegen bei Cloud-
Anbietern die Metadaten-Dienste.
2026-08-03 18:11:40 +02:00
florian.wilde 8b6e05020b feat(domains): Mehrere Kurzlink-Domains je Installation
short.example.de/messe und qr.example.de/messe koennen auf
unterschiedliche Ziele zeigen. Die Domain ergibt sich aus dem
Host-Header; Slugs sind nur noch innerhalb ihrer Domain eindeutig.

Bestandslinks tragen nach der Migration den Platzhalter 0 und werden
beim ersten Zugriff der aus APP_BASE_URL erzeugten Standarddomain
zugeordnet. Ein Sentinel statt NULL, weil SQLite NULL-Werte in einem
UNIQUE-Index als jeweils verschieden behandelt - die Eindeutigkeit
waere sonst nicht durchgesetzt.

Unbekannte Hosts landen auf der Standarddomain, damit bestehende Links
auch beim Aufruf ueber die IP oder hinter einem Proxy funktionieren.
2026-08-03 18:11:39 +02:00
florian.wilde eea019bb68 feat(qr): QR-Code-Generator mit Farben, Logo und Live-Vorschau
Erzeugt PNG und SVG ueber endroid/qr-code. Die eingestellte Groesse gilt
fuer das fertige Bild inklusive Quiet Zone; der Rand wird in Modulen
angegeben und ueber die Matrix in Pixel umgerechnet.

Kontrastwarnung nach WCAG, zusaetzlich Hinweis auf invertierte Codes -
die erkennen viele Scanner nicht. Mit Logo wird die Fehlerkorrektur
zwingend auf Level H gesetzt.

Uploads werden gegen GD geprueft (getimagesize allein akzeptiert auch
beschaedigte Dateien), in der Groesse und den Abmessungen begrenzt und
ausserhalb des Webroots abgelegt. SVGs werden vor dem Speichern von
Skripten, Event-Handlern, Entities und externen Referenzen befreit.
2026-08-03 18:11:39 +02:00
florian.wilde f48f7916b3 feat(links): Kurzlinks, Weiterleitung und Klickstatistik
Slugs aus 34 Zeichen (0 und 1 fehlen wegen Verwechslung mit o und l),
kollisionssicher mit wachsender Laenge. Wunsch-Slugs werden normalisiert
und gegen eine Liste reservierter Pfade geprueft.

Die URL-Validierung kommt bewusst ohne FILTER_VALIDATE_URL aus: dieses
verwirft jedes Nicht-ASCII-Zeichen und lehnt damit Umlaut-Domains und
Umlaute im Pfad ab. Erlaubt sind nur http und https, darin aber auch
IDN, IPv6 und interne Hostnamen ohne Punkt. Gespeichert wird die
lesbare Schreibweise; Punycode entsteht erst im Location-Header, weil
HTTP-Header laut RFC 7230 ASCII sein muessen.

Die Statistik speichert keine IP-Adressen (optional nur einen taeglich
rotierenden Hash) und laeuft nach dem Ausliefern der Weiterleitung.
2026-08-03 18:11:39 +02:00
florian.wilde e3d8e70a38 feat(security): Sessions, Login, CSRF, Rate-Limiting und Setup-Token
Session-Login mit genau einem Admin-Konto, Passwort als bcrypt-Hash.
Sessions mit HttpOnly, SameSite=Lax, strict mode, ID-Wechsel beim Login,
Inaktivitaets- und absoluter Zeitgrenze; ueber HTTPS wird das Cookie
automatisch auf secure gesetzt. Ein Passwortwechsel entwertet
bestehende Sitzungen.

Das Rate-Limiting zaehlt Fehlversuche pro IP-Hash - die IP selbst wird
nie gespeichert.

SetupToken schliesst das Zeitfenster zwischen Upload und erstem Aufruf:
ohne das Einmal-Token aus dem Datenverzeichnis laesst sich kein Konto
anlegen.
2026-08-03 18:11:39 +02:00
florian.wilde 3ca6a03149 feat(http): Request, Response, Router und Template-Engine
Der Router kennt Platzhalter fuer ein Pfadsegment und liefert 405, wenn
nur die Methode nicht passt. Response kann Arbeit fuer die Zeit NACH dem
Ausliefern vormerken (afterSend) - so verzoegert das Protokollieren
eines Klicks die Weiterleitung nicht.

BasePath deckt drei Installationsarten ab: Webroot auf public/,
Unterverzeichnis und Shared Hosting ohne eigenen Webroot.
2026-08-03 18:11:39 +02:00
florian.wilde 2c9fbdec87 feat(core): Konfiguration, PDO-Datenbankschicht und Migrationen
SQLite als Standard (keine Einrichtung noetig, WAL aktiviert), optional
MySQL/MariaDB. Beide Treiber nutzen dieselben vorbereiteten Statements.

Die Migrationen laufen automatisch beim ersten Request; jede Migration
liefert das SQL je Treiber. Damit entfaellt ein manueller Schritt beim
Deployment ueber FTP.

data/ enthaelt eine eigene .htaccess als zweite Absicherung, falls das
Verzeichnis beim Hoster doch einmal im Webroot landet.
2026-08-03 18:11:38 +02:00
florian.wilde b41dd1d3d7 chore: Projektgeruest, Abhaengigkeiten und Werkzeuge
Plain PHP 8.2+ ohne Framework. Einzige Laufzeit-Abhaengigkeit ist
endroid/qr-code; intl und imagick sind optional (es gibt jeweils einen
Rueckfallweg). Konfiguration ueber .env oder Umgebungsvariablen, damit
dieselbe Anwendung per FTP und per Docker deploybar ist.
2026-08-03 18:11:38 +02:00