Browse Source

fix: prevent api_key overwrite and support global ShopRenter credentials #83

- Add fallback to environment variables (SHOPRENTER_CLIENT_ID/SECRET) when stores.api_key/api_secret are NULL
- Always backup client credentials to alt_data during token refresh
- Explicitly preserve api_key and api_secret columns (never overwrite during token refresh)
- Add defensive checks for corrupted api_key (when it contains access_token)
- Ensure alt_data is properly merged using spread operator to preserve existing fields
- Add last_token_refresh timestamp to alt_data for debugging

This fixes the issue where api_key was being overwritten with access_token during token refresh.
Now supports both global credentials (for testing) and per-store credentials.
Claude 5 months ago
parent
commit
effff3a14e
1 changed files with 35 additions and 15 deletions
  1. 35 15
      supabase/functions/_shared/shoprenter-client.ts

+ 35 - 15
supabase/functions/_shared/shoprenter-client.ts

@@ -291,23 +291,35 @@ export async function getValidAccessToken(storeId: string): Promise<string> {
     throw new Error('ShopRenter store not found')
   }
 
-  // Try to get client credentials from api_key/api_secret first (primary location)
+  // Get client credentials - prioritize database, fallback to environment variables
   let clientId = store.api_key
   let clientSecret = store.api_secret
 
-  // If api_key looks like an access_token (JWT format), get credentials from alt_data
+  // If api_key looks like an access_token (JWT format), it was corrupted - get from alt_data
   const isAccessToken = store.api_key && store.api_key.includes('.')
 
   if (isAccessToken) {
-    // api_key contains a token (old bug), try alt_data for client credentials
-    console.log('[ShopRenter] WARNING: api_key appears to be an access_token, retrieving credentials from alt_data')
+    console.log('[ShopRenter] WARNING: api_key appears to be an access_token (corrupted), retrieving credentials from alt_data')
     clientId = store.alt_data?.client_id
     clientSecret = store.alt_data?.client_secret
   }
 
+  // If api_key/api_secret are NULL/empty (manual testing), try alt_data backup
+  if (!clientId || !clientSecret) {
+    clientId = store.alt_data?.client_id
+    clientSecret = store.alt_data?.client_secret
+  }
+
+  // Final fallback: use global credentials from environment (for testing or when store has NULL credentials)
+  if (!clientId || !clientSecret) {
+    console.log('[ShopRenter] No client credentials in database, using global credentials from environment')
+    clientId = Deno.env.get('SHOPRENTER_CLIENT_ID')
+    clientSecret = Deno.env.get('SHOPRENTER_CLIENT_SECRET')
+  }
+
   // Validate client credentials are available
   if (!clientId || !clientSecret) {
-    throw new Error('ShopRenter client credentials not found in database. Please reconnect the store.')
+    throw new Error('ShopRenter client credentials not found in database or environment. Please reconnect the store or configure SHOPRENTER_CLIENT_ID and SHOPRENTER_CLIENT_SECRET.')
   }
 
   // If we have client credentials, use client_credentials flow to get a fresh token
@@ -320,8 +332,9 @@ export async function getValidAccessToken(storeId: string): Promise<string> {
       const expiresAt = new Date(Date.now() + (tokenData.expires_in * 1000)).toISOString()
 
       // Update store with the new access token
-      // IMPORTANT: Store tokens in access_token/refresh_token columns
-      // Keep client credentials in api_key/api_secret for future token renewals
+      // IMPORTANT: Only update access_token, refresh_token, and token_expires_at
+      // NEVER update api_key or api_secret (preserve them for manual testing or per-store credentials)
+      // Always backup client credentials to alt_data for recovery
       await supabase
         .from('stores')
         .update({
@@ -329,9 +342,10 @@ export async function getValidAccessToken(storeId: string): Promise<string> {
           refresh_token: tokenData.refresh_token || null,
           token_expires_at: expiresAt,
           alt_data: {
-            ...store.alt_data,
+            ...(store.alt_data || {}),
             client_id: clientId,
-            client_secret: clientSecret
+            client_secret: clientSecret,
+            last_token_refresh: new Date().toISOString()
           }
         })
         .eq('id', storeId)
@@ -367,7 +381,9 @@ export async function getValidAccessToken(storeId: string): Promise<string> {
           const newExpiresAt = new Date(Date.now() + (newTokenData.expires_in * 1000)).toISOString()
 
           // Update store with new tokens
-          // IMPORTANT: Also preserve client credentials in alt_data for backup
+          // IMPORTANT: Only update access_token, refresh_token, and token_expires_at
+          // NEVER update api_key or api_secret (preserve them)
+          // Always backup client credentials to alt_data for recovery
           await supabase
             .from('stores')
             .update({
@@ -375,9 +391,10 @@ export async function getValidAccessToken(storeId: string): Promise<string> {
               refresh_token: newTokenData.refresh_token || store.refresh_token,
               token_expires_at: newExpiresAt,
               alt_data: {
-                ...store.alt_data,
+                ...(store.alt_data || {}),
                 client_id: clientId,
-                client_secret: clientSecret
+                client_secret: clientSecret,
+                last_token_refresh: new Date().toISOString()
               }
             })
             .eq('id', storeId)
@@ -399,7 +416,9 @@ export async function getValidAccessToken(storeId: string): Promise<string> {
             const expiresAt = new Date(Date.now() + (tokenData.expires_in * 1000)).toISOString()
 
             // Update store with new tokens
-            // IMPORTANT: Also preserve client credentials in alt_data for backup
+            // IMPORTANT: Only update access_token, refresh_token, and token_expires_at
+            // NEVER update api_key or api_secret (preserve them)
+            // Always backup client credentials to alt_data for recovery
             await supabase
               .from('stores')
               .update({
@@ -407,9 +426,10 @@ export async function getValidAccessToken(storeId: string): Promise<string> {
                 refresh_token: tokenData.refresh_token || null,
                 token_expires_at: expiresAt,
                 alt_data: {
-                  ...store.alt_data,
+                  ...(store.alt_data || {}),
                   client_id: clientId,
-                  client_secret: clientSecret
+                  client_secret: clientSecret,
+                  last_token_refresh: new Date().toISOString()
                 }
               })
               .eq('id', storeId)