Skip to content

Commit fb72755

Browse files
feat: capture raw body bytes for v4 hmac calculation
TICKET: CAAS-659
1 parent 8b4a279 commit fb72755

4 files changed

Lines changed: 375 additions & 7 deletions

File tree

modules/express/src/clientRoutes.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,27 @@ async function handleNetworkV1EnterpriseClientConnections(
11921192
return handleProxyReq(req, res, next);
11931193
}
11941194

1195+
/**
1196+
* Helper to send request body, using raw bytes when available.
1197+
* For v4 HMAC authentication, we need to send the exact bytes that were
1198+
* received from the client to ensure the HMAC signature matches.
1199+
*
1200+
* @param request - The superagent request object
1201+
* @param req - The Express request containing body and rawBodyBuffer
1202+
* @returns The request with body attached
1203+
*/
1204+
function sendRequestBody(request: ReturnType<BitGo['post']>, req: express.Request) {
1205+
if (req.rawBodyBuffer) {
1206+
const contentTypeHeader = req.headers['content-type'];
1207+
if (contentTypeHeader) {
1208+
request.set('Content-Type', Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : contentTypeHeader);
1209+
}
1210+
return request.send(req.rawBodyBuffer.toString('utf8'));
1211+
}
1212+
// Fall back to parsed body for backward compatibility
1213+
return request.send(req.body);
1214+
}
1215+
11951216
/**
11961217
* Redirect a request using the bitgo request functions.
11971218
* @param bitgo
@@ -1214,19 +1235,19 @@ export function redirectRequest(
12141235
request = bitgo.get(url);
12151236
break;
12161237
case 'POST':
1217-
request = bitgo.post(url).send(req.body);
1238+
request = sendRequestBody(bitgo.post(url), req);
12181239
break;
12191240
case 'PUT':
1220-
request = bitgo.put(url).send(req.body);
1241+
request = sendRequestBody(bitgo.put(url), req);
12211242
break;
12221243
case 'PATCH':
1223-
request = bitgo.patch(url).send(req.body);
1244+
request = sendRequestBody(bitgo.patch(url), req);
12241245
break;
12251246
case 'OPTIONS':
1226-
request = bitgo.options(url).send(req.body);
1247+
request = sendRequestBody(bitgo.options(url), req);
12271248
break;
12281249
case 'DELETE':
1229-
request = bitgo.del(url).send(req.body);
1250+
request = sendRequestBody(bitgo.del(url), req);
12301251
break;
12311252
}
12321253

@@ -1267,7 +1288,12 @@ function apiResponse(status: number, result: any, message?: string): ApiResponse
12671288
return new ApiResponseError(message, status, result);
12681289
}
12691290

1270-
const expressJSONParser = bodyParser.json({ limit: '20mb' });
1291+
const expressJSONParser = bodyParser.json({
1292+
limit: '20mb',
1293+
verify: (req, res, buf) => {
1294+
(req as express.Request).rawBodyBuffer = buf;
1295+
},
1296+
});
12711297

12721298
/**
12731299
* Perform body parsing here only on routes we want

modules/express/src/expressApp.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,18 @@ export function app(cfg: Config): express.Application {
302302
checkPreconditions(cfg);
303303
debug('preconditions satisfied');
304304

305-
app.use(bodyParser.json({ limit: '20mb' }));
305+
app.use(
306+
bodyParser.json({
307+
limit: '20mb',
308+
verify: (req, res, buf) => {
309+
// Store the raw body buffer on the request object.
310+
// This preserves the exact bytes before JSON parsing,
311+
// which may alter whitespace, key ordering, etc.
312+
// Required for v4 HMAC authentication.
313+
(req as express.Request).rawBodyBuffer = buf;
314+
},
315+
})
316+
);
306317

307318
// Be more robust about accepting URLs with double slashes
308319
app.use(function replaceUrlSlashes(req, res, next) {

0 commit comments

Comments
 (0)