diff --git a/keyserver/src/responders/farcaster-webhook-responders.js b/keyserver/src/responders/farcaster-webhook-responders.js --- a/keyserver/src/responders/farcaster-webhook-responders.js +++ b/keyserver/src/responders/farcaster-webhook-responders.js @@ -1,20 +1,47 @@ // @flow +import { createHmac } from 'crypto'; import type { $Request } from 'express'; +import { type NeynarWebhookCastCreatedEvent } from 'lib/types/farcaster-types.js'; import { neynarWebhookCastCreatedEventValidator } from 'lib/types/validators/farcaster-webhook-validators.js'; +import { ServerError } from 'lib/utils/errors.js'; import { assertWithValidator } from 'lib/utils/validation-utils.js'; +import { getNeynarConfig } from '../utils/fc-cache.js'; + const taggedCommFarcasterInputValidator = neynarWebhookCastCreatedEventValidator; -async function taggedCommFarcasterResponder(request: $Request): Promise { - const event = assertWithValidator( - request.body, - taggedCommFarcasterInputValidator, - ); +async function verifyNeynarWebhookSignature( + signature: string, + event: NeynarWebhookCastCreatedEvent, +): Promise { + const neynarSecret = await getNeynarConfig(); + if (!neynarSecret?.neynarWebhookSecret) { + throw new ServerError('missing_webhook_secret'); + } + + const hmac = createHmac('sha512', neynarSecret.neynarWebhookSecret); + hmac.update(JSON.stringify(event)); + + return hmac.digest('hex') === signature; +} + +async function taggedCommFarcasterResponder(req: $Request): Promise { + const { body } = req; + + const event = assertWithValidator(body, taggedCommFarcasterInputValidator); + + const signature = req.header('X-Neynar-Signature'); + if (!signature) { + throw new ServerError('missing_neynar_signature'); + } - console.log(event); + const isValidSignature = await verifyNeynarWebhookSignature(signature, event); + if (!isValidSignature) { + throw new ServerError('invalid_webhook_signature'); + } } export { taggedCommFarcasterResponder, taggedCommFarcasterInputValidator };