Skip to content

Commit c046f9d

Browse files
committed
security enhancement
1 parent 666acac commit c046f9d

File tree

4 files changed

+83
-12
lines changed

4 files changed

+83
-12
lines changed

README.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
![GitHub issues](https://img.shields.io/github/issues/riyons/nextjs-centralized-error-handler)
77
![GitHub license](https://img.shields.io/github/license/riyons/nextjs-centralized-error-handler)
88

9-
A comprehensive error-handling package designed specifically for Next.js applications. This package provides centralized error handling through custom error classes and higher-order functions, solving common limitations in Next.js API routes. By serializing error messages in a frontend-compatible format, it enhances frontend-backend integration, making it easy to handle errors across the entire stack.
9+
**A comprehensive, secure error-handling package designed specifically for Next.js applications.** By leveraging centralized error handling through custom error classes and a higher-order function, this package overcomes limitations in Next.js API routes, where traditional middleware isn’t supported. It catches all errors—both expected and unexpected—ensuring consistent responses, preventing information leakage, and eliminating the need for repetitive `try-catch` blocks. Errors are serialized in a frontend-compatible JSON format, enhancing frontend-backend integration and delivering user-friendly feedback. Ideal for scalable applications, `nextjs-centralized-error-handler` simplifies error management across the entire stack while maintaining robust security.
1010

1111
## Table of Contents
1212

@@ -27,6 +27,7 @@ A comprehensive error-handling package designed specifically for Next.js applica
2727
- [Customizing Error Handling Behavior](#customizing-error-handling-behavior)
2828
- [Error Handler Options](#error-handler-options)
2929
- [Customizing Error Responses](#customizing-error-responses)
30+
- [Security Considerations](#security-considerations)
3031
- [Examples](#examples)
3132
- [Integration with Logging Services](#integration-with-logging-services)
3233
- [Enhanced Logging with Sentry](#enhanced-logging-with-sentry)
@@ -263,6 +264,49 @@ Developers can pass a `formatError` function to customize how errors are returne
263264

264265
---
265266

267+
## Security Considerations
268+
269+
### Comprehensive Exception Handling
270+
The provided `errorHandler` higher-order function catches all exceptions in the route handler—not just those thrown using the package's custom error classes. This approach intercepts any uncaught errors, regardless of their origin, which eliminates the need for repetitive `try-catch` blocks in each route. By wrapping your route handlers with `errorHandler`, you rely on a consistent, centralized error-handling strategy across all routes, minimizing code redundancy and enhancing maintainability.
271+
272+
### Preventing Information Leakage
273+
The `errorHandler` is designed to prevent sensitive information from being exposed to clients:
274+
275+
- **Custom Errors Only**: Only errors that are instances of `CustomError` (or its subclasses) will have their `statusCode` and `message` sent to the client. This provides meaningful, user-friendly error messages for known issues.
276+
277+
- **Generic Handling of Other Errors**: For unexpected errors (such as those thrown by third-party libraries or unanticipated issues), `errorHandler` defaults to a `statusCode` of 500 and a generic error message, ensuring internal server details are kept private.
278+
279+
### Example: Handling Unexpected Errors Safely
280+
Consider an API route that relies on a third-party library, which may throw errors we can’t predict:
281+
282+
```javascript
283+
const { errorHandler } = require('nextjs-centralized-error-handler');
284+
285+
const handler = async (req, res) => {
286+
// An error from a third-party library
287+
await thirdPartyLibrary.doSomething(); // Throws an unexpected error
288+
};
289+
290+
export default errorHandler(handler);
291+
```
292+
### What Happens Under the Hood
293+
If `thirdPartyLibrary.doSomething()` throws an error that isn’t a `CustomError`, `errorHandler` will:
294+
295+
1. **Set `statusCode`** to 500 (or the configured `defaultStatusCode`).
296+
2. **Set `message`** to "An internal server error occurred. Please try again later." (or `defaultMessage` if configured).
297+
3. **Prevent Information Leakage**: Ensures no sensitive details (e.g., the original error message or stack trace) are sent to the client.
298+
4. **Log the Error Server-Side**: Uses the provided logger for internal monitoring to record the error.
299+
300+
### Note on Error Handling Strategy
301+
The `errorHandler` function distinguishes between custom errors and unexpected errors:
302+
303+
- **Custom Errors (`CustomError` instances)**: The specific `statusCode` and `message` you define are sent to the client, offering clear and user-friendly feedback for known issues.
304+
- **Other Errors**: A default `statusCode` and message are used to safeguard against information leakage from unexpected errors.
305+
306+
By catching all errors in this way, `nextjs-centralized-error-handler` provides a robust, secure, and unified error-handling solution tailored for Next.js applications, with built-in protections to prevent unintended data exposure.
307+
308+
---
309+
266310
## Examples
267311

268312
Here are a few real-world scenarios that showcase the package’s usage:

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nextjs-centralized-error-handler",
3-
"version": "1.0.1",
3+
"version": "1.0.2",
44
"main": "index.js",
55
"scripts": {
66
"test": "jest",

src/errorHandler.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// src/errorHandler.js
22

3+
const { CustomError } = require('./customErrors'); // Ensure you import CustomError
4+
35
function errorHandler(handler, options = {}) {
46
const {
5-
logger = console.error, // Default to console.error
7+
logger = console.error,
68
defaultStatusCode = 500,
79
defaultMessage = 'An internal server error occurred. Please try again later.',
810
formatError = null,
@@ -12,28 +14,27 @@ function errorHandler(handler, options = {}) {
1214
try {
1315
await handler(req, res);
1416
} catch (error) {
15-
// Log the error
1617
logger('API Route Error:', error);
1718

18-
// Determine status code and message
19-
const statusCode = error.statusCode || defaultStatusCode;
20-
const message =
21-
statusCode === 500 ? defaultMessage : error.message || defaultMessage;
19+
let statusCode = defaultStatusCode;
20+
let message = defaultMessage;
21+
22+
if (error instanceof CustomError) {
23+
statusCode = error.statusCode;
24+
message = error.message || defaultMessage;
25+
}
2226

23-
// Build error response
2427
let errorResponse = {
2528
error: {
2629
message,
2730
type: error.name || 'Error',
2831
},
2932
};
3033

31-
// Allow custom error formatting
3234
if (formatError && typeof formatError === 'function') {
3335
errorResponse = formatError(error, req);
3436
}
3537

36-
// Send response
3738
res.status(statusCode).json(errorResponse);
3839
}
3940
};

tests/errorHandler.test.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// tests/errorHandler.test.js
22

33
const errorHandler = require('../src/errorHandler');
4-
const { BadRequestError } = require('../src/customErrors');
4+
const { BadRequestError, CustomError } = require('../src/customErrors');
55

66
beforeAll(() => {
77
jest.spyOn(console, 'error').mockImplementation(() => {});
@@ -137,3 +137,29 @@ describe('errorHandler with custom formatError', () => {
137137
});
138138
});
139139
});
140+
141+
describe('errorHandler security', () => {
142+
test('should use default status code and message for non-custom errors', async () => {
143+
const req = {};
144+
const res = {
145+
status: jest.fn().mockReturnThis(),
146+
json: jest.fn(),
147+
};
148+
149+
const handler = async () => {
150+
const error = new Error('Sensitive internal error message');
151+
error.statusCode = 400; // Error with statusCode and message
152+
throw error;
153+
};
154+
155+
await errorHandler(handler)(req, res);
156+
157+
expect(res.status).toHaveBeenCalledWith(500); // Should use defaultStatusCode
158+
expect(res.json).toHaveBeenCalledWith({
159+
error: {
160+
message: 'An internal server error occurred. Please try again later.', // Should use defaultMessage
161+
type: 'Error',
162+
},
163+
});
164+
});
165+
});

0 commit comments

Comments
 (0)