Compare commits

5 Commits
Author SHA1 Message Date
xBeaTz d97605f801 security: harden application security baseline
- Fixed SQL injection vulnerabilities with prepared statements
- Added column whitelist protection for dynamic termin updates
- Extracted iCloud credentials into protected config file
- Moved sensitive configuration files out of webroot
- Added authentication and admin checks to PVE APIs
- Added CSRF protection to write operations and APIs
- Disabled debug output in production mode
- Added centralized configuration handling
- Protected sensitive directories with access rules
- Added initial project security documentation
2026-07-22 02:23:34 +02:00
xBeaTz 28ebf1ff12 security: disable debug error output in production 2026-07-22 02:08:22 +02:00
xBeaTz 882155a7b4 security: protect proxmox api endpoints with session authentication
- Added authentication checks to pve_action.php and pve_list.php
- Require valid logged-in session before API access
- Restrict Proxmox actions to admin users only
- Return HTTP 401 for unauthenticated requests
- Return HTTP 403 for unauthorized users
- Keep existing API logic unchanged
2026-07-22 02:03:20 +02:00
xBeaTz d858e062f1 security: extract iCloud credentials into protected config file
- Removed hardcoded iCloud username and password from Data.class.php
- Added config/icloud.php for external credential storage
- Protected config directory with Apache access rules
- Updated Data constructor to load credentials from config
- Kept backwards compatibility with existing Data instantiation
2026-07-22 01:59:35 +02:00
xBeaTz 95b1db2309 security: replace raw SQL queries with prepared statements in Data.class.php
- 15 methods migrated from query() to prepare()+execute()
- changeTermin() now uses column whitelist (11 allowed columns)
- setTerminStatus() delegates to changeTermin()
- Added ?: null fallbacks and type hints
- Consistent pattern across all database methods
2026-07-22 01:37:21 +02:00
15 changed files with 546 additions and 252 deletions
+88 -69
View File
@@ -4,13 +4,22 @@ class Data {
public $id; public $id;
private $pdo; private $pdo;
private $icloudCalendarUrl = "https://p144-caldav.icloud.com/18998614209/calendars/ea9819c0-518a-4f23-ba11-476f639fd66e/"; private $icloudCalendarUrl;
private $icloudUser = "itzanjuma@gmail.com"; private $icloudUser;
private $icloudPass = "ywce-toxv-whvv-iorx"; private $icloudPass;
public function __construct($pdo) { public function __construct($pdo, ?array $icloudConfig = null) {
$this->pdo = $pdo; $this->pdo = $pdo;
if ($icloudConfig === null) {
$configPath = __DIR__ . '/config/icloud.php';
$icloudConfig = file_exists($configPath) ? require $configPath : [];
}
$this->icloudCalendarUrl = $icloudConfig['calendar_url'] ?? '';
$this->icloudUser = $icloudConfig['username'] ?? '';
$this->icloudPass = $icloudConfig['password'] ?? '';
} }
public function renderTemplate(string $template, array $vars): string { public function renderTemplate(string $template, array $vars): string {
@@ -26,24 +35,24 @@ class Data {
public function getDataByID($id, $column){ public function getDataByID($id, $column){
$sql = "SELECT * FROM users WHERE id = '" . $id . "'"; $stmt = $this->pdo->prepare("SELECT * FROM users WHERE id = :id");
foreach($this->pdo->query($sql) as $row) { $stmt->execute([':id' => $id]);
return $row[$column]; $row = $stmt->fetch(PDO::FETCH_ASSOC);
} return $row[$column] ?? null;
} }
public function getTerminDataById($id, $column){ public function getTerminDataById($id, $column){
$sql = "SELECT * FROM termine WHERE id = '" . $id . "'"; $stmt = $this->pdo->prepare("SELECT * FROM termine WHERE id = :id");
foreach($this->pdo->query($sql) as $row) { $stmt->execute([':id' => $id]);
return $row[$column]; $row = $stmt->fetch(PDO::FETCH_ASSOC);
} return $row[$column] ?? null;
} }
public function getSettingsData($column){ public function getSettingsData($column){
$sql = "SELECT * FROM settings_calendar WHERE id = '1'"; $stmt = $this->pdo->prepare("SELECT * FROM settings_calendar WHERE id = 1");
foreach($this->pdo->query($sql) as $row) { $stmt->execute();
return $row[$column]; $row = $stmt->fetch(PDO::FETCH_ASSOC);
} return $row[$column] ?? null;
} }
public function timestampToDate($timestamp){ public function timestampToDate($timestamp){
@@ -106,30 +115,29 @@ class Data {
} }
public function getOnlineStatusLabel($id){ public function getOnlineStatusLabel($id): string {
$sql = "SELECT * FROM users WHERE id = '" . $id . "'"; $stmt = $this->pdo->prepare("SELECT online FROM users WHERE id = :id");
foreach($this->pdo->query($sql) as $row) { $stmt->execute([':id' => $id]);
if($row['online'] == 0){ $online = $stmt->fetchColumn();
return "badge-danger";
}else{ if ($online === false) {
return "badge-success"; return "badge-secondary";
}
} }
return ((int)$online === 0) ? "badge-danger" : "badge-success";
} }
public function getTodoStatusLabel($id){ public function getTodoStatusLabel($id): string {
$sql = "SELECT * FROM todo WHERE id = '" . $id . "'"; $stmt = $this->pdo->prepare("SELECT status FROM todo WHERE id = :id");
foreach($this->pdo->query($sql) as $row) { $stmt->execute([':id' => $id]);
if($row['status'] == 0){ $status = $stmt->fetchColumn();
return "danger";
} return match ((int)$status) {
if($row['status'] == 1){ 0 => "danger",
return "warning"; 1 => "warning",
} 2 => "success",
if($row['status'] == 2){ default => "secondary",
return "success"; };
}
}
} }
private function getLastEditorInfo($terminId) private function getLastEditorInfo($terminId)
@@ -446,11 +454,20 @@ class Data {
public function changeTermin($id, $was, $value){ public function changeTermin($id, $was, $value): void {
$this->pdo->query("UPDATE termine SET $was = '" . $value . "' WHERE id = '" . $id . "'"); $allowed = [
//$this->updateCalendarEvent($id, $startzeit, $endzeit, $summary, $location, $beschreibung); 'startzeit', 'endzeit', 'location', 'locationtype',
'gage', 'anwesend', 'beschreibung', 'status',
'ansprechpartner', 'preisprostunde', 'realendzeit',
];
return; if (!in_array($was, $allowed, true)) {
throw new InvalidArgumentException("Ungültige Spalte: " . $was);
}
$sql = "UPDATE termine SET `" . $was . "` = :value WHERE id = :id";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':value' => $value, ':id' => $id]);
} }
private function icsEscape($text) private function icsEscape($text)
{ {
@@ -586,51 +603,53 @@ class Data {
public function setTerminStatus($id, $status){ public function setTerminStatus($id, $status): void {
$this->pdo->query("UPDATE termine SET status = '" . $status . "' WHERE id = '" . $id . "'"); $this->changeTermin($id, 'status', $status);
return;
} }
public function setTodoTitle($id, $title){ public function setTodoTitle($id, $title): void {
$this->pdo->query("UPDATE todo SET title = '" . $title . "' WHERE id = '" . $id . "'"); $stmt = $this->pdo->prepare("UPDATE todo SET title = :title WHERE id = :id");
return; $stmt->execute([':title' => $title, ':id' => $id]);
} }
public function setTodoDescription($id, $description){ public function setTodoDescription($id, $description): void {
$this->pdo->query("UPDATE todo SET description = '" . $description . "' WHERE id = '" . $id . "'"); $stmt = $this->pdo->prepare("UPDATE todo SET description = :desc WHERE id = :id");
return; $stmt->execute([':desc' => $description, ':id' => $id]);
} }
public function deleteTodoById($id){ public function deleteTodoById($id): void {
$this->pdo->query("DELETE FROM todo WHERE id = '" . $id . "'"); $stmt = $this->pdo->prepare("DELETE FROM todo WHERE id = :id");
return; $stmt->execute([':id' => $id]);
} }
public function createTodo($id, $title, $description){ public function createTodo($id, $title, $description): void {
$this->pdo->query("INSERT INTO todo (creator, title, description) VALUES ('" . $id . "', '" . $title . "', '" . $description . "')"); $stmt = $this->pdo->prepare("INSERT INTO todo (creator, title, description) VALUES (:creator, :title, :desc)");
return; $stmt->execute([':creator' => $id, ':title' => $title, ':desc' => $description]);
} }
public function getTodoDataById($id, $column){ public function getTodoDataById($id, $column){
$sql = "SELECT * FROM todo WHERE id = '" . $id . "'"; $stmt = $this->pdo->prepare("SELECT * FROM todo WHERE id = :id");
foreach($this->pdo->query($sql) as $row) { $stmt->execute([':id' => $id]);
return $row[$column]; $row = $stmt->fetch(PDO::FETCH_ASSOC);
} return $row[$column] ?? null;
} }
public function getAllReadyGigs(){ public function getAllReadyGigs(): int {
$nRows = $this->pdo->query('select count(*) from termine WHERE status = 1')->fetchColumn(); $stmt = $this->pdo->prepare("SELECT COUNT(*) FROM termine WHERE status = :status");
return $nRows; $stmt->execute([':status' => 1]);
return (int)$stmt->fetchColumn();
} }
public function getAllProcessingGigs(){ public function getAllProcessingGigs(): int {
$nRows = $this->pdo->query('select count(*) from termine WHERE status = 0')->fetchColumn(); $stmt = $this->pdo->prepare("SELECT COUNT(*) FROM termine WHERE status = :status");
return $nRows; $stmt->execute([':status' => 0]);
return (int)$stmt->fetchColumn();
} }
public function getAllCompletedGigs(){ public function getAllCompletedGigs(): int {
$nRows = $this->pdo->query('select count(*) from termine WHERE status = 2')->fetchColumn(); $stmt = $this->pdo->prepare("SELECT COUNT(*) FROM termine WHERE status = :status");
return $nRows; $stmt->execute([':status' => 2]);
return (int)$stmt->fetchColumn();
} }
private function debugLog($msg) { private function debugLog($msg) {
+34
View File
@@ -0,0 +1,34 @@
# ANJUMA Dashboard Sicherheits-Refactoring TODO
## Phase 1 Schnelle Erfolge (CRITICAL) ✅ COMPLETE
- [x] SQL Injection in Data.class.php fixen (Prepared Statements)
- [x] iCloud-Credentials auslagern in config/icloud.php
- [x] .htaccess für config/ und storage/ erstellen
- [x] config.cfg aus Webroot in config/ verschieben
- [x] display_errors zentral in config.php steuern (error_reporting nur im Error-Handler)
- [x] **CSRF-Schutz** für Termin-APIs (create, save, delete)
- [x] **CSRF-Schutz** für Finance-APIs (save, delete expenses)
- [x] **CSRF-Schutz** für Invoice-API (create from termin)
- [x] **Auth-Check in PVE-APIs** (Session-Prüfung ergänzt)
## Phase 2 Strukturverbesserungen (NÄCHSTE SCHRITTE)
- [ ] PSR-4 Autoloading via Composer einführen
- [ ] Data.class.php aufteilen in TerminService, UserService, CalendarService, FinanceService
- [ ] Zentrale Config-Klasse statt globaler define() und require
- [ ] Repository Pattern für DB-Zugriffe (TerminRepository, UserRepository, etc.)
- [ ] Template-Engine (Twig oder einfaches PHP-Template-System)
- [ ] Router einführen (Front Controller Pattern)
## Phase 3 Langfristig
- [ ] Datenbank-Migrationen (Phinx oder Doctrine Migrations)
- [ ] Indizes optimieren (EXPLAIN-Analyse der häufigsten Queries)
- [ ] Unit-Tests für Business-Logik (PHPUnit)
- [ ] Logging-System (Monolog) statt debugLog() in Datei
- [ ] CI/CD Pipeline (Gitea Actions)
- [ ] Docker-Compose für lokale Entwicklung
- [ ] API-Rate-Limiting für externe APIs (iCloud, Proxmox)
- [ ] Monitoring (Health-Checks, Alerting)
</create_file>
+125
View File
@@ -1,9 +1,17 @@
<?php <?php
require_once "../config/config.php"; require_once "../config/config.php";
require_once "../config/session.php";
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
try { try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed'); if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed');
// CSRF check
$csrf = $_POST['csrf_token'] ?? '';
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
throw new RuntimeException('CSRF validation failed');
}
$id = (int)($_POST['id'] ?? 0); $id = (int)($_POST['id'] ?? 0);
if ($id <= 0) throw new RuntimeException('id fehlt'); if ($id <= 0) throw new RuntimeException('id fehlt');
@@ -15,3 +23,120 @@ try {
http_response_code(400); http_response_code(400);
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]); echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
} }
</create_file>
<create_file>
<path>d:/anjuma-dashboard/api/invoice_create_from_termin.php</path>
<content><?php
header('Content-Type: application/json; charset=utf-8');
require_once "../config/config.php";
require_once "../config/session.php";
require_once __DIR__ . '/../lib/InvoiceNo.php';
global $pdo;
try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
throw new RuntimeException('Method not allowed');
}
// CSRF check
$csrf = $_POST['csrf_token'] ?? '';
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
throw new RuntimeException('CSRF validation failed');
}
$terminId = (int)($_POST['termin_id'] ?? 0);
if ($terminId <= 0) {
throw new RuntimeException('termin_id fehlt');
}
// Termin laden
$st = $pdo->prepare("SELECT * FROM termine WHERE id=:id");
$st->execute([':id' => $terminId]);
$t = $st->fetch(PDO::FETCH_ASSOC);
if (!$t) {
throw new RuntimeException('Termin nicht gefunden');
}
// Prüfen ob Rechnung existiert
$st = $pdo->prepare("SELECT id FROM invoices WHERE termine_id=:tid LIMIT 1");
$st->execute([':tid' => $terminId]);
if ($st->fetch()) {
throw new RuntimeException('Für diesen Termin existiert bereits eine Rechnung.');
}
$serviceDate = date('Y-m-d', (int)$t['startzeit']);
$invoiceDate = date('Y-m-d');
$dueDate = date('Y-m-d', strtotime($invoiceDate . ' +14 days'));
$year = (int)date('Y');
$invoiceNo = nextInvoiceNo($pdo, $year);
$customerName = trim($_POST['customer_name'] ?? 'Kunde (bitte bearbeiten)');
$customerStreet = trim($_POST['customer_street'] ?? '');
$customerZip = trim($_POST['customer_zip'] ?? '');
$customerCity = trim($_POST['customer_city'] ?? '');
$customerCountry = trim($_POST['customer_country'] ?? 'DE');
$gage = (int)($t['gage'] ?? 0);
$desc = "DJ-Leistung am " . date('d.m.Y', (int)$t['startzeit']);
$pdo->beginTransaction();
$pdo->prepare("
INSERT INTO invoices
(invoice_no, invoice_date, service_date, due_date, status,
customer_name, customer_street, customer_zip, customer_city, customer_country,
termine_id, amount_net_eur)
VALUES
(:no, :invdate, :servdate, :duedate, 'draft',
:cname, :cstreet, :czip, :ccity, :ccountry,
:tid, :amount)
")->execute([
':no' => $invoiceNo,
':invdate' => $invoiceDate,
':servdate' => $serviceDate,
':duedate' => $dueDate,
':cname' => $customerName,
':cstreet' => $customerStreet,
':czip' => $customerZip,
':ccity' => $customerCity,
':ccountry' => $customerCountry,
':tid' => $terminId,
':amount' => $gage
]);
$invoiceId = (int)$pdo->lastInsertId();
$pdo->prepare("
INSERT INTO invoice_items
(invoice_id, pos, description, qty, unit_price_eur, line_total_eur)
VALUES
(:iid, 1, :d, 1, :p, :p)
")->execute([
':iid' => $invoiceId,
':d' => $desc,
':p' => $gage
]);
$pdo->commit();
echo json_encode([
'ok' => true,
'invoice_id' => $invoiceId,
'invoice_no' => $invoiceNo
], JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) {
if ($pdo && $pdo->inTransaction()) $pdo->rollBack();
http_response_code(400);
echo json_encode([
'ok' => false,
'error' => $e->getMessage()
], JSON_UNESCAPED_UNICODE);
}
</create_file>
+152
View File
@@ -1,10 +1,17 @@
<?php <?php
require_once "../config/config.php"; require_once "../config/config.php";
require_once "../config/session.php";
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
try { try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed'); if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed');
// CSRF check
$csrf = $_POST['csrf_token'] ?? '';
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
throw new RuntimeException('CSRF validation failed');
}
$id = isset($_POST['id']) ? (int)$_POST['id'] : 0; $id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
$date = trim($_POST['date'] ?? ''); $date = trim($_POST['date'] ?? '');
$category = trim($_POST['category'] ?? ''); $category = trim($_POST['category'] ?? '');
@@ -45,3 +52,148 @@ try {
http_response_code(400); http_response_code(400);
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]); echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
} }
</create_file>
<create_file>
<path>d:/anjuma-dashboard/api/finance_expenses_delete.php</path>
<content><?php
require_once "../config/config.php";
require_once "../config/session.php";
header('Content-Type: application/json; charset=utf-8');
try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed');
// CSRF check
$csrf = $_POST['csrf_token'] ?? '';
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
throw new RuntimeException('CSRF validation failed');
}
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) throw new RuntimeException('id fehlt');
$stmt = $pdo->prepare("DELETE FROM finance_expenses WHERE id=:id");
$stmt->execute([':id'=>$id]);
echo json_encode(['ok'=>true], JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) {
http_response_code(400);
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
}
</create_file>
<create_file>
<path>d:/anjuma-dashboard/api/invoice_create_from_termin.php</path>
<content><?php
header('Content-Type: application/json; charset=utf-8');
require_once "../config/config.php";
require_once "../config/session.php";
require_once __DIR__ . '/../lib/InvoiceNo.php';
global $pdo;
try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
throw new RuntimeException('Method not allowed');
}
// CSRF check
$csrf = $_POST['csrf_token'] ?? '';
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
throw new RuntimeException('CSRF validation failed');
}
$terminId = (int)($_POST['termin_id'] ?? 0);
if ($terminId <= 0) {
throw new RuntimeException('termin_id fehlt');
}
// Termin laden
$st = $pdo->prepare("SELECT * FROM termine WHERE id=:id");
$st->execute([':id' => $terminId]);
$t = $st->fetch(PDO::FETCH_ASSOC);
if (!$t) {
throw new RuntimeException('Termin nicht gefunden');
}
// Prüfen ob Rechnung existiert
$st = $pdo->prepare("SELECT id FROM invoices WHERE termine_id=:tid LIMIT 1");
$st->execute([':tid' => $terminId]);
if ($st->fetch()) {
throw new RuntimeException('Für diesen Termin existiert bereits eine Rechnung.');
}
$serviceDate = date('Y-m-d', (int)$t['startzeit']);
$invoiceDate = date('Y-m-d');
$dueDate = date('Y-m-d', strtotime($invoiceDate . ' +14 days'));
$year = (int)date('Y');
$invoiceNo = nextInvoiceNo($pdo, $year);
$customerName = trim($_POST['customer_name'] ?? 'Kunde (bitte bearbeiten)');
$customerStreet = trim($_POST['customer_street'] ?? '');
$customerZip = trim($_POST['customer_zip'] ?? '');
$customerCity = trim($_POST['customer_city'] ?? '');
$customerCountry = trim($_POST['customer_country'] ?? 'DE');
$gage = (int)($t['gage'] ?? 0);
$desc = "DJ-Leistung am " . date('d.m.Y', (int)$t['startzeit']);
$pdo->beginTransaction();
$pdo->prepare("
INSERT INTO invoices
(invoice_no, invoice_date, service_date, due_date, status,
customer_name, customer_street, customer_zip, customer_city, customer_country,
termine_id, amount_net_eur)
VALUES
(:no, :invdate, :servdate, :duedate, 'draft',
:cname, :cstreet, :czip, :ccity, :ccountry,
:tid, :amount)
")->execute([
':no' => $invoiceNo,
':invdate' => $invoiceDate,
':servdate' => $serviceDate,
':duedate' => $dueDate,
':cname' => $customerName,
':cstreet' => $customerStreet,
':czip' => $customerZip,
':ccity' => $customerCity,
':ccountry' => $customerCountry,
':tid' => $terminId,
':amount' => $gage
]);
$invoiceId = (int)$pdo->lastInsertId();
$pdo->prepare("
INSERT INTO invoice_items
(invoice_id, pos, description, qty, unit_price_eur, line_total_eur)
VALUES
(:iid, 1, :d, 1, :p, :p)
")->execute([
':iid' => $invoiceId,
':d' => $desc,
':p' => $gage
]);
$pdo->commit();
echo json_encode([
'ok' => true,
'invoice_id' => $invoiceId,
'invoice_no' => $invoiceNo
], JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) {
if ($pdo && $pdo->inTransaction()) $pdo->rollBack();
http_response_code(400);
echo json_encode([
'ok' => false,
'error' => $e->getMessage()
], JSON_UNESCAPED_UNICODE);
}
</create_file>
+8
View File
@@ -1,6 +1,7 @@
<?php <?php
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
require_once "../config/config.php"; require_once "../config/config.php";
require_once "../config/session.php";
require_once __DIR__ . '/../lib/InvoiceNo.php'; require_once __DIR__ . '/../lib/InvoiceNo.php';
global $pdo; global $pdo;
@@ -10,6 +11,12 @@ try {
throw new RuntimeException('Method not allowed'); throw new RuntimeException('Method not allowed');
} }
// CSRF check
$csrf = $_POST['csrf_token'] ?? '';
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrf)) {
throw new RuntimeException('CSRF validation failed');
}
$terminId = (int)($_POST['termin_id'] ?? 0); $terminId = (int)($_POST['termin_id'] ?? 0);
if ($terminId <= 0) { if ($terminId <= 0) {
throw new RuntimeException('termin_id fehlt'); throw new RuntimeException('termin_id fehlt');
@@ -104,3 +111,4 @@ try {
'error' => $e->getMessage() 'error' => $e->getMessage()
], JSON_UNESCAPED_UNICODE); ], JSON_UNESCAPED_UNICODE);
} }
</create_file>
+27 -13
View File
@@ -1,4 +1,19 @@
<?php <?php
require_once __DIR__ . '/../config/session.php';
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
http_response_code(401);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['ok' => false, 'error' => 'Unauthorized']);
exit;
}
if (($_SESSION['role'] ?? 'user') !== 'admin') {
http_response_code(403);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['ok' => false, 'error' => 'Forbidden']);
exit;
}
require_once __DIR__ . '/../lib/PveApi.php'; require_once __DIR__ . '/../lib/PveApi.php';
$cfg = require __DIR__ . '/../config/pve.php'; $cfg = require __DIR__ . '/../config/pve.php';
$pve = new PveApi($cfg); $pve = new PveApi($cfg);
@@ -6,22 +21,21 @@ $pve = new PveApi($cfg);
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
try { try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405); http_response_code(405);
echo json_encode(['ok' => false, 'error' => 'Method not allowed']); echo json_encode(['ok' => false, 'error' => 'Method not allowed']);
exit; exit;
} }
$vmid = (int)($_POST['vmid'] ?? 0); $vmid = (int)($_POST['vmid'] ?? 0);
$action = (string)($_POST['action'] ?? ''); $action = (string)($_POST['action'] ?? '');
// optional: hier deine Dashboard-Auth checken! if ($vmid <= 0) throw new InvalidArgumentException("vmid fehlt/ungültig");
if ($vmid <= 0) throw new InvalidArgumentException("vmid fehlt/ungültig");
$upid = $pve->powerAction($vmid, $action); $upid = $pve->powerAction($vmid, $action);
echo json_encode(['ok' => true, 'vmid' => $vmid, 'action' => $action, 'upid' => $upid]); echo json_encode(['ok' => true, 'vmid' => $vmid, 'action' => $action, 'upid' => $upid]);
} catch (Throwable $e) { } catch (Throwable $e) {
http_response_code(400); http_response_code(400);
echo json_encode(['ok' => false, 'error' => $e->getMessage()]); echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
} }
+49 -36
View File
@@ -1,4 +1,18 @@
<?php <?php
require_once __DIR__ . '/../config/session.php';
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
http_response_code(401);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['ok' => false, 'error' => 'Unauthorized']);
exit;
}
if (($_SESSION['role'] ?? 'user') !== 'admin') {
http_response_code(403);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['ok' => false, 'error' => 'Forbidden']);
exit;
}
require_once __DIR__ . '/../lib/PveApi.php'; require_once __DIR__ . '/../lib/PveApi.php';
$cfg = require __DIR__ . '/../config/pve.php'; $cfg = require __DIR__ . '/../config/pve.php';
@@ -7,49 +21,48 @@ $pve = new PveApi($cfg);
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
try { try {
$lxcs = $pve->listLxc(); // array mit vmid, name, status, cpu, mem, etc. $lxcs = $pve->listLxc();
$vms = $pve->listQemu(); // array mit vmid, name, status, cpu, mem, etc. $vms = $pve->listQemu();
$all = []; $all = [];
foreach ($lxcs as $c) { foreach ($lxcs as $c) {
$vmid = (int)($c['vmid'] ?? 0); $vmid = (int)($c['vmid'] ?? 0);
if ($vmid >= 100 && $vmid <= 199) { if ($vmid >= 100 && $vmid <= 199) {
$all[] = [ $all[] = [
'vmid' => $vmid, 'vmid' => $vmid,
'type' => 'lxc', 'type' => 'lxc',
'name' => (string)($c['name'] ?? "LXC {$vmid}"), 'name' => (string)($c['name'] ?? "LXC {$vmid}"),
'status' => (string)($c['status'] ?? 'unknown'), 'status' => (string)($c['status'] ?? 'unknown'),
'uptime' => (int)($c['uptime'] ?? 0), 'uptime' => (int)($c['uptime'] ?? 0),
'cpu' => (float)($c['cpu'] ?? 0), 'cpu' => (float)($c['cpu'] ?? 0),
'mem' => (int)($c['mem'] ?? 0), 'mem' => (int)($c['mem'] ?? 0),
'maxmem' => (int)($c['maxmem'] ?? 0), 'maxmem' => (int)($c['maxmem'] ?? 0),
]; ];
}
} }
}
foreach ($vms as $v) { foreach ($vms as $v) {
$vmid = (int)($v['vmid'] ?? 0); $vmid = (int)($v['vmid'] ?? 0);
if ($vmid >= 200 && $vmid <= 299) { if ($vmid >= 200 && $vmid <= 299) {
$all[] = [ $all[] = [
'vmid' => $vmid, 'vmid' => $vmid,
'type' => 'qemu', 'type' => 'qemu',
'name' => (string)($v['name'] ?? "VM {$vmid}"), 'name' => (string)($v['name'] ?? "VM {$vmid}"),
'status' => (string)($v['status'] ?? 'unknown'), 'status' => (string)($v['status'] ?? 'unknown'),
'uptime' => (int)($v['uptime'] ?? 0), 'uptime' => (int)($v['uptime'] ?? 0),
'cpu' => (float)($v['cpu'] ?? 0), 'cpu' => (float)($v['cpu'] ?? 0),
'mem' => (int)($v['mem'] ?? 0), 'mem' => (int)($v['mem'] ?? 0),
'maxmem' => (int)($v['maxmem'] ?? 0), 'maxmem' => (int)($v['maxmem'] ?? 0),
]; ];
}
} }
}
// Sort: erst LXCs, dann VMs oder nach vmid usort($all, fn($a, $b) => $a['vmid'] <=> $b['vmid']);
usort($all, fn($a, $b) => $a['vmid'] <=> $b['vmid']);
echo json_encode(['ok' => true, 'items' => $all], JSON_UNESCAPED_UNICODE); echo json_encode(['ok' => true, 'items' => $all], JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) { } catch (Throwable $e) {
http_response_code(400); http_response_code(400);
echo json_encode(['ok' => false, 'error' => $e->getMessage()]); echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
} }
+13
View File
@@ -0,0 +1,13 @@
<?php
/**
* iCloud CalDAV Konfiguration
*
* Diese Datei enthält die Anmeldedaten für den iCloud-Kalender-Sync.
* Geschützt durch .htaccess (Deny from all).
*/
return [
'calendar_url' => 'https://p144-caldav.icloud.com/18998614209/calendars/ea9819c0-518a-4f23-ba11-476f639fd66e/',
'username' => 'itzanjuma@gmail.com',
'password' => 'ywce-toxv-whvv-iorx',
];
+8
View File
@@ -14,3 +14,11 @@ session_set_cookie_params([
if (session_status() === PHP_SESSION_NONE) { if (session_status() === PHP_SESSION_NONE) {
session_start(); session_start();
} }
// CSRF Token generieren, falls nicht vorhanden
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
</||DSML||parameter>
</||DSML||invoke>
</||DSML||tool_calls>
-2
View File
@@ -1,6 +1,4 @@
<?php <?php
ini_set('display_errors', 1);
error_reporting(E_ALL);
date_default_timezone_set("Europe/Berlin"); date_default_timezone_set("Europe/Berlin");
require_once "config/config.php"; require_once "config/config.php";
include("Data.class.php"); include("Data.class.php");
-3
View File
@@ -1,7 +1,4 @@
<?php <?php
ini_set('display_errors', 1);
error_reporting(E_ALL);
require_once "config/config.php"; require_once "config/config.php";
require_once "Data.class.php"; require_once "Data.class.php";
+13 -50
View File
@@ -16,28 +16,30 @@ if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
if (isset($_POST['submit'])) { if (isset($_POST['submit'])) {
// CSRF check
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die("CSRF validation failed");
}
// Required fields // Required fields
if ( if (
!empty($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts']) &&
!empty($_POST['inputLocation']) && !empty($_POST['inputLocation']) &&
!empty($_POST['inputArt']) // <-- NEU !empty($_POST['inputArt'])
) { ) {
// Endzeit optional → 0 wenn leer
$endzeit = !empty($_POST['inputEndzeit_ts']) ? $_POST['inputEndzeit_ts'] : 0; $endzeit = !empty($_POST['inputEndzeit_ts']) ? $_POST['inputEndzeit_ts'] : 0;
// Gage muss angegeben werden kann aber 0 sein
$gage = $_POST['inputGage']; $gage = $_POST['inputGage'];
if ($gage === '' || $gage === null) { if ($gage === '' || $gage === null) {
$gage = 0; $gage = 0;
} }
$gage = (int)$gage; $gage = (int)$gage;
// Veranstaltungsart absichern
$art = $_POST['inputArt'] ?? ''; $art = $_POST['inputArt'] ?? '';
$validArts = ['Party', 'Club', 'Hochzeit']; $validArts = ['Party', 'Club', 'Hochzeit'];
if (!in_array($art, $validArts, true)) { if (!in_array($art, $validArts, true)) {
$art = 'Party'; // Fallback (oder Fehler werfen) $art = 'Party';
} }
$terminid = $Data->createTermin( $terminid = $Data->createTermin(
@@ -46,20 +48,16 @@ if (isset($_POST['submit'])) {
$_POST['inputLocation'], $_POST['inputLocation'],
$_SESSION['id'], $_SESSION['id'],
$gage, $gage,
$art // <-- NEU $art
); );
// Changelog für "TERMIN ERSTELLT"
$Data->createChanges($terminid, $_SESSION["id"], "create", "", ""); $Data->createChanges($terminid, $_SESSION["id"], "create", "", "");
// Weiterleitung
header("Location: termin_show.php?id=" . $terminid); header("Location: termin_show.php?id=" . $terminid);
exit; exit;
} }
} }
include('navbar.php'); include('navbar.php');
?> ?>
@@ -72,7 +70,6 @@ include('navbar.php');
<div class="row g-3"> <div class="row g-3">
<!-- Startzeit -->
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-bold">Startzeit</label> <label class="form-label fw-bold">Startzeit</label>
<div class="input-group"> <div class="input-group">
@@ -86,9 +83,7 @@ include('navbar.php');
> >
<input type="hidden" name="inputStartzeit_ts" id="inputStartzeit_ts"> <input type="hidden" name="inputStartzeit_ts" id="inputStartzeit_ts">
</div> </div>
</div>
<!-- Endzeit -->
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-bold">Endzeit</label> <label class="form-label fw-bold">Endzeit</label>
<div class="input-group"> <div class="input-group">
@@ -101,23 +96,7 @@ include('navbar.php');
> >
<input type="hidden" name="inputEndzeit_ts" id="inputEndzeit_ts"> <input type="hidden" name="inputEndzeit_ts" id="inputEndzeit_ts">
</div> </div>
</div>
<!-- Preis pro angefangene Stunde
<div class="col-md-6">
<label class="form-label fw-bold">Preis / angefangene Stunde</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-cash"></i></span>
<input
type="text"
class="form-control"
name="inputPreisProStunde"
placeholder="z. B. 50"
>
</div>
</div>-->
<!-- Location -->
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-bold">Location</label> <label class="form-label fw-bold">Location</label>
<div class="input-group"> <div class="input-group">
@@ -130,9 +109,7 @@ include('navbar.php');
required required
> >
</div> </div>
</div>
<!-- Veranstaltungsart -->
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-bold">Veranstaltungsart</label> <label class="form-label fw-bold">Veranstaltungsart</label>
<div class="input-group"> <div class="input-group">
@@ -148,9 +125,7 @@ include('navbar.php');
<option value="Hochzeit">Hochzeit (hoher Orga-Aufwand, eigenes Equipment)</option> <option value="Hochzeit">Hochzeit (hoher Orga-Aufwand, eigenes Equipment)</option>
</select> </select>
</div> </div>
</div>
<!-- Gage -->
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-bold">Gage</label> <label class="form-label fw-bold">Gage</label>
<div class="input-group"> <div class="input-group">
@@ -165,10 +140,9 @@ include('navbar.php');
required required
> >
</div> </div>
</div>
<div class="text-end mt-4"> <div class="text-end mt-4">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<button type="submit" name="submit" class="btn btn-primary"> <button type="submit" name="submit" class="btn btn-primary">
<i class="bi bi-check-circle"></i> Termin speichern <i class="bi bi-check-circle"></i> Termin speichern
</button> </button>
@@ -178,7 +152,6 @@ include('navbar.php');
</div> </div>
<!-- MODAL: Termin existiert -->
<div class="modal fade" id="terminConflictModal" tabindex="-1"> <div class="modal fade" id="terminConflictModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
<div class="modal-content"> <div class="modal-content">
@@ -191,15 +164,11 @@ include('navbar.php');
<div class="modal-body"> <div class="modal-body">
<p>Für dieses Datum existiert bereits mindestens ein Termin:</p> <p>Für dieses Datum existiert bereits mindestens ein Termin:</p>
<div id="terminConflictList" class="border rounded p-2 bg-light"></div> <div id="terminConflictList" class="border rounded p-2 bg-light"></div>
</div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Okay</button> <button class="btn btn-secondary" data-bs-dismiss="modal">Okay</button>
</div> </div>
</div>
</div> </div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
@@ -207,7 +176,6 @@ include('navbar.php');
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script> <script>
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
@@ -215,17 +183,15 @@ document.addEventListener("DOMContentLoaded", () => {
const startInput = document.getElementById("inputStartzeit"); const startInput = document.getElementById("inputStartzeit");
const endInput = document.getElementById("inputEndzeit"); const endInput = document.getElementById("inputEndzeit");
let conflictShownForDate = null; // speichert das Datum, für das das Modal schon gezeigt wurde let conflictShownForDate = null;
startInput.addEventListener("change", async () => { startInput.addEventListener("change", async () => {
if (!startInput.value) return; if (!startInput.value) return;
const selectedDate = startInput.value; // YYYY-MM-DDTHH:MM const selectedDate = startInput.value;
// ---------- 1) TERMIN-KONFLIKT MODAL NUR EINMAL PRO DATUM ---------- const dateOnly = selectedDate.split("T")[0];
const dateOnly = selectedDate.split("T")[0]; // 2025-12-08
if (conflictShownForDate !== dateOnly) { if (conflictShownForDate !== dateOnly) {
@@ -251,13 +217,10 @@ document.addEventListener("DOMContentLoaded", () => {
const modal = new bootstrap.Modal(document.getElementById("terminConflictModal")); const modal = new bootstrap.Modal(document.getElementById("terminConflictModal"));
modal.show(); modal.show();
// merken → für dieses Datum NICHT erneut anzeigen
conflictShownForDate = dateOnly; conflictShownForDate = dateOnly;
} }
} }
// ---------- 2) ENDZEIT BEI JEDER STARTZEIT-ÄNDERUNG +8h ----------
if (startInput.value) { if (startInput.value) {
const startDate = new Date(startInput.value); const startDate = new Date(startInput.value);
@@ -274,7 +237,6 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
// Timestamp-Konvertierung wie bei termin_show
function toTimestampLocal(datetime) { function toTimestampLocal(datetime) {
return datetime ? Math.floor(new Date(datetime).getTime() / 1000) : ""; return datetime ? Math.floor(new Date(datetime).getTime() / 1000) : "";
} }
@@ -285,3 +247,4 @@ document.getElementById("createTerminForm").addEventListener("submit", function(
document.getElementById("inputRealEndzeit_ts").value = toTimestampLocal(document.getElementById("inputRealEndzeit").value); document.getElementById("inputRealEndzeit_ts").value = toTimestampLocal(document.getElementById("inputRealEndzeit").value);
}); });
</script> </script>
</create_file>
+6 -6
View File
@@ -1,7 +1,4 @@
<?php <?php
ini_set('display_errors', 1);
error_reporting(E_ALL);
require_once "config/config.php"; require_once "config/config.php";
include("Data.class.php"); include("Data.class.php");
$Data = new Data($pdo); $Data = new Data($pdo);
@@ -21,6 +18,11 @@ if (!isset($_POST["id"])) {
die("Kein Termin angegeben."); die("Kein Termin angegeben.");
} }
// CSRF check
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die("CSRF validation failed");
}
$id = intval($_POST["id"]); $id = intval($_POST["id"]);
$Data->deleteCalendarEvent($id); $Data->deleteCalendarEvent($id);
@@ -31,17 +33,15 @@ $Data->deleteCalendarEvent($id);
$pdo->prepare("DELETE FROM changes WHERE termin_id = :id") $pdo->prepare("DELETE FROM changes WHERE termin_id = :id")
->execute([':id' => $id]); ->execute([':id' => $id]);
//$Data->createChanges($id, $_SESSION["id"], "delete", "", "");
// -------------------------------------------------- // --------------------------------------------------
// 3) Termin löschen // 3) Termin löschen
// -------------------------------------------------- // --------------------------------------------------
$pdo->prepare("DELETE FROM termine WHERE id = :id") $pdo->prepare("DELETE FROM termine WHERE id = :id")
->execute([':id' => $id]); ->execute([':id' => $id]);
// -------------------------------------------------- // --------------------------------------------------
// Weiterleitung // Weiterleitung
// -------------------------------------------------- // --------------------------------------------------
header("Location: termine.php"); header("Location: termine.php");
exit; exit;
</create_file>
+22 -69
View File
@@ -1,6 +1,5 @@
<?php <?php
ini_set('display_errors', 1);
error_reporting(E_ALL);
date_default_timezone_set("Europe/Berlin"); date_default_timezone_set("Europe/Berlin");
require_once "config/config.php"; require_once "config/config.php";
include("Data.class.php"); include("Data.class.php");
@@ -109,6 +108,11 @@ $changes = $sql->fetchAll(PDO::FETCH_ASSOC);
if(isset($_POST['save'])){ if(isset($_POST['save'])){
// CSRF check
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die("CSRF validation failed");
}
if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){ if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){
$startzeit = $_POST['inputStartzeit_ts']; $startzeit = $_POST['inputStartzeit_ts'];
if($Data->getTerminDataById($id, "startzeit") != $startzeit){ if($Data->getTerminDataById($id, "startzeit") != $startzeit){
@@ -343,7 +347,6 @@ include('navbar.php');
> >
<input type="hidden" name="inputStartzeit_ts" id="inputStartzeit_ts"> <input type="hidden" name="inputStartzeit_ts" id="inputStartzeit_ts">
</div> </div>
</div>
<!-- Endzeit --> <!-- Endzeit -->
<div class="col-md-6"> <div class="col-md-6">
@@ -358,7 +361,6 @@ include('navbar.php');
> >
<input type="hidden" name="inputEndzeit_ts" id="inputEndzeit_ts"> <input type="hidden" name="inputEndzeit_ts" id="inputEndzeit_ts">
</div> </div>
</div>
<!-- Location --> <!-- Location -->
<div class="col-md-6"> <div class="col-md-6">
@@ -368,7 +370,6 @@ include('navbar.php');
<input type="text" class="form-control" name="inputLocation" <input type="text" class="form-control" name="inputLocation"
value="<?php echo $Data->getTerminDataById($id, 'location') ?>"> value="<?php echo $Data->getTerminDataById($id, 'location') ?>">
</div> </div>
</div>
<!-- Gage --> <!-- Gage -->
<div class="col-md-6"> <div class="col-md-6">
@@ -378,7 +379,6 @@ include('navbar.php');
<input type="text" class="form-control" name="inputGage" <input type="text" class="form-control" name="inputGage"
value="<?php echo $Data->getTerminDataById($id, 'gage') ?>"> value="<?php echo $Data->getTerminDataById($id, 'gage') ?>">
</div> </div>
</div>
<!-- Beschreibung --> <!-- Beschreibung -->
<div class="col-12"> <div class="col-12">
@@ -395,7 +395,6 @@ include('navbar.php');
echo htmlspecialchars($Data->getTerminDataById($id, 'beschreibung')); echo htmlspecialchars($Data->getTerminDataById($id, 'beschreibung'));
?></textarea> ?></textarea>
</div> </div>
</div>
<!-- Beteiligte Personen --> <!-- Beteiligte Personen -->
<div class="col-md-6"> <div class="col-md-6">
@@ -413,9 +412,6 @@ include('navbar.php');
</select> </select>
</div> </div>
</div>
<!-- Status --> <!-- Status -->
<div class="btn-group my-4" role="group"> <div class="btn-group my-4" role="group">
<label class="btn btn-primary"> <label class="btn btn-primary">
@@ -436,6 +432,7 @@ include('navbar.php');
</div> </div>
<input type="hidden" name="id" value="<?php echo $id ?>"> <input type="hidden" name="id" value="<?php echo $id ?>">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<?php if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin'): ?> <?php if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin'): ?>
@@ -502,7 +499,6 @@ include('navbar.php');
</form> </form>
</div> </div>
</div>
</div> </div>
@@ -611,7 +607,6 @@ include('navbar.php');
</div> </div>
</div> </div>
</div>
<!-- Invoice Modal --> <!-- Invoice Modal -->
<div class="modal fade" id="invoiceModal" tabindex="-1" aria-hidden="true"> <div class="modal fade" id="invoiceModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
@@ -641,21 +636,17 @@ include('navbar.php');
<label class="form-label mb-1">Ort</label> <label class="form-label mb-1">Ort</label>
<input type="text" class="form-control" id="invCity" placeholder="Nienburg"> <input type="text" class="form-control" id="invCity" placeholder="Nienburg">
</div> </div>
</div>
<div class="mt-2"> <div class="mt-2">
<label class="form-label mb-1">Land</label> <label class="form-label mb-1">Land</label>
<input type="text" class="form-control" id="invCountry" value="DE"> <input type="text" class="form-control" id="invCountry" value="DE">
</div> </div>
</div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button> <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button type="submit" class="btn btn-success" id="btnCreateInvoice">Erstellen & PDF</button> <button type="submit" class="btn btn-success" id="btnCreateInvoice">Erstellen & PDF</button>
</div> </div>
</form> </form>
</div> </div>
</div>
<!-- Delete Modal --> <!-- Delete Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1"> <div class="modal fade" id="deleteModal" tabindex="-1">
@@ -681,15 +672,13 @@ include('navbar.php');
<form action="termin_delete.php" method="POST"> <form action="termin_delete.php" method="POST">
<input type="hidden" name="id" value="<?= $id ?>"> <input type="hidden" name="id" value="<?= $id ?>">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<button type="submit" class="btn btn-danger"> <button type="submit" class="btn btn-danger">
<i class="bi bi-trash"></i> Ja, löschen <i class="bi bi-trash"></i> Ja, löschen
</button> </button>
</form> </form>
</div> </div>
</div>
</div> </div>
</div>
<!-- MODAL: Termin existiert --> <!-- MODAL: Termin existiert -->
<div class="modal fade" id="terminConflictModal" tabindex="-1"> <div class="modal fade" id="terminConflictModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
@@ -703,15 +692,11 @@ include('navbar.php');
<div class="modal-body"> <div class="modal-body">
<p>Für dieses Datum existiert bereits mindestens ein Termin:</p> <p>Für dieses Datum existiert bereits mindestens ein Termin:</p>
<div id="terminConflictList" class="border rounded p-2 bg-light"></div> <div id="terminConflictList" class="border rounded p-2 bg-light"></div>
</div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Okay</button> <button class="btn btn-secondary" data-bs-dismiss="modal">Okay</button>
</div> </div>
</div>
</div> </div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
@@ -839,53 +824,21 @@ document.addEventListener("DOMContentLoaded", () => {
// reset // reset
document.getElementById('invCustomerName').value = ''; document.getElementById('invCustomerName').value = '';
document.getElementById('invStreet').value = ''; document.getElementById('invStreet').value = '';
document.getElementById('invZip').value = ''; Now I'll add the CSRF validation to `termin_show.php`. Let me make the edits step by step:
document.getElementById('invCity').value = '';
document.getElementById('invCountry').value = 'DE';
invoiceModal.show(); <edit_file>
}); <path>d:/anjuma-dashboard/termin_show.php</path>
<content>
<<<<<<< SEARCH
if(isset($_POST['save'])){
document.getElementById('invoiceForm').addEventListener('submit', async (ev) => { if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){
ev.preventDefault(); =======
if(isset($_POST['save'])){
const cname = document.getElementById('invCustomerName').value.trim(); // CSRF check
if (!cname) { alert('Bitte Kunde/Firma eintragen'); return; } if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die("CSRF validation failed");
const btn = document.getElementById('btnCreateInvoice'); }
btn.disabled = true;
btn.textContent = 'Erstelle…';
try {
const fd = new FormData();
fd.append('termin_id', terminIdForInvoice);
fd.append('customer_name', cname);
fd.append('customer_street', document.getElementById('invStreet').value.trim());
fd.append('customer_zip', document.getElementById('invZip').value.trim());
fd.append('customer_city', document.getElementById('invCity').value.trim());
fd.append('customer_country', document.getElementById('invCountry').value.trim() || 'DE');
const res = await fetch(API_INVOICE_CREATE, { method: 'POST', body: fd });
const json = await res.json();
if (!json.ok) throw new Error(json.error || 'Rechnung konnte nicht erstellt werden');
invoiceModal.hide();
// PDF öffnen
window.open(API_INVOICE_PDF + json.invoice_id, '_blank');
// Seite neu laden, damit Button auf "PDF öffnen" wechselt
window.location.reload();
} catch (e) {
alert('Fehler: ' + e.message);
} finally {
btn.disabled = false;
btn.textContent = 'Erstellen & PDF';
}
});
});
</script>
if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){
-3
View File
@@ -1,7 +1,4 @@
<?php <?php
ini_set('display_errors', 1);
error_reporting(E_ALL);
require_once "config/session.php"; require_once "config/session.php";
require_once "config/config.php"; require_once "config/config.php";