Commit Graph
45 Commits
Author SHA1 Message Date
clemensandClaude Opus 5 e39c00105e Bei Telefonanfragen auf die Sprachnachricht in PraxisKI hinweisen
anfragen.nachricht enthaelt bei source='telefon' nur das automatisch
erzeugte Transkript der Sprachnachricht, das Erkennungsfehler enthalten
kann. Bisher war aus der Oberflaeche nicht ersichtlich, dass es die
Aufnahme selbst noch gibt und wo sie liegt.

Neue Funktion GetSprachnachrichtHinweis() gibt fuer source='telefon' einen
Hinweis mit Link auf https://ki-praxis.praxis.local/ aus, fuer jede andere
Herkunft einen leeren String. Der Link ist nur aus dem Praxisnetz
erreichbar, das steht im Hinweis dabei.

Eingesetzt an allen fuenf Stellen, an denen eine Telefonanfrage sichtbar
wird: Anfrageliste, Antwortformular, Hinweisseite fuer Anfragen ohne
zugeordneten Patienten, Antwort einsehen und Patientenhistorie. Die
Abfragen fuer die letzten beiden lasen source bisher nicht mit, die Spalte
ist dort ergaenzt.

Nachgeprueft gegen die Datenbank: alle vier Abfragen liefern source, und
der Hinweis erscheint ueber alle 12785 Anfragen hinweg genau dann, wenn
source='telefon' ist - je Zeile geprueft, keine Ausnahme. Beide Dateien
bleiben UTF-8 ohne BOM.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 13:07:29 +02:00
clemensandClaude Opus 5 f19c490f44 Telefon-Anfragen ohne Personenbezug wieder anzeigen
Seit dem 02.09.2026 legt die Telefonanlage Anfragen mit source='telefon'
an. Von 309 solchen Zeilen haben 207 keine requester_person_id, weil der
Anrufer nicht zugeordnet werden konnte - darunter alle 33 offenen. Saemtliche
Anzeigepfade haengen per INNER JOIN an persons, wodurch genau diese Zeilen
kommentarlos aus dem Ergebnis fielen. Die Ansicht "Unbeantwortete Anfragen"
zeigte dadurch 2 statt 35 Eintraege.

Bei mail- und portal-Anfragen konnte das nicht auffallen: die Formulare
legen die Person vorher in persons an und brechen sonst ab (rezepte.php),
entsprechend hat dort keine einzige Zeile eine fehlende Person. personid
und userid stehen zwar auch bei mail auf 0, sind aber tote Legacy-Spalten
und tragen den Personenbezug nicht.

Die Anzeigepfade verwenden jetzt LEFT JOIN und zeigen ohne Treffer
"Kein Patient zugeordnet" statt einer leeren Zeile: Liste, Antworten,
Loesch-Dialog, Loeschen ohne Mail und Antwort einsehen. Die beiden Knoepfe,
die zwingend eine Mailadresse brauchen - Antworten und Loeschen mit Mail -
erscheinen nur noch mit zugeordneter Person; wer das Antwortformular direkt
aufruft, bekommt einen Hinweis samt Transkript statt eines Abbruchs.
"Telefonisch beantwortet" brauchte keine Aenderung, dieser Weg fragt nur
die anfrageid ab.

Der Mailversand in functions.inc.php und die patientenbezogene Historie
behalten ihren INNER JOIN - dort ist eine Person Voraussetzung.

Neu ist GetAnfrageHerkunft(). Die Liste leitete die Herkunft bisher aus
sicherenachricht ab, was Telefonanfragen mangels Wert als "Mailanfrage"
etikettierte. Nur dieser dritte Fall kommt aus source. Die Unterscheidung
intern/Mail bleibt bewusst an sicherenachricht haengen, weil insertAnfrage()
kein source setzt und 304 interne Anfragen deshalb als source='mail' in der
Tabelle stehen; ueber source vergeben haette das Label 9871 Bestandszeilen
umetikettiert, darunter die datenschutzrelevante Unterscheidung.

Nachgeprueft gegen die Datenbank: die Ansicht liefert statt 2 nun 35 Zeilen
mit allen 33 offenen Telefon-Anfragen. Der Renderblock ueber alle 12784
Zeilen simuliert, error_reporting=E_ALL als Exception, ohne Warnung; das
Herkunfts-Label bleibt fuer 12475 Bestandszeilen unveraendert und aendert
sich nur fuer die 309 Telefon-Zeilen. Alle fuenf von der Liste erreichbaren
Aktionen laden fuer eine Zeile ohne Person.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:56:45 +02:00
clemensandClaude Opus 5 6d903ac4fc CSRF-Schutz auf intern/ ausweiten und BOMs entfernen
intern/ enthaelt dieselben Patientendaten wie der Adminbereich und war
bisher genauso ungeschuetzt. config.inc.php aktiviert den Token-Filter
jetzt fuer beide Verzeichnisse. Eigenen AJAX-Code oder Nicht-HTML-Ausgaben
gibt es dort nicht, die Umstellung betrifft nur Formulare.

intern/login.php rief nach dem Include ein zweites session_start() auf und
setzte danach session.gc_maxlifetime und session.cookie_lifetime per
ini_set(). Beides lief ins Leere, weil die Sitzung zu dem Zeitpunkt schon
laeuft, und erzeugte Warnungen. Die Lebensdauer kommt aus
session_set_cookie_params() in config.inc.php.

13 Dateien begannen mit einem UTF-8-BOM. Die drei Bytes gehen vor dem
Include raus, womit die Header gesendet sind und
session_set_cookie_params() sowie session_start() in config.inc.php
scheitern - genau die Flags, um die es hier geht. Sichtbar wird das nur
ohne output_buffering, aber darauf sollte sich die Sitzungssicherheit
nicht verlassen.

Nachgeprueft mit output_buffering=0 und error_reporting=E_ALL: admin/ und
intern/ melden keine Header- oder Sessionwarnungen mehr, POSTs ohne Token
liefern 403, mit Token laufen sie durch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 15:16:40 +02:00
clemensandClaude Opus 5 86115b18a7 Patientendaten maskieren und SQL-Injection in togoadmin entfernen
Stored XSS
Die Anfrageuebersicht und das Antwortformular haben Nachricht, Medikamente,
Dateiname und Adressdaten des Patienten roh in HTML-Strings gesetzt.
Gespeichert wird der Text nur mit trim(), ein Patient konnte darueber
Skriptcode in den Browser der Mitarbeiterin einschleusen - mit deren
Sitzung. Die Werte laufen jetzt beim Auslesen durch e(). Der dritte Zweig
(Antwort einsehen) maskierte bereits bei der Ausgabe und bleibt, wie er
ist.

SQL-Injection
togoadmin.php interpolierte $_GET["id"] an drei Stellen ungecastet in
UPDATE-Statements, waehrend die Nachbarzeilen bereits (int) verwenden.
Dazu drei reflektierte Ausgaben desselben Wertes in Formularfelder.
Beides auf (int) umgestellt.

Formularziele
$_SERVER['PHP_SELF'] enthaelt bei Aufrufen wie /admin/anfragen.php/"><script>
auch den angehaengten Pfad und landete an 72 Stellen ungeprueft im HTML.
Ersetzt durch self_action() aus inc/security.inc.php, das den Basisnamen
des Skripts maskiert zurueckgibt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 14:19:13 +02:00
clemensandClaude Opus 5 19f1ac7ec8 CSRF-Schutz, Brute-Force-Bremse und wirksame Session-Cookie-Flags
Neu: inc/security.inc.php, eingebunden von inc/config.inc.php.

CSRF
Der Adminbereich hatte keinerlei Schutz gegen fremde POSTs. Jetzt haengt
csrf_inject_output() als Ausgabefilter das Token an jedes POST-Formular und
csrf_require() weist POSTs ohne gueltiges Token ab. Beides wird nur fuer
Skripte unterhalb von admin/ aktiviert, der oeffentliche Bereich bleibt
unveraendert. Der Umweg ueber den Ausgabefilter erspart es, die rund 60
bestehenden Formulare einzeln anzufassen; der AJAX-Aufruf auf
mailtemplate.php schickt das Token als Feld mit.

Session-Cookie-Flags
config.inc.php setzt secure, httponly und samesite - aber 25 Dateien in
admin/ und intern/ riefen session_start() vor dem Include auf, womit die
Parameter wirkungslos waren. Die vorgezogenen Aufrufe sind entfernt,
config.inc.php startet die Sitzung nur noch, wenn keine laeuft.
admin/logout.php musste umgestellt werden, weil dort session_destroy()
vor dem Include stand; die Cookies werden jetzt mit denselben Parametern
geloescht, mit denen sie gesetzt wurden.

Brute-Force
Nach 5 Fehlversuchen je Konto oder 20 je IP ist die Anmeldung 15 Minuten
gesperrt, gezaehlt in der neuen Tabelle login_attempts. Fehlt die Tabelle,
laeuft der Login wie bisher - gleiche Vorgehensweise wie bei
securitytokensHatAblaufspalte(). Eine erfolgreiche Anmeldung raeumt die
Fehlversuche des Kontos ab.

Passwort vergessen
admin/passwortvergessen.php uebergab $mail und $body an SendMailMessage();
beide Variablen gibt es dort nicht, sie heissen $empfaenger und $text. Die
Mail ging deshalb nie raus, obwohl der Reset-Code gesetzt wurde.

Getestet gegen einen lokalen PHP-Server: Token wird eingesetzt, POST ohne
Token liefert 403, mit Token laeuft der Login normal, der sechste
Fehlversuch wird gesperrt, das Sitzungscookie traegt secure/HttpOnly/
SameSite. Oeffentliche Seiten sind unveraendert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 14:18:59 +02:00
clemensandClaude Opus 5 fbe6f4ce5a Loeschen ohne Mailversand reparieren, Fehlertext praezisieren
Die Abfrage in aktion=10 stammte noch aus der Zeit der user-Tabelle und
selektierte p.mail, p.jahrgang sowie a.timeid. Keine dieser Spalten
existiert: persons fuehrt email und geburtstag, anfragen hat kein timeid.
Die Abfrage warf damit eine PDOException, und da der Block kein try/catch
hat, brach das Skript vor dem UPDATE ab - "Anforderung loeschen (ohne
Mail)" war komplett wirkungslos.

Spaltennamen korrigiert und die uebrig gebliebene Debug-Ausgabe von
timeid entfernt, die als einzige Stelle den Wert genutzt hat. Ein Scan
aller persons-Aliasse gegen das Schema zeigt keine weiteren Fundstellen.

Der Fehlertext beim fehlgeschlagenen Mailversand in aktion=3 behauptete,
es sei nichts gespeichert worden. Tatsaechlich laeuft das UPDATE vorher.
Die Meldung benennt jetzt den echten Zustand und bittet darum, den
Vorgang erneut auszufuehren.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 12:43:43 +02:00
clemensandClaude Opus 5 00b7811446 Telefonisch beantwortete Anfragen ohne Mailversand abschliessen
Der Button "Telefonisch beantwortet" (aktion=20) hat bisher die Mailvorlage
42 an die private Mailadresse des Patienten geschickt. Telefonisch erledigte
Anfragen duerfen die Praxis nie per Mail verlassen, der Versand entfaellt
daher ersatzlos.

Die Bestaetigungstexte benennen jetzt den tatsaechlichen Vorgang statt
faelschlich von einer Loeschung mit schriftlicher Mailbestaetigung zu
sprechen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 11:43:20 +02:00
clemensandClaude Opus 5 e6c2e7e41a Lauftext: schliessendes PHP-Tag aus einem Kommentar entfernen
Der Kommentar des vorigen Commits enthielt die Zeichenfolge des
schliessenden PHP-Tags, um das urspruengliche Problem zu erklaeren.
Genau die beendet den PHP-Block aber auch innerhalb eines
//-Kommentars - der gesamte nachfolgende Code wurde dadurch als Text
ins HTML geschrieben und das Laufband blieb leer.

