|
@@ -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×tamp=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}×tamp=${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' } }
|