فهرست منبع

fix: preserve ShopRenter client credentials during token refresh #79

- Fix bug where access_token was overwriting api_key (client_id)
- Store OAuth tokens in access_token/refresh_token columns
- Preserve client credentials in api_key/api_secret for future token renewals
- Store client credentials in alt_data as backup
- Update getValidAccessToken() to read from correct columns
- Fix initial store creation to use proper column separation
- Add backward compatibility for stores with tokens in api_key column
Claude 5 ماه پیش
والد
کامیت
198b383bd9
2فایلهای تغییر یافته به همراه47 افزوده شده و 36 حذف شده
  1. 33 33
      supabase/functions/_shared/shoprenter-client.ts
  2. 14 3
      supabase/functions/api/index.ts

+ 33 - 33
supabase/functions/_shared/shoprenter-client.ts

@@ -282,7 +282,7 @@ export async function getValidAccessToken(storeId: string): Promise<string> {
   // Fetch store data
   const { data: store, error: storeError } = await supabase
     .from('stores')
-    .select('store_name, api_key, api_secret, alt_data, token_expires_at')
+    .select('store_name, api_key, api_secret, access_token, refresh_token, alt_data, token_expires_at')
     .eq('id', storeId)
     .eq('platform_name', 'shoprenter')
     .single()
@@ -291,17 +291,17 @@ export async function getValidAccessToken(storeId: string): Promise<string> {
     throw new Error('ShopRenter store not found')
   }
 
-  // Try to get client credentials from alt_data first (most reliable)
-  let clientId = store.alt_data?.client_id
-  let clientSecret = store.alt_data?.client_secret
+  // Try to get client credentials from api_key/api_secret first (primary location)
+  let clientId = store.api_key
+  let clientSecret = store.api_secret
 
-  // Check if api_key contains a client_id (32 hex chars) instead of a token
-  const isClientId = store.api_key && /^[a-f0-9]{32}$/i.test(store.api_key)
+  // If api_key looks like an access_token (JWT format), get credentials from alt_data
+  const isAccessToken = store.api_key && store.api_key.includes('.')
 
-  // If not in alt_data and api_key looks like client_id, use it
-  if (!clientId && isClientId) {
-    clientId = store.api_key
-    clientSecret = store.api_secret
+  if (isAccessToken) {
+    // api_key contains a token (old bug), try alt_data for client credentials
+    clientId = store.alt_data?.client_id
+    clientSecret = store.alt_data?.client_secret
   }
 
   // If still no client credentials, try environment variables (global app credentials)
@@ -326,12 +326,13 @@ 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: Always preserve client credentials in alt_data for future token renewals
+      // IMPORTANT: Store tokens in access_token/refresh_token columns
+      // Keep client credentials in api_key/api_secret for future token renewals
       await supabase
         .from('stores')
         .update({
-          api_key: tokenData.access_token,
-          api_secret: tokenData.refresh_token || clientSecret,
+          access_token: tokenData.access_token,
+          refresh_token: tokenData.refresh_token || null,
           token_expires_at: expiresAt,
           alt_data: {
             ...store.alt_data,
@@ -349,9 +350,9 @@ export async function getValidAccessToken(storeId: string): Promise<string> {
     }
   }
 
-  // Check if we have tokens in api_key/api_secret (legacy)
-  if (store.api_key) {
-    const expiresAt = store.token_expires_at // Use dedicated column instead of alt_data
+  // Check if we have an existing access token
+  if (store.access_token) {
+    const expiresAt = store.token_expires_at
     if (expiresAt) {
       const expiryTime = new Date(expiresAt).getTime()
       const now = Date.now()
@@ -359,24 +360,24 @@ export async function getValidAccessToken(storeId: string): Promise<string> {
 
       // Token is still valid
       if (expiryTime - now > bufferTime) {
-        console.log('[ShopRenter] Using existing valid token, expires at:', expiresAt)
-        return store.api_key
+        console.log('[ShopRenter] Using existing valid access_token, expires at:', expiresAt)
+        return store.access_token
       }
 
       // Token needs refresh
       console.log('[ShopRenter] Token expired or expiring soon, refreshing...')
-      if (store.api_secret) {
+      if (store.refresh_token) {
         try {
-          const newTokenData = await refreshAccessToken(store.store_name, store.api_secret)
+          const newTokenData = await refreshAccessToken(store.store_name, store.refresh_token)
 
           const newExpiresAt = new Date(Date.now() + (newTokenData.expires_in * 1000)).toISOString()
 
-          // Update store with new tokens (use token_expires_at column, not alt_data)
+          // Update store with new tokens
           await supabase
             .from('stores')
             .update({
-              api_key: newTokenData.access_token,
-              api_secret: newTokenData.refresh_token || store.api_secret,
+              access_token: newTokenData.access_token,
+              refresh_token: newTokenData.refresh_token || store.refresh_token,
               token_expires_at: newExpiresAt
             })
             .eq('id', storeId)
@@ -386,23 +387,22 @@ export async function getValidAccessToken(storeId: string): Promise<string> {
         } catch (refreshError) {
           console.error('[ShopRenter] Token refresh failed:', refreshError)
 
-          // If we have client credentials stored in alt_data, try using client_credentials flow as fallback
-          if (store.alt_data?.client_id && store.alt_data?.client_secret) {
-            console.log('[ShopRenter] Attempting fallback to client_credentials flow with stored credentials')
+          // If refresh fails and we have client credentials, try client_credentials flow as fallback
+          if (clientId && clientSecret) {
+            console.log('[ShopRenter] Attempting fallback to client_credentials flow')
             const tokenData = await getTokenWithClientCredentials(
               store.store_name,
-              store.alt_data.client_id,
-              store.alt_data.client_secret
+              clientId,
+              clientSecret
             )
 
             const expiresAt = new Date(Date.now() + (tokenData.expires_in * 1000)).toISOString()
 
-            // Update store with the new access token
             await supabase
               .from('stores')
               .update({
-                api_key: tokenData.access_token,
-                api_secret: tokenData.refresh_token || store.api_secret,
+                access_token: tokenData.access_token,
+                refresh_token: tokenData.refresh_token || null,
                 token_expires_at: expiresAt
               })
               .eq('id', storeId)
@@ -418,8 +418,8 @@ export async function getValidAccessToken(storeId: string): Promise<string> {
     }
 
     // No expiration info, just return the token
-    console.log('[ShopRenter] No expiration info found, using token as-is')
-    return store.api_key
+    console.log('[ShopRenter] No expiration info found, using access_token as-is')
+    return store.access_token
   }
 
   throw new Error('No access token found for ShopRenter store')

+ 14 - 3
supabase/functions/api/index.ts

@@ -169,7 +169,13 @@ serve(async (req) => {
         )
       }
 
+      // Get client credentials from environment (app-level credentials)
+      const clientId = Deno.env.get('SHOPRENTER_CLIENT_ID')
+      const clientSecret = Deno.env.get('SHOPRENTER_CLIENT_SECRET')
+
       // Create store record with phone_number_id
+      // IMPORTANT: Store client credentials in api_key/api_secret for token renewal
+      // Store OAuth tokens in access_token/refresh_token columns
       const { data: newStore, error: storeError } = await supabaseAdmin
         .from('stores')
         .insert({
@@ -177,8 +183,11 @@ serve(async (req) => {
           platform_name: 'shoprenter',
           store_name: installation.shopname,
           store_url: `https://${installation.shopname}.shoprenter.hu`,
-          api_key: installation.access_token,
-          api_secret: installation.refresh_token,
+          api_key: clientId,
+          api_secret: clientSecret,
+          access_token: installation.access_token,
+          refresh_token: installation.refresh_token,
+          token_expires_at: new Date(Date.now() + (installation.expires_in * 1000)).toISOString(),
           scopes: installation.scopes || [],
           phone_number_id: phoneNumberId,
           data_access_permissions: {
@@ -189,7 +198,9 @@ serve(async (req) => {
           alt_data: {
             token_type: installation.token_type,
             expires_in: installation.expires_in,
-            connectedAt: new Date().toISOString()
+            connectedAt: new Date().toISOString(),
+            client_id: clientId,
+            client_secret: clientSecret
           }
         })
         .select('id')