Add encrypted Etsy app credential settings
Deploy HickLab3D Dashboard / Deploy to production (push) Successful in 23s
Deploy HickLab3D Dashboard / Deploy to production (push) Successful in 23s
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\EtsyAppCredential;
|
||||
use App\Models\EtsyConnection;
|
||||
use App\Services\Etsy;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class EtsySettingsController extends Controller
|
||||
{
|
||||
public function index(Request $request, Etsy $etsy): View
|
||||
{
|
||||
abort_unless($request->user()->role === 'admin', 403);
|
||||
|
||||
return view('settings.index', [
|
||||
'storedCredentials' => EtsyAppCredential::find(1) !== null,
|
||||
'configured' => $etsy->configured(),
|
||||
'redirectUri' => $etsy->redirectUri(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request): RedirectResponse
|
||||
{
|
||||
abort_unless($request->user()->role === 'admin', 403);
|
||||
|
||||
$credentials = EtsyAppCredential::find(1);
|
||||
$requireCredentials = $credentials === null ? 'required' : 'nullable';
|
||||
$validated = $request->validate([
|
||||
'keystring' => [$requireCredentials, 'string', 'max:255'],
|
||||
'shared_secret' => [$requireCredentials, 'string', 'max:255'],
|
||||
]);
|
||||
|
||||
$previousKeystring = $credentials?->keystring ?: config('services.etsy.keystring');
|
||||
$credentials ??= new EtsyAppCredential;
|
||||
$credentials->id = 1;
|
||||
$credentials->fill(array_filter($validated, fn (?string $value): bool => filled($value)));
|
||||
$credentials->save();
|
||||
|
||||
$keystringChanged = $previousKeystring !== $credentials->keystring;
|
||||
if ($keystringChanged) {
|
||||
EtsyConnection::query()->delete();
|
||||
}
|
||||
|
||||
return redirect()->route('settings.index')->with('status', $keystringChanged
|
||||
? 'Etsy credentials saved. Reconnect your shop with the new app key.'
|
||||
: 'Etsy credentials saved.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Database\Factories\EtsyAppCredentialFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class EtsyAppCredential extends Model
|
||||
{
|
||||
/** @use HasFactory<EtsyAppCredentialFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['keystring', 'shared_secret'];
|
||||
|
||||
protected $hidden = ['keystring', 'shared_secret'];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'keystring' => 'encrypted',
|
||||
'shared_secret' => 'encrypted',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ class EtsyConnection extends Model
|
||||
{
|
||||
protected $fillable = ['shop_id', 'shop_name', 'access_token', 'refresh_token', 'expires_at'];
|
||||
|
||||
protected $hidden = ['access_token', 'refresh_token'];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
|
||||
+29
-6
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\EtsyAppCredential;
|
||||
use App\Models\EtsyConnection;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use RuntimeException;
|
||||
@@ -10,9 +11,14 @@ class Etsy
|
||||
{
|
||||
private const API_URL = 'https://api.etsy.com/v3';
|
||||
|
||||
/** @var array{keystring: ?string, shared_secret: ?string}|null */
|
||||
private ?array $credentials = null;
|
||||
|
||||
public function configured(): bool
|
||||
{
|
||||
return filled(config('services.etsy.keystring')) && filled(config('services.etsy.shared_secret'));
|
||||
$credentials = $this->credentials();
|
||||
|
||||
return filled($credentials['keystring']) && filled($credentials['shared_secret']);
|
||||
}
|
||||
|
||||
public function authorizationUrl(string $state, string $verifier): string
|
||||
@@ -24,7 +30,7 @@ class Etsy
|
||||
|
||||
return 'https://www.etsy.com/oauth/connect?'.http_build_query([
|
||||
'response_type' => 'code', 'redirect_uri' => $this->redirectUri(),
|
||||
'scope' => 'transactions_r listings_r', 'client_id' => config('services.etsy.keystring'),
|
||||
'scope' => 'transactions_r listings_r', 'client_id' => $this->credentials()['keystring'],
|
||||
'state' => $state, 'code_challenge' => $challenge, 'code_challenge_method' => 'S256',
|
||||
], '', '&', PHP_QUERY_RFC3986);
|
||||
}
|
||||
@@ -33,7 +39,7 @@ class Etsy
|
||||
{
|
||||
$tokens = Http::asForm()->acceptJson()->connectTimeout(3)->timeout(10)
|
||||
->post(self::API_URL.'/public/oauth/token', [
|
||||
'grant_type' => 'authorization_code', 'client_id' => config('services.etsy.keystring'),
|
||||
'grant_type' => 'authorization_code', 'client_id' => $this->credentials()['keystring'],
|
||||
'redirect_uri' => $this->redirectUri(), 'code' => $code, 'code_verifier' => $verifier,
|
||||
])->throw()->json();
|
||||
$userId = explode('.', $tokens['access_token'] ?? '')[0];
|
||||
@@ -77,7 +83,7 @@ class Etsy
|
||||
}
|
||||
$tokens = Http::asForm()->acceptJson()->connectTimeout(3)->timeout(10)
|
||||
->post(self::API_URL.'/public/oauth/token', [
|
||||
'grant_type' => 'refresh_token', 'client_id' => config('services.etsy.keystring'),
|
||||
'grant_type' => 'refresh_token', 'client_id' => $this->credentials()['keystring'],
|
||||
'refresh_token' => $connection->refresh_token,
|
||||
])->throw()->json();
|
||||
if (empty($tokens['access_token']) || empty($tokens['refresh_token'])) {
|
||||
@@ -93,13 +99,30 @@ class Etsy
|
||||
|
||||
private function get(string $path, string $accessToken, array $query = []): array
|
||||
{
|
||||
return Http::withHeaders(['x-api-key' => config('services.etsy.keystring').':'.config('services.etsy.shared_secret')])
|
||||
$credentials = $this->credentials();
|
||||
|
||||
return Http::withHeaders(['x-api-key' => $credentials['keystring'].':'.$credentials['shared_secret']])
|
||||
->withToken($accessToken)->acceptJson()->connectTimeout(3)->timeout(10)
|
||||
->get(self::API_URL.'/'.$path, $query)->throw()->json() ?? [];
|
||||
}
|
||||
|
||||
private function redirectUri(): string
|
||||
public function redirectUri(): string
|
||||
{
|
||||
return config('services.etsy.redirect_uri') ?: route('etsy.callback');
|
||||
}
|
||||
|
||||
/** @return array{keystring: ?string, shared_secret: ?string} */
|
||||
private function credentials(): array
|
||||
{
|
||||
if ($this->credentials !== null) {
|
||||
return $this->credentials;
|
||||
}
|
||||
|
||||
$stored = EtsyAppCredential::find(1);
|
||||
|
||||
return $this->credentials = [
|
||||
'keystring' => $stored?->keystring ?: config('services.etsy.keystring'),
|
||||
'shared_secret' => $stored?->shared_secret ?: config('services.etsy.shared_secret'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user