Skip to content

Commit 3ba7a2b

Browse files
committed
feat(HTTPReceiver): add invalidRequestSignatureHandler callback
Adds an optional invalidRequestSignatureHandler to HTTPReceiver, matching the callback added to AwsLambdaReceiver in PR #2154. When signature verification fails, the handler fires with the raw body, signature header, and timestamp. Defaults to a noop. Refs #2156
1 parent a8b7880 commit 3ba7a2b

1 file changed

Lines changed: 23 additions & 0 deletions

File tree

src/receivers/HTTPReceiver.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ import type { ParamsIncomingMessage } from './ParamsIncomingMessage';
3434
import { type CustomRoute, type ReceiverRoutes, buildReceiverRoutes } from './custom-routes';
3535
import { verifyRedirectOpts } from './verify-redirect-opts';
3636

37+
export interface HTTPReceiverInvalidRequestSignatureHandlerArgs {
38+
rawBody: string;
39+
signature: string | undefined;
40+
ts: number | undefined;
41+
}
42+
3743
// Option keys for tls.createServer() and tls.createSecureContext(), exclusive of those for http.createServer()
3844
const httpsOptionKeys = [
3945
'ALPNProtocols',
@@ -81,6 +87,7 @@ export interface HTTPReceiverOptions {
8187
logLevel?: LogLevel;
8288
processBeforeResponse?: boolean;
8389
signatureVerification?: boolean;
90+
invalidRequestSignatureHandler?: (args: HTTPReceiverInvalidRequestSignatureHandlerArgs) => void;
8491
clientId?: string;
8592
clientSecret?: string;
8693
stateSecret?: InstallProviderOptions['stateSecret']; // required when using default stateStore
@@ -137,6 +144,8 @@ export default class HTTPReceiver implements Receiver {
137144

138145
private signatureVerification: boolean;
139146

147+
private invalidRequestSignatureHandler: (args: HTTPReceiverInvalidRequestSignatureHandlerArgs) => void;
148+
140149
private app?: App;
141150

142151
public requestListener: RequestListener;
@@ -178,6 +187,7 @@ export default class HTTPReceiver implements Receiver {
178187
logLevel = LogLevel.INFO,
179188
processBeforeResponse = false,
180189
signatureVerification = true,
190+
invalidRequestSignatureHandler,
181191
clientId = undefined,
182192
clientSecret = undefined,
183193
stateSecret = undefined,
@@ -195,6 +205,8 @@ export default class HTTPReceiver implements Receiver {
195205
this.signingSecret = signingSecret;
196206
this.processBeforeResponse = processBeforeResponse;
197207
this.signatureVerification = signatureVerification;
208+
this.invalidRequestSignatureHandler =
209+
invalidRequestSignatureHandler ?? this.defaultInvalidRequestSignatureHandler.bind(this);
198210
this.logger =
199211
logger ??
200212
(() => {
@@ -448,6 +460,13 @@ export default class HTTPReceiver implements Receiver {
448460
const e = err as Error;
449461
if (this.signatureVerification) {
450462
this.logger.warn(`Failed to parse and verify the request data: ${e.message}`);
463+
const requestWithRawBody = req as IncomingMessage & { rawBody?: string };
464+
const rawBody = typeof requestWithRawBody.rawBody === 'string' ? requestWithRawBody.rawBody : '';
465+
this.invalidRequestSignatureHandler({
466+
rawBody,
467+
signature: req.headers['x-slack-signature'] as string | undefined,
468+
ts: req.headers['x-slack-request-timestamp'] ? Number(req.headers['x-slack-request-timestamp']) : undefined,
469+
});
451470
} else {
452471
this.logger.warn(`Failed to parse the request body: ${e.message}`);
453472
}
@@ -565,4 +584,8 @@ export default class HTTPReceiver implements Receiver {
565584
installer.handleCallback(req, res, installCallbackOptions).catch(errorHandler);
566585
}
567586
}
587+
588+
private defaultInvalidRequestSignatureHandler(_args: HTTPReceiverInvalidRequestSignatureHandlerArgs): void {
589+
// noop - signature verification failure is already logged and a 401 is returned
590+
}
568591
}

0 commit comments

Comments
 (0)