From f4de7a37ed504561c31a834080f58e65a00118aa Mon Sep 17 00:00:00 2001 From: "Marc S. Brooks" Date: Thu, 25 Apr 2024 13:43:17 -0700 Subject: [PATCH] Refactor, added Error exception assert Installed chai-as-promised (again) --- package-lock.json | 22 +++-- package.json | 1 + plugins/GoogleRecaptchaHandler/src/plugin.js | 96 ++++++++++---------- plugins/GoogleRecaptchaHandler/test/unit.js | 72 +++++++++++++-- 4 files changed, 125 insertions(+), 66 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5157630..ae502f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,9 @@ "": { "name": "middleware", "license": "MIT", + "dependencies": { + "chai-as-promised": "^7.1.1" + }, "devDependencies": { "@lambda-lambda-lambda/router": "latest", "@types/aws-lambda": "^8.10.111", @@ -838,7 +841,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, "engines": { "node": "*" } @@ -987,7 +989,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", - "dev": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", @@ -1001,6 +1002,17 @@ "node": ">=4" } }, + "node_modules/chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dependencies": { + "check-error": "^1.0.2" + }, + "peerDependencies": { + "chai": ">= 2.1.2 < 5" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1021,7 +1033,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, "dependencies": { "get-func-name": "^2.0.2" }, @@ -1174,7 +1185,6 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, "dependencies": { "type-detect": "^4.0.0" }, @@ -1660,7 +1670,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, "engines": { "node": "*" } @@ -2215,7 +2224,6 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "dev": true, "dependencies": { "get-func-name": "^2.0.1" } @@ -2773,7 +2781,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, "engines": { "node": "*" } @@ -3324,7 +3331,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, "engines": { "node": ">=4" } diff --git a/package.json b/package.json index 1ee9a83..953c762 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@lambda-lambda-lambda/router": "latest", "@types/aws-lambda": "^8.10.111", "chai": "^4.3.7", + "chai-as-promised": "^7.1.1", "clone": "^2.1.2", "cookie": "^0.5.0", "eslint": "^7.32.0", diff --git a/plugins/GoogleRecaptchaHandler/src/plugin.js b/plugins/GoogleRecaptchaHandler/src/plugin.js index af9f603..ec99f5c 100644 --- a/plugins/GoogleRecaptchaHandler/src/plugin.js +++ b/plugins/GoogleRecaptchaHandler/src/plugin.js @@ -2,60 +2,56 @@ const https = require('https'); +const {RouterError} = require('@lambda-lambda-lambda/router/src/router/Error'); + /** * Middleware to validate Google ReCAPTCHA invisible responses. * * @requires AppConfigPlugin */ module.exports = async (req, res, next) => { - return new Promise(function(resolve) { - const {google} = req.plugin('config'); - - const secretKey = google?.reCaptcha?.secretKey; - - if (!secretKey) { - /* istanbul ignore next */ - throw new Error('Missing Google API secret key'); - } - - if (req.method() === 'POST') { - const captchaResponse = req.param('g-recaptcha-response'); - - const options = { - hostname: 'www.google.com', - port: 443, - path: '/recaptcha/api/siteverify', - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - } - }; - - const httpRequest = https.request(options, hr => { - - /* istanbul ignore next */ - if (hr.statusCode === 200) { - hr.on('data', buffer => { - const {success} = JSON.parse(buffer.toString()); - - if (success !== true) { - - // Return error response. - res.status(400).send('Bad Request'); - } - - resolve(); - }); - } else { - console.warn(`Google API error: ${hr.statusMessage}`); - - // Return error response. - res.status(500).send('Internal Server Error'); - } - }); - - httpRequest.write(`secret=${secretKey}&response=${captchaResponse}`); - httpRequest.end(); - } - }); + const {google} = req.plugin('config'); + + const secretKey = google?.reCaptcha?.secretKey; + + if (!secretKey) { + throw new RouterError('Missing Google API secret key'); + } + + /* istanbul ignore next */ + if (req.method() === 'POST') { + const captchaResponse = req.param('g-recaptcha-response'); + + const options = { + hostname: 'www.google.com', + port: 443, + path: '/recaptcha/api/siteverify', + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + }; + + const httpRequest = https.request(options, hr => { + if (hr.statusCode === 200) { + hr.on('data', buffer => { + const {success} = JSON.parse(buffer.toString()); + + if (success !== true) { + + // Return error response. + res.status(400).send('Bad Request'); + } + }); + } else { + console.warn(`Google API error: ${hr.statusMessage}`); + + // Return error response. + res.status(500).send('Internal Server Error'); + } + }); + + httpRequest.write(`secret=${secretKey}&response=${captchaResponse}`); + httpRequest.end(); + } }; diff --git a/plugins/GoogleRecaptchaHandler/test/unit.js b/plugins/GoogleRecaptchaHandler/test/unit.js index bc17c68..3c2a26e 100644 --- a/plugins/GoogleRecaptchaHandler/test/unit.js +++ b/plugins/GoogleRecaptchaHandler/test/unit.js @@ -1,16 +1,26 @@ 'use strict'; -const event = require(`${PACKAGE_ROOT}/event.json`); -const chai = require('chai'); +const event = require(`${PACKAGE_ROOT}/event.json`); +const chai = require('chai'); +const chaiAsPromised = require('chai-as-promised'); +const sinon = require('sinon'); +const https = require('https'); + +chai.use(chaiAsPromised); const expect = chai.expect; // Load modules. -const Request = require('@lambda-lambda-lambda/router/src/router/Request.js'); -const Response = require('@lambda-lambda-lambda/router/src/router/Response.js'); -const Stack = require('@lambda-lambda-lambda/router/src/router/Stack.js'); -const Utils = require('@lambda-lambda-lambda/router/src/router/Utils.js'); -const middleware = require(PLUGIN_ROOT); +const {RouterError} = require('@lambda-lambda-lambda/router/src/router/Error'); +const Request = require('@lambda-lambda-lambda/router/src/router/Request.js'); +const Response = require('@lambda-lambda-lambda/router/src/router/Response.js'); +const Stack = require('@lambda-lambda-lambda/router/src/router/Stack.js'); +const Utils = require('@lambda-lambda-lambda/router/src/router/Utils.js'); +const middleware = require(PLUGIN_ROOT); + +afterEach(() => { + sinon.restore(); +}); describe('GoogleReCaptchaHandler', function() { describe('success', function() { @@ -33,7 +43,7 @@ describe('GoogleReCaptchaHandler', function() { Utils.setFuncName(dependency, 'middleware'); Utils.setFuncName(middleware, 'middleware'); - const route = function(req, res) { + const route = async function(req, res, next) { res.status(200).send(); }; @@ -69,4 +79,50 @@ describe('GoogleReCaptchaHandler', function() { expect(result.body).to.be.undefined; }); }); + + describe('error', function() { + const stack = new Stack(); + + const dependency = function(req, res, next) { + const config = { + google: { + reCaptcha: { + secretKey: '' + } + } + }; + + req.plugin('config', config); + next(); + }; + + Utils.setFuncName(dependency, 'middleware'); + Utils.setFuncName(middleware, 'middleware'); + + const route = async function(req, res, next) { + res.status(200).send(); + }; + + Utils.setFuncName(route, 'route:index'); + + stack.middleware = [dependency, middleware]; + stack.routes = route; + + // Define form POST parameters. + event.Records[0].cf.request.method = 'POST'; + event.Records[0].cf.request.uri = '/path/to/resource'; + event.Records[0].cf.request.body = { + data: Buffer.from('g-recaptcha-response=GOOGLE_RESPONSE') + .toString('base64') + }; + + const req = new Request(event.Records[0].cf.request, {}); + const res = new Response({}); + + it('should throw RouterError', function() { + const result = stack.exec(req, res); + + return expect(result).to.be.rejectedWith(RouterError, /Missing Google API secret key/); + }); + }); });