|
@@ -8,24 +8,36 @@ const corsHeaders = {
|
|
|
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
|
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Validate HMAC signature from ShopRenter
|
|
|
|
|
-function validateHMAC(query: Record<string, string>, clientSecret: string): boolean {
|
|
|
|
|
- const { hmac, ...params } = query
|
|
|
|
|
-
|
|
|
|
|
- if (!hmac) {
|
|
|
|
|
- console.error('[ShopRenter] HMAC missing from request')
|
|
|
|
|
|
|
+// Validate HMAC signature from ShopRenter using raw query string
|
|
|
|
|
+function validateHMACFromRawQuery(rawQueryString: string, clientSecret: string): boolean {
|
|
|
|
|
+ if (!clientSecret) {
|
|
|
|
|
+ console.error('[ShopRenter] Client secret is empty or undefined')
|
|
|
return false
|
|
return false
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (!clientSecret) {
|
|
|
|
|
- console.error('[ShopRenter] Client secret is empty or undefined')
|
|
|
|
|
|
|
+ // Parse query string into key-value pairs preserving URL encoding
|
|
|
|
|
+ const params = new Map<string, string>()
|
|
|
|
|
+ let hmacValue = ''
|
|
|
|
|
+
|
|
|
|
|
+ rawQueryString.split('&').forEach(pair => {
|
|
|
|
|
+ const [key, ...valueParts] = pair.split('=')
|
|
|
|
|
+ const value = valueParts.join('=') // Handle values that contain '='
|
|
|
|
|
+ if (key === 'hmac') {
|
|
|
|
|
+ hmacValue = value
|
|
|
|
|
+ } else {
|
|
|
|
|
+ params.set(key, value)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (!hmacValue) {
|
|
|
|
|
+ console.error('[ShopRenter] HMAC missing from request')
|
|
|
return false
|
|
return false
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Build sorted query string without HMAC
|
|
|
|
|
- const sortedParams = Object.keys(params)
|
|
|
|
|
|
|
+ // Build sorted query string without HMAC (using URL-encoded values)
|
|
|
|
|
+ const sortedParams = Array.from(params.keys())
|
|
|
.sort()
|
|
.sort()
|
|
|
- .map(key => `${key}=${params[key]}`)
|
|
|
|
|
|
|
+ .map(key => `${key}=${params.get(key)}`)
|
|
|
.join('&')
|
|
.join('&')
|
|
|
|
|
|
|
|
console.log(`[ShopRenter] HMAC validation - sorted params: ${sortedParams}`)
|
|
console.log(`[ShopRenter] HMAC validation - sorted params: ${sortedParams}`)
|
|
@@ -36,14 +48,14 @@ function validateHMAC(query: Record<string, string>, clientSecret: string): bool
|
|
|
.update(sortedParams)
|
|
.update(sortedParams)
|
|
|
.digest('hex')
|
|
.digest('hex')
|
|
|
|
|
|
|
|
- console.log(`[ShopRenter] HMAC validation - received hmac: ${hmac}`)
|
|
|
|
|
|
|
+ console.log(`[ShopRenter] HMAC validation - received hmac: ${hmacValue}`)
|
|
|
console.log(`[ShopRenter] HMAC validation - calculated hmac: ${calculatedHmac}`)
|
|
console.log(`[ShopRenter] HMAC validation - calculated hmac: ${calculatedHmac}`)
|
|
|
|
|
|
|
|
// Timing-safe comparison
|
|
// Timing-safe comparison
|
|
|
try {
|
|
try {
|
|
|
const result = timingSafeEqual(
|
|
const result = timingSafeEqual(
|
|
|
new TextEncoder().encode(calculatedHmac),
|
|
new TextEncoder().encode(calculatedHmac),
|
|
|
- new TextEncoder().encode(hmac)
|
|
|
|
|
|
|
+ new TextEncoder().encode(hmacValue)
|
|
|
)
|
|
)
|
|
|
console.log(`[ShopRenter] HMAC validation result: ${result}`)
|
|
console.log(`[ShopRenter] HMAC validation result: ${result}`)
|
|
|
return result
|
|
return result
|
|
@@ -158,18 +170,11 @@ serve(wrapHandler('oauth-shoprenter-callback', async (req) => {
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Validate HMAC
|
|
|
|
|
- const queryParams: Record<string, string> = {
|
|
|
|
|
- shopname,
|
|
|
|
|
- code,
|
|
|
|
|
- timestamp,
|
|
|
|
|
- hmac
|
|
|
|
|
- }
|
|
|
|
|
- if (app_url) {
|
|
|
|
|
- queryParams.app_url = app_url
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // Validate HMAC using raw query string (preserves URL encoding)
|
|
|
|
|
+ const rawQueryString = url.search.slice(1) // Remove leading '?'
|
|
|
|
|
+ console.log(`[ShopRenter] Raw query string: ${rawQueryString}`)
|
|
|
|
|
|
|
|
- if (!validateHMAC(queryParams, shoprenterClientSecret)) {
|
|
|
|
|
|
|
+ if (!validateHMACFromRawQuery(rawQueryString, shoprenterClientSecret)) {
|
|
|
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' } }
|