* 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; } }