Kommentar umformuliert, ohne die Zeichenfolge zu nennen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 00:04:14 +02:00
clemensandClaude Opus 5 8e3da39a40 Lauftext auf der Startseite reparieren
Der Lauftext stand in Short-Open-Tags:

    +++<? echo $Laufband1 ?>+++

short_open_tag ist auf dem Server abgeschaltet, deshalb landete der
PHP-Quelltext woertlich im ausgelieferten HTML. Der Browser hat
"<? echo $Laufband1 ?>" als unbekanntes Tag verworfen - sichtbar
blieben nur die Pluszeichen. Ueberprueft am ausgelieferten HTML der
Startseite, dort stand der Quelltext unveraendert drin.

Beide Vorkommen nutzen jetzt <?php und eine gemeinsam aufbereitete
Ausgabe:

- Leere Eintraege werden uebersprungen; sind alle sechs leer, entfaellt
  das Laufband ganz, statt leere +++ durchs Bild zu schicken.
- Der Inhalt kommt aus dem Editor in webseitenadmin.php und ist in der
  Datenbank HTML. config.inc.php entfernt zwar die Tags, die Entities
  bleiben aber stehen - daher erst html_entity_decode(), dann
  htmlspecialchars(). Sonst waere aus &amp; ein sichtbares "&amp;" und
  aus &Ouml; ein "&Ouml;" geworden.
- Beide Bloecke trugen dieselben id-Attribute (marquee-cont, scroll).
  Doppelte ids sind ungueltiges HTML; sie sind durch eine Klasse
  ersetzt, css/ticker.css spricht jetzt beide Schreibweisen an.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 00:00:58 +02:00
clemensandClaude Opus 5 864218c1b8 Zugriffsluecken und SQL-Injection in intern/ und admin/ beheben
Ergebnis der Durchsicht beider Bereiche (47 Dateien).

Patientendaten ohne Anmeldung abrufbar
--------------------------------------
admin/mailtemplate.php hatte keine Zugriffspruefung. Der Endpunkt
liefert gerenderte Mailvorlagen und damit Vorname, Nachname,
Geburtstag, Adresse, Medikamente, Anfragetext und den Anfragen-Link
mit dem hash. Ein anonymer POST wurde bis in die Datenbank verarbeitet
(geprueft mit ungueltiger templetid, es sind keine Daten geflossen).
Jetzt check_admin_user() mit HTTP 401. Ausserdem ging die
Exception-Meldung an den Aufrufer zurueck - sie geht jetzt ins Log.

intern/meineanfragen.php filterte beim Detailaufruf ausschliesslich
auf die anfrageid aus $_POST, ohne Bezug zum angemeldeten Benutzer -
und setzte sie unmaskiert ins SQL. Jeder registrierte Patient konnte
damit fremde Anfragen samt Geburtstag, Adresse und Telefonnummer
lesen. Jetzt Prepared Statement und zusaetzlich an die E-Mail des
angemeldeten Benutzers gebunden, wie in der Listenansicht derselben
Datei.

SQL-Injection
-------------
admin/togoadmin.php: 14 Abfragen bauten $_GET/$_POST direkt in das
SQL. Ganzzahlige Spalten bekommen einen (int)-Cast, damit die
umgebenden mysqli-Schleifen unveraendert bleiben; alle INSERT- und
UPDATE-Anweisungen mit Textwerten sind auf Prepared Statements
umgestellt. create_time dort jetzt per NOW() statt PHP-date().
Ein abschliessender Scan ueber intern/, admin/ und zeiterfassung/
findet keine verkettete Nutzereingabe in SQL mehr.

