Skip to content

Commit 95ac0af

Browse files
committed
fix(api): add cors headers to error responses
The errorHandler middleware was not adding CORS headers to the error responses it generated. This caused browsers to block client-side applications from reading the response body due to the Same-Origin Policy, resulting in generic network errors instead of specific API error messages. This change refactors the errorHandler to use a helper function that constructs the JSON error response while also adding the necessary `Access-Control-Allow-Origin` header. This ensures that all error responses are readable by the client, fixing the bug.
1 parent 46bba64 commit 95ac0af

File tree

1 file changed

+50
-28
lines changed

1 file changed

+50
-28
lines changed

lib/src/middlewares/error_handler.dart

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,54 +19,38 @@ Middleware errorHandler() {
1919
} on HtHttpException catch (e, stackTrace) {
2020
// Handle specific HtHttpExceptions from the client/repository layers
2121
final statusCode = _mapExceptionToStatusCode(e);
22-
final errorCode = _mapExceptionToCodeString(e);
2322
print('HtHttpException Caught: $e\n$stackTrace'); // Log for debugging
24-
return Response.json(
23+
return _jsonErrorResponse(
2524
statusCode: statusCode,
26-
body: {
27-
'error': {'code': errorCode, 'message': e.message},
28-
},
25+
exception: e,
26+
context: context,
2927
);
3028
} on CheckedFromJsonException catch (e, stackTrace) {
3129
// Handle json_serializable validation errors. These are client errors.
3230
final field = e.key ?? 'unknown';
3331
final message = 'Invalid request body: Field "$field" has an '
3432
'invalid value or is missing. ${e.message}';
3533
print('CheckedFromJsonException Caught: $e\n$stackTrace');
36-
return Response.json(
34+
return _jsonErrorResponse(
3735
statusCode: HttpStatus.badRequest, // 400
38-
body: {
39-
'error': {
40-
'code': 'invalidField',
41-
'message': message,
42-
},
43-
},
36+
exception: InvalidInputException(message),
37+
context: context,
4438
);
4539
} on FormatException catch (e, stackTrace) {
4640
// Handle data format/parsing errors (often indicates bad client input)
4741
print('FormatException Caught: $e\n$stackTrace'); // Log for debugging
48-
return Response.json(
42+
return _jsonErrorResponse(
4943
statusCode: HttpStatus.badRequest, // 400
50-
body: {
51-
'error': {
52-
'code': 'invalidFormat',
53-
'message': 'Invalid data format: ${e.message}',
54-
},
55-
},
44+
exception: InvalidInputException('Invalid data format: ${e.message}'),
45+
context: context,
5646
);
5747
} catch (e, stackTrace) {
5848
// Handle any other unexpected errors
5949
print('Unhandled Exception Caught: $e\n$stackTrace');
60-
return Response.json(
50+
return _jsonErrorResponse(
6151
statusCode: HttpStatus.internalServerError, // 500
62-
body: {
63-
'error': {
64-
'code': 'internalServerError',
65-
'message': 'An unexpected internal server error occurred.',
66-
// Avoid leaking sensitive details in production responses
67-
// 'details': e.toString(), // Maybe include in dev mode only
68-
},
69-
},
52+
exception: const UnknownException('An unexpected internal server error occurred.'),
53+
context: context,
7054
);
7155
}
7256
};
@@ -108,3 +92,41 @@ String _mapExceptionToCodeString(HtHttpException exception) {
10892
_ => 'unknownError', // Default
10993
};
11094
}
95+
96+
/// Creates a standardized JSON error response with appropriate CORS headers.
97+
///
98+
/// This helper ensures that error responses sent to the client include the
99+
/// necessary `Access-Control-Allow-Origin` header, allowing the client-side
100+
/// application to read the error message body.
101+
Response _jsonErrorResponse({
102+
required int statusCode,
103+
required HtHttpException exception,
104+
required RequestContext context,
105+
}) {
106+
final errorCode = _mapExceptionToCodeString(exception);
107+
final headers = <String, String>{
108+
HttpHeaders.contentTypeHeader: 'application/json',
109+
};
110+
111+
// Add CORS headers to error responses to allow the client to read them.
112+
// This logic mirrors the behavior of `shelf_cors_headers` for development.
113+
final origin = context.request.headers['Origin'];
114+
if (origin != null) {
115+
// A simple check for localhost development environments.
116+
// For production, this should be a more robust check against a list
117+
// of allowed origins from environment variables.
118+
if (Uri.tryParse(origin)?.host == 'localhost') {
119+
headers[HttpHeaders.accessControlAllowOriginHeader] = origin;
120+
headers[HttpHeaders.accessControlAllowMethodsHeader] =
121+
'GET, POST, PUT, DELETE, OPTIONS';
122+
headers[HttpHeaders.accessControlAllowHeadersHeader] =
123+
'Origin, Content-Type, Authorization';
124+
}
125+
}
126+
127+
return Response.json(
128+
statusCode: statusCode,
129+
body: {'error': {'code': errorCode, 'message': exception.message}},
130+
headers: headers,
131+
);
132+
}

0 commit comments

Comments
 (0)