Commit Graph
40 Commits
Author SHA1 Message Date
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