Zugriffspruefung ohne Wirkung
-----------------------------
admin/anrufbeantworter.php und admin/kalender.php riefen
check_admin_user() auf, werteten den Rueckgabewert aber nie aus. Die
Funktion liefert bei fehlender Anmeldung nur null, sie bricht nicht
ab - beide Seiten rendered fuer anonyme Besucher weiter. Daten flossen
nicht ab, aber die Oberflaeche war sichtbar. Jetzt gleiches Muster wie
admin/index.php.

Tokens ohne Ablauf
------------------
Die Tabelle securitytokens hatte keine Ablaufspalte: ein erbeutetes
Admin-Cookie galt unbegrenzt. Der Patientenbereich setzt 30 Tage.
securitytokensHatAblaufspalte() prueft die Spalte zur Laufzeit, damit
der Code vor und nach der Migration laeuft; admin/login.php setzt den
Ablauf beim Anlegen, check_admin_user() beruecksichtigt ihn beim
Lesen. Migration in admin/sql/. Die Cookie-Laufzeit war auf 365 Tage
gesetzt und ist jetzt deckungsgleich mit dem Token.

Entfernt
--------
admin/phpinfo.php lieferte ohne Anmeldung 102 KB Serverkonfiguration.
intern/admin.php, admin/admin.php sowie mailtemplatebody.php und
mailtemplatebetreff.php in beiden Verzeichnissen waren tot (falscher
relativer require-Pfad, HTTP 500) - die mailtemplate-Vorgaenger
enthielten zudem rohe SQL-Injection mit Patientendaten. Keine der
sechs Dateien wird irgendwo aufgerufen.
admin/sql war als leere Datei statt als Verzeichnis angelegt.

Kleinere Korrekturen
--------------------
intern/authentifizierung.php und admin/passwortzuruecksetzen.php
verglichen das Ergebnis von fetch() mit null statt false und pruefen
den Ablauf des Codes jetzt in SQL statt mit strtotime()/time() -
dieselbe Zeitzonenfalle wie bei den 2FA-Codes, hier in die harmlose
Richtung. Ein toter password_hash()-Aufruf auf einer nie gesetzten
Variablen ist entfallen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 23:48:45 +02:00
clemensandClaude Opus 5 67b2314878 Oeffentlich abrufbare .vscode-Zugangsdaten abgestellt
Beim Umstellen der git-ftp-Konfiguration ist aufgefallen, dass
.vscode/ auf dem Server lag und per HTTPS ohne jede Sperre abrufbar
war: sftp.json und ftp-sync.json lieferten mit HTTP 200 das
FTP-Passwort im Klartext aus, settings.json Host, Datenbankname und
Benutzer der MySQL-Verbindung (dort ohne Passwort).

Die drei Dateien und das Verzeichnis sind vom Server geloescht, die
URLs antworten jetzt mit 404. .git/ war bereits serverseitig
gesperrt, ebenso .git-ftp.log und die .sql-Datei im Root.

Zusaetzlich sperrt die Root-.htaccess jetzt Editor-, Versions- und
Konfigurationsdateien: Punkt-Verzeichnisse (.git, .vscode, .svn, .hg,
.idea, .env) per RedirectMatch 404 sowie die Endungen jsonc, sql,
log, ini, bak, old, orig, save, swp, dist. Vorher geprueft, dass
nichts davon per HTTP geladen wird; .json ist bewusst nicht
gesperrt, damit Bibliotheken unter admin/ weiter funktionieren.
Nach dem Ausrollen verifiziert: Startseite, /termine (Rewrite),
intern, zeiterfassung, admin, CSS, JS und Bilder liefern weiter 200.

.git-ftp.log ist aus der Versionierung genommen. Die Datei ist der
Deployment-Zustand, den git-ftp auf dem Server fuehrt; die lokale
Kopie stammt nur aus dem Sync und kann den eigenen Commit
naturgemaess nie enthalten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 22:05:22 +02:00
clemensandClaude Opus 5 f352555e58 Zugangsdaten aus der Versionierung nehmen, Sicherungskopien entfernen
Sicherungskopien
----------------
intern/neueanfrage-old1.php und -old2.php waren toter Code, aber als
.php im Web-Root oeffentlich aufrufbar - inklusive eigener DB- und
Mailzugriffe. Auf sie verweist nichts, sie sind jetzt entfernt.
Der Stand bleibt ueber die Git-Historie erreichbar.

