crypto = $crypto; } /** * Create a new API key (replacing any existing one). Returns the plain key: show it once. */ public function generate_api_key(): string { $key = self::KEY_PREFIX . wp_generate_password(40, false, false); update_option(self::OPT_API_HASH, hash('sha256', $key), false); update_option(self::OPT_API_HINT, substr($key, 0, 9) . '…' . substr($key, -4), false); update_option(self::OPT_API_CREATED, time(), false); delete_option(self::OPT_API_LAST_USED); return $key; } public function revoke_api_key(): void { delete_option(self::OPT_API_HASH); delete_option(self::OPT_API_HINT); delete_option(self::OPT_API_CREATED); delete_option(self::OPT_API_LAST_USED); } public function has_api_key(): bool { return (string)get_option(self::OPT_API_HASH, '') !== ''; } public function api_key_info(): array { return [ 'hint' => (string)get_option(self::OPT_API_HINT, ''), 'created' => (int)get_option(self::OPT_API_CREATED, 0), 'last_used' => (int)get_option(self::OPT_API_LAST_USED, 0), ]; } public function verify_api_key(string $key): bool { $hash = (string)get_option(self::OPT_API_HASH, ''); if ($hash === '' || $key === '' || strpos($key, self::KEY_PREFIX) !== 0) { return false; } $ok = hash_equals($hash, hash('sha256', $key)); if ($ok && (int)get_option(self::OPT_API_LAST_USED, 0) < time() - 60) { update_option(self::OPT_API_LAST_USED, time(), false); } return $ok; } /** * One string a dashboard can paste to connect: "swpb1:" + base64url(JSON {url, key}). */ public static function connection_string(string $key): string { $json = (string)wp_json_encode(['url' => rest_url(SWPB_REST_NAMESPACE), 'key' => $key, 'site' => get_bloginfo('name')]); return self::CONNECTION_PREFIX . rtrim(strtr(base64_encode($json), '+/', '-_'), '='); } public function cron_key(): string { $stored = (string)get_option(self::OPT_CRON_KEY, ''); $key = $stored !== '' ? $this->crypto->decrypt($stored) : ''; if ($key === '') { $key = $this->regenerate_cron_key(); } return $key; } public function regenerate_cron_key(): string { $key = wp_generate_password(32, false, false); update_option(self::OPT_CRON_KEY, $this->crypto->encrypt($key), false); return $key; } public function verify_cron_key(string $key): bool { return $key !== '' && hash_equals($this->cron_key(), $key); } public function cron_url(): string { return add_query_arg('key', $this->cron_key(), rest_url(SWPB_REST_NAMESPACE . '/cron')); } public function webhook(): array { $data = $this->crypto->decrypt_array((string)get_option(self::OPT_WEBHOOK, '')); return [ 'url' => (string)($data['url'] ?? ''), 'secret' => (string)($data['secret'] ?? ''), ]; } public function save_webhook(string $url): void { $url = esc_url_raw(trim($url), ['https', 'http']); if ($url === '') { delete_option(self::OPT_WEBHOOK); return; } $current = $this->webhook(); $secret = $current['secret'] !== '' ? $current['secret'] : wp_generate_password(32, false, false); update_option(self::OPT_WEBHOOK, $this->crypto->encrypt_array(['url' => $url, 'secret' => $secret]), false); } public function clear_all(): void { $this->revoke_api_key(); delete_option(self::OPT_CRON_KEY); delete_option(self::OPT_WEBHOOK); } }