dolibarr.netdiag/ChangeLog.md
Eduard Wisch bb6681ac37
All checks were successful
Deploy netdiag / deploy (push) Successful in 13s
Reiter am Kunden nur bei vorhandenen Protokollen (1.3.1) [deploy]
Der Reiter stand auf jeder Kundenkarte; die Leiste dort traegt ueber 20
Eintraege, auf Prod gibt es 20 Protokolle bei 6 von 111 Kunden. Er
erscheint jetzt nur bei Bestand, mit Anzahl. Am AUFTRAG bleibt er fest.

Als Hook (class/actions_netdiag.class.php, completeTabsHead) statt
festem Tab, Muster wie bei Mahnung und ElektroPlanung. Uebernimmt einen
bereits vorhandenen Eintrag desselben Schluessels statt einen zweiten
anzuhaengen — kein doppelter Reiter auch vor der Reaktivierung.

Lokal verifiziert: Kunde mit 13 Protokollen zeigt 'Netzwerk-Diagnose 13'
einmalig, Kunde ohne Protokoll zeigt den Reiter gar nicht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 21:27:51 +02:00

344 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Changelog NetDiag
## 1.3.1 — 2026-09-07
### Reiter am Kunden nur noch bei vorhandenen Protokollen
Der Reiter „Netzwerk-Diagnose" stand auf **jeder** Kundenkarte. Die Reiterleiste dort trägt
über 20 Einträge; auf Prod gibt es 20 Protokolle bei 6 von 111 Kunden — bei den übrigen 105
zeigte er eine leere Seite. Er erscheint jetzt nur bei Bestand, mit der Anzahl daneben.
**Am Auftrag bleibt er unverändert ein fester Tab.**
Technisch als Hook statt festem Tab (`class/actions_netdiag.class.php`, `completeTabsHead`),
weil die Bedingung eines Descriptor-Tabs keine Datenbankabfrage zulässt — Muster wie bei den
Modulen Mahnung und ElektroPlanung. **Nach dem Deploy Modul einmal reaktivieren**, sonst
steht der alte feste Reiter bis dahin als `MAIN_MODULE_NETDIAG_TABS_0` in der Datenbank; der
Hook übernimmt einen bereits vorhandenen Eintrag statt einen zweiten anzuhängen, doppelte
Reiter kann es also auch im Zwischenzustand nicht geben.
## 1.3.0 — 2026-08-19
### Neues Werkzeug: SIP-Erreichbarkeit (Telefonie)
Die App fragt Telefonanlage oder Provider mit einer OPTIONS-Anfrage (RFC 3261) —
ohne Anruf, ohne Zugangsdaten, ohne dass irgendwo ein Telefon klingelt. Modulseitig
kommt der PDF-Zweig dazu.
- `netdiagPdfSip()` in `lib/netdiag_pdf.lib.php`: Ziel, Port und Statuszeile in der
Kopfzeile, darunter der Befund im Klartext, dann eine Zeile je Transportweg.
**Warum ein eigener Zweig:** in einer flachen Schlüssel/Wert-Liste sieht „401
Unauthorized" wie ein Fehler aus. Tatsächlich ist es der Beweis, dass die Instanz
erreichbar ist und antwortet — sie will nur Zugangsdaten sehen. Diese Unterscheidung
ist im Kundendokument der Unterschied zwischen „Anlage tot" und „Zugangsdaten prüfen".
- Der Grenzhinweis steht mit im PDF: gemessen ist die **Erreichbarkeit**, NICHT die
Sprachqualität. RTP-Jitter ohne aufgebautes Gespräch zu behaupten wäre eine
Falschaussage.
- `netdiagToolName()`: `sipcheck` → „SIP-Erreichbarkeit".
- `netdiagKundenfelder()` um die Felder des Werkzeugs ergänzt (`befund`,
`statuszeile`, `antwortWeg`, `anmeldungNoetig`, `versuche`, `unterstuetzteMethoden`,
`zielIp`, `zielHerkunft`, `zielPort`, `laufzeitMs`).
**Achtung, dabei aufgefallen:** der naheliegende Schlüssel `port` war bereits vom
SNMP-Werkzeug als „Switch-Port" belegt. Ein SIP-Port unter diesem Namen stünde im
Kundendokument falsch beschriftet — deshalb `zielPort`.
### WLAN-Kanalanalyse: Warnungen werden nicht mehr abgeschnitten
`netdiagPdfWifiKanal()` kürzte jede Warnung nach 160 Zeichen (`dol_trunc`). Betroffen
war genau die Begründung: „… blockiert damit praktisch 2 der 3 störungsfreien Kanäle.
In dicht besiedelter Umgebung meist ein Fehle…". Eine Warnung ohne ihren Grund ist im
Kundendokument wertlos; MultiCell bricht ohnehin sauber um.
### Sicherheit: Token in der Adresszeile nur noch für den APK-Download
`netdiag_api_read_token()` nimmt `?jwt=` nicht mehr allgemein an, sondern nur noch,
wenn der Endpunkt es ausdrücklich erlaubt (`netdiag_api_authenticate($db, true)`).
Einzige Stelle: `update.php?download=1`.
Warum überhaupt: ein Token in der URL landet in Zugriffs- und Proxy-Logs und gilt
sieben Tage für die **gesamte** Kunden-API. Vorher hätte damit jeder, der ein Log
liest, Aufträge, Kunden und Protokolle abrufen können.
Warum die eine Ausnahme bleibt: die im Feld installierten App-Fassungen bauen die
Download-Adresse mit dem Token darin. Wird der Zweig sofort geschlossen, kommt kein
altes Gerät mehr an die neue APK — und die neue APK ist genau die, die den Zweig
überflüssig macht. **Zu entfernen, sobald der Rollout bestätigt ist** (Hinweis steht
im Code an beiden Stellen).
`pdf.php` nimmt ab sofort ausschließlich den `Authorization: Bearer`-Header.
## 1.2.0 — 2026-08-17
### Anmeldung nur noch über AWL-Auth — der eigene JWT-Pfad ist weg
**Achtung, das ist ein Schnitt:** Geräte mit einer App-Fassung, die noch ein
modul-eigenes netdiag-JWT benutzt, können sich nicht mehr anmelden und brauchen
die aktuelle APK. Eddy hat den Rollout am 17.08.2026 als abgeschlossen bestätigt.
Warum das mehr ist als Aufräumen: Der alte Pfad prüfte die Signatur eines selbst
ausgestellten Tokens und holte damit einen Benutzer aus der Datenbank — **an der
Sitzungsliste von awlauth vorbei**. „Gerät abmelden" hatte auf ein solches Token
keine Wirkung; ein verlorenes Handy blieb bis zum Ablauf der TTL angemeldet. Jetzt
gibt es genau eine Stelle, an der Sitzungen entstehen und enden.
- `api/auth.php`: der Rückfallweg (`checkLoginPassEntity` + eigenes JWT) ist
entfernt. Ohne aktives AWL-Auth antwortet die Anmeldung mit 503 und einer
klaren Meldung, statt auf einen zweiten Weg auszuweichen.
- `api/netdiag_api.lib.php`: `netdiag_api_authenticate()` prüft nur noch
`awlauth_verify_token()`. Die Hilfsfunktionen `netdiag_jwt_encode/decode`,
`netdiag_jwt_secret` und die Base64-URL-Helfer sind ersatzlos entfallen.
- **Konstanten `NETDIAG_API_JWT_SECRET` und `NETDIAG_API_TOKEN_TTL`** sind aus
dem Modul-Descriptor raus. Bestehende Werte in `llx_const` werden bewusst
NICHT gelöscht — sie schaden nicht, und ein Löschlauf beim Modul-Update ist
das größere Risiko.
- **TTL-Feld aus `admin/setup.php` entfernt.** Die Gültigkeit kommt jetzt allein
aus `AWLAUTH_TTL` (awlauth-Setup, Standard 7 Tage, dort 1180 Tage
einstellbar). Zwei Stellschrauben für dieselbe Sache sind eine Fehlerquelle —
die hiesige hätte nach diesem Umbau still gar nichts mehr getan. An ihrer
Stelle steht jetzt ein Verweis.
- Sprachdateien de_DE und en_US nachgezogen, beide wieder deckungsgleich
(57 Schlüssel).
Geprüft gegen die lokale Testinstanz: Anmeldung mit falschem Passwort → sauberer
Fehler von awlauth; API mit ungültigem Token → 401 „Token ungültig oder
abgelaufen"; ohne Token → 401 „Kein Token übermittelt". Kein PHP-Fehler durch
die entfernten Funktionen.
## 1.1.7 — 2026-08-17
### PDF-Zweig und Feldtabelle für den Geräte-Vergleich
`netdiagPdfGeraeteDiff()` gibt die drei Listen getrennt aus: neu hinzugekommen,
nicht mehr erreichbar, mit neuer IP-Adresse — plus „Gleiche Adresse, andere
offene Ports". Beim Wiederholungstermin zählt, WELCHES Gerät neu ist, nicht nur
wie viele; als `|`-Kette in einer Zelle wäre das vorhanden, aber nicht lesbar.
An jeder Zeile steht, worüber das Gerät wiedererkannt wurde. „Erkannt über IP
und Portmuster" ist eine schwächere Aussage als „über Netzwerkname", und dieser
Unterschied gehört ins Dokument statt in eine Fußnote — die Wiedererkennung
läuft bewusst nicht über MAC-Adressen, die Android seit Version 10 nicht mehr
herausgibt.
Neu in der Whitelist: `verglichenMit`, `geraeteVorher`, `geraeteJetzt`,
`mitNeuerAdresse`, `andereOffenePorts`, `unveraendert`; Werkzeugname
`geraetediff` → „Geräte-Vergleich". `neu` und `nichtMehrErreichbar` standen
bereits drin (der IP-Scan nutzt dieselben Schlüssel).
Geprüft im Durchstich: in der App gegen echte Serverdaten gemessen
(Vergleich ND2026-0016 gegen ND2026-0006), synchronisiert, PDF erzeugt und
angesehen.
## 1.1.6 — 2026-08-17
### PDF-Zweig für die DNS-Prüfung
`netdiagPdfDns()` — je befragtem Server eine eigene Zeile statt einer
`|`-getrennten Kette. Bei zwei Servern ging die generische Darstellung noch,
bei den vier bis fünf Einträgen eines Firmennetzes wäre die eigentliche Aussage
— welcher Server antwortet und welcher nicht — nicht mehr auffindbar gewesen.
Damit hat jedes Werkzeug mit Listenergebnis einen eigenen Renderer: Dauertest,
WLAN-Kanal, Geräte-Monitor, Internet-Kette, DNS-Prüfung.
Geprüft im vollständigen Durchstich: in der App gemessen (Emulator gegen die
lokale Testinstanz), synchronisiert, PDF aus Protokoll ND2026-0016 erzeugt und
angesehen.
## 1.1.5 — 2026-08-16
### Feldtabelle für das neue Werkzeug „DNS-Prüfung"
Die App fragt jeden DNS-Server des Netzes einzeln und namentlich (eigener
UDP-Client auf Port 53). Damit die Aussage im Kundendokument ankommt, sind
`gefragteServer` und `antworten` in der Whitelist ergänzt und `dnscheck` in
der Werkzeugliste eingetragen — ohne diese drei Zeilen stünde dort nur die
Ampel. Die einzelnen Serverzeilen („192.168.1.1: antwortet, kennt den Namen
nicht") sind das eigentliche Ergebnis.
## 1.1.4 — 2026-08-16
### Drei Falschaussagen im Kundendokument, alle aus Altdaten
- **„0 DHCP-Server (!)" in Rot.** In der Produktionsdatenbank stehen drei
ausgelieferte Messungen des entfernten Werkzeugs `dhcpcheck` mit Ergebnis
`{"count":0,"server":[],"hinweis":""}` und Status 2. Bei einer Kabelverbindung
gibt Android die DHCP-Angaben aber grundsätzlich nicht heraus (nur `DhcpInfo`,
nur WLAN, deprecated) — gemessen wurde also nichts, „0 gefunden" war der
Rückgabewert für „nicht ermittelbar". `netdiagAltlastKorrektur()` zeigt diese
Messungen jetzt als „nicht messbar" mit Erklärung, in PDF **und**
Technikeransicht. Die gespeicherten Rohdaten bleiben unangetastet (Beleg);
korrigiert wird nur die Darstellung, und die wird bei jedem Abruf neu erzeugt.
Die Ergebniszahlen werden bei so korrigierten Messungen unterdrückt —
„Gefundene Geräte: 0" widerspräche der Aussage direkt darüber.
- **„Lease-Dauer: 864000 s s".** Ältere App-Fassungen haben die Einheit in den
Wert geschrieben, die Feldtabelle hängt sie erneut an. Nachgewiesen an
Prod-Messung #42. Die Einheit wird jetzt nur noch angehängt, wenn sie nicht
schon am Wert steht.
- **Adress- und Lease-Felder mit dem Wert 0** (`server`, `dhcpServer`,
`gateway`, `ipv4`, `lease` …) sind keine Messung, sondern das „nichts
ermittelt" der alten Android-API. Sie erscheinen jetzt als „nicht ermittelbar"
statt als „0".
Neues Feld in der Whitelist: `dhcpQuelle``system` (LinkProperties, ab
Android 11, gilt für jeden Anschlusstyp) oder `wlan` (alter Weg, nur WLAN, dafür
mit Lease-Dauer). Damit ist im Nachhinein klärbar, ob eine fehlende DHCP-Angabe
an der Leitung lag oder an der Android-Version.
## 1.1.3 — 2026-08-16
### Internet-Kette im PDF + ein Zeichenfehler, der jedes Protokoll betraf
- **`netdiagPdfKette()`** für das neue App-Werkzeug „Internet-Kette": Fehlerglied
als eigene hervorgehobene Zeile, darunter die vier geprüften Stufen einzeln,
dann Systemurteil und Hinweis. Ohne eigenen Zweig stünden die Stufen als
`|`-Kette in einer Zelle und das Fehlerglied — die eigentliche Aussage —
mittendrin.
- **`netdiagPdfText()`: Pfeile wurden als „?" gedruckt.** `pdf_getPDFFont()`
liefert hier Helvetica, einen Core-Font mit WinAnsi-Kodierung; alles außerhalb
dieses Zeichenvorrats setzt TCPDF wortlos als „?" (bekannt aus KB #1025).
Aufgefallen an „www.google.de → 142.250.185.67", das im Kundendokument als
„www.google.de ? 142.250.185.67" ankam — der Pfeil ist dort kein Schmuck,
er trennt Frage und Antwort. Die Ersetzung greift jetzt in **allen**
PDF-Zweigen, also auch für Altdaten und künftige Werkzeuge.
Bewusst keine Unicode-Schrift: `dejavusans` würde Schriftbild und Dateigröße
aller Protokolle ändern. Gedankenstrich, Mittelpunkt, Auslassungspunkte und
Anführungszeichen sind in WinAnsi enthalten und bleiben unangetastet
(im erzeugten Dokument nachgeprüft).
- Feld-Whitelist um `fehlerglied`, `stufen`, `systemUrteil`, `validated` und
`captivePortal` erweitert, Werkzeugname `internetkette` → „Internet-Kette".
`validated`/`captivePortal` kommen aus dem LinkInfo-Objekt und erscheinen
damit auch beim IP-Test an der Dose.
Geprüft an vier Fällen gegen die lokale Testinstanz, zwei davon sind die echten
Emulator-Messungen dieser Nacht (Gutfall und Flugmodus).
## 1.1.2 — 2026-08-16
### Geräte-Monitor bekommt einen eigenen PDF-Zweig
Die Dauerüberwachung lief bisher in den generischen `else`-Zweig von
`netdiag_pdf.lib.php` und wurde von `netdiagPdfFlattenResult()` zu **einer**
`|`-getrennten Zeile zusammengeschoben. Bei zehn überwachten Geräten standen
damit die zehn Geräte-Zeilen UND sämtliche Einzelausfälle in einer einzigen
Tabellenzelle — vollständig, aber beim Kunden nicht lesbar. Dauertest und
WLAN-Kanal hatten längst je einen Tabellen-Renderer, der Monitor nicht.
- **`netdiagPdfMonitor()`**: Kennzahlenzeile, Tabelle „Überwachte Geräte",
Tabelle „Einzelne Ausfälle" mit Uhrzeiten, Hinweis zum Messverfahren am Ende.
- **Die Falle dabei**, gegen die es einen Kommentar im Code gibt: beim Dauertest
führt ein gesetztes `hinweis` zum sofortigen Abbruch der Ausgabe. Der Monitor
legt aber IMMER einen Hinweis an (das Messverfahren gehört zur Aussage dazu) —
derselbe Aufbau hätte hier bei **jedem** Lauf alle Zahlen verschluckt. Nur
`fehler` bricht ab, der Hinweis steht am Schluss.
- Tabellenköpfe wiederholen sich nach einem Seitenumbruch („— Fortsetzung"),
wie in der Geräteliste.
- Obergrenze 200 Ausfallzeilen gegen den pathologischen Fall (ein im
Sekundentakt flappendes Gerät erzeugt über einen Arbeitstag vierstellig viele
Segmente). Gekürzt wird **sichtbar**: „… und N weitere Ausfälle" steht im
Dokument, sonst läse sich das Protokoll als vollständig.
- **`ausfallzeitSek` heißt jetzt „Ausfallzeit (schlechtestes Gerät)"** — der
Wert ist der des schlechtesten Geräts, nicht die Summe über alle. Bei zehn
überwachten Geräten las sich „Ausfallzeit: 8 min" wie eine Gesamtaussage.
Gleiche Korrektur in `app/src/lib/messfelder.ts`, damit App und PDF dasselbe
Feld gleich benennen.
Geprüft gegen die lokale Testinstanz mit vier konstruierten Fällen (10 Geräte
mit Aussetzern, ein durchgehend totes Gerät, 250 Einzelausfälle über sechs
Seiten, abgerissener Lauf) und gegen die echte Monitor-Messung aus ND2026-0016.
## 1.1.1 — 2026-08-16
### Vier stille Fehler im Kundendokument und im Sync
Alle vier waren bereits ausgeliefert und fielen nicht auf, weil das PDF ja
weiterhin erzeugt wurde — nur ohne die betroffene Zeile. Gefunden durch einen
systematischen Abgleich der Whitelist gegen den erzeugenden App-Code.
- **`'typ'` war ein toter Schlüssel.** Kein Werkzeug erzeugt ihn; die App reicht
ein `LinkInfo`-Objekt durch, dort heißt das Feld `'type'`. Folge: im
Abnahmebeleg für eine Netzwerkdose fehlte die Angabe, **ob per LAN oder WLAN
gemessen wurde**. Zusätzlich `'rssi'` aufgenommen — bei einer WLAN-Dose ist
der Empfangspegel der eigentliche Messwert und wurde bisher verworfen.
Die Rohwerte sind englisch; `netdiagKundentext()` bildet sie jetzt ab
(`ethernet` → „LAN (Kabel)"), sonst stünde „Anschlussart: ethernet" im
Abnahmeprotokoll eines deutschen Handwerksbetriebs.
- **Leeres Array druckte „Offene Ports: " ohne Wert.** Die Leerprüfung ist ein
`elseif` hinter dem Array-Zweig und wurde nie erreicht. Betraf ausgerechnet
die *guten* Ergebnisse: Portscan ohne offenen Port, IP-Konflikt ohne Konflikt,
IP-Scan ohne Veränderung. Jetzt „keine" — Weglassen wäre schlechter, weil der
Kunde sonst nicht unterscheiden kann, ob nichts gefunden oder nichts geprüft
wurde.
- **`'error'` fehlte in der Whitelist.** Der IP-Konflikt gibt im Abbruchfall
`{ error: … }` mit roter Ampel zurück — im Kundendokument stand eine rote
Ampel ohne einen Buchstaben Erklärung. Die App vereinheitlicht künftig auf
`'fehler'`, aber Altdaten lassen sich nicht ändern.
- **`tool`/`category`/`label` wurden beim Sync nicht auf die Spaltenlänge
gekürzt** (varchar 64/32/255). Bei striktem SQL-Modus kippt **ein** zu langes
Label den gesamten Sync per `rollback()` — der Techniker steht beim Kunden mit
einem Protokoll da, das sich nicht abschließen lässt, weil eine Beschriftung
zu lang war. Die App setzt Labels durch Verketten zusammen, 255 Zeichen sind
erreichbar. `found_via` wurde 20 Zeilen darüber längst gekappt.
### Rechteprüfung im GET-Zweig der Protokoll-API
Bisher prüfte der GET-Zweig **gar nichts**: jeder angemeldete Benutzer konnte
mit einer geratenen ID ein fremdes Protokoll samt Geräteliste, IP-Adressen und
offenen Ports abrufen (der POST-Zweig verlangte seit jeher `write`). Das ist
beim Kunden erhobene Netzstruktur und gehört hinter ein Recht.
`write` wird bewusst mit akzeptiert: die Rechte sind in Dolibarr einzeln
vergebbar, und ein Techniker mit Schreib- ohne ausdrückliches Leserecht dürfte
nicht ausgesperrt werden — das wäre erst beim Kunden aufgefallen.
### Geräte-Monitor erscheint jetzt im Kundendokument
Die App liefert ab dieser Version eine Auswertung der Dauerüberwachung als
richtige Messung (Verfügbarkeit, Ausfallzeit, Aussetzer je Gerät). Die sechs
neuen Schlüssel sind in der Whitelist ergänzt — ohne sie stünde im PDF nur die
Ampel.
### Kleinigkeit
`netdiagDauerLesbar()`: „1 Tage 0 Std" → „1 Tag". `floor()` liefert in PHP einen
Float, und `1.0 === 1` ist false — die Einzahl hatte deshalb nie gegriffen.
## 1.1.0 — 2026-08-14
### Anmeldung läuft über das zentrale Auth-Modul (awlauth)
NetDiag war die letzte AWL-App mit eigenem Login: eigenes JWT, eigener Schlüssel
(`NETDIAG_API_JWT_SECRET`), eigene Gültigkeitsdauer, keine Möglichkeit, ein verlorenes
Handy gezielt abzumelden. Ab sofort stellt awlauth das Token aus — dasselbe Token,
derselbe Schlüssel und dieselbe Sitzungs-Registry wie bei den sechs Browser-Apps.
- `api/auth.php` prüft das Passwort über `awlauth_login()`. Damit greifen dort auch die
**Brute-Force-Bremse** (5 Fehlversuche je IP in 15 min, 10 je Benutzername in 30 min)
und die einheitliche, nicht-verräterische Fehlermeldung. Das Token kommt aus
`awlauth_issue_bearer()` und legt eine Sitzungszeile an — das Gerät erscheint als
„NetDiag-App · Android" in der awlauth-Geräteliste und ist dort einzeln abmeldbar.
- `netdiag_api_authenticate()` prüft zuerst das awlauth-Token. Ein **abgelaufenes oder
widerrufenes** Token führt sofort zu 401, auch wenn die Signatur noch stimmt.
- **Übergangsweise** wird ein bereits ausgestelltes altes netdiag-JWT weiterhin akzeptiert,
damit die Umstellung niemanden mitten im Einsatz aussperrt. Dieser Pfad entfällt, sobald
alle Geräte einmal neu angemeldet sind.
- Ohne aktives awlauth-Modul läuft NetDiag unverändert mit dem eigenen JWT weiter.
**Die Antwortform von `auth.php` ist bewusst unverändert** (`{token, expiresIn, user}`).
Bereits installierte APKs laufen nach einer **einmaligen Neuanmeldung** ohne Update weiter.
### CORS enger gefasst
`Access-Control-Allow-Origin: *` ist raus. Die API liefert Kundendaten aus; ein Wildcard
erlaubt jeder beliebigen Webseite, die Antwort auszulesen, sobald sie an ein Token kommt.
Erlaubt sind jetzt nur die App-Origins (`https://localhost`, `capacitor://localhost`,
`http://localhost`, `ionic://localhost`) und der Vite-Dev-Server; dazu `Vary: Origin`.
Anfragen ohne Origin-Header (nativer HTTP-Client, der APK-Downloader im Kotlin-Plugin,
curl) bekommen wie bisher eine Antwort — dort greift die Same-Origin-Policy ohnehin nicht.
## 1.0.0
- Erstversion
- Datenmodell: Protokoll, Gerät, Messung
- JSON-API: auth, customers, orders, protocols (Sync), pdf
- Tab "Netzwerk-Diagnose" an Kunde und Auftrag
- PDF-Protokoll-Generator + ECM-Ablage
- Backend: Protokoll-Liste und Detailansicht
- Admin: Token-Einstellung, QR-Code zum App-Download