From 8433c8d88056c43afc5aaa09d4ac631cdd7261bd Mon Sep 17 00:00:00 2001 From: Troy Grunt Date: Sun, 1 Feb 2026 17:46:40 +0100 Subject: [PATCH] =?UTF-8?q?F=C3=BCge=20neue=20Funktionen=20hinzu:=20UUID-V?= =?UTF-8?q?alidierung,=20Datei-Download=20und=20verbessere=20die=20Fehlerb?= =?UTF-8?q?ehandlung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 + www/.htaccess | 2 + www/_files/.gitkeep | 0 www/card.php | 209 ++++++++++++++++++++++++++++++++++++++++++-- www/download.php | 82 +++++++++++++++++ 5 files changed, 288 insertions(+), 8 deletions(-) create mode 100644 www/_files/.gitkeep create mode 100644 www/download.php diff --git a/README.md b/README.md index 8c3051c..e336dbe 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ # businesscard +## TODO +- flood control +- admin https://chatgpt.com/share/697f82e6-1ed0-800e-b8d1-1ba0ce969dcf \ No newline at end of file diff --git a/www/.htaccess b/www/.htaccess index d27d670..8e8fcfd 100644 --- a/www/.htaccess +++ b/www/.htaccess @@ -1,5 +1,7 @@ RewriteEngine On +RewriteRule ^_.*$ - [R=404,L] + # Nur wenn keine echte Datei / kein echtes Verzeichnis RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d diff --git a/www/_files/.gitkeep b/www/_files/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/www/card.php b/www/card.php index 8a95bbe..1b651bc 100644 --- a/www/card.php +++ b/www/card.php @@ -1,16 +1,209 @@ true]); + if ($_SESSION['is_admin'] ?? false) { + header('Location: /admin.php?uuid=' . urlencode($uuid)); + exit; } } -echo "UUID: " . htmlspecialchars($uuid); +/** + * UUID auflösen + */ +$token = $sql->single( + "SELECT * FROM access_tokens + WHERE uuid = ? + AND (expires_at IS NULL OR expires_at > NOW())", + "s", + [$uuid] +); -//TODO +if (!$token) { + http_response_code(404); + exit('Identität nicht gefunden'); +} +/** + * Sichtbare Stammdaten laden + */ +$fields = $sql->get( + "SELECT f.field_key, f.field_value + FROM identity_fields f + JOIN token_permissions p ON p.field_key = f.field_key + WHERE f.identity_id = ? AND p.token_id = ? + ORDER BY f.field_key", + "ii", + [$token['identity_id'], $token['id']] +); + +/** + * Sichtbare Dateien laden + */ +$files = $sql->get( + "SELECT id, filename, mime_type + FROM files + WHERE identity_id = ? + AND (token_id IS NULL OR token_id = ?) + ORDER BY uploaded_at DESC", + "ii", + [$token['identity_id'], $token['id']] +); + +/** + * Feldnamen hübsch machen + */ +function label(string $key): string { + return match ($key) { + 'name' => 'Name', + 'email' => 'E-Mail', + 'phone' => 'Telefon', + 'address' => 'Adresse', + default => ucfirst($key), + }; +} +?> + + + + +Digitale Identität + + + + + + +
+

Identität

+ + +
+
+
+ +
+
+ + + +
+

Dateien

+ + + + +
+ + +
+ Zugriff über sicheren Link +
+
+ + diff --git a/www/download.php b/www/download.php new file mode 100644 index 0000000..61fd922 --- /dev/null +++ b/www/download.php @@ -0,0 +1,82 @@ +single( + "SELECT id, identity_id + FROM access_tokens + WHERE uuid = ? + AND (expires_at IS NULL OR expires_at > NOW())", + "s", + [$uuid] +); + +if (!$token) { + http_response_code(403); + exit('Kein Zugriff'); +} + +/** + * Datei laden & Berechtigung prüfen + */ +$file = $sql->single( + "SELECT * + FROM files + WHERE id = ? + AND identity_id = ? + AND (token_id IS NULL OR token_id = ?)", + "iii", + [$fileId, $token['identity_id'], $token['id']] +); + +if (!$file) { + http_response_code(404); + exit('Datei nicht gefunden'); +} + +/** + * Dateipfad + */ +$basePath = '/var/www/files/'; +$path = realpath($basePath . $file['stored_name']); + +if (!$path || !str_starts_with($path, $basePath) || !is_file($path)) { + http_response_code(404); + exit('Datei fehlt'); +} + +/** + * Saubere Ausgabe + */ +$filename = $file['filename']; +$mime = $file['mime_type'] ?: 'application/octet-stream'; +$size = filesize($path); + +header('Content-Description: File Transfer'); +header('Content-Type: ' . $mime); +header('Content-Disposition: attachment; filename="' . basename($filename) . '"'); +header('Content-Length: ' . $size); +header('X-Content-Type-Options: nosniff'); +header('Cache-Control: private, no-store, no-cache, must-revalidate'); +header('Pragma: no-cache'); +header('Expires: 0'); + +while (ob_get_level()) { + ob_end_clean(); +} + +readfile($path); +exit;