Move integration credentials into encrypted dashboard settings
Deploy HickLab3D Dashboard / Deploy to production (push) Successful in 1m42s

This commit is contained in:
Hickmeister
2026-10-11 16:15:02 +01:00
parent 96561dc3d2
commit ff9960fe06
23 changed files with 493 additions and 41 deletions
+1 -1
View File
@@ -20,6 +20,6 @@ class BambuddyController extends Controller
$integrationError = 'BamBuddy is unavailable. Check its API key and connection.';
}
return view('printers.index', compact('printers', 'integrationError'));
return view('printers.index', compact('printers', 'integrationError') + ['bambuddyUrl' => $bambuddy->url()]);
}
}
+5 -3
View File
@@ -6,6 +6,7 @@ use App\Models\EtsyConnection;
use App\Services\Bambuddy;
use App\Services\DryerClient;
use App\Services\Etsy;
use App\Services\IntegrationSettings;
use App\Services\Parcel2Go;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
@@ -14,7 +15,7 @@ use Throwable;
class DashboardController extends Controller
{
public function __invoke(Request $request, Etsy $etsy, Bambuddy $bambuddy, Parcel2Go $parcel2Go, DryerClient $dryer): View
public function __invoke(Request $request, Etsy $etsy, Bambuddy $bambuddy, Parcel2Go $parcel2Go, DryerClient $dryer, IntegrationSettings $settings): View
{
$isAdmin = $request->user()->role === 'admin';
$etsyStatus = $isAdmin ? 'Not connected' : null;
@@ -42,7 +43,8 @@ class DashboardController extends Controller
}
}
if ($isAdmin && filled(config('services.parcel2go.client_id')) && filled(config('services.parcel2go.client_secret'))) {
$shippingConfig = $settings->for('parcel2go');
if ($isAdmin && filled($shippingConfig['client_id'] ?? null) && filled($shippingConfig['client_secret'] ?? null) && filled($shippingConfig['auth_url'] ?? null)) {
try {
$shipments = array_slice($parcel2Go->fetch('me/orders/detail')['Orders'] ?? [], 0, 4);
$shippingStatus = 'Live account';
@@ -85,7 +87,7 @@ class DashboardController extends Controller
});
$dryerState = 'Not configured';
if (filled(config('services.dryer.api_key'))) {
if ($dryer->configured()) {
try {
$dryerState = ucfirst((string) ($dryer->status()['state'] ?? 'Unknown'));
} catch (Throwable $exception) {
+2 -2
View File
@@ -10,9 +10,9 @@ use Throwable;
class DryerController extends Controller
{
public function index(): View
public function index(DryerClient $dryer): View
{
return view('dryer');
return view('dryer', ['dryerUrl' => $dryer->dashboardUrl()]);
}
public function status(DryerClient $dryer): JsonResponse
@@ -4,21 +4,49 @@ namespace App\Http\Controllers;
use App\Models\EtsyAppCredential;
use App\Models\EtsyConnection;
use App\Models\IntegrationSetting;
use App\Services\Etsy;
use App\Services\IntegrationSettings;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class EtsySettingsController extends Controller
{
public function index(Request $request, Etsy $etsy): View
public function index(Request $request, Etsy $etsy, IntegrationSettings $settings): View
{
abort_unless($request->user()->role === 'admin', 403);
$credentials = EtsyAppCredential::find(1);
$storedIntegrations = IntegrationSetting::query()->whereIn('provider', ['parcel2go', 'bambuddy', 'dryer'])
->pluck('provider')->all();
$parcel2go = $settings->for('parcel2go');
$bambuddy = $settings->for('bambuddy');
$dryer = $settings->for('dryer');
return view('settings.index', [
'storedCredentials' => EtsyAppCredential::find(1) !== null,
'storedCredentials' => $credentials !== null,
'configured' => $etsy->configured(),
'redirectUri' => $etsy->redirectUri(),
'shopName' => $credentials?->shop_name ?: config('services.etsy.shop_name', 'HickLab3D'),
'integrations' => [
'parcel2go' => [
'stored' => in_array('parcel2go', $storedIntegrations, true),
'configured' => filled($parcel2go['client_id'] ?? null) && filled($parcel2go['client_secret'] ?? null) && filled($parcel2go['auth_url'] ?? null),
'auth_url' => $parcel2go['auth_url'] ?? '',
'base_url' => $parcel2go['base_url'] ?? '',
],
'bambuddy' => [
'stored' => in_array('bambuddy', $storedIntegrations, true),
'configured' => filled($bambuddy['url'] ?? null) && filled($bambuddy['api_key'] ?? null),
'url' => $bambuddy['url'] ?? '',
],
'dryer' => [
'stored' => in_array('dryer', $storedIntegrations, true),
'configured' => filled($dryer['url'] ?? null) && filled($dryer['api_key'] ?? null),
'url' => $dryer['url'] ?? '',
],
],
]);
}
@@ -27,15 +55,26 @@ class EtsySettingsController extends Controller
abort_unless($request->user()->role === 'admin', 403);
$credentials = EtsyAppCredential::find(1);
$requireCredentials = $credentials === null ? 'required' : 'nullable';
$requireKeystring = $credentials === null && blank(config('services.etsy.keystring')) ? 'required' : 'nullable';
$requireSecret = $credentials === null && blank(config('services.etsy.shared_secret')) ? 'required' : 'nullable';
$validated = $request->validate([
'keystring' => [$requireCredentials, 'string', 'max:255'],
'shared_secret' => [$requireCredentials, 'string', 'max:255'],
'keystring' => [$requireKeystring, 'string', 'max:255'],
'shared_secret' => [$requireSecret, 'string', 'max:255'],
'shop_name' => ['nullable', 'string', 'max:255'],
'redirect_uri' => ['nullable', 'url:https', 'max:2048'],
]);
$previousKeystring = $credentials?->keystring ?: config('services.etsy.keystring');
$credentials ??= new EtsyAppCredential;
$credentials->id = 1;
if (! $credentials->exists) {
$credentials->fill([
'keystring' => config('services.etsy.keystring'),
'shared_secret' => config('services.etsy.shared_secret'),
'shop_name' => config('services.etsy.shop_name', 'HickLab3D'),
'redirect_uri' => config('services.etsy.redirect_uri'),
]);
}
$credentials->fill(array_filter($validated, fn (?string $value): bool => filled($value)));
$credentials->save();
@@ -0,0 +1,41 @@
<?php
namespace App\Http\Controllers;
use App\Models\IntegrationSetting;
use App\Services\IntegrationSettings;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class IntegrationSettingsController extends Controller
{
public function update(Request $request, string $provider, IntegrationSettings $settings): RedirectResponse
{
abort_unless($request->user()->role === 'admin', 403);
abort_unless(in_array($provider, ['parcel2go', 'bambuddy', 'dryer'], true), 404);
$rules = match ($provider) {
'parcel2go' => [
'client_id' => ['nullable', 'string', 'max:255'],
'client_secret' => ['nullable', 'string', 'max:255'],
'auth_url' => ['nullable', 'url:https', 'max:2048'],
'base_url' => ['nullable', 'url:https', 'max:2048'],
],
default => [
'url' => ['nullable', 'url:https', 'max:2048'],
'api_key' => ['nullable', 'string', 'max:512'],
],
};
$submitted = array_filter($request->validate($rules), fn (?string $value): bool => filled($value));
$effective = $settings->for($provider);
if ($submitted === [] && ! collect($effective)->contains(fn ($value): bool => filled($value))) {
return redirect()->route('settings.index')->with('status', 'No integration settings changed.');
}
$record = IntegrationSetting::find($provider) ?? new IntegrationSetting(['provider' => $provider]);
$record->settings = array_replace($effective, $submitted);
$record->save();
return redirect()->route('settings.index')->with('status', ucfirst($provider).' settings saved.');
}
}