Weather Witness

First Flight #40
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: low
Likelihood: low
Invalid

Error Handling in GetWeather.js

Summary

The error handling in GetWeather.js throws errors without providing specific details about what went wrong, making debugging difficult if API calls fail.

Vulnerability Details

The error handling in GetWeather.js is minimal and lacks specific error information:

if (geoCodingResponse.error) throw Error("Request failed, try checking the params provided");
if (weatherResponse.error) throw Error("Request failed, try checking the params provided");

These generic error messages don't provide any details about:

  • What specific parameter might be incorrect

  • What type of error occurred (network, authentication, etc.)

  • How to resolve the issue

Proof of Concept

// SPDX-License-Identifier: MIT
// This is a JavaScript test file that demonstrates the error handling issues in GetWeather.js
// Mock the Chainlink Functions environment
const Functions = {
makeHttpRequest: async (params) => {
// Mock implementation that returns predefined responses based on test case
if (testCase === "invalid_api_key") {
return {
error: {
status: 401,
statusText: "Unauthorized",
message: "Invalid API key. Please see http://openweathermap.org/faq#error401 for more info."
}
};
} else if (testCase === "invalid_location") {
return {
error: {
status: 404,
statusText: "Not Found",
message: "zip code not found"
}
};
} else if (testCase === "rate_limit") {
return {
error: {
status: 429,
statusText: "Too Many Requests",
message: "API rate limit exceeded"
}
};
} else if (testCase === "server_error") {
return {
error: {
status: 500,
statusText: "Internal Server Error",
message: "Internal server error"
}
};
} else if (testCase === "network_error") {
throw new Error("Network error: Unable to reach API endpoint");
} else {
// Default success case
if (params.url.includes("geo/1.0/zip")) {
return {
data: {
lat: 40.7128,
lon: -74.0060
}
};
} else if (params.url.includes("data/2.5/weather")) {
return {
data: {
weather: [
{
id: 800
}
],
main: {
temp: 20
}
}
};
}
}
}
};
// Import the GetWeather function (simplified version for testing)
const getWeather = (args, secrets) => {
return new Promise(async (resolve) => {
try {
// Get location data
const geoCodingRequest = await Functions.makeHttpRequest({
url: "http://api.openweathermap.org/geo/1.0/zip",
method: "GET",
params: { zip: `${args[0]},${args[1]}`, appid: secrets.apiKey }
});
// Current implementation with poor error handling
if (geoCodingRequest.error) {
throw Error("Request failed, try checking the params provided");
}
// Get weather data
const weatherRequest = await Functions.makeHttpRequest({
url: "https://api.openweathermap.org/data/2.5/weather",
method: "GET",
params: {
lat: geoCodingRequest.data.lat,
lon: geoCodingRequest.data.lon,
appid: secrets.apiKey
}
});
// Current implementation with poor error handling
if (weatherRequest.error) {
throw Error("Request failed, try checking the params provided");
}
// Extract weather ID and map to enum
const weather_id = weatherRequest.data.weather[0].id;
const weather_id_x = Math.floor(weather_id / 100);
let weather_enum;
// Weather mapping logic
if (weather_id_x === 2) weather_enum = 3;
else if (weather_id_x === 3 || weather_id_x === 5) weather_enum = 2;
else if (weather_id_x === 6) weather_enum = 5;
else if (weather_id === 800) weather_enum = 0;
else if (weather_id_x === 8) weather_enum = 1;
else weather_enum = 4;
// Return the result
resolve([weather_enum, Math.round(weatherRequest.data.main.temp)]);
} catch (error) {
console.error("Error in getWeather:", error.message);
resolve([0, 0]); // Default to SUNNY and 0 temperature on error
}
});
};
// Improved version with better error handling
const getWeatherImproved = (args, secrets) => {
return new Promise(async (resolve) => {
try {
// Get location data
const geoCodingRequest = await Functions.makeHttpRequest({
url: "http://api.openweathermap.org/geo/1.0/zip",
method: "GET",
params: { zip: `${args[0]},${args[1]}`, appid: secrets.apiKey }
});
// Improved error handling with specific details
if (geoCodingRequest.error) {
const errorDetails = {
message: "Geocoding request failed",
status: geoCodingRequest.error.status,
statusText: geoCodingRequest.error.statusText,
details: geoCodingRequest.error.message,
params: {
zip: `${args[0]},${args[1]}`,
appid: "API key used (masked)"
}
};
throw Error(JSON.stringify(errorDetails));
}
// Get weather data
const weatherRequest = await Functions.makeHttpRequest({
url: "https://api.openweathermap.org/data/2.5/weather",
method: "GET",
params: {
lat: geoCodingRequest.data.lat,
lon: geoCodingRequest.data.lon,
appid: secrets.apiKey
}
});
// Improved error handling with specific details
if (weatherRequest.error) {
const errorDetails = {
message: "Weather data request failed",
status: weatherRequest.error.status,
statusText: weatherRequest.error.statusText,
details: weatherRequest.error.message,
params: {
lat: geoCodingRequest.data.lat,
lon: geoCodingRequest.data.lon,
appid: "API key used (masked)"
}
};
throw Error(JSON.stringify(errorDetails));
}
// Extract weather ID and map to enum
const weather_id = weatherRequest.data.weather[0].id;
const weather_id_x = Math.floor(weather_id / 100);
let weather_enum;
// Weather mapping logic
if (weather_id_x === 2) weather_enum = 3;
else if (weather_id_x === 3 || weather_id_x === 5) weather_enum = 2;
else if (weather_id_x === 6) weather_enum = 5;
else if (weather_id === 800) weather_enum = 0;
else if (weather_id_x === 8) weather_enum = 1;
else weather_enum = 4;
// Return the result
resolve([weather_enum, Math.round(weatherRequest.data.main.temp)]);
} catch (error) {
console.error("Error in getWeatherImproved:", error.message);
// Try to parse the error if it's a JSON string
let errorObj = { message: error.message };
try {
if (error.message.startsWith('{')) {
errorObj = JSON.parse(error.message);
}
} catch (e) {
// If parsing fails, use the original error message
}
// Log detailed error information
console.error("Detailed error:", errorObj);
// Return a default value with error indication
resolve([0, 0, { error: errorObj }]); // Default to SUNNY and 0 temperature with error info
}
});
};
// Test cases to demonstrate the issue
async function runTests() {
const args = ["10001", "US"]; // NYC zip code
const secrets = { apiKey: "mock_api_key" };
console.log("Testing Error Handling Issues in GetWeather.js:");
// Test case 1: Invalid API key
console.log("\nTest Case 1: Invalid API Key");
testCase = "invalid_api_key";
console.log("Original implementation:");
try {
const result1 = await getWeather(args, secrets);
console.log(`Result: ${JSON.stringify(result1)}`);
} catch (error) {
console.log(`Error: ${error.message}`);
}
console.log("\nImproved implementation:");
try {
const result1Improved = await getWeatherImproved(args, secrets);
console.log(`Result: ${JSON.stringify(result1Improved)}`);
} catch (error) {
console.log(`Error: ${error.message}`);
}
// Test case 2: Invalid location
console.log("\nTest Case 2: Invalid Location");
testCase = "invalid_location";
console.log("Original implementation:");
try {
const result2 = await getWeather(args, secrets);
console.log(`Result: ${JSON.stringify(result2)}`);
} catch (error) {
console.log(`Error: ${error.message}`);
}
console.log("\nImproved implementation:");
try {
const result2Improved = await getWeatherImproved(args, secrets);
console.log(`Result: ${JSON.stringify(result2Improved)}`);
} catch (error) {
console.log(`Error: ${error.message}`);
}
// Test case 3: Network error
console.log("\nTest Case 3: Network Error");
testCase = "network_error";
console.log("Original implementation:");
try {
const result3 = await getWeather(args, secrets);
console.log(`Result: ${JSON.stringify(result3)}`);
} catch (error) {
console.log(`Error: ${error.message}`);
}
console.log("\nImproved implementation:");
try {
const result3Improved = await getWeatherImproved(args, secrets);
console.log(`Result: ${JSON.stringify(result3Improved)}`);
} catch (error) {
console.log(`Error: ${error.message}`);
}
}
// Global variable to control test case
let testCase = "success";
// Run the tests
runTests();

