fix(zoho): P0/P1 criticals — credential check, response validation, timeout, null normalization (Neo N1)

This commit is contained in:
null 2026-05-17 15:01:04 -05:00
parent a963dc4dcc
commit 5b0a509e70
1 changed files with 38 additions and 15 deletions

View File

@ -208,8 +208,8 @@ const supportSchema = z.object({
const ZOHO_ENABLED = process.env.ZOHO_ENABLED === 'true' const ZOHO_ENABLED = process.env.ZOHO_ENABLED === 'true'
const ZOHO_API_DOMAIN = process.env.ZOHO_API_DOMAIN || 'https://www.zohoapis.com' const ZOHO_API_DOMAIN = process.env.ZOHO_API_DOMAIN || 'https://www.zohoapis.com'
const ZOHO_CLIENT_ID = process.env.ZOHO_CLIENT_ID || '' const ZOHO_CLIENT_ID = process.env.ZOHO_CLIENT_ID || ''
const ZOHO_CLIENT_SECRET = process.env.ZOHO_CLIENT_SECRET || '' const ZOHO_CLIENT_SECRET = process.env.ZOHO_CLIENT_SECRET || null
const ZOHO_REFRESH_TOKEN = process.env.ZOHO_REFRESH_TOKEN || '' const ZOHO_REFRESH_TOKEN = process.env.ZOHO_REFRESH_TOKEN || null
const ZOHO_REDIRECT_URI = process.env.ZOHO_REDIRECT_URI || '' const ZOHO_REDIRECT_URI = process.env.ZOHO_REDIRECT_URI || ''
// In-memory access token cache // In-memory access token cache
@ -253,6 +253,12 @@ async function getZohoAccessToken() {
async function forwardToZoho(leadData) { async function forwardToZoho(leadData) {
if (!ZOHO_ENABLED) return if (!ZOHO_ENABLED) return
// Issue #2: Short-circuit if Zoho credentials are missing
if (!ZOHO_CLIENT_SECRET || !ZOHO_REFRESH_TOKEN) {
log.warn('[Zoho] Skipping forwarding - ZOHO_CLIENT_SECRET or ZOHO_REFRESH_TOKEN not configured')
return
}
try { try {
const accessToken = await getZohoAccessToken() const accessToken = await getZohoAccessToken()
if (!accessToken) { if (!accessToken) {
@ -270,26 +276,43 @@ async function forwardToZoho(leadData) {
Phone: leadData.phone || '', Phone: leadData.phone || '',
Zip_Code: leadData.zip || '', Zip_Code: leadData.zip || '',
Description: leadData.message || '', Description: leadData.message || '',
Service_Interest: leadData.service_interest || '', Service_Interest: leadData.service_interest || null,
}, },
], ],
} }
const response = await fetch(url, { // Issue #5: Add timeout using AbortController
method: 'POST', const controller = new AbortController()
headers: { const timeoutId = setTimeout(() => controller.abort(), 10000) // 10 second timeout
'Authorization': `Zoho-oauthtoken ${accessToken}`,
'Content-Type': 'application/json', try {
}, const response = await fetch(url, {
body: JSON.stringify(payload), method: 'POST',
}) headers: {
'Authorization': `Zoho-oauthtoken ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
signal: controller.signal,
})
// Issue #3: Check response.ok before processing
if (!response.ok) {
const text = await response.text()
log.error(`[Zoho] Lead forwarding failed (${response.status}):`, text)
return
}
if (response.ok) {
const result = await response.json() const result = await response.json()
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')
} else { } catch (fetchErr) {
const text = await response.text() if (fetchErr.name === 'AbortError') {
log.error(`[Zoho] Lead forwarding failed (${response.status}):`, text) log.warn('[Zoho] Lead forwarding timed out after 10 seconds')
} else {
log.error('[Zoho] Forwarding error:', fetchErr.message)
}
} finally {
clearTimeout(timeoutId)
} }
} catch (err) { } catch (err) {
log.error('[Zoho] Forwarding error:', err.message) log.error('[Zoho] Forwarding error:', err.message)