|
|
@@ -1,7 +1,30 @@
|
|
|
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
|
|
|
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
|
|
|
import { wrapHandler, logError } from '../_shared/error-handler.ts'
|
|
|
-import { createHmac, timingSafeEqual } from 'https://deno.land/std@0.168.0/node/crypto.ts'
|
|
|
+
|
|
|
+// Helper function to convert ArrayBuffer to hex string
|
|
|
+function bufferToHex(buffer: ArrayBuffer): string {
|
|
|
+ const byteArray = new Uint8Array(buffer);
|
|
|
+ return Array.from(byteArray)
|
|
|
+ .map((byte) => byte.toString(16).padStart(2, "0"))
|
|
|
+ .join("");
|
|
|
+}
|
|
|
+
|
|
|
+// Calculate HMAC-SHA256 using Deno's native crypto.subtle API
|
|
|
+async function calculateHmacSha256(secret: string, message: string): Promise<string> {
|
|
|
+ const encoder = new TextEncoder();
|
|
|
+ const keyData = encoder.encode(secret);
|
|
|
+ const key = await crypto.subtle.importKey(
|
|
|
+ "raw",
|
|
|
+ keyData,
|
|
|
+ { name: "HMAC", hash: { name: "SHA-256" } },
|
|
|
+ false,
|
|
|
+ ["sign"]
|
|
|
+ );
|
|
|
+ const messageData = encoder.encode(message);
|
|
|
+ const signature = await crypto.subtle.sign("HMAC", key, messageData);
|
|
|
+ return bufferToHex(signature);
|
|
|
+}
|
|
|
|
|
|
const corsHeaders = {
|
|
|
'Access-Control-Allow-Origin': '*',
|
|
|
@@ -10,7 +33,7 @@ const corsHeaders = {
|
|
|
|
|
|
// Validate HMAC signature from ShopRenter
|
|
|
// Per ShopRenter documentation, HMAC is calculated from code, shopname, timestamp only
|
|
|
-function validateHMAC(params: URLSearchParams, clientSecret: string, clientId: string): boolean {
|
|
|
+async function validateHMAC(params: URLSearchParams, clientSecret: string, clientId: string): Promise<boolean> {
|
|
|
if (!clientSecret) {
|
|
|
console.error('[ShopRenter] Client secret is empty or undefined')
|
|
|
return false
|
|
|
@@ -35,43 +58,28 @@ function validateHMAC(params: URLSearchParams, clientSecret: string, clientId: s
|
|
|
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 Deno's native crypto.subtle API (SHA-256)
|
|
|
+ const calculatedHmacWithSecret = await calculateHmacSha256(clientSecret, sortedParams)
|
|
|
|
|
|
// Also try with Client ID (in case of documentation confusion)
|
|
|
- const calculatedHmacWithId = createHmac('sha256', clientId)
|
|
|
- .update(sortedParams)
|
|
|
- .digest('hex')
|
|
|
+ const calculatedHmacWithId = await calculateHmacSha256(clientId, sortedParams)
|
|
|
|
|
|
console.log(`[ShopRenter] HMAC validation - received hmac: ${hmacValue}`)
|
|
|
console.log(`[ShopRenter] HMAC validation - calculated hmac (with secret): ${calculatedHmacWithSecret}`)
|
|
|
console.log(`[ShopRenter] HMAC validation - calculated hmac (with id): ${calculatedHmacWithId}`)
|
|
|
|
|
|
- // Timing-safe comparison - try both
|
|
|
- try {
|
|
|
- const resultWithSecret = timingSafeEqual(
|
|
|
- new TextEncoder().encode(calculatedHmacWithSecret),
|
|
|
- new TextEncoder().encode(hmacValue)
|
|
|
- )
|
|
|
- console.log(`[ShopRenter] HMAC validation result (with secret): ${resultWithSecret}`)
|
|
|
+ // Compare HMACs
|
|
|
+ const resultWithSecret = calculatedHmacWithSecret === hmacValue
|
|
|
+ console.log(`[ShopRenter] HMAC validation result (with secret): ${resultWithSecret}`)
|
|
|
|
|
|
- if (resultWithSecret) {
|
|
|
- return true
|
|
|
- }
|
|
|
+ if (resultWithSecret) {
|
|
|
+ return true
|
|
|
+ }
|
|
|
|
|
|
- const resultWithId = timingSafeEqual(
|
|
|
- new TextEncoder().encode(calculatedHmacWithId),
|
|
|
- new TextEncoder().encode(hmacValue)
|
|
|
- )
|
|
|
- console.log(`[ShopRenter] HMAC validation result (with id): ${resultWithId}`)
|
|
|
+ const resultWithId = calculatedHmacWithId === hmacValue
|
|
|
+ console.log(`[ShopRenter] HMAC validation result (with id): ${resultWithId}`)
|
|
|
|
|
|
- return resultWithId
|
|
|
- } catch (error) {
|
|
|
- console.error('[ShopRenter] HMAC comparison error:', error)
|
|
|
- return false
|
|
|
- }
|
|
|
+ return resultWithId
|
|
|
}
|
|
|
|
|
|
// Validate timestamp to prevent replay attacks
|
|
|
@@ -191,7 +199,7 @@ serve(wrapHandler('oauth-shoprenter-callback', async (req) => {
|
|
|
}
|
|
|
|
|
|
// Validate HMAC using decoded parameter values (per ShopRenter docs)
|
|
|
- if (!validateHMAC(url.searchParams, shoprenterClientSecret, shoprenterClientId)) {
|
|
|
+ if (!(await validateHMAC(url.searchParams, shoprenterClientSecret, shoprenterClientId))) {
|
|
|
return new Response(
|
|
|
JSON.stringify({ error: 'HMAC validation failed' }),
|
|
|
{ status: 403, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|