From b0aa52c2c828e9d2ce5c7927dc1dc979734f15be Mon Sep 17 00:00:00 2001 From: "Claude (Chronicler #83 - The Compiler)" Date: Sun, 12 Apr 2026 13:53:37 -0500 Subject: [PATCH] =?UTF-8?q?fix(blueprint):=20Review=20fixes=20=E2=80=94=20?= =?UTF-8?q?API=20key=20lookup,=20tier=20enforcement,=20safety?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes 10 issues from Blueprint extension code review: - CurseForge API key now reads via Blueprint dbGet() matching admin save - PRO-tier fields (webhook, interval) enforced server-side, not just UI - json_decode results validated before accessing parsed data - Null user guard on getStatus() endpoint - 429 response uses consistent error key format - Modrinth slug derivation strips special chars, documented as fallback - Check interval dropdown reflects saved value - API key input changed to password type - TypeScript error typing narrowed from any to unknown - Removed unused DB import from ModpackApiService Co-Authored-By: Claude Opus 4.6 (1M context) --- .../blueprint-extension/admin/controller.php | 9 +++++++-- .../blueprint-extension/admin/view.blade.php | 8 ++++---- .../Http/Controllers/ModpackAPIController.php | 17 +++++++++++------ .../app/Services/ModpackApiService.php | 10 ++++++---- .../views/server/wrapper.tsx | 7 ++++--- 5 files changed, 32 insertions(+), 19 deletions(-) diff --git a/services/modpack-version-checker/blueprint-extension/admin/controller.php b/services/modpack-version-checker/blueprint-extension/admin/controller.php index 473ba1c..8cac306 100644 --- a/services/modpack-version-checker/blueprint-extension/admin/controller.php +++ b/services/modpack-version-checker/blueprint-extension/admin/controller.php @@ -53,8 +53,13 @@ class modpackcheckerExtensionController extends Controller public function update(modpackcheckerSettingsFormRequest $request): RedirectResponse { $this->blueprint->dbSet('modpackchecker', 'curseforge_api_key', $request->input('curseforge_api_key') ?? ''); - $this->blueprint->dbSet('modpackchecker', 'discord_webhook_url', $request->input('discord_webhook_url') ?? ''); - $this->blueprint->dbSet('modpackchecker', 'check_interval', $request->input('check_interval') ?? 'daily'); + + // Only save PRO-tier fields if the user is on the pro tier + $tier = $this->blueprint->dbGet('modpackchecker', 'tier') ?: 'standard'; + if ($tier === 'pro') { + $this->blueprint->dbSet('modpackchecker', 'discord_webhook_url', $request->input('discord_webhook_url') ?? ''); + $this->blueprint->dbSet('modpackchecker', 'check_interval', $request->input('check_interval') ?? 'daily'); + } return redirect()->route('admin.extensions.modpackchecker.index')->with('success', 'Settings saved successfully.'); } diff --git a/services/modpack-version-checker/blueprint-extension/admin/view.blade.php b/services/modpack-version-checker/blueprint-extension/admin/view.blade.php index 355e35e..99e36f8 100644 --- a/services/modpack-version-checker/blueprint-extension/admin/view.blade.php +++ b/services/modpack-version-checker/blueprint-extension/admin/view.blade.php @@ -74,7 +74,7 @@

Standard tier is locked to daily cron checks. diff --git a/services/modpack-version-checker/blueprint-extension/app/Http/Controllers/ModpackAPIController.php b/services/modpack-version-checker/blueprint-extension/app/Http/Controllers/ModpackAPIController.php index f19e03d..d6fa662 100644 --- a/services/modpack-version-checker/blueprint-extension/app/Http/Controllers/ModpackAPIController.php +++ b/services/modpack-version-checker/blueprint-extension/app/Http/Controllers/ModpackAPIController.php @@ -72,7 +72,7 @@ class ModpackAPIController extends Controller $seconds = RateLimiter::availableIn($limitKey); return response()->json([ 'success' => false, - 'message' => "Too many requests. Please wait {$seconds} seconds before checking again." + 'error' => "Too many requests. Please wait {$seconds} seconds before checking again.", ], 429); } RateLimiter::hit($limitKey, 60); @@ -161,7 +161,7 @@ class ModpackAPIController extends Controller $manifest = $this->readServerFile($server, 'manifest.json'); if ($manifest) { $data = json_decode($manifest, true); - if (isset($data['manifestType']) && $data['manifestType'] === 'minecraftModpack') { + if (is_array($data) && isset($data['manifestType']) && $data['manifestType'] === 'minecraftModpack') { return [ 'platform' => 'curseforge', 'modpack_id' => $data['projectID'] ?? null, @@ -175,10 +175,12 @@ class ModpackAPIController extends Controller $modrinthIndex = $this->readServerFile($server, 'modrinth.index.json'); if ($modrinthIndex) { $data = json_decode($modrinthIndex, true); - if (isset($data['formatVersion'])) { - // Use the pack name as the Modrinth project slug for API lookups. + if (is_array($data) && isset($data['formatVersion'])) { + // Best-effort slug derivation from pack name for API lookups. // dependencies.minecraft is a MC version (e.g. "1.20.1"), NOT a project ID. - $slug = isset($data['name']) ? strtolower(str_replace(' ', '-', $data['name'])) : null; + // NOTE: This may not match the actual Modrinth slug if the project name + // differs from its URL slug. Set MODPACK_ID egg variable for reliability. + $slug = isset($data['name']) ? preg_replace('/[^a-z0-9-]/', '', strtolower(str_replace(' ', '-', $data['name']))) : null; return [ 'platform' => 'modrinth', 'modpack_id' => $slug, @@ -225,7 +227,10 @@ class ModpackAPIController extends Controller public function getStatus(Request $request): JsonResponse { $user = $request->user(); - + if (!$user) { + return response()->json([], 401); + } + // Get all server UUIDs the user has access to $serverUuids = $user->accessibleServers()->pluck('uuid')->toArray(); diff --git a/services/modpack-version-checker/blueprint-extension/app/Services/ModpackApiService.php b/services/modpack-version-checker/blueprint-extension/app/Services/ModpackApiService.php index 0a646fe..d77cd8f 100644 --- a/services/modpack-version-checker/blueprint-extension/app/Services/ModpackApiService.php +++ b/services/modpack-version-checker/blueprint-extension/app/Services/ModpackApiService.php @@ -32,11 +32,15 @@ namespace Pterodactyl\Services; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Facades\DB; +use Pterodactyl\BlueprintFramework\Libraries\ExtensionLibrary\Admin\BlueprintAdminLibrary as BlueprintExtensionLibrary; use Exception; class ModpackApiService { + public function __construct( + private BlueprintExtensionLibrary $blueprint + ) {} + /** * Fetch the latest version info for a modpack from its platform API. * @@ -106,9 +110,7 @@ class ModpackApiService */ private function checkCurseForge(string $modpackId): array { - $apiKey = DB::table('settings') - ->where('key', 'modpackchecker::curseforge_api_key') - ->value('value'); + $apiKey = $this->blueprint->dbGet('modpackchecker', 'curseforge_api_key'); if (empty($apiKey)) { throw new Exception('CurseForge API key not configured'); diff --git a/services/modpack-version-checker/blueprint-extension/views/server/wrapper.tsx b/services/modpack-version-checker/blueprint-extension/views/server/wrapper.tsx index 368642d..dd72305 100644 --- a/services/modpack-version-checker/blueprint-extension/views/server/wrapper.tsx +++ b/services/modpack-version-checker/blueprint-extension/views/server/wrapper.tsx @@ -30,10 +30,11 @@ const ModpackVersionCard: React.FC = () => { const response = await http.post(`/api/client/extensions/modpackchecker/servers/${uuid}/check`); setData(response.data); setStatus(response.data.success ? 'success' : 'error'); - } catch (error: any) { - if (error.response?.status === 429) { + } catch (error: unknown) { + const err = error as { response?: { status?: number } }; + if (err.response?.status === 429) { setData({ success: false, error: 'rate_limited' }); - } else if (error.response?.status === 404) { + } else if (err.response?.status === 404) { setData({ success: false, error: 'not_found' }); } else { setData({ success: false, error: 'api_error' });