-
Notifications
You must be signed in to change notification settings - Fork 390
/
verify-request.ts
106 lines (93 loc) · 3.73 KB
/
verify-request.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// Deprecated: this function will be removed in the near future. Use HTTPModuleFunctions instead.
import { createHmac } from 'crypto';
import type { IncomingMessage, ServerResponse } from 'http';
import { ConsoleLogger, Logger } from '@slack/logger';
import tsscmp from 'tsscmp';
import { BufferedIncomingMessage } from './BufferedIncomingMessage';
import { HTTPModuleFunctions, RequestVerificationOptions } from './HTTPModuleFunctions';
// ------------------------------
// HTTP module independent methods
// ------------------------------
const verifyErrorPrefix = 'Failed to verify authenticity';
export interface SlackRequestVerificationOptions {
signingSecret: string;
body: string;
headers: {
'x-slack-signature': string,
'x-slack-request-timestamp': number,
};
nowMilliseconds?: number;
logger?: Logger;
}
/**
* Verifies the signature of an incoming request from Slack.
* If the request is invalid, this method throws an exception with the error details.
*/
export function verifySlackRequest(options: SlackRequestVerificationOptions): void {
const requestTimestampSec = options.headers['x-slack-request-timestamp'];
const signature = options.headers['x-slack-signature'];
if (Number.isNaN(requestTimestampSec)) {
throw new Error(
`${verifyErrorPrefix}: header x-slack-request-timestamp did not have the expected type (${requestTimestampSec})`,
);
}
// Calculate time-dependent values
const nowMs = options.nowMilliseconds ?? Date.now();
const requestTimestampMaxDeltaMin = 5;
const fiveMinutesAgoSec = Math.floor(nowMs / 1000) - 60 * requestTimestampMaxDeltaMin;
// Enforce verification rules
// Rule 1: Check staleness
if (requestTimestampSec < fiveMinutesAgoSec) {
throw new Error(`${verifyErrorPrefix}: x-slack-request-timestamp must differ from system time by no more than ${requestTimestampMaxDeltaMin
} minutes or request is stale`);
}
// Rule 2: Check signature
// Separate parts of signature
const [signatureVersion, signatureHash] = signature.split('=');
// Only handle known versions
if (signatureVersion !== 'v0') {
throw new Error(`${verifyErrorPrefix}: unknown signature version`);
}
// Compute our own signature hash
const hmac = createHmac('sha256', options.signingSecret);
hmac.update(`${signatureVersion}:${requestTimestampSec}:${options.body}`);
const ourSignatureHash = hmac.digest('hex');
if (!signatureHash || !tsscmp(signatureHash, ourSignatureHash)) {
throw new Error(`${verifyErrorPrefix}: signature mismatch`);
}
}
/**
* Verifies the signature of an incoming request from Slack.
* If the request is invalid, this method returns false.
*/
export function isValidSlackRequest(options: SlackRequestVerificationOptions): boolean {
try {
verifySlackRequest(options);
return true;
} catch (e) {
if (options.logger) {
options.logger.debug(`Signature verification error: ${e}`);
}
}
return false;
}
// ------------------------------
// legacy methods (deprecated)
// ------------------------------
const consoleLogger = new ConsoleLogger();
// Deprecated: this function will be removed in the near future. Use HTTPModuleFunctions instead.
export interface VerifyOptions extends RequestVerificationOptions {
enabled?: boolean;
signingSecret: string;
nowMs?: () => number;
logger?: Logger;
}
// Deprecated: this function will be removed in the near future. Use HTTPModuleFunctions instead.
export async function verify(
options: VerifyOptions,
req: IncomingMessage,
res?: ServerResponse,
): Promise<BufferedIncomingMessage> {
consoleLogger.warn('This method is deprecated. Use HTTPModuleFunctions.parseAndVerifyHTTPRequest(options, req, res) instead.');
return HTTPModuleFunctions.parseAndVerifyHTTPRequest(options, req, res);
}