Files
deskhub/DeskHub-Webseite/staff/api_keys.php
T
2026-10-16 19:12:18 +00:00

111 lines
7.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once __DIR__ . '/../includes/bootstrap.php';
require_once __DIR__ . '/_ui.php';
$staff = require_admin();
$pdo = get_db();
$errors = [];
$newKey = $_SESSION['new_api_key'] ?? null; // wird nur einmal angezeigt
unset($_SESSION['new_api_key']);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
verify_csrf();
$action = $_POST['action'] ?? '';
if ($action === 'create') {
try {
$created = api_key_create($pdo, (string) ($_POST['name'] ?? ''), !empty($_POST['can_create']), !empty($_POST['can_read']), (string) ($_POST['ip_whitelist'] ?? ''), (int) $staff['id']);
$_SESSION['new_api_key'] = ['name' => trim($_POST['name']), 'key' => $created['key']];
audit_log($staff['id'], 'API-Schlüssel erstellt: ' . trim($_POST['name']));
redirect('/staff/api_keys.php');
} catch (AdminModelException $e) {
$errors[] = $e->getMessage();
}
}
if ($action === 'toggle') {
$pdo->prepare('UPDATE api_keys SET is_active = 1 - is_active WHERE id = ?')->execute([(int) $_POST['id']]);
audit_log($staff['id'], 'API-Schlüssel umgeschaltet: #' . (int) $_POST['id']);
redirect('/staff/api_keys.php');
}
if ($action === 'delete') {
$pdo->prepare('DELETE FROM api_keys WHERE id = ?')->execute([(int) $_POST['id']]);
audit_log($staff['id'], 'API-Schlüssel gelöscht: #' . (int) $_POST['id']);
flash('success', 'Schlüssel gelöscht. Systeme, die ihn nutzen, werden ab sofort abgewiesen.');
redirect('/staff/api_keys.php');
}
}
$keys = $pdo->query('SELECT k.*, s.name AS creator FROM api_keys k LEFT JOIN staff s ON s.id = k.created_by ORDER BY k.created_at DESC')->fetchAll();
$base = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https://' : 'http://') . ($_SERVER['HTTP_HOST'] ?? 'ihre-domain.de');
$pageTitle = 'API-Schlüssel';
$activePage = 'api_keys';
require __DIR__ . '/_header.php';
?>
<?php foreach ($errors as $err): ?><div class="flash error"><?= e($err) ?></div><?php endforeach; ?>
<?php if ($newKey): ?>
<div class="flash success">
<strong>Schlüssel „<?= e($newKey['name']) ?>“ wurde erstellt.</strong> Kopiere ihn jetzt – aus Sicherheitsgründen wird er nie wieder angezeigt:
<div style="margin-top:8px"><code style="font-size:1rem;user-select:all;padding:6px 10px;background:rgba(0,0,0,.08);border-radius:6px;display:inline-block"><?= e($newKey['key']) ?></code></div>
</div>
<?php endif; ?>
<div style="display:flex;justify-content:space-between;align-items:center;gap:12px">
<h1 style="margin:0">API-Schlüssel</h1>
</div>
<p class="muted">Mit einem Schlüssel können Fremdsysteme (Website-Formular, Shop, Monitoring …) Tickets über die öffentliche API anlegen oder deren Status abfragen. Ereignisse an andere Systeme meldest du über <a href="/staff/webhooks.php">Webhooks</a>.</p>
<div class="card">
<table>
<thead><tr><th>Name</th><th>Schlüssel</th><th>Rechte</th><th>Erlaubte IPs</th><th>Status</th><th>Zuletzt genutzt</th><th>Erstellt</th><th></th></tr></thead>
<tbody>
<?php foreach ($keys as $k): ?>
<tr>
<td><strong><?= e($k['name']) ?></strong><div class="muted" style="font-size:.76rem"><?= e($k['creator'] ?? '') ?></div></td>
<td><code><?= e($k['key_prefix']) ?>…</code></td>
<td style="font-size:.85rem"><?= $k['can_create_tickets'] ? 'Tickets anlegen' : '' ?><?= $k['can_create_tickets'] && $k['can_read_tickets'] ? '<br>' : '' ?><?= $k['can_read_tickets'] ? 'Tickets lesen' : '' ?></td>
<td class="muted" style="font-size:.82rem"><?= e($k['ip_whitelist'] ?: 'alle') ?></td>
<td><strong><?= $k['is_active'] ? 'Aktiv' : 'Deaktiviert' ?></strong></td>
<td class="muted" style="font-size:.82rem"><?= e(cron_last_run_label($k['last_used_at'] ?? null)) ?></td>
<td class="muted" style="font-size:.82rem"><?= e(ui_date($k['created_at'] ?? null)) ?></td>
<td style="white-space:nowrap">
<form method="post" style="display:inline"><?= csrf_field() ?><input type="hidden" name="action" value="toggle"><input type="hidden" name="id" value="<?= (int) $k['id'] ?>">
<button type="submit" style="background:none;border:none;color:var(--primary);cursor:pointer;padding:0;font-size:.9rem"><?= $k['is_active'] ? 'Deaktivieren' : 'Aktivieren' ?></button></form> ·
<form method="post" style="display:inline" onsubmit="return confirm('Schlüssel endgültig löschen?')"><?= csrf_field() ?><input type="hidden" name="action" value="delete"><input type="hidden" name="id" value="<?= (int) $k['id'] ?>">
<button type="submit" style="background:none;border:none;color:#dc2626;cursor:pointer;padding:0;font-size:.9rem">Löschen</button></form>
</td>
</tr>
<?php endforeach; ?>
<?php if (!$keys): ?><tr><td colspan="8" class="muted">Noch keine API-Schlüssel angelegt.</td></tr><?php endif; ?>
</tbody>
</table>
</div>
<div class="card">
<h3 style="margin-top:0">Neuen Schlüssel erstellen</h3>
<form method="post" autocomplete="off">
<?= csrf_field() ?>
<input type="hidden" name="action" value="create">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;max-width:760px">
<div><label for="name">Name *</label><input type="text" id="name" name="name" placeholder="z. B. Website-Kontaktformular" required></div>
<div><label for="ip_whitelist">Erlaubte IP-Adressen (optional)</label><input type="text" id="ip_whitelist" name="ip_whitelist" placeholder="leer = von überall; sonst kommagetrennt"></div>
</div>
<label style="display:flex;align-items:center;gap:8px;font-weight:400;margin-top:12px"><input type="checkbox" name="can_create" value="1" checked style="width:auto"> Darf Tickets anlegen</label>
<label style="display:flex;align-items:center;gap:8px;font-weight:400"><input type="checkbox" name="can_read" value="1" style="width:auto"> Darf Ticketstatus lesen (mit Ticketnummer + E-Mail des Anfragenden)</label>
<div class="btn-row"><button type="submit" class="btn">Schlüssel erstellen</button></div>
</form>
</div>
<div class="card">
<h3 style="margin-top:0">So nutzt du die API</h3>
<p class="muted" style="margin-top:0">Ticket anlegen (JSON). Antwort: <code>201</code> mit der Ticketnummer. Höchstens 60 Tickets pro Minute.</p>
<pre style="background:var(--panel2,#f1f4f7);padding:12px;border-radius:8px;overflow:auto;font-size:.82rem"><?= e("curl -X POST {$base}/api/public/v1/tickets.php \\\n -H \"X-API-Key: dh_…\" -H \"Content-Type: application/json\" \\\n -d '{\"name\":\"Max Muster\",\"email\":\"max@example.de\",\"subject\":\"Frage zur Rechnung\",\"message\":\"Hallo …\",\"helpTopicId\":1}'") ?></pre>
<p class="muted">Status abfragen:</p>
<pre style="background:var(--panel2,#f1f4f7);padding:12px;border-radius:8px;overflow:auto;font-size:.82rem"><?= e("curl \"{$base}/api/public/v1/tickets.php?number=240915-0012&email=max@example.de\" -H \"X-API-Key: dh_…\"") ?></pre>
<p class="muted" style="font-size:.82rem;margin-bottom:0">Optionale Felder beim Anlegen: <code>departmentId</code>, <code>priorityId</code>, <code>helpTopicId</code>. Ticket-Regeln und SLA-Pläne gelten wie bei jedem anderen Ticket.</p>
</div>
<?php require __DIR__ . '/_footer.php'; ?>