Datenbank-Zugangsdaten
----------------------
Host, Benutzer, Passwort und Datenbankname standen im Klartext in
inc/config.inc.php UND in zeiterfassung/inc/config.inc.php, beide
versioniert. Sie liegen jetzt in inc/credentials.php, das per
.gitignore ausgenommen ist; beide Configs laden es und brechen mit
HTTP 500 und einem Log-Eintrag ab, wenn es fehlt, statt sich still
ohne Zugangsdaten zu verbinden. inc/credentials.example.php ist als
versionierte Vorlage dabei.

FTP-Zugangsdaten
----------------
.vscode/ftp-sync.json und .vscode/sftp.json enthalten das
FTP-Passwort und waren versioniert - die .gitignore-Eintraege dafuer
gab es zwar, sie greifen bei bereits getrackten Dateien aber nicht.
Beide sind jetzt per "git rm --cached" aus der Versionierung genommen
und bleiben lokal liegen. Die passenden .example-Dateien existieren
bereits.

Verzeichnisschutz
-----------------
inc/ und zeiterfassung/inc/ enthalten ausschliesslich Includes, waren
als Teil des Web-Roots aber direkt per URL abrufbar. Beide bekommen
eine .htaccess, die den HTTP-Zugriff verweigert. PHP-includes sind
davon nicht betroffen, und per HTTP greift nichts auf diese
Verzeichnisse zu.

Wichtig: das Passwort steht weiterhin in der Git-Historie und wurde
bereits gepusht. Diese Aenderung verhindert nur die Weiterverbreitung.
Wirksam entschaerft ist es erst, wenn DB- und FTP-Passwort gewechselt
werden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 21:26:51 +02:00
clemensandClaude Opus 5 e5f6482811 Fehlerausgabe, stiller Mailversand und fehlende Registrierungs-Mail
Drei zusammenhaengende Punkte aus der Analyse des Login-Problems.

1. PHP-Fehler wurden an Besucher ausgegeben

display_errors war in impfwarteliste.php (oeffentlich), intern/
impfwarteliste.php, intern/neueanfrage.php, den beiden neueanfrage-old
Sicherungen und zeiterfassung/api/vacations.php fest eingeschaltet.
Die oeffentliche Impfwarteliste zeigte Patienten bei einem Fatal Error
sogar Meldung, Dateipfad und Zeilennummer. Ueberall auf log_errors
umgestellt: E_ALL wird weiterhin vollstaendig erfasst, landet aber im
Server-Log statt auf der Seite. Der Shutdown-Handler der Impfwarteliste
protokolliert die Details und zeigt nur noch einen neutralen Hinweis.
Auch register.php gab die rohe Exception-Meldung aus.

2. SendMailMessageSilent() verschluckte jeden Fehler

Der catch-Block war leer. Ein SMTP-Ausfall war dadurch von aussen nicht
von einem falschen Code zu unterscheiden - der Benutzer landete auf
verify_2fa.php und wartete auf eine Mail, die nie kam. Die Funktion
protokolliert jetzt und liefert einen bool zurueck. login.php wertet
das aus, nimmt bei Fehlschlag den 2FA-Datensatz und die Session-Vormerkung
zurueck und sagt es auf der Login-Seite, statt weiterzuleiten.
Nebenbei entfernt: ein uebrig gebliebenes echo, das den Mailserver-Namen
mitten in die Seite schrieb, sowie ein zweites mysqli_fetch_assoc() auf
demselben Result, das nur NULL liefern konnte.

3. register.php verschickte keine Bestaetigungsmail