This PoC demonstrates the issues with the current error handling in GetWeather.js by comparing it with an improved implementation:

  1. The original implementation provides generic error messages that don't help with debugging:

    • "Request failed, try checking the params provided" for all types of errors

    • No distinction between different error types (API key, location, network, etc.)

    • No specific information about what went wrong

  2. The improved implementation provides detailed error information:

    • Specific error messages for different types of errors

    • HTTP status codes and status text

    • Details from the API response

    • Parameters that were used in the request

    • Structured error objects that can be parsed and handled appropriately

The PoC shows how the improved error handling makes debugging easier by providing actionable information about what went wrong and how to fix it.

Impact

The limited error handling:

  • Makes debugging difficult when API calls fail

  • Provides no actionable information to resolve issues

  • Could lead to extended downtime during troubleshooting

  • Reduces the system's maintainability

Recommendations

Enhance error handling with more specific error messages and details:

if (geoCodingResponse.error) {
const errorDetails = {
message: "Geocoding request failed",
error: geoCodingResponse.error,
params: {
zip: `${args[0]},${args[1]}`,
appid: "API key used"
}
};
throw Error(JSON.stringify(errorDetails));
}
if (weatherResponse.error) {
const errorDetails = {
message: "Weather data request failed",
error: weatherResponse.error,
params: {
lat: geoCodingResponse.data.lat,
lon: geoCodingResponse.data.lon,
appid: "API key used"
}
};
throw Error(JSON.stringify(errorDetails));
}

Additionally, implement try-catch blocks to handle unexpected errors:

try {
const geoCodingRequest = Functions.makeHttpRequest({
url: "http://api.openweathermap.org/geo/1.0/zip",
method: "GET",
params: { zip: `${args[0]},${args[1]}`, appid: secrets.apiKey }
});
const geoCodingResponse = await geoCodingRequest;
// Process response
} catch (error) {
throw Error(`Unexpected error in geocoding request: ${error.message}`);
}
Updates

Appeal created

bube Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.