asas
This commit is contained in:
@@ -6,3 +6,9 @@
|
||||
define('APP_API_BASE', 'https://api.' . APP_DOMAIN_PRIMARY);
|
||||
define('APP_DB_ENABLED', true); // set true to enable DB connection
|
||||
|
||||
// Crypto-Key für verschlüsselte Felder (Telefon, Kinder etc.)
|
||||
// Bitte in Staging per Hosting-ENV setzen; dieses putenv dient nur als Fallback/Beispiel.
|
||||
if (getenv('DATA_KEY') === false) {
|
||||
// Beispiel-Key (unbedingt in Staging durch sicheren Wert ersetzen, 32 Byte, base64)
|
||||
putenv('DATA_KEY=base64:TSLBgK39KnwqMGT+ytJ+O8FwpVm+99VYZwi97TeloBw=');
|
||||
}
|
||||
|
||||
@@ -6,3 +6,9 @@
|
||||
define('APP_API_BASE', 'https://api.' . APP_DOMAIN_PRIMARY);
|
||||
define('APP_DB_ENABLED', true); // set true to enable DB connection
|
||||
|
||||
// Crypto-Key für verschlüsselte Felder (Telefon, Kinder etc.)
|
||||
// Bitte in Staging per Hosting-ENV setzen; dieses putenv dient nur als Fallback/Beispiel.
|
||||
if (getenv('DATA_KEY') === false) {
|
||||
// Beispiel-Key (unbedingt in Staging durch sicheren Wert ersetzen, 32 Byte, base64)
|
||||
putenv('DATA_KEY=base64:FIanxMlz5/bn7Oyqv57BXVcFelqHV9qj3hkiTDyerls=');
|
||||
}
|
||||
|
||||
@@ -9,32 +9,44 @@ $flash = $app->flash()->get();
|
||||
$userId = (int)$_SESSION['user_id'];
|
||||
$error = '';
|
||||
$info = '';
|
||||
$crypto = null;
|
||||
try { $crypto = new \App\Crypto($app->config()); } catch (\Throwable) {}
|
||||
|
||||
// POST Aktionen
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_POST['action'] ?? '';
|
||||
try {
|
||||
if ($action === 'profile') {
|
||||
$stmt = $pdo->prepare('UPDATE user_profiles SET display_name=:name, zip=:zip, city=:city, profession=:prof, languages=:langs, about=:about, updated_at=NOW() WHERE user_id=:id');
|
||||
$languages = $_POST['languages'] ?? '';
|
||||
if (is_array($languages)) {
|
||||
$languages = implode(', ', array_map('trim', $languages));
|
||||
}
|
||||
$phoneEnc = $crypto ? $crypto->encrypt(trim((string)$_POST['contact_phone'])) : trim((string)$_POST['contact_phone']);
|
||||
$stmt = $pdo->prepare('UPDATE user_profiles SET display_name=:name, first_name=:fname, last_name=:lname, zip=:zip, city=:city, profession=:prof, languages=:langs, about=:about, contact_phone=:phone, updated_at=NOW() WHERE user_id=:id');
|
||||
$stmt->execute([
|
||||
'name' => trim((string)$_POST['display_name']),
|
||||
'zip' => trim((string)$_POST['zip']),
|
||||
'city' => trim((string)$_POST['city']),
|
||||
'prof' => trim((string)$_POST['profession']),
|
||||
'langs' => trim((string)$_POST['languages']),
|
||||
'name' => trim((string)$_POST['display_name']),
|
||||
'fname' => trim((string)$_POST['first_name']),
|
||||
'lname' => trim((string)$_POST['last_name']),
|
||||
'zip' => trim((string)$_POST['zip']),
|
||||
'city' => trim((string)$_POST['city']),
|
||||
'prof' => trim((string)$_POST['profession']),
|
||||
'langs' => trim((string)$languages),
|
||||
'about' => trim((string)$_POST['about']),
|
||||
'id' => $userId,
|
||||
'phone' => $phoneEnc,
|
||||
'id' => $userId,
|
||||
]);
|
||||
$info = 'Profil gespeichert.';
|
||||
} elseif ($action === 'child_add') {
|
||||
$firstNameEnc = $crypto ? $crypto->encrypt(trim((string)$_POST['first_name'])) : trim((string)$_POST['first_name']);
|
||||
$noteEnc = $crypto ? $crypto->encrypt(trim((string)$_POST['note'])) : trim((string)$_POST['note']);
|
||||
$stmt = $pdo->prepare('INSERT INTO children (user_id, gender, birthdate, age_years, encrypted_first_name, note, created_at, updated_at) VALUES (:uid, :gender, :birthdate, :age, :name, :note, NOW(), NOW())');
|
||||
$stmt->execute([
|
||||
'uid' => $userId,
|
||||
'gender' => $_POST['gender'] ?? 'unknown',
|
||||
'birthdate' => $_POST['birthdate'] ?: null,
|
||||
'age' => $_POST['age_years'] ?: null,
|
||||
'name' => trim((string)$_POST['first_name']),
|
||||
'note' => trim((string)$_POST['note']),
|
||||
'name' => $firstNameEnc,
|
||||
'note' => $noteEnc,
|
||||
]);
|
||||
$info = 'Kind hinzugefügt.';
|
||||
} elseif ($action === 'event_add') {
|
||||
@@ -63,23 +75,37 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Daten laden
|
||||
$profile = [
|
||||
'display_name' => '',
|
||||
'first_name' => '',
|
||||
'last_name' => '',
|
||||
'zip' => '',
|
||||
'city' => '',
|
||||
'profession' => '',
|
||||
'languages' => '',
|
||||
'about' => '',
|
||||
'email' => '',
|
||||
'contact_phone' => '',
|
||||
];
|
||||
$stmt = $pdo->prepare('SELECT u.email, u.status, p.display_name, p.zip, p.city, p.profession, p.languages, p.about FROM users u LEFT JOIN user_profiles p ON p.user_id = u.id WHERE u.id = :id LIMIT 1');
|
||||
$stmt = $pdo->prepare('SELECT u.email, u.status, p.display_name, p.first_name, p.last_name, p.zip, p.city, p.profession, p.languages, p.about, p.contact_phone FROM users u LEFT JOIN user_profiles p ON p.user_id = u.id WHERE u.id = :id LIMIT 1');
|
||||
$stmt->execute(['id' => $userId]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($row) {
|
||||
$profile = array_merge($profile, array_filter($row, fn($v) => $v !== null));
|
||||
if ($crypto && !empty($profile['contact_phone'])) {
|
||||
$profile['contact_phone'] = $crypto->decrypt((string)$profile['contact_phone']) ?: '';
|
||||
}
|
||||
}
|
||||
|
||||
$children = [];
|
||||
$stmt = $pdo->prepare('SELECT id, encrypted_first_name AS first_name, gender, birthdate, age_years FROM children WHERE user_id = :id ORDER BY id DESC');
|
||||
$stmt = $pdo->prepare('SELECT id, encrypted_first_name AS first_name, note, gender, birthdate, age_years FROM children WHERE user_id = :id ORDER BY id DESC');
|
||||
$stmt->execute(['id' => $userId]);
|
||||
$children = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
$childrenRaw = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
foreach ($childrenRaw as $c) {
|
||||
if ($crypto) {
|
||||
$c['first_name'] = $crypto->decrypt((string)$c['first_name']) ?: '';
|
||||
$c['note'] = $crypto->decrypt((string)($c['note'] ?? '')) ?: '';
|
||||
}
|
||||
$children[] = $c;
|
||||
}
|
||||
|
||||
$events = [];
|
||||
$stmt = $pdo->prepare('SELECT id, title, teaser_public, starts_at, city, visibility FROM events WHERE created_by = :id ORDER BY starts_at DESC');
|
||||
@@ -113,8 +139,11 @@ $events = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
<div class="badge">Profil</div>
|
||||
<h3>Deine Angaben</h3>
|
||||
<ul class="dash-list">
|
||||
<li>Name: <?= htmlspecialchars(trim($profile['first_name'] . ' ' . $profile['last_name']), ENT_QUOTES) ?></li>
|
||||
<li>Anzeigename: <?= htmlspecialchars($profile['display_name'], ENT_QUOTES) ?></li>
|
||||
<li>Ort: <?= htmlspecialchars($profile['city'], ENT_QUOTES) ?> <?= htmlspecialchars($profile['zip'], ENT_QUOTES) ?></li>
|
||||
<li>E-Mail: <?= htmlspecialchars($profile['email'], ENT_QUOTES) ?></li>
|
||||
<li>Telefon: <?= htmlspecialchars($profile['contact_phone'], ENT_QUOTES) ?></li>
|
||||
<li>Beruf: <?= htmlspecialchars($profile['profession'], ENT_QUOTES) ?></li>
|
||||
<li>Sprachen: <?= htmlspecialchars($profile['languages'], ENT_QUOTES) ?></li>
|
||||
<li>About: <?= htmlspecialchars($profile['about'], ENT_QUOTES) ?></li>
|
||||
@@ -180,6 +209,16 @@ $events = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
<label class="label" for="pName">Anzeigename</label>
|
||||
<input id="pName" name="display_name" class="input" value="<?= htmlspecialchars($profile['display_name'], ENT_QUOTES) ?>">
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pFirst">Vorname</label>
|
||||
<input id="pFirst" name="first_name" class="input" value="<?= htmlspecialchars($profile['first_name'], ENT_QUOTES) ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pLast">Nachname</label>
|
||||
<input id="pLast" name="last_name" class="input" value="<?= htmlspecialchars($profile['last_name'], ENT_QUOTES) ?>">
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pCity">Ort</label>
|
||||
<input id="pCity" name="city" class="input" value="<?= htmlspecialchars($profile['city'], ENT_QUOTES) ?>">
|
||||
@@ -191,13 +230,30 @@ $events = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
<input id="pZip" name="zip" class="input" value="<?= htmlspecialchars($profile['zip'], ENT_QUOTES) ?>">
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pProf">Beruf</label>
|
||||
<input id="pProf" name="profession" class="input" value="<?= htmlspecialchars($profile['profession'], ENT_QUOTES) ?>">
|
||||
<label class="label" for="pPhone">Telefon (mobil)</label>
|
||||
<input id="pPhone" name="contact_phone" class="input" value="<?= htmlspecialchars($profile['contact_phone'], ENT_QUOTES) ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pLang">Sprachen</label>
|
||||
<input id="pLang" name="languages" class="input" value="<?= htmlspecialchars($profile['languages'], ENT_QUOTES) ?>">
|
||||
<label class="label">Sprachen (Mehrfachauswahl)</label>
|
||||
<div class="chips" style="flex-wrap: wrap;">
|
||||
<?php
|
||||
$langOptions = ['Deutsch','Englisch','Französisch','Spanisch','Türkisch','Arabisch','Polnisch'];
|
||||
$currentLangs = array_filter(array_map('trim', explode(',', (string)$profile['languages'])));
|
||||
?>
|
||||
<?php foreach ($langOptions as $opt): ?>
|
||||
<label class="chip" style="cursor:pointer;">
|
||||
<input type="checkbox" name="languages[]" value="<?= htmlspecialchars($opt, ENT_QUOTES) ?>" <?= in_array($opt, $currentLangs, true) ? 'checked' : '' ?> style="margin-right:6px;">
|
||||
<?= htmlspecialchars($opt, ENT_QUOTES) ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<label class="label" for="pLangCustom">Weitere Sprachen (Kommagetrennt)</label>
|
||||
<input id="pLangCustom" name="languages[]" class="input" placeholder="z. B. Italienisch, Niederländisch">
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pProf">Beruf</label>
|
||||
<input id="pProf" name="profession" class="input" value="<?= htmlspecialchars($profile['profession'], ENT_QUOTES) ?>">
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pAbout">Kurzvorstellung</label>
|
||||
|
||||
@@ -20,7 +20,7 @@ $isLoggedIn = isset($_SESSION['user_id']);
|
||||
<div class="nav-actions">
|
||||
<?php if ($isLoggedIn): ?>
|
||||
<a class="btn ghost" href="/dashboard">Dashboard</a>
|
||||
<a class="btn" href="/dashboard#events">Neues Event</a>
|
||||
<a class="btn ghost" href="/logout">Logout</a>
|
||||
<?php else: ?>
|
||||
<a class="btn ghost" href="/login">Anmelden</a>
|
||||
<a class="btn" href="/register">Kostenlos registrieren</a>
|
||||
@@ -35,7 +35,7 @@ $isLoggedIn = isset($_SESSION['user_id']);
|
||||
<a href="/#faq">FAQ</a>
|
||||
<?php if ($isLoggedIn): ?>
|
||||
<a class="btn ghost" href="/dashboard">Dashboard</a>
|
||||
<a class="btn block" href="/dashboard#events">Neues Event</a>
|
||||
<a class="btn block" href="/logout">Logout</a>
|
||||
<?php else: ?>
|
||||
<a class="btn ghost" href="/login">Anmelden</a>
|
||||
<a class="btn block" href="/register">Kostenlos registrieren</a>
|
||||
|
||||
8
public/page/logout.php
Normal file
8
public/page/logout.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$app = app();
|
||||
$app->session()->start();
|
||||
session_destroy();
|
||||
$app->flash()->set('info', 'Du wurdest abgemeldet.');
|
||||
redirect('/login');
|
||||
@@ -20,6 +20,8 @@ CREATE TABLE users (
|
||||
CREATE TABLE user_profiles (
|
||||
user_id BIGINT UNSIGNED PRIMARY KEY,
|
||||
display_name VARCHAR(120) NOT NULL,
|
||||
first_name VARCHAR(120) NULL,
|
||||
last_name VARCHAR(120) NULL,
|
||||
share_level ENUM('basic','papa','papa_contact') NOT NULL DEFAULT 'basic',
|
||||
children_visibility ENUM('hidden','age_only','details') NOT NULL DEFAULT 'hidden',
|
||||
zip CHAR(5) NULL,
|
||||
|
||||
68
src/App/Crypto.php
Normal file
68
src/App/Crypto.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
final class Crypto
|
||||
{
|
||||
private string $key;
|
||||
|
||||
public function __construct(Config $config)
|
||||
{
|
||||
if (!extension_loaded('sodium')) {
|
||||
throw new \RuntimeException('libsodium extension not available');
|
||||
}
|
||||
|
||||
$raw = getenv('DATA_KEY') ?: '';
|
||||
$raw = trim($raw);
|
||||
if ($raw === '') {
|
||||
throw new \RuntimeException('DATA_KEY env not set');
|
||||
}
|
||||
|
||||
// base64?
|
||||
if (str_starts_with($raw, 'base64:')) {
|
||||
$raw = substr($raw, 7);
|
||||
}
|
||||
$decoded = base64_decode($raw, true);
|
||||
if ($decoded !== false && strlen($decoded) >= SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES) {
|
||||
$raw = $decoded;
|
||||
} elseif (ctype_xdigit($raw) && strlen($raw) >= SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES * 2) {
|
||||
$raw = hex2bin($raw);
|
||||
}
|
||||
|
||||
if (strlen($raw) < SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES) {
|
||||
throw new \RuntimeException('DATA_KEY invalid length');
|
||||
}
|
||||
|
||||
$this->key = substr($raw, 0, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES);
|
||||
}
|
||||
|
||||
public function encrypt(string $plaintext): string
|
||||
{
|
||||
if ($plaintext === '') {
|
||||
return '';
|
||||
}
|
||||
$nonce = random_bytes(SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
|
||||
$cipher = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt($plaintext, '', $nonce, $this->key);
|
||||
return base64_encode($nonce . $cipher);
|
||||
}
|
||||
|
||||
public function decrypt(?string $blob): string
|
||||
{
|
||||
if ($blob === null || $blob === '') {
|
||||
return '';
|
||||
}
|
||||
$raw = base64_decode($blob, true);
|
||||
if ($raw === false || strlen($raw) <= SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES) {
|
||||
return '';
|
||||
}
|
||||
$nonce = substr($raw, 0, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
|
||||
$cipher = substr($raw, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
|
||||
try {
|
||||
$plain = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt($cipher, '', $nonce, $this->key);
|
||||
return $plain === false ? '' : $plain;
|
||||
} catch (\Throwable) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user