Skip to content

Commit

Permalink
Revert receiver refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Barlock committed Mar 13, 2020
1 parent 8faa0b5 commit 2313c46
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 231 deletions.
3 changes: 1 addition & 2 deletions src/App.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { assert } from 'chai';
import { Override, mergeOverrides, createFakeLogger, delay } from './test-helpers';
import rewiremock from 'rewiremock';
import { ErrorCode, UnknownError } from './errors';
import { SayFn, NextMiddleware } from './types';
import { Receiver, ReceiverEvent } from './receiver';
import { Receiver, ReceiverEvent, SayFn, NextMiddleware } from './types';
import { ConversationStore } from './conversation-store';
import { LogLevel } from '@slack/logger';
import App, { ViewConstraints } from './App';
Expand Down
3 changes: 2 additions & 1 deletion src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ import {
BlockAction,
InteractiveMessage,
SlackViewAction,
Receiver,
ReceiverEvent,
RespondArguments,
} from './types';
import { IncomingEventType, getTypeAndConversation, assertNever } from './helpers';
import { Receiver, ReceiverEvent } from './receiver';
import {
CodedError,
asCodedError,
Expand Down
89 changes: 88 additions & 1 deletion src/ExpressReceiver.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { AnyMiddlewareArgs, Receiver, ReceiverEvent } from './types';
import { createServer, Server } from 'http';
import express, { Request, Response, Application, RequestHandler, NextFunction } from 'express';
import rawBody from 'raw-body';
import querystring from 'querystring';
import crypto from 'crypto';
import tsscmp from 'tsscmp';
import App from './App';
import { verifySignatureAndParseBody, Receiver, ReceiverEvent } from './receiver';
import { ReceiverAuthenticityError } from './errors';
import { Logger, ConsoleLogger } from '@slack/logger';

Expand Down Expand Up @@ -195,3 +198,87 @@ function logError(logger: Logger, message: string, error: any): void {
: `${message} (error: ${error})`;
logger.warn(logMessage);
}

function verifyRequestSignature(
signingSecret: string,
body: string,
signature: string | undefined,
requestTimestamp: string | undefined,
): void {
if (signature === undefined || requestTimestamp === undefined) {
throw new ReceiverAuthenticityError(
'Slack request signing verification failed. Some headers are missing.',
);
}

const ts = Number(requestTimestamp);
if (isNaN(ts)) {
throw new ReceiverAuthenticityError(
'Slack request signing verification failed. Timestamp is invalid.',
);
}

// Divide current date to match Slack ts format
// Subtract 5 minutes from current time
const fiveMinutesAgo = Math.floor(Date.now() / 1000) - (60 * 5);

if (ts < fiveMinutesAgo) {
throw new ReceiverAuthenticityError(
'Slack request signing verification failed. Timestamp is too old.',
);
}

const hmac = crypto.createHmac('sha256', signingSecret);
const [version, hash] = signature.split('=');
hmac.update(`${version}:${ts}:${body}`);

if (!tsscmp(hash, hmac.digest('hex'))) {
throw new ReceiverAuthenticityError(
'Slack request signing verification failed. Signature mismatch.',
);
}
}

/**
* This request handler has two responsibilities:
* - Verify the request signature
* - Parse request.body and assign the successfully parsed object to it.
*/
function verifySignatureAndParseBody(
signingSecret: string,
body: string,
headers: Record<string, any>,
): AnyMiddlewareArgs['body'] {
// *** Request verification ***
const {
'x-slack-signature': signature,
'x-slack-request-timestamp': requestTimestamp,
'content-type': contentType,
} = headers;

verifyRequestSignature(
signingSecret,
body,
signature,
requestTimestamp,
);

return parseRequestBody(body, contentType);
}

function parseRequestBody(
stringBody: string,
contentType: string | undefined,
): any {
if (contentType === 'application/x-www-form-urlencoded') {
const parsedBody = querystring.parse(stringBody);

if (typeof parsedBody.payload === 'string') {
return JSON.parse(parsedBody.payload);
}

return parsedBody;
}

return JSON.parse(stringBody);
}
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export {

export * from './errors';
export * from './middleware/builtin';
export * from './receiver';
export * from './types';

export {
Expand Down
123 changes: 0 additions & 123 deletions src/receiver.spec.ts

This file was deleted.

103 changes: 0 additions & 103 deletions src/receiver.ts

This file was deleted.

1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './command';
export * from './events';
export * from './options';
export * from './view';
export * from './receiver';
16 changes: 16 additions & 0 deletions src/types/receiver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import App from '../App';
import { AckFn } from '../types';
import { StringIndexed } from './helpers';

export interface ReceiverEvent {
body: StringIndexed;
// TODO: there should maybe be some more help for implementors of Receiver to know what kind of argument the AckFn
// is expected to deal with.
ack: AckFn<any>;
}

export interface Receiver {
init(app: App): void;
start(...args: any[]): Promise<unknown>;
stop(...args: any[]): Promise<unknown>;
}

0 comments on commit 2313c46

Please sign in to comment.