Erweiterte Features (2/2): Cleanup/DSGVO, Webhooks, REST-API, Backup, CI, Docker
- Auto-Cleanup/DSGVO: cron_cleanup.php (token-geschützt) löscht abgelaufene Voucher, Audit-Log, Login-Versuche und Reset-Tokens nach konfigurierbaren Fristen - Webhooks: includes/Notifier.php (Slack/Teams/generisch), Auslösung bei Voucher-Erstellung (Web + API) - REST-API: includes/ApiKey.php (SHA-256, Präfix-Lookup), api/bootstrap.php, api/vouchers.php (GET/POST), api/sites.php; admin/api_keys.php zur Verwaltung - Config-Backup/Restore: admin/backup.php (JSON Export/Import, Cron-Token geschützt) - admin/integrations.php: Trusted-Proxy, Webhook, Cleanup-Fristen - CI: .github/workflows/ci.yml (php -l auf 7.4 & 8.2, lang-Validierung) - Docker: Dockerfile, docker-compose.yml (MariaDB), entrypoint (config aus ENV), .dockerignore - Nav + i18n (DE/EN) für alle neuen Admin-Seiten
This commit is contained in:
parent
eec28f77b8
commit
9463486225
18 changed files with 972 additions and 0 deletions
71
includes/ApiKey.php
Normal file
71
includes/ApiKey.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
/**
|
||||
* ApiKey – Erzeugung & Verifizierung von API-Schlüsseln für die REST-API.
|
||||
*
|
||||
* Schlüsselformat: uvt_<prefix(8)><secret(32)>
|
||||
* Gespeichert wird nur der SHA-256-Hash plus ein indexierbares Präfix – der
|
||||
* Klartext-Schlüssel wird dem Admin genau einmal bei der Erstellung gezeigt.
|
||||
*/
|
||||
class ApiKey {
|
||||
/** Neuen Schlüssel erzeugen. Gibt plain/prefix/hash zurück. */
|
||||
public static function generate() {
|
||||
$prefix = bin2hex(random_bytes(4)); // 8 Zeichen
|
||||
$secret = bin2hex(random_bytes(16)); // 32 Zeichen
|
||||
$plain = 'uvt_' . $prefix . $secret;
|
||||
return [
|
||||
'plain' => $plain,
|
||||
'prefix' => $prefix,
|
||||
'hash' => hash('sha256', $plain),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifiziert einen Klartext-Schlüssel gegen die DB. Aktualisiert
|
||||
* last_used_at und gibt den Key-Datensatz zurück – oder false.
|
||||
*/
|
||||
public static function verify($plain, $db) {
|
||||
if (!is_string($plain) || strpos($plain, 'uvt_') !== 0 || strlen($plain) < 44) {
|
||||
return false;
|
||||
}
|
||||
$prefix = substr($plain, 4, 8);
|
||||
$hash = hash('sha256', $plain);
|
||||
try {
|
||||
$row = $db->fetchOne(
|
||||
"SELECT * FROM api_keys WHERE key_prefix = ? AND is_active = 1",
|
||||
[$prefix]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
if (!$row || !hash_equals($row['key_hash'], $hash)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$db->query("UPDATE api_keys SET last_used_at = NOW() WHERE id = ?", [$row['id']]);
|
||||
} catch (\Exception $e) { /* ignore */ }
|
||||
return $row;
|
||||
}
|
||||
|
||||
/** Liest den Schlüssel aus dem Request (Authorization: Bearer / X-API-Key). */
|
||||
public static function fromRequest() {
|
||||
$headers = [];
|
||||
if (function_exists('getallheaders')) {
|
||||
foreach (getallheaders() as $k => $v) {
|
||||
$headers[strtolower($k)] = $v;
|
||||
}
|
||||
}
|
||||
if (!empty($headers['authorization']) && preg_match('/Bearer\s+(\S+)/i', $headers['authorization'], $m)) {
|
||||
return $m[1];
|
||||
}
|
||||
if (!empty($headers['x-api-key'])) {
|
||||
return trim($headers['x-api-key']);
|
||||
}
|
||||
if (!empty($_SERVER['HTTP_X_API_KEY'])) {
|
||||
return trim($_SERVER['HTTP_X_API_KEY']);
|
||||
}
|
||||
if (!empty($_SERVER['HTTP_AUTHORIZATION']) && preg_match('/Bearer\s+(\S+)/i', $_SERVER['HTTP_AUTHORIZATION'], $m)) {
|
||||
return $m[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue