diff --git a/packages/webhook/src/error.ts b/packages/webhook/src/error.ts new file mode 100644 index 00000000..891ee417 --- /dev/null +++ b/packages/webhook/src/error.ts @@ -0,0 +1,5 @@ +export class InvalidSignatureError extends Error { + constructor() { + super('Invalid Signature') + } +} diff --git a/packages/webhook/src/handler.ts b/packages/webhook/src/handler.ts index 01e8c390..f506a55e 100644 --- a/packages/webhook/src/handler.ts +++ b/packages/webhook/src/handler.ts @@ -5,13 +5,15 @@ import type { IncomingMessage, ServerResponse } from 'http' import type { BusinessEvents } from './event.enum' import type { ExtendedEventEmitter } from './types' +import { InvalidSignatureError } from './error' + interface CreateHandlerOptions { secret: string events?: 'all' | BusinessEvents[] } - +export type RequestWithJSONBody = IncomingMessage & Request & { body: object } type Handler = { - (req: IncomingMessage, res: ServerResponse): void + (req: RequestWithJSONBody, res: ServerResponse): void } & { emitter: ExtendedEventEmitter } @@ -21,48 +23,34 @@ export const createHandler = (options: CreateHandlerOptions): Handler => { const handler: Handler = async function (req, res) { try { - const signature = req.headers['x-webhook-signature'] - assert( - typeof signature === 'string', - 'X-Webhook-Signature must be string', - ) - const event = req.headers['x-webhook-event'] - const signature256 = req.headers['x-webhook-signature256'] - assert( - typeof signature256 === 'string', - 'X-Webhook-Signature256 must be string', - ) + const data = await readDataFromRequest({ req, secret }) - const obj = (req as any).body || (await parseJSONFromRequest(req)) - const stringifyPayload = JSON.stringify(obj) - const isValid = - verifyWebhook(secret, stringifyPayload, signature256 as string) && - verifyWebhookSha1(secret, stringifyPayload, signature as string) - if (isValid) { - if (event === 'health_check') { - res.statusCode = 200 - res.setHeader('Content-Type', 'application/json') - res.end(JSON.stringify({ ok: 1 })) - return - } + const { event, payload } = data - handler.emitter.emit(event as BusinessEvents, obj) - handler.emitter.emit('*', { - type: event, - payload: obj, - }) + if (event === 'health_check') { res.statusCode = 200 res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify({ ok: 1 })) - } else { - console.error('revice a invalidate webhook payload', req.headers) + return + } + + handler.emitter.emit(event as BusinessEvents, payload) + handler.emitter.emit('*', { + type: event, + payload, + }) + res.statusCode = 200 + res.setHeader('Content-Type', 'application/json') + res.end(JSON.stringify({ ok: 1 })) + } catch (err) { + if (err instanceof InvalidSignatureError) { handler.emitter.emit('error', new Error('invalidate signature')) res.statusCode = 400 res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify({ ok: 0, message: 'Invalid Signature' })) + return } - } catch (err) { res.statusCode = 500 res.end(JSON.stringify({ ok: 0, message: err.message })) } @@ -72,20 +60,37 @@ export const createHandler = (options: CreateHandlerOptions): Handler => { return handler } -function parseJSONFromRequest(req: IncomingMessage) { - return new Promise((resolve, reject) => { - let body = '' - req.on('data', (chunk) => { - body += chunk - }) - req.on('end', () => { - try { - resolve(JSON.parse(body)) - } catch (err) { - reject(err) - } - }) - }) +export const readDataFromRequest = async ({ + req, + secret, +}: { + secret: string + req: RequestWithJSONBody +}) => { + const signature = req.headers['x-webhook-signature'] + assert(typeof signature === 'string', 'X-Webhook-Signature must be string') + const event = req.headers['x-webhook-event'] + const signature256 = req.headers['x-webhook-signature256'] + assert( + typeof signature256 === 'string', + 'X-Webhook-Signature256 must be string', + ) + + const obj = req.body + const stringifyPayload = JSON.stringify(obj) + const isValid = + verifyWebhook(secret, stringifyPayload, signature256 as string) && + verifyWebhookSha1(secret, stringifyPayload, signature as string) + + if (isValid) { + return { + event, + payload: obj, + } + } else { + console.error('revice a invalidate webhook payload', req.headers) + throw new InvalidSignatureError() + } } export function verifyWebhook( diff --git a/packages/webhook/src/index.ts b/packages/webhook/src/index.ts index cd2bde25..eab4b90f 100644 --- a/packages/webhook/src/index.ts +++ b/packages/webhook/src/index.ts @@ -1,3 +1,4 @@ export * from './event.enum' export * from './handler' export * from './types' +export * from './error'