mailreg blieb 0, jeder neue Benutzer landete nach dem Login auf der
Aufforderung, die Authentifizierung selbst anzustossen. Die Mail geht
jetzt direkt nach der Registrierung raus. Erzeugung und Text liegen in
der neuen Funktion sendeAuthentifizierungsMail(), die authmeldung.php
ebenfalls benutzt - dort wurde der Rueckgabewert des Versands bisher
einer Variablen zugewiesen und nie ausgewertet, die Seite meldete
Erfolg auch bei fehlgeschlagenem Versand.

Der Versand laeuft nach dem commit(), deshalb prueft der catch-Block in
register.php jetzt inTransaction(), bevor er rollBack() aufruft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 20:53:10 +02:00
clemensandClaude Opus 5 3b08be899c Ablaufpruefung fuer Passwort-Reset-Codes wieder aktiviert
Die Pruefung war auskommentiert, Reset-Links galten damit unbegrenzt,
obwohl die E-Mail "innerhalb der naechsten 24 Stunden" verspricht.

Sie wird jetzt in SQL ausgewertet statt mit strtotime()/time():
passwortcode_time wird in passwortvergessen.php mit NOW() gesetzt, also
mit der Uhr des DB-Servers (Europe/Berlin), waehrend der Webserver in
UTC laeuft. Eine Pruefung in PHP waere um diese zwei Stunden falsch -
dieselbe Ursache, die schon die 2FA-Codes unbrauchbar gemacht hat.
So benutzen Setzen und Pruefen dieselbe Uhr.

Ausserdem: fetch() liefert false und nicht null, wenn kein Benutzer
gefunden wird. Die Bedingung $user === null hat deshalb nie gegriffen,
und der Code lief mit false weiter in den Array-Zugriff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 20:36:02 +02:00
clemensandClaude Opus 5 f8bcfebc9e 2FA-Code-Pruefung: Zeitzonen-Fehler und Eingabe-Normalisierung
login.php erzeugte expires_at mit PHPs date(), verify_2fa.php verglich
gegen MySQLs NOW(). Webserver (secureserver.net) und Datenbankserver
(mysql2fda.netcup.net) liegen bei verschiedenen Anbietern und sind
unabhaengig voneinander konfiguriert. Laeuft PHP in UTC und MySQL in
Europe/Berlin, liegt expires_at (PHP-Zeit + 5 Minuten) zwei Stunden
vor NOW() - jeder Code gilt sofort als abgelaufen und wird als
"Falscher oder abgelaufener Code" abgewiesen.

Die Ablaufzeit wird jetzt beim INSERT per DATE_ADD(NOW(), INTERVAL 5
MINUTE) berechnet. Erzeugung und Pruefung benutzen damit dieselbe Uhr,
unabhaengig davon, wie die beiden Server eingestellt sind.

Ausserdem:
- Der eingegebene Code wird auf Ziffern reduziert. Aus HTML-Mails
  kopierte Codes schleppen oft Leerzeichen oder geschuetzte
  Leerzeichen mit, die den Hash-Vergleich scheitern liessen.
- Abgelaufener Code und falscher Code werden getrennt gemeldet, damit
  ein solcher Fall kuenftig ohne Raten eingegrenzt werden kann.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 20:00:42 +02:00
clemensandClaude Opus 5 52712aeff0 Anmeldung im internen Bereich repariert
index.php und neueanfrage.php gaben das Header-Template aus, bevor
check_intern_user() aufgerufen wurde. Dadurch schlugen setcookie(),
session_regenerate_id() und header('Location: login.php') mit
"headers already sent" fehl: die Session wurde geloescht ohne neues
Cookie zu setzen, der rotierte Remember-Token landete nur in der DB,
und die Weiterleitung blieb wirkungslos. Ergebnis war eine halb
gerenderte Seite statt des Logins. Beide Seiten puffern jetzt mit
ob_start(), wie login.php und verify_2fa.php es bereits tun.

Weitere Fehler im selben Ablauf:

