From 4333b1c55dcfdb73cb8d039d956b42a04d8438c7 Mon Sep 17 00:00:00 2001 From: dlawler489 <104159223@student.swin.edu.au> Date: Sat, 13 Jun 2026 11:12:50 +1000 Subject: [PATCH] Add Etsy shared secret for API calls Etsy returns 403 'Shared secret is required in x-api-key header' when the keystring is used on resource endpoints: the keystring is only the OAuth client id. Store the shared secret alongside it (Settings form + env fallback) and send it as x-api-key on users/me and receipt requests. Co-Authored-By: Claude Fable 5 --- client/src/pages/Settings.tsx | 22 ++++++++++++++++++++++ server/.env.example | 1 + server/src/models/EtsySettings.ts | 4 ++++ server/src/routes/etsy.ts | 25 ++++++++++++++++++++----- server/src/services/etsyApi.ts | 27 +++++++++++++++++---------- 5 files changed, 64 insertions(+), 15 deletions(-) diff --git a/client/src/pages/Settings.tsx b/client/src/pages/Settings.tsx index b1a4436..dd7d7c8 100644 --- a/client/src/pages/Settings.tsx +++ b/client/src/pages/Settings.tsx @@ -16,6 +16,7 @@ interface EtsyStatus { interface EtsyConfig { configured: boolean; apiKeyMasked: string | null; + sharedSecretMasked: string | null; redirectUri: string | null; source: 'database' | 'environment' | null; } @@ -33,6 +34,7 @@ const Settings = () => { const [etsyStatus, setEtsyStatus] = useState(null); const [etsyConfig, setEtsyConfig] = useState(null); const [apiKeyInput, setApiKeyInput] = useState(''); + const [sharedSecretInput, setSharedSecretInput] = useState(''); const [redirectUriInput, setRedirectUriInput] = useState(''); const [isSavingConfig, setIsSavingConfig] = useState(false); const [isConnecting, setIsConnecting] = useState(false); @@ -90,10 +92,12 @@ const Settings = () => { try { await api.put('/etsy/config', { apiKey: apiKeyInput.trim() || undefined, + sharedSecret: sharedSecretInput.trim() || undefined, redirectUri: redirectUriInput.trim(), }); toast.success('Etsy configuration saved'); setApiKeyInput(''); + setSharedSecretInput(''); loadEtsyConfig(); } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to save Etsy configuration'); @@ -266,6 +270,24 @@ const Settings = () => { onChange={(e) => setApiKeyInput(e.target.value)} /> +
+ + setSharedSecretInput(e.target.value)} + /> +

+ Etsy requires this for API calls — it's on the same page as your keystring. +

+