Skip to content

Commit 9cd0e8e

Browse files
authored
fix(node): Ensure express requests are properly handled (#14850)
Fixes #14847 After some digging, I figured out that the `req.user` handling on express is not working anymore, because apparently express does not mutate the actual http `request` object, but clones/forkes it (??) somehow. So since we now set the request in the SentryHttpInstrumentation generally, it would not pick up express-specific things anymore. IMHO this is not great and we do not want this anymore in v9 anyhow, but it was the behavior before. This PR fixes this by setting the express request again on the isolation scope in an express middleware, which is registered by `Sentry.setupExpressErrorHandler(app)`. Note that we plan to change this behavior here: #14806 but I figured it still makes sense to fix this on develop first, so we have a proper history/tests for this. I will backport this to v8 too. Then, in PR #14806 I will remove the middleware again.
1 parent 6f0251d commit 9cd0e8e

File tree

4 files changed

+123
-5
lines changed

4 files changed

+123
-5
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
const { loggingTransport } = require('@sentry-internal/node-integration-tests');
2+
const Sentry = require('@sentry/node');
3+
4+
Sentry.init({
5+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
6+
release: '1.0',
7+
transport: loggingTransport,
8+
debug: true,
9+
});
10+
11+
// express must be required after Sentry is initialized
12+
const express = require('express');
13+
const cors = require('cors');
14+
const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests');
15+
16+
const app = express();
17+
18+
app.use(cors());
19+
20+
app.use((req, _res, next) => {
21+
// We simulate this, which would in other cases be done by some middleware
22+
req.user = {
23+
id: '1',
24+
email: 'test@sentry.io',
25+
};
26+
27+
next();
28+
});
29+
30+
app.get('/test1', (_req, _res) => {
31+
throw new Error('error_1');
32+
});
33+
34+
app.use((_req, _res, next) => {
35+
Sentry.setUser({
36+
id: '2',
37+
email: 'test2@sentry.io',
38+
});
39+
40+
next();
41+
});
42+
43+
app.get('/test2', (_req, _res) => {
44+
throw new Error('error_2');
45+
});
46+
47+
Sentry.setupExpressErrorHandler(app);
48+
49+
startExpressServerAndSendPortToRunner(app);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';
2+
3+
describe('express user handling', () => {
4+
afterAll(() => {
5+
cleanupChildProcesses();
6+
});
7+
8+
test('picks user from request', done => {
9+
createRunner(__dirname, 'server.js')
10+
.expect({
11+
event: {
12+
user: {
13+
id: '1',
14+
email: 'test@sentry.io',
15+
},
16+
exception: {
17+
values: [
18+
{
19+
value: 'error_1',
20+
},
21+
],
22+
},
23+
},
24+
})
25+
.start(done)
26+
.makeRequest('get', '/test1', { expectError: true });
27+
});
28+
29+
test('setUser overwrites user from request', done => {
30+
createRunner(__dirname, 'server.js')
31+
.expect({
32+
event: {
33+
user: {
34+
id: '2',
35+
email: 'test2@sentry.io',
36+
},
37+
exception: {
38+
values: [
39+
{
40+
value: 'error_2',
41+
},
42+
],
43+
},
44+
},
45+
})
46+
.start(done)
47+
.makeRequest('get', '/test2', { expectError: true });
48+
});
49+
});

packages/core/src/utils-hoist/requestdata.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,8 +295,8 @@ export function addNormalizedRequestDataToEvent(
295295

296296
if (Object.keys(extractedUser).length) {
297297
event.user = {
298-
...event.user,
299298
...extractedUser,
299+
...event.user,
300300
};
301301
}
302302
}

packages/node/src/integrations/tracing/express.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ interface MiddlewareError extends Error {
9191
};
9292
}
9393

94-
type ExpressMiddleware = (
94+
type ExpressMiddleware = (req: http.IncomingMessage, res: http.ServerResponse, next: () => void) => void;
95+
96+
type ExpressErrorMiddleware = (
9597
error: MiddlewareError,
9698
req: http.IncomingMessage,
9799
res: http.ServerResponse,
@@ -109,13 +111,17 @@ interface ExpressHandlerOptions {
109111
/**
110112
* An Express-compatible error handler.
111113
*/
112-
export function expressErrorHandler(options?: ExpressHandlerOptions): ExpressMiddleware {
114+
export function expressErrorHandler(options?: ExpressHandlerOptions): ExpressErrorMiddleware {
113115
return function sentryErrorMiddleware(
114116
error: MiddlewareError,
115-
_req: http.IncomingMessage,
117+
request: http.IncomingMessage,
116118
res: http.ServerResponse,
117119
next: (error: MiddlewareError) => void,
118120
): void {
121+
// Ensure we use the express-enhanced request here, instead of the plain HTTP one
122+
// When an error happens, the `expressRequestHandler` middleware does not run, so we set it here too
123+
getIsolationScope().setSDKProcessingMetadata({ request });
124+
119125
const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError;
120126

121127
if (shouldHandleError(error)) {
@@ -127,6 +133,19 @@ export function expressErrorHandler(options?: ExpressHandlerOptions): ExpressMid
127133
};
128134
}
129135

136+
function expressRequestHandler(): ExpressMiddleware {
137+
return function sentryRequestMiddleware(
138+
request: http.IncomingMessage,
139+
_res: http.ServerResponse,
140+
next: () => void,
141+
): void {
142+
// Ensure we use the express-enhanced request here, instead of the plain HTTP one
143+
getIsolationScope().setSDKProcessingMetadata({ request });
144+
145+
next();
146+
};
147+
}
148+
130149
/**
131150
* Add an Express error handler to capture errors to Sentry.
132151
*
@@ -152,9 +171,10 @@ export function expressErrorHandler(options?: ExpressHandlerOptions): ExpressMid
152171
* ```
153172
*/
154173
export function setupExpressErrorHandler(
155-
app: { use: (middleware: ExpressMiddleware) => unknown },
174+
app: { use: (middleware: ExpressMiddleware | ExpressErrorMiddleware) => unknown },
156175
options?: ExpressHandlerOptions,
157176
): void {
177+
app.use(expressRequestHandler());
158178
app.use(expressErrorHandler(options));
159179
ensureIsWrapped(app.use, 'express');
160180
}

0 commit comments

Comments
 (0)