- passwortvergessen.php uebergab $con (mysqli) an SendMailMessage(),
  das PDO erwartet -> fataler TypeError, "Passwort vergessen" brach
  immer mit HTTP 500 ab und verschickte nie eine Mail.
- logout.php war aus dem Admin-Bereich kopiert und loeschte weder
  intern_securitytokens noch die Cookies remember_device /
  remember_device_token. Der Logout war damit wirkungslos, weil
  check_intern_user() sofort wieder ueber das Cookie anmeldete.
- is_checked_in_index() pruefte das Admin-Cookie 'identifier' statt
  'remember_device'.
- index.php gab das undefinierte $email_value im Login-Formular aus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 19:35:04 +02:00
clemens 2c0e092e67 Website-Stand vom FTP-Server synchronisieren 2026-09-02 19:24:09 +02:00
clemens fd320ba0c6 Praxis Webseite Update 2026-04-02 01:30:14 +02:00
clemens aae89a45a8 Abwesenheitskalender erweitert 2026-04-01 23:59:28 +02:00
clemens 6360af272a Impfverwaltung anpassen 2026-03-30 21:52:10 +02:00
clemens 091702c2a2 Menü und Hilfe 2026-03-30 21:24:55 +02:00
clemens c9b0026f52 Menüanpassung und Sonderzeichen 2026-03-30 21:04:39 +02:00
clemens bb422005d0 PDF Ausgabe erweitert 2026-03-30 20:54:41 +02:00
clemens 098c2d4275 Betriebsurlaub 2026-03-30 20:48:55 +02:00
clemens 7388b5b379 Schließung aller offnen Fehler 2026-03-30 20:46:08 +02:00
clemens 016753293c Merge branch 'main' of https://git.ctb-it.de/clemens/praxis-creutzburg-web 2026-03-30 20:37:17 +02:00
clemens 874e8a04c0 Zeiterfassung Mail anpassung 2026-03-30 20:35:13 +02:00
clemens 0084516414 zeiterfassung 2026-03-30 20:34:27 +02:00
clemens e22dbc980c Anpassung Ladezeit Impfen + Urlaubsplaner 2026-03-30 08:44:45 +02:00
clemens 8470e90f56 ftp Einstellungen 2026-03-29 22:27:22 +02:00
clemens 26666aef30 anpassung anfragen Seite 2026-03-24 15:39:18 +01:00
clemens 3fee4eefe2 Anpassung Startseite 2026-03-24 15:36:32 +01:00
clemens 6dd0ac86b2 Impfworkflow + Patientensuche repariert 2026-03-24 14:57:21 +01:00
clemens 211ce11e06 Abgleich mit Live-Daten 2026-03-24 14:45:06 +01:00
clemens 00077aa09a Merge branch 'main' of https://git.ctb-it.de/clemens/praxis-creutzburg-web 2026-03-23 17:14:11 +01:00
clemens 4b4c1f74df impfwarteliste angepasst 2026-03-23 17:14:09 +01:00
clemens f5ffaf297d Stellenausschreibung rausgenommen 2026-03-23 17:02:57 +01:00
clemens 7ef1bbb2e9 Änderung Stellenanzeige 2026-03-23 16:36:22 +01:00
clemens 3bd55a2bcb Stellenangebote rausnehmen 2026-03-23 16:16:17 +01:00
clemens 70a78c9586 Enhance waitlist functionality: update queries to count distinct users, add new impfwarteliste.php page, and improve form handling in functions.impfen.inc.php 2026-03-21 17:04:37 +01:00
clemens 347188bd0c Add schema check and migration scripts for impf workflow and warteliste 2026-03-21 15:27:47 +01:00
clemens 780da7913a Add zeitraum_id to warteliste and update related queries; enhance impfWorkflow functions 2026-03-20 19:48:56 +01:00
clemens 8d40855402 Änderung Impfadmin 2026-03-20 17:15:22 +01:00
clemens c043ee9a52 Inital 2026-03-20 17:13:38 +01:00
clemens 4c84735b75 Initial commit 2026-03-20 17:06:13 +01:00