|
Some checks are pending
CI / PHP Lint (push) Waiting to run
CI / PHP Lint-1 (push) Waiting to run
CI / Unit Tests & Static Analysis (push) Waiting to run
CI / PHP Lint (pull_request) Waiting to run
CI / PHP Lint-1 (pull_request) Waiting to run
CI / Unit Tests & Static Analysis (pull_request) Waiting to run
- CI-Badge von github.com entfernt (zeigte ins Leere); stattdessen ein statisches Test-Badge und ein Link zum Repository - Clone-Befehl in der Installation auf git.loheide.cloud umgestellt - neuer Abschnitt "Repository & Mitwirken" mit HTTPS-/SSH-Adressen, Hinweis auf Issues und Pull Requests sowie der tea-CLI - vermerkt, dass .github/workflows von Forgejo Actions mitgelesen wird und docker-publish.yml (ghcr.io) noch aus der GitHub-Zeit stammt - klargestellt, dass der Auto-Updater unabhängig davon über update.loheide.eu läuft - Entwicklungs-Abschnitt nennt jetzt auch die CI-Prüfungen der Sprachdateien - composer.json: homepage, authors und support-Links ergänzt Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| .github/workflows | ||
| admin | ||
| api | ||
| assets | ||
| docker | ||
| docs/screenshots | ||
| includes | ||
| lang | ||
| tests | ||
| tools | ||
| updater | ||
| uploads | ||
| .dockerignore | ||
| .gitignore | ||
| .htaccess | ||
| composer.json | ||
| config.php | ||
| cron_cleanup.php | ||
| cron_sync.php | ||
| cron_test.php | ||
| database.sql | ||
| docker-compose.yml | ||
| Dockerfile | ||
| forgot_password.php | ||
| health.php | ||
| index.php | ||
| install.php | ||
| login.php | ||
| login_simple.php | ||
| logout.php | ||
| m365_callback.php | ||
| m365_debug.php | ||
| oidc_callback.php | ||
| phpstan.neon | ||
| phpunit.xml.dist | ||
| Readme.md | ||
| reset_password.php | ||
| test.php | ||
🎫 UniFi Voucher Management System
Webbasiertes WLAN-Voucher-Management für UniFi OS – mit Multi-Site-Support, Benutzerverwaltung, Microsoft-365-Login und integriertem Auto-Updater.
Entwickelt von Loheide.eu
✨ Features
- 🎟️ Voucher-Erstellung mit sofortiger QR-Code-Anzeige, Druckvorlage und E-Mail-Versand
- 📦 Bulk-Erstellung – bis zu 20 Vouchers auf einmal, inkl. Sammeldruck-Layout
- 🧩 Voucher-Profile/Templates – vordefinierte Laufzeiten & Gerätelimits per Schnellauswahl
- 🏢 Multi-Site-Support – beliebig viele UniFi-Standorte zentral verwalten
- 👥 Benutzerverwaltung mit granularer Site-Zugriffskontrolle
- 🔐 Authentifizierung via lokale Accounts oder Microsoft 365 OAuth
- 🔒 2FA (TOTP) – optional, mit Recovery-Codes & erzwingbarer Admin-Pflicht
- 📈 Reporting – Auswertungen mit Charts und CSV-/PDF-Export
- 🩺 Health-Endpoint (
/health.php) für Monitoring/Uptime - 🔑 Passwort-Reset per E-Mail (token-basiert, zeitlich begrenzt)
- 🚦 Bandbreiten- & Datenlimits pro Voucher/Profil (UniFi QoS)
- 🧰 REST-API mit API-Schlüsseln – Scopes (read/write), Rate-Limit, OpenAPI-Spec
- 🪪 Single Sign-On via generisches OpenID Connect (zusätzlich zu M365)
- 📲 SMS-Versand der Codes (Twilio) · CSV-Batch-Import von Vouchern
- 🤖 CAPTCHA (Rechenaufgabe oder hCaptcha) im öffentlichen Modus
- 🗄️ DB-gestützte Sessions (opt-in) mit „überall abmelden"
- 🔔 Webhooks (Slack / Teams / generisch) für Erstellung, Ausfälle, neue Login-IP
- 🧹 Auto-Cleanup & DSGVO – Aufbewahrungsfristen per Cron
- 💾 Config-Backup & -Restore (JSON Export/Import)
- 🐳 Docker – Dockerfile + docker-compose (MariaDB)
- 🌍 Öffentlicher Modus – optional ohne Login nutzbar (mit CSRF-Schutz & Throttle)
- 🎨 Einheitliches Design-System – ein Stylesheet für Frontend, Login und Backend (Tokens, Komponenten, Light/Dark)
- 🏷️ Login-Seite individualisierbar – Firmenname, Logo, Texte, Hintergrundbild bzw. Farbverlauf
- 🖌️ Eigene Markenfarben – Akzentfarbe, Verlauf und Eckenradius wirken auf die gesamte Oberfläche
- ⬆️ Bild-Upload für Logo, Favicon und Login-Hintergrund (kein externes Hosting nötig)
- 🔒 Keine externen CDNs – Schrift, Icons, Diagramme und Editor werden lokal ausgeliefert (DSGVO, Offline-Netze)
- ♿ Barrierearm – Kontraste nach WCAG AA, Sprungmarke, aria-Beschriftungen,
prefers-reduced-motion - 📱 Mobil nutzbar – Tabellen werden auf schmalen Geräten zu Karten
- 🌗 Dark Mode – umschaltbar, Einstellung wird im Browser gespeichert
- 🌐 Mehrsprachig – Deutsch / Englisch per Umschalter (
lang/) - 📱 Responsive Admin-Layout mit Hamburger-Menü & Sidebar-Overlay
- 📊 Admin-Dashboard mit Live-Statistiken und Sync-Funktion
- 📝 Audit-Log – nachvollziehbare Protokollierung von Login & Änderungen (mit Filter)
- 📥 CSV-Export aller Vouchers pro Site
- 🔄 Integrierter Auto-Updater – Updates & DB-Migrationen per Klick aus dem Admin-Bereich
- 🛡️ Security-by-default: CSRF-Schutz, bcrypt-Passwörter, Prepared Statements, Login-Rate-Limiting, OAuth-State-Validierung, Verschlüsselung sensibler Daten
📸 Screenshots
Alle Screenshots stammen aus der Oberfläche in Version 2.5.0 (neues Design-System).
Anmeldung & Voucher-Erstellung
Administration
REST-API, 2FA & Integrationen
Updater & Wartungsmodus
📋 Anforderungen
| Komponente | Version |
|---|---|
| PHP | 7.4+ (mit PDO, PDO_MySQL, cURL, mbstring, JSON; empfohlen: OpenSSL/libsodium, Zip) |
| Datenbank | MySQL 5.7+ / MariaDB 10.2+ |
| Webserver | Apache / Nginx |
| UniFi | Network Application 7.0+ mit UniFi OS (z. B. UDM, UDR, UniFi OS Server) |
🚀 Installation
git clone https://git.loheide.cloud/friloo/Unifi-Voucher-Tool.git
cd Unifi-Voucher-Tool
- Dateien auf den Webserver hochladen
https://ihre-domain.de/install.phpöffnen- Den 5-Schritte-Assistenten durchlaufen:
- Datenbank-Verbindungsdaten
- Administrator-Account
- Allgemeine Einstellungen (Titel, Logo, öffentlicher Zugriff)
- Microsoft 365 Integration (optional)
- Installation abschließen
install.phpnach erfolgreicher Installation löschen
🔒 Der Installer erzeugt automatisch einen zufälligen
APP_KEY(für die Verschlüsselung der UniFi-Passwörter) und eine.htaccess, die sensible Dateien sperrt. Ein erneuter Aufruf voninstall.php?reinstall=1ist nur für angemeldete Administratoren möglich.
Sites konfigurieren
- Administration → Sites verwalten → Neue Site hinzufügen
- Felder ausfüllen:
- Name: Anzeigename (z. B. „Hauptgebäude")
- Site ID: UniFi Site ID (meist
default) - Controller URL:
https://unifi.example.com:11443 - Benutzername / Passwort: UniFi Admin-Zugangsdaten
- Verbindung testen klicken, dann speichern
Voucher erstellen
- Startseite öffnen (Login je nach Konfiguration optional)
- Voucher-Name, Anzahl Geräte und Standort wählen
- Voucher erstellen – Code und QR-Code werden sofort angezeigt
- Code per E-Mail senden oder ausdrucken
🔄 Auto-Updater
Das System bringt einen vollständig integrierten Updater mit, der Quellcode und
Datenbank-Migrationen über einen zentralen Update-Proxy nachzieht – ganz ohne
SSH oder manuelles git pull.
[diese Instanz] ←HTTPS→ [Update-Proxy] ←pull→ [Git-Repo]
Aufruf: Administration → System-Update (/admin/update.php)
- 🔍 „Auf Updates prüfen" zeigt verfügbare Versionen inkl. Changelog
- ⬇️ „Update installieren" spielt das Update ein – mit Wartungsseite,
geschützten Pfaden (
config.php, Uploads, …), automatischen DB-Migrationen und OPcache-Reset - 🔀 Channel-Auswahl zwischen
stableunddevelopment - 📊 Migrations-Status in einem eigenen Tab – inkl. Button „Ausstehende Migrationen ausführen" (legt z. B. neue Tabellen für bestehende Installationen an, ohne dass ein Code-Update nötig ist)
Während eines Updates wird die Anwendung kurz in den Wartungsmodus versetzt:
Der Updater ist vollständig isoliert im Ordner
updater/gekapselt. Details zur Architektur und eine Rückbau-Anleitung stehen inupdater/README.md.
⚙️ Konfiguration
config.php
Wird automatisch durch den Installer erstellt:
<?php
define('DB_HOST', 'localhost');
define('DB_NAME', 'unifi_voucher');
define('DB_USER', 'username');
define('DB_PASS', 'password');
define('APP_KEY', '...'); // Schlüssel für Verschlüsselung-at-rest
define('SESSION_LIFETIME', 3600); // Session-Timeout in Sekunden
date_default_timezone_set('Europe/Berlin');
Microsoft 365 OAuth (optional)
- Im Azure Portal eine App-Registrierung anlegen
- Umleitungs-URI:
https://ihre-domain.de/m365_callback.php - API-Berechtigungen:
openid,profile,email,User.Read - Client ID, Client Secret und Tenant ID in Administration → Einstellungen eintragen
Cron-Job (empfohlen)
Automatische Synchronisation alle 30 Minuten:
*/30 * * * * curl -s "https://ihre-domain.de/cron_sync.php?token=IHR_CRON_TOKEN"
Den Token finden Sie unter Administration → Einstellungen → Cron.
🎨 Design-System
Frontend, Login-Seiten, Installer, Updater und der gesamte Admin-Bereich nutzen ein
gemeinsames Stylesheet: assets/global.css.
- Design-Tokens (
:rootbzw.[data-theme="dark"]) für Flächen, Text, Linien, Markenfarbe, Statusfarben, Radien, Schatten und Layout-Maße - Komponenten darauf aufgebaut: Buttons, Formularfelder, Cards, Tabellen, Badges, Alerts, Tabs, Pagination, Modals, Toasts, Statistik-Kacheln, Sidebar/Topbar
- Dark Mode ausschließlich über Tokens – keine
!important-Overrides mehr - Schriftart Inter (via Google Fonts) mit System-Font-Fallback
Markenfarben ohne Code
Unter Administration → Einstellungen → Design lassen sich Akzentfarbe
(hell und dunkel), Markenverlauf und Eckenradius setzen. Abgeleitete Töne –
Hover, weiche Flächen, Rahmen, Fokusring – berechnet das System per color-mix
aus der Grundfarbe; eine Farbe genügt also. Eine Live-Vorschau zeigt Button,
Badge, Chip und Logo-Kachel sofort im neuen Ton.
Die Werte landen als schlanker :root-Override im Seitenkopf und gelten überall,
auch auf Login-Seite, Installer und Updater. Wer lieber in CSS arbeitet, kann
dieselben Variablen weiterhin in assets/global.css überschreiben:
:root {
--accent: #0f766e; /* Primärfarbe (Buttons, aktive Navigation) */
--brand-gradient: linear-gradient(135deg, #0f766e 0%, #0ea5e9 100%);
--r-lg: 14px; /* Eckenradius für Cards */
}
Bilder hochladen
Logo, Favicon, Login-Logo und Login-Hintergrund lassen sich direkt hochladen –
alternativ bleibt das URL-Feld bestehen. Die Dateien landen unter uploads/
(Docker: eigenes Volume, siehe unten). Erlaubt sind PNG, JPG, WEBP, GIF und SVG
bis 3 MB; SVGs werden vor dem Speichern von Skripten und externen Verweisen
befreit, und im Upload-Ordner sperrt eine .htaccess die PHP-Ausführung.
Assets ohne Drittanbieter
Schrift (Inter), Icons (Font Awesome), Diagramme (Chart.js), QR-Codes und der
WYSIWYG-Editor (TinyMCE) liegen unter assets/vendor/ und kommen vom eigenen
Server. Das hält Besucher-IPs bei Ihnen – und die Oberfläche funktioniert auch
dort, wo das Netz keinen Weg nach außen hat. Details und Aktualisierungs-Hinweise:
assets/vendor/README.md.
Alle Asset-URLs tragen einen Versionsstempel (?v=…), damit Browser nach einem
Update nicht die alten Dateien aus dem Cache verwenden.
Login-Seite individualisieren
Unter Administration → Einstellungen → Login-Seite lässt sich die Anmeldeseite ohne Code-Änderung an das eigene Haus anpassen. Leere Felder verwenden jeweils den Standardwert – eine frische Installation sieht also unverändert aus.
| Einstellung | Wirkung |
|---|---|
| Linke Bildspalte anzeigen | Split-Screen an/aus. Aus = zentrierte Anmeldekarte |
| Firmenname | Name neben dem Logo bzw. in der Fußzeile (leer = Anwendungstitel) |
| Logo (URL) | Eigenes Logo in der Bildspalte (leer = allgemeines Logo) |
| Überschrift / Beschreibungstext | Claim in der Bildspalte |
| Stichpunkte | Liste mit Haken – ein Stichpunkt pro Zeile, leer = keine Liste |
| Fußzeile | z. B. © 2026 Muster GmbH · Datenschutz · Impressum |
| Hintergrundbild (URL) | Formatfüllendes Bild der Bildspalte |
| Verlauf Start-/Endfarbe | Farbverlauf, wenn kein Bild gesetzt ist |
| Abdunklung (%) | Dunkle Ebene über dem Bild, damit der Text lesbar bleibt |
Über „Vorschau öffnen" lässt sich die Login-Seite als angemeldeter Administrator
ansehen (login.php?preview=1), ohne sich abzumelden.
🛡️ Sicherheit
Das Tool ist auf einen sicheren Standardbetrieb ausgelegt:
| Schutz | Umsetzung |
|---|---|
| Passwörter | bcrypt (password_hash) für Accounts |
| UniFi-Passwörter | Verschlüsselt-at-rest (AES-256-GCM / libsodium) über APP_KEY |
| SQL-Injection | Durchgängig Prepared Statements (PDO) |
| CSRF | Token für alle schreibenden Aktionen – auch im öffentlichen Modus |
| OAuth | state-Validierung gegen Login-CSRF |
| Brute-Force | Login-Rate-Limiting + Throttle der öffentlichen Voucher-Erstellung |
| Sessions | HttpOnly, SameSite, strict mode + absolutes Timeout |
| Fehler | display_errors aus, log_errors an (kein Info-Leak) |
Mitgeliefert wird eine .htaccess im Projektstamm mit Sicherheits-Headern
(X-Content-Type-Options, X-Frame-Options, Referrer-Policy,
Permissions-Policy und einer Content-Security-Policy). Da alle Assets lokal
liegen, erlaubt die CSP nur noch die eigene Herkunft – externe Verbindungen
bleiben lediglich für hCaptcha offen, falls es aktiviert wird. Ordner wie
includes/, tools/, tests/ und uploads/ schützen sich über eigene
.htaccess-Dateien.
Apache:
AllowOverride Allmuss für das Verzeichnis gesetzt sein, sonst werden die.htaccess-Dateien ignoriert. Das mitgelieferte Docker-Image erledigt das bereits.
Für Nginx entspricht das:
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; frame-ancestors 'self'" always;
location ~ ^/(includes|tools|tests)/ { deny all; }
location ~ ^/updater/(storage|migrations)/ { deny all; }
location ~ ^/(config\.php|database\.sql)$ { deny all; }
location ^~ /uploads/ { location ~ \.php$ { deny all; } }
-- Dedizierter Datenbank-Benutzer mit minimalen Rechten
CREATE USER 'unifi_voucher'@'localhost' IDENTIFIED BY 'sicheres_passwort';
GRANT SELECT, INSERT, UPDATE, DELETE ON unifi_voucher.* TO 'unifi_voucher'@'localhost';
🩺 Problembehandlung
Login funktioniert nicht
- Datenbankverbindung und PHP-Session-Konfiguration prüfen
- Fehler werden ins PHP-Error-Log geschrieben (nicht mehr in den Browser)
UniFi-Verbindung schlägt fehl
- Controller-URL im Browser testen
- Port 11443 für UniFi OS verwenden (nicht 8443)
- Benutzername, Passwort und Site ID prüfen
- cURL-Extension muss aktiviert sein
UniFi OS: HTTP 404 oder 401
- Login-Endpunkt ist
/api/auth/login(nicht/api/login) - API-Pfade benötigen Präfix
/proxy/network/api/s/{site}/... - Älterer UniFi Network Controller (ohne UniFi OS) wird ab Version 2.1.0 nicht mehr unterstützt
Microsoft 365 Login funktioniert nicht
- Redirect URI in Azure AD prüfen
- Client ID, Secret und Tenant ID kontrollieren
- Diagnose unter
m365_debug.php(nur als Admin erreichbar)
📡 API-Dokumentation (UniFi OS)
# Login
POST /api/auth/login
Body: {"username": "admin", "password": "password"}
Response-Header: X-CSRF-Token: <token>
# Voucher erstellen
POST /proxy/network/api/s/{site_id}/cmd/hotspot
X-CSRF-Token: <token>
Body: {"cmd": "create-voucher", "expire": 480, "n": 1, "note": "Name", "quota": 1}
# Vouchers abrufen
GET /proxy/network/api/s/{site_id}/stat/voucher
# Voucher löschen
POST /proxy/network/api/s/{site_id}/cmd/hotspot
Body: {"cmd": "delete-voucher", "_id": "<voucher_id>"}
🧰 REST-API
Voucher lassen sich programmatisch erstellen (z. B. aus Buchungssystemen oder
Self-Service-Terminals). Schlüssel werden unter Administration → API-Schlüssel
verwaltet. Authentifizierung per Authorization: Bearer <key> oder X-API-Key.
# Voucher erstellen
curl -X POST https://ihre-domain.de/api/vouchers.php \
-H "Authorization: Bearer uvt_…" \
-H "Content-Type: application/json" \
-d '{"site_id":1,"name":"API Gast","max_uses":1,"expire_minutes":480,
"qos":{"down":10000,"up":2000,"quota_mb":500}}'
# Sites auflisten
curl https://ihre-domain.de/api/sites.php -H "X-API-Key: uvt_…"
# Voucher einer Site abrufen
curl "https://ihre-domain.de/api/vouchers.php?site_id=1" -H "X-API-Key: uvt_…"
| Methode | Endpunkt | Zweck |
|---|---|---|
POST |
/api/vouchers.php |
Voucher erstellen (optional mit QoS-Limits) |
GET |
/api/vouchers.php?site_id=<id> |
Voucher einer Site auflisten |
GET |
/api/sites.php |
Aktive Sites auflisten |
🔒 2FA, Webhooks & Wartung
- 2FA: Unter Administration → Sicherheit (2FA) aktivierbar – QR-Code scannen, Code bestätigen. Danach wird bei jeder Anmeldung ein Authenticator- Code abgefragt.
- Webhooks & Trusted-Proxy & Datenhaltung: unter Administration → Integration & Wartung konfigurierbar.
- Auto-Cleanup (DSGVO): täglicher Cron, löscht abgelaufene Voucher,
Audit-Log, Login-Versuche nach einstellbaren Fristen:
0 3 * * * curl -s "https://ihre-domain.de/cron_cleanup.php?token=IHR_CRON_TOKEN"
🐳 Docker
# APP_KEY erzeugen und in docker-compose.yml eintragen:
php -r 'echo base64_encode(random_bytes(32))."\n";'
docker compose up -d # App auf http://localhost:8080
Das Schema wird beim ersten Start automatisch in MariaDB geladen; danach den
Installer (/install.php) für den Admin-Account aufrufen oder Config per ENV
setzen (DB_*, APP_KEY).
Hochgeladene Logos und Hintergründe liegen im Volume uploads und überstehen
damit ein Image-Update. Bei eigener Apache-/Nginx-Installation muss uploads/
für den Webserver beschreibbar sein:
chown -R www-data:www-data uploads updater/storage
🧪 Entwicklung
Für Screenshots und einen schnellen Durchlauf aller Seiten gibt es eine
Demo-Instanz ohne Datenbank – Database und Auth werden durch Stubs mit
festen Beispieldaten ersetzt:
python3 tools/demo/build.py /tmp/uvt-demo
php -S 127.0.0.1:8123 -t /tmp/uvt-demo &
# Bilder in docs/screenshots neu erzeugen (benötigt headless Chromium)
CHROME_BIN=/usr/bin/chromium python3 tools/screenshots.py
Details und die verfügbaren Demo-Zustände: tools/README.md.
Tests und statische Analyse:
composer install
vendor/bin/phpunit # 29 Tests (Crypto, TOTP, API-Keys, Upload, Ui)
vendor/bin/phpstan analyse # Level 5
Die Pipeline (.github/workflows/ci.yml) führt zusätzlich einen
Syntax-Check über alle PHP-Dateien aus und prüft, ob lang/de.php und
lang/en.php dieselben Schlüssel enthalten und jeder im Code verwendete
Schlüssel existiert. Dieselben Schritte lassen sich lokal ausführen.
📦 Repository & Mitwirken
Der Quellcode liegt auf der eigenen Forgejo-Instanz – nicht auf GitHub:
https://git.loheide.cloud/friloo/Unifi-Voucher-Tool
# HTTPS
git clone https://git.loheide.cloud/friloo/Unifi-Voucher-Tool.git
# SSH (Port 2222)
git clone ssh://git@git.loheide.cloud:2222/friloo/Unifi-Voucher-Tool.git
Fehlerberichte und Änderungsvorschläge laufen über die Issues und Pull
Requests dort. Für die Kommandozeile eignet sich tea,
die Gitea-/Forgejo-CLI:
tea pr create # Pull Request öffnen
tea issues ls # offene Tickets ansehen
Die Workflows unter
.github/workflows/werden von Forgejo Actions mitgelesen; das Badge oben ist bewusst statisch, solange kein Runner registriert ist.docker-publish.ymlveröffentlicht nachghcr.iound stammt noch aus der GitHub-Zeit – für den Forgejo-Betrieb entweder auf die eigene Registry umstellen oder entfernen.
Der Auto-Updater ist davon unabhängig: er zieht seine Pakete über
update.loheide.eu (Channels stable und development) und nicht direkt aus
dem Git-Hoster.
🗺️ Roadmap
- Voucher-Templates (vordefinierte Laufzeiten)
- Bulk-Voucher-Erstellung
- Mehrsprachigkeit (DE/EN)
- Dark Mode
- Passwort-Reset
- Audit-Log
- Auto-Updater mit DB-Migrationen
- REST-API mit API-Schlüsseln
- 2FA (TOTP), Webhooks, Bandbreitenlimits
- Docker-Container
- Erweiterte Reporting-Funktionen (CSV/PDF) + Health-Endpoint
- 2FA-Recovery-Codes, API-Scopes/Rate-Limit/OpenAPI, Test-Suite (PHPUnit/PHPStan)
- Gemeinsames Design-System für Frontend, Login und Backend
- Branding über die Oberfläche (Farben, Logo, Login-Seite)
- Assets lokal ausliefern (keine Drittanbieter-CDNs)
- Vollständige englische Übersetzung des Admin-Bereichs
Version 2.6.0 · Autor: Friederich Loheide · Lizenz: MIT
Entwickelt von Loheide.eu