fix(server): Zoho token endpoint hardening + version bump to 0.5.4 (batch 0.6.0)

This commit is contained in:
null 2026-05-17 15:18:24 -05:00
parent c4d40c39ba
commit 25ab4c7986
2 changed files with 38 additions and 6 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "queuenorth-website", "name": "queuenorth-website",
"private": true, "private": true,
"version": "0.5.3", "version": "0.5.4",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "concurrently \"vite\" \"node server/index.js\"", "dev": "concurrently \"vite\" \"node server/index.js\"",

View File

@ -216,6 +216,9 @@ const ZOHO_REDIRECT_URI = process.env.ZOHO_REDIRECT_URI || ''
let zohoAccessToken = null let zohoAccessToken = null
let zohoTokenExpiry = 0 let zohoTokenExpiry = 0
// 10 second timeout for all Zoho API calls
const ZOHO_TIMEOUT_MS = 10000
async function getZohoAccessToken() { async function getZohoAccessToken() {
// Return cached token if still valid (with 60s buffer) // Return cached token if still valid (with 60s buffer)
if (zohoAccessToken && Date.now() < zohoTokenExpiry - 60000) { if (zohoAccessToken && Date.now() < zohoTokenExpiry - 60000) {
@ -232,7 +235,36 @@ async function getZohoAccessToken() {
redirect_uri: ZOHO_REDIRECT_URI, redirect_uri: ZOHO_REDIRECT_URI,
}) })
const response = await fetch(`${url}?${params.toString()}`, { method: 'POST' }) const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), ZOHO_TIMEOUT_MS)
let response
try {
response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params.toString(),
signal: controller.signal,
})
} catch (err) {
if (err.name === 'AbortError') {
log.warn('[Zoho] Token fetch timed out after', ZOHO_TIMEOUT_MS, 'ms')
} else {
log.error('[Zoho] Token fetch error:', err.message)
}
clearTimeout(timeoutId)
return null
} finally {
clearTimeout(timeoutId)
}
// Issue #3: Check response.ok before parsing JSON
if (!response.ok) {
const text = await response.text()
log.error(`[Zoho] Token fetch failed (${response.status}):`, text)
return null
}
const data = await response.json() const data = await response.json()
if (data.access_token) { if (data.access_token) {
@ -266,7 +298,8 @@ async function forwardToZoho(leadData) {
return return
} }
const url = `${ZOHO_API_DOMAIN}/crm/v8/Leads` // Issue #8: Prevent double-slash in URL path
const url = `${ZOHO_API_DOMAIN.replace(/\/$/, '')}/crm/v8/Leads`
const payload = { const payload = {
data: [ data: [
{ {
@ -281,9 +314,8 @@ async function forwardToZoho(leadData) {
], ],
} }
// Issue #5: Add timeout using AbortController
const controller = new AbortController() const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 10000) // 10 second timeout const timeoutId = setTimeout(() => controller.abort(), ZOHO_TIMEOUT_MS)
try { try {
const response = await fetch(url, { const response = await fetch(url, {
@ -307,7 +339,7 @@ async function forwardToZoho(leadData) {
log.info('[Zoho] Lead forwarded successfully:', result.data?.[0]?.details?.id || 'no id returned') log.info('[Zoho] Lead forwarded successfully:', result.data?.[0]?.details?.id || 'no id returned')
} catch (fetchErr) { } catch (fetchErr) {
if (fetchErr.name === 'AbortError') { if (fetchErr.name === 'AbortError') {
log.warn('[Zoho] Lead forwarding timed out after 10 seconds') log.warn('[Zoho] Lead forwarding timed out after', ZOHO_TIMEOUT_MS, 'ms')
} else { } else {
log.error('[Zoho] Forwarding error:', fetchErr.message) log.error('[Zoho] Forwarding error:', fetchErr.message)
} }