diff --git a/src/receivers/AwsLambdaReceiver.spec.ts b/src/receivers/AwsLambdaReceiver.spec.ts index 813b32758..b6035147a 100644 --- a/src/receivers/AwsLambdaReceiver.spec.ts +++ b/src/receivers/AwsLambdaReceiver.spec.ts @@ -542,6 +542,42 @@ describe('AwsLambdaReceiver', function () { ); assert.equal(response.statusCode, 401); }); + + it('does not perform signature verification if signature verification flag is set to false', async () => { + const awsReceiver = new AwsLambdaReceiver({ + signingSecret: '', + signatureVerification: false, + logger: noopLogger, + }); + const handler = awsReceiver.toHandler(); + const awsEvent = { + resource: '/slack/events', + path: '/slack/events', + httpMethod: 'POST', + headers: { + Accept: 'application/json,*/*', + 'Content-Type': 'application/json', + Host: 'xxx.execute-api.ap-northeast-1.amazonaws.com', + 'User-Agent': 'Slackbot 1.0 (+https://api.slack.com/robots)', + 'X-Slack-Request-Timestamp': 'far back dude', + 'X-Slack-Signature': 'very much invalid', + }, + multiValueHeaders: {}, + queryStringParameters: null, + multiValueQueryStringParameters: null, + pathParameters: null, + stageVariables: null, + requestContext: {}, + body: urlVerificationBody, + isBase64Encoded: false, + }; + const response = await handler( + awsEvent, + {}, + (_error, _result) => {}, + ); + assert.equal(response.statusCode, 200); + }); }); // Composable overrides diff --git a/src/receivers/AwsLambdaReceiver.ts b/src/receivers/AwsLambdaReceiver.ts index 717201a34..5fe9a6d85 100644 --- a/src/receivers/AwsLambdaReceiver.ts +++ b/src/receivers/AwsLambdaReceiver.ts @@ -40,9 +40,41 @@ export interface AwsResponse { export type AwsHandler = (event: AwsEvent, context: any, callback: AwsCallback) => Promise; export interface AwsLambdaReceiverOptions { + /** + * The Slack Signing secret to be used as an input to signature verification to ensure that requests are coming from + * Slack. + * + * If the {@link signatureVerification} flag is set to `false`, this can be set to any value as signature verification + * using this secret will not be performed. + * + * @see {@link https://api.slack.com/authentication/verifying-requests-from-slack#about} for details about signing secrets + */ signingSecret: string; + /** + * The {@link Logger} for the receiver + * + * @default ConsoleLogger + */ logger?: Logger; + /** + * The {@link LogLevel} to be used for the logger. + * + * @default LogLevel.INFO + */ logLevel?: LogLevel; + /** + * Flag that determines whether Bolt should {@link https://api.slack.com/authentication/verifying-requests-from-slack|verify Slack's signature on incoming requests}. + * + * @default true + */ + signatureVerification?: boolean; + /** + * Optional `function` that can extract custom properties from an incoming receiver event + * @param request The API Gateway event {@link AwsEvent} + * @returns An object containing custom properties + * + * @default noop + */ customPropertiesExtractor?: (request: AwsEvent) => StringIndexed; } @@ -59,16 +91,20 @@ export default class AwsLambdaReceiver implements Receiver { private logger: Logger; + private signatureVerification: boolean; + private customPropertiesExtractor: (request: AwsEvent) => StringIndexed; public constructor({ signingSecret, logger = undefined, logLevel = LogLevel.INFO, + signatureVerification = true, customPropertiesExtractor = (_) => ({}), }: AwsLambdaReceiverOptions) { // Initialize instance variables, substituting defaults for each value this.signingSecret = signingSecret; + this.signatureVerification = signatureVerification; this.logger = logger ?? (() => { const defaultLogger = new ConsoleLogger(); @@ -130,12 +166,14 @@ export default class AwsLambdaReceiver implements Receiver { return Promise.resolve({ statusCode: 200, body: '' }); } - // request signature verification - const signature = this.getHeaderValue(awsEvent.headers, 'X-Slack-Signature') as string; - const ts = Number(this.getHeaderValue(awsEvent.headers, 'X-Slack-Request-Timestamp')); - if (!this.isValidRequestSignature(this.signingSecret, rawBody, signature, ts)) { - this.logger.info(`Invalid request signature detected (X-Slack-Signature: ${signature}, X-Slack-Request-Timestamp: ${ts})`); - return Promise.resolve({ statusCode: 401, body: '' }); + if (this.signatureVerification) { + // request signature verification + const signature = this.getHeaderValue(awsEvent.headers, 'X-Slack-Signature') as string; + const ts = Number(this.getHeaderValue(awsEvent.headers, 'X-Slack-Request-Timestamp')); + if (!this.isValidRequestSignature(this.signingSecret, rawBody, signature, ts)) { + this.logger.info(`Invalid request signature detected (X-Slack-Signature: ${signature}, X-Slack-Request-Timestamp: ${ts})`); + return Promise.resolve({ statusCode: 401, body: '' }); + } } // url_verification (Events API)