diff --git a/app/server.js b/app/server.js index e7dc6c4..1a1f5a8 100644 --- a/app/server.js +++ b/app/server.js @@ -14,21 +14,32 @@ const server = Fastify({ // Response to Webhooks server.post("/webhook", async (req, reply) => { - // Get site ID from webhook payload - const { site } = req.body; + // Get signature and timestamp headers for validation + const request_signature = req.headers["x-webflow-signature"]; + const request_timestamp = req.headers["x-webflow-timestamp"]; - // Get the site's access token - const token = await app.data.get(site); + // Validate the request signature to ensure this request came from Webflow + if (app.validateRequestSignature(request_signature, request_timestamp, req.body, CLIENT_SECRET)){ + // Get site ID from webhook payload + const { site } = req.body; - // Initialize a new Webflow client - const webflow = new Webflow({ token }); + // Get the site's access token + const token = await app.data.get(site); - // Make calls to the Webflow API - const user = await webflow.get("/user"); - // Do other stuff with the API... + // Initialize a new Webflow client + const webflow = new Webflow({ token }); - // Return a 200 response to Webflow - reply.statusCode = 200; + // Make calls to the Webflow API + const user = await webflow.get("/user"); + + // Do other stuff with the API... + + // Return a 200 response to Webflow + reply.statusCode = 200; + } else { + // Return a 403 response to Webflow if the request doesn't pass validation + reply.statusCode = 403; + } }); // Install the App diff --git a/app/webflow.js b/app/webflow.js index 8e8e11e..a4a6006 100644 --- a/app/webflow.js +++ b/app/webflow.js @@ -1,5 +1,6 @@ -import { AuthorizationCode } from "simple-oauth2"; import Client from "webflow-api"; +import crypto from "crypto"; +import { AuthorizationCode } from "simple-oauth2"; import { Level } from "level"; class App { @@ -26,6 +27,30 @@ class App { authorizeHost: "https://webflow.com", }, }); + + // Webhook Request Validation + this.validateRequestSignature = function(signature, timestamp, body_json, consumer_secret){ + // Return false if timestamp is more than 5 minutes old + if (((Date.now() - Number(timestamp)) / 60000) > 5){ + return false + }; + + // Concatinate the request timestamp header and request body + const content = Number(timestamp) + ":" + JSON.stringify(body_json); + + // Generate an HMAC signature from the timestamp and body + const hmac = crypto + .createHmac('sha256', consumer_secret) + .update(content) + .digest('hex'); + + // Create a Buffers from the generated signature and signature header + const hmac_buffer = Buffer.from(hmac); + const signature_buffer = Buffer.from(signature); + + // Compare generated signature with signature header checksum + return crypto.timingSafeEqual(hmac_buffer, signature_buffer); + } } /**