Просмотр исходного кода

fix: use only code/shopname/timestamp for HMAC and test with both secret and id #96

- Changed HMAC validation to use only the 3 params from documentation example
- Added test with Client ID as alternative to Client Secret
- Added detailed logging for both calculations
Claude 5 месяцев назад
Родитель
Сommit
dcd0198cc2
1 измененных файлов с 36 добавлено и 24 удалено
  1. 36 24
      supabase/functions/oauth-shoprenter-callback/index.ts

+ 36 - 24
supabase/functions/oauth-shoprenter-callback/index.ts

@@ -9,8 +9,8 @@ const corsHeaders = {
 }
 }
 
 
 // Validate HMAC signature from ShopRenter
 // Validate HMAC signature from ShopRenter
-// Per ShopRenter documentation, HMAC is calculated from all query params except 'hmac' itself
-function validateHMAC(params: URLSearchParams, clientSecret: string): boolean {
+// Per ShopRenter documentation, HMAC is calculated from code, shopname, timestamp only
+function validateHMAC(params: URLSearchParams, clientSecret: string, clientId: string): boolean {
   if (!clientSecret) {
   if (!clientSecret) {
     console.error('[ShopRenter] Client secret is empty or undefined')
     console.error('[ShopRenter] Client secret is empty or undefined')
     return false
     return false
@@ -22,40 +22,52 @@ function validateHMAC(params: URLSearchParams, clientSecret: string): boolean {
     return false
     return false
   }
   }
 
 
-  // Get all parameters except 'hmac' and sort them alphabetically
-  // Per ShopRenter docs pseudocode: const { hmac, ...dataToValidate } = params;
-  const dataToValidate: Record<string, string> = {}
-  for (const [key, value] of params.entries()) {
-    if (key !== 'hmac') {
-      dataToValidate[key] = value
-    }
-  }
+  // Get only code, shopname, timestamp as per the ShopRenter documentation example
+  // The documentation shows: code=0907a61c0c8d55e99db179b68161bc00&shopname=example&timestamp=1337178173
+  const code = params.get('code') || ''
+  const shopname = params.get('shopname') || ''
+  const timestamp = params.get('timestamp') || ''
 
 
-  // Sort parameters alphabetically by key and create query string
-  const sortedParams = Object.keys(dataToValidate)
-    .sort()
-    .map(key => `${key}=${dataToValidate[key]}`)
-    .join('&')
+  // Build params string with only these 3 params, sorted alphabetically
+  const sortedParams = `code=${code}&shopname=${shopname}&timestamp=${timestamp}`
 
 
   console.log(`[ShopRenter] HMAC validation - sorted params: ${sortedParams}`)
   console.log(`[ShopRenter] HMAC validation - sorted params: ${sortedParams}`)
   console.log(`[ShopRenter] HMAC validation - client secret length: ${clientSecret.length}`)
   console.log(`[ShopRenter] HMAC validation - client secret length: ${clientSecret.length}`)
+  console.log(`[ShopRenter] HMAC validation - client id length: ${clientId.length}`)
+
+  // Calculate HMAC using sha256 with Client Secret
+  const calculatedHmacWithSecret = createHmac('sha256', clientSecret)
+    .update(sortedParams)
+    .digest('hex')
 
 
-  // Calculate HMAC using sha256
-  const calculatedHmac = createHmac('sha256', clientSecret)
+  // Also try with Client ID (in case of documentation confusion)
+  const calculatedHmacWithId = createHmac('sha256', clientId)
     .update(sortedParams)
     .update(sortedParams)
     .digest('hex')
     .digest('hex')
 
 
   console.log(`[ShopRenter] HMAC validation - received hmac: ${hmacValue}`)
   console.log(`[ShopRenter] HMAC validation - received hmac: ${hmacValue}`)
-  console.log(`[ShopRenter] HMAC validation - calculated hmac: ${calculatedHmac}`)
+  console.log(`[ShopRenter] HMAC validation - calculated hmac (with secret): ${calculatedHmacWithSecret}`)
+  console.log(`[ShopRenter] HMAC validation - calculated hmac (with id): ${calculatedHmacWithId}`)
 
 
-  // Timing-safe comparison
+  // Timing-safe comparison - try both
   try {
   try {
-    const result = timingSafeEqual(
-      new TextEncoder().encode(calculatedHmac),
+    const resultWithSecret = timingSafeEqual(
+      new TextEncoder().encode(calculatedHmacWithSecret),
       new TextEncoder().encode(hmacValue)
       new TextEncoder().encode(hmacValue)
     )
     )
-    console.log(`[ShopRenter] HMAC validation result: ${result}`)
-    return result
+    console.log(`[ShopRenter] HMAC validation result (with secret): ${resultWithSecret}`)
+
+    if (resultWithSecret) {
+      return true
+    }
+
+    const resultWithId = timingSafeEqual(
+      new TextEncoder().encode(calculatedHmacWithId),
+      new TextEncoder().encode(hmacValue)
+    )
+    console.log(`[ShopRenter] HMAC validation result (with id): ${resultWithId}`)
+
+    return resultWithId
   } catch (error) {
   } catch (error) {
     console.error('[ShopRenter] HMAC comparison error:', error)
     console.error('[ShopRenter] HMAC comparison error:', error)
     return false
     return false
@@ -179,7 +191,7 @@ serve(wrapHandler('oauth-shoprenter-callback', async (req) => {
     }
     }
 
 
     // Validate HMAC using decoded parameter values (per ShopRenter docs)
     // Validate HMAC using decoded parameter values (per ShopRenter docs)
-    if (!validateHMAC(url.searchParams, shoprenterClientSecret)) {
+    if (!validateHMAC(url.searchParams, shoprenterClientSecret, shoprenterClientId)) {
       return new Response(
       return new Response(
         JSON.stringify({ error: 'HMAC validation failed' }),
         JSON.stringify({ error: 'HMAC validation failed' }),
         { status: 403, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
         { status: 403, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }