Füge neue Funktionen hinzu: UUID-Validierung, Datei-Download und verbessere die Fehlerbehandlung
This commit is contained in:
@@ -1,2 +1,5 @@
|
|||||||
# businesscard
|
# businesscard
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
- flood control
|
||||||
|
- admin https://chatgpt.com/share/697f82e6-1ed0-800e-b8d1-1ba0ce969dcf
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
RewriteEngine On
|
RewriteEngine On
|
||||||
|
|
||||||
|
RewriteRule ^_.*$ - [R=404,L]
|
||||||
|
|
||||||
# Nur wenn keine echte Datei / kein echtes Verzeichnis
|
# Nur wenn keine echte Datei / kein echtes Verzeichnis
|
||||||
RewriteCond %{REQUEST_FILENAME} !-f
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
RewriteCond %{REQUEST_FILENAME} !-d
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
|||||||
0
www/_files/.gitkeep
Normal file
0
www/_files/.gitkeep
Normal file
209
www/card.php
209
www/card.php
@@ -1,16 +1,209 @@
|
|||||||
<?php
|
<?php
|
||||||
require '_sql.php';
|
require '_sql.php';
|
||||||
|
|
||||||
$uuid = $_GET['uuid'] ?? 'keine UUID';
|
$uuid = $_GET['uuid'] ?? null;
|
||||||
if(isset($_COOKIE['PHPSESSID'])){
|
if (!$uuid) {
|
||||||
session_start();
|
http_response_code(400);
|
||||||
if($_SESSION['is_admin']??false){
|
exit('Ungültige Anfrage');
|
||||||
header('Location: /admin.php?uuid='. $uuid);
|
}
|
||||||
exit(0);
|
|
||||||
|
/**
|
||||||
|
* Admin-Weiterleitung (nur wenn Session existiert)
|
||||||
|
*/
|
||||||
|
if (isset($_COOKIE['PHPSESSID'])) {
|
||||||
|
session_start(['read_and_close' => 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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Digitale Identität</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #0f172a;
|
||||||
|
--card: #020617;
|
||||||
|
--text: #e5e7eb;
|
||||||
|
--muted: #94a3b8;
|
||||||
|
--accent: #38bdf8;
|
||||||
|
--border: #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #020617, #0f172a);
|
||||||
|
color: var(--text);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 520px;
|
||||||
|
background: var(--card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 1.75rem;
|
||||||
|
box-shadow: 0 20px 40px rgba(0,0,0,.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: .75rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--muted);
|
||||||
|
letter-spacing: .05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-top: .25rem;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files {
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: .5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file span {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: .85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
font-size: .75rem;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="card">
|
||||||
|
<h1>Identität</h1>
|
||||||
|
|
||||||
|
<?php foreach ($fields as $field): ?>
|
||||||
|
<div class="field">
|
||||||
|
<div class="label"><?= htmlspecialchars(label($field['field_key'])) ?></div>
|
||||||
|
<div class="value">
|
||||||
|
<?= nl2br(htmlspecialchars($field['field_value'])) ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php if ($files): ?>
|
||||||
|
<div class="files">
|
||||||
|
<h2 style="font-size:1rem;margin-bottom:.5rem;">Dateien</h2>
|
||||||
|
|
||||||
|
<?php foreach ($files as $file): ?>
|
||||||
|
<div class="file">
|
||||||
|
<span><?= htmlspecialchars($file['filename']) ?></span>
|
||||||
|
<a href="/download.php?id=<?= (int)$file['id'] ?>&uuid=<?= urlencode($uuid) ?>">
|
||||||
|
Download
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
Zugriff über sicheren Link
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|||||||
82
www/download.php
Normal file
82
www/download.php
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
require '_sql.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eingaben prüfen
|
||||||
|
*/
|
||||||
|
$fileId = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||||
|
$uuid = $_GET['uuid'] ?? null;
|
||||||
|
|
||||||
|
if ($fileId <= 0 || !$uuid) {
|
||||||
|
http_response_code(400);
|
||||||
|
exit('Ungültige Anfrage');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token validieren
|
||||||
|
*/
|
||||||
|
$token = $sql->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;
|
||||||
Reference in New Issue
Block a user