From bf6c35ea50e77efd745126d0ad5c8d51d6fce55c Mon Sep 17 00:00:00 2001 From: Mark Moffat Date: Thu, 7 Nov 2019 20:13:38 +1030 Subject: [PATCH] Properly handle stripe hook with sig verification --- app.js | 10 +++++++++- config/stripe.json | 3 ++- config/stripeSchema.json | 3 +++ package-lock.json | 20 +++++++++++++------- package.json | 2 +- routes/payments/stripe.js | 22 ++++++++++++++++++---- 6 files changed, 46 insertions(+), 14 deletions(-) diff --git a/app.js b/app.js index 11d4d68..d1aa5c5 100644 --- a/app.js +++ b/app.js @@ -272,7 +272,6 @@ app.enable('trust proxy'); app.use(helmet()); app.set('port', process.env.PORT || 1111); app.use(logger('dev')); -app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser(config.secretCookie)); app.use(session({ @@ -287,6 +286,15 @@ app.use(session({ store: store })); +app.use(bodyParser.json({ + // Only on Stripe URL's which need the rawBody + verify: (req, res, buf) => { + if(req.originalUrl === '/stripe/subscription_update'){ + req.rawBody = buf.toString(); + } + } +})); + // Set locales from session app.use(i18n.init); diff --git a/config/stripe.json b/config/stripe.json index 7bb38dc..a3ffd28 100644 --- a/config/stripe.json +++ b/config/stripe.json @@ -3,5 +3,6 @@ "publicKey": "pk_test_this_is_not_real", "stripeCurrency": "usd", "stripeDescription": "expressCart payment", - "stripeLogoURL": "http://localhost:1111/images/stripelogo.png" + "stripeLogoURL": "http://localhost:1111/images/stripelogo.png", + "stripeWebhookSecret": "" } \ No newline at end of file diff --git a/config/stripeSchema.json b/config/stripeSchema.json index 04adff9..32becde 100644 --- a/config/stripeSchema.json +++ b/config/stripeSchema.json @@ -16,6 +16,9 @@ "stripeLogoURL": { "type": "string", "format": "uri-template" + }, + "stripeWebhookSecret": { + "type": "string" } }, "required": [ diff --git a/package-lock.json b/package-lock.json index f95a6ea..98ce640 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7614,7 +7614,8 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true }, "quick-lru": { "version": "1.1.0", @@ -8726,13 +8727,18 @@ "dev": true }, "stripe": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-5.10.0.tgz", - "integrity": "sha512-AUDmXfNAAY/oOfW87HPO4bDzNWJp8iQd0blVWwwEgPxO1DmEC//foI0C9rhr2ZNsuF6kLypPfNtGB9Uf+RCQzQ==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-7.12.0.tgz", + "integrity": "sha512-h/NMB7E+0WgDuEOdfrS9giYmTfQRvOoKHdYaKzo9V0hxilXopVJd3ZZQ47193rAOHjIhmuCDtQRb3gEEm24gKg==", "requires": { - "lodash.isplainobject": "^4.0.6", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1" + "qs": "^6.6.0" + }, + "dependencies": { + "qs": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.0.tgz", + "integrity": "sha512-27RP4UotQORTpmNQDX8BHPukOnBP3p1uUJY5UnDhaJB+rMt9iMsok724XL+UHU23bEFOHRMQ2ZhI99qOWUMGFA==" + } } }, "superagent": { diff --git a/package.json b/package.json index 82d4b09..24007e0 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "sanitize-html": "^1.20.1", "sitemap": "^1.6.0", "strip-bom": "^3.0.0", - "stripe": "^5.10.0", + "stripe": "^7.12.0", "uglifycss": "0.0.27" }, "devDependencies": { diff --git a/routes/payments/stripe.js b/routes/payments/stripe.js index 6299e61..315f618 100644 --- a/routes/payments/stripe.js +++ b/routes/payments/stripe.js @@ -115,13 +115,27 @@ router.post('/checkout_action', (req, res, next) => { // Subscription hook from Stripe router.all('/subscription_update', async (req, res, next) => { const db = req.app.db; + const stripeSigSecret = common.getPaymentConfig().stripeWebhookSecret; + const stripeSig = req.headers['stripe-signature']; - if(!req.body.data.object.customer){ - return res.status(400).json({ message: 'Customer not found' }); + let hook; + if(stripeSigSecret){ + try{ + hook = await stripe.webhooks.constructEvent(req.rawBody, stripeSig, stripeSigSecret); + console.info('Stripe Webhook received'); + }catch(err){ + return res.status(400).send(`Webhook Error: ${err.message}`); + } + + if(!hook.data.object.customer){ + return res.status(400).json({ message: 'Customer not found' }); + } + }else{ + hook = req.body; } const order = await db.orders.findOne({ - orderCustomer: req.body.data.object.customer, + orderCustomer: hook.data.object.customer, orderType: 'Subscription' }); @@ -130,7 +144,7 @@ router.all('/subscription_update', async (req, res, next) => { } let orderStatus = 'Paid'; - if(req.body.type === 'invoice.payment_failed'){ + if(hook.type === 'invoice.payment_failed'){ orderStatus = 'Declined'; }