diff --git a/.secrets.baseline b/.secrets.baseline index 7fba76be2..4fba8e27e 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -26,9 +26,6 @@ { "name": "GitHubTokenDetector" }, - { - "name": "GitLabTokenDetector" - }, { "name": "HexHighEntropyString", "limit": 3.0 @@ -39,9 +36,6 @@ { "name": "IbmCosHmacDetector" }, - { - "name": "IPPublicDetector" - }, { "name": "JwtTokenDetector" }, @@ -55,15 +49,9 @@ { "name": "NpmDetector" }, - { - "name": "OpenAIDetector" - }, { "name": "PrivateKeyDetector" }, - { - "name": "PypiTokenDetector" - }, { "name": "SendGridDetector" }, @@ -79,9 +67,6 @@ { "name": "StripeDetector" }, - { - "name": "TelegramBotTokenDetector" - }, { "name": "TwilioKeyDetector" } @@ -129,7 +114,7 @@ "filename": "app/controllers/web-payments/apple-pay/merchant-validation.controller.js", "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", "is_verified": false, - "line_number": 18 + "line_number": 20 } ], "test/controllers/web-payments/apple-pay/normalise-apple-pay-payload.test.js": [ @@ -155,6 +140,15 @@ "line_number": 44 } ], + "test/controllers/web-payments/apple-pay/old-merchant-validation.controller.test.js": [ + { + "type": "Private Key", + "filename": "test/controllers/web-payments/apple-pay/old-merchant-validation.controller.test.js", + "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", + "is_verified": false, + "line_number": 69 + } + ], "test/controllers/web-payments/google-pay/normalise-google-pay-payload.test.js": [ { "type": "Base64 High Entropy String", @@ -391,5 +385,5 @@ } ] }, - "generated_at": "2024-07-24T08:52:06Z" + "generated_at": "2024-07-29T10:56:11Z" } diff --git a/app/controllers/web-payments/apple-pay/merchant-validation.controller.js b/app/controllers/web-payments/apple-pay/merchant-validation.controller.js index b8367ec5c..d7b7b09f1 100644 --- a/app/controllers/web-payments/apple-pay/merchant-validation.controller.js +++ b/app/controllers/web-payments/apple-pay/merchant-validation.controller.js @@ -1,11 +1,13 @@ 'use strict' +const request = require('requestretry') // to be removed once axios is in use const logger = require('../../../utils/logger')(__filename) const { getLoggingFields } = require('../../../utils/logging-fields-helper') const axios = require('axios') const https = require('https') const { HttpsProxyAgent } = require('https-proxy-agent') const proxyUrl = process.env.HTTPS_PROXY +const applePayMerchantValidationViaAxios = process.env.APPLE_PAY_MERCHANT_VALIDATION_VIA_AXIOS === 'true' function getCertificateMultiline (cert) { @@ -60,35 +62,71 @@ module.exports = async (req, res) => { const proxyAgent = proxyUrl ? new HttpsProxyAgent(proxyUrl) : null - const options = { - url: url, - method: 'post', - headers: { 'Content-Type': 'application/json' }, - data: { - merchantIdentifier: merchantIdentityVars.merchantIdentifier, - displayName: 'GOV.UK Pay', - initiative: 'web', - initiativeContext: process.env.APPLE_PAY_MERCHANT_DOMAIN - }, - httpsAgent: proxyUrl ? proxyAgent : httpsAgent - } + const options = applePayMerchantValidationViaAxios ? + { + url: url, + method: 'post', + headers: { 'Content-Type': 'application/json' }, + data: { + merchantIdentifier: merchantIdentityVars.merchantIdentifier, + displayName: 'GOV.UK Pay', + initiative: 'web', + initiativeContext: process.env.APPLE_PAY_MERCHANT_DOMAIN + }, + httpsAgent: proxyUrl ? proxyAgent : httpsAgent + } : + { + url: url, + cert: merchantIdentityVars.cert, + key: merchantIdentityVars.key, + method: 'post', + body: { + merchantIdentifier: merchantIdentityVars.merchantIdentifier, + displayName: 'GOV.UK Pay', + initiative: 'web', + initiativeContext: process.env.APPLE_PAY_MERCHANT_DOMAIN + }, + json: true + } - try { - let response + if (applePayMerchantValidationViaAxios) { + logger.info('Generating Apple Pay session via axios') + try { + let response - if (proxyUrl) { - response = await axios(options, httpsAgent) - } else { - response = await axios(options) + if (proxyUrl) { + response = await axios(options, httpsAgent) + } else { + response = await axios(options) + } + logger.info('Apple Pay session successfully generated via axios') + res.status(200).send(response.data) + } catch (error) { + const errorResponseData = error.response ? error.response.data : null + logger.info('Error generating Apple Pay session', { + ...getLoggingFields(req), + error: error, + response: error.response, + data: errorResponseData + }) + logger.info('Apple Pay session via axios failed', errorResponseData ? errorResponseData : 'Apple Pay Error') + res.status(500).send(errorResponseData ? errorResponseData : 'Apple Pay Error') } - res.status(200).send(response.data) - } catch (error) { - logger.info('Error generating Apple Pay session', { - ...getLoggingFields(req), - error: error, - response: error.response, - data: error.response ? error.response.data : null + } else { + logger.info('Generating Apple Pay session via request retry') + request(options, (err, response, body) => { + if (err) { + logger.info('Error generating Apple Pay session', { + ...getLoggingFields(req), + error: err, + response: response, + body: body + }) + logger.info('Apple Pay session via request retry failed', body) + return res.status(500).send(body) + } + logger.info('Apple Pay session successfully generated via request retry') + res.status(200).send(body) }) - res.status(500).send(error.response ? error.response.data : 'Apple Pay Error') } } diff --git a/test/controllers/web-payments/apple-pay/merchant-validation.controller.test.js b/test/controllers/web-payments/apple-pay/merchant-validation.controller.test.js index 0084dc630..bae859538 100644 --- a/test/controllers/web-payments/apple-pay/merchant-validation.controller.test.js +++ b/test/controllers/web-payments/apple-pay/merchant-validation.controller.test.js @@ -35,6 +35,7 @@ describe('Validate with Apple the merchant is legitimate', () => { process.env.STRIPE_APPLE_PAY_MERCHANT_ID = stripeMerchantId process.env.STRIPE_APPLE_PAY_MERCHANT_ID_CERTIFICATE = stripeCertificate process.env.STRIPE_APPLE_PAY_MERCHANT_ID_CERTIFICATE_KEY = stripeKey + process.env.APPLE_PAY_MERCHANT_VALIDATION_VIA_AXIOS = 'true' sendSpy = sinon.spy() res = { diff --git a/test/controllers/web-payments/apple-pay/old-merchant-validation.controller.test.js b/test/controllers/web-payments/apple-pay/old-merchant-validation.controller.test.js new file mode 100644 index 000000000..3ba68ef8a --- /dev/null +++ b/test/controllers/web-payments/apple-pay/old-merchant-validation.controller.test.js @@ -0,0 +1,184 @@ +'use strict' + +const sinon = require('sinon') +const proxyquire = require('proxyquire') + +const merchantDomain = 'www.pymnt.uk' +const worldpayMerchantId = 'worldpay.merchant.id' +const worldpayCertificate = 'A-WORLDPAY-CERTIFICATE' +const worldpayKey = 'A-WORLDPAY-KEY' +const stripeMerchantId = 'stripe.merchant.id' +const stripeCertificate = 'A-STRIPE-CERTIFICATE' +const stripeKey = 'A-STRIPE-KEY' +const url = 'https://fakeapple.url' + +const oldAppleResponse = { status: 200 } +const oldAppleResponseBody = { foo: 'bar' } + +function getControllerWithMocks (requestMock) { + return proxyquire('../../../../app/controllers/web-payments/apple-pay/merchant-validation.controller', { + requestretry: requestMock + }) +} + +describe('Validate with Apple the merchant is legitimate', () => { + let res, sendSpy + + beforeEach(() => { + process.env.APPLE_PAY_MERCHANT_DOMAIN = merchantDomain + process.env.WORLDPAY_APPLE_PAY_MERCHANT_ID = worldpayMerchantId + process.env.WORLDPAY_APPLE_PAY_MERCHANT_ID_CERTIFICATE = worldpayCertificate + process.env.WORLDPAY_APPLE_PAY_MERCHANT_ID_CERTIFICATE_KEY = worldpayKey + process.env.STRIPE_APPLE_PAY_MERCHANT_ID = stripeMerchantId + process.env.STRIPE_APPLE_PAY_MERCHANT_ID_CERTIFICATE = stripeCertificate + process.env.STRIPE_APPLE_PAY_MERCHANT_ID_CERTIFICATE_KEY = stripeKey + process.env.APPLE_PAY_MERCHANT_VALIDATION_VIA_AXIOS = 'false' + + sendSpy = sinon.spy() + res = { + status: sinon.spy(() => ({ send: sendSpy })), + sendStatus: sinon.spy() + } + }) + + it('should return a payload for a Worldpay payment if Merchant is valid', async () => { + const mockRequest = sinon.stub().yields(null, oldAppleResponse, oldAppleResponseBody) + const controller = getControllerWithMocks(mockRequest) + + const req = { + body: { + url, + paymentProvider: 'worldpay' + } + } + await controller(req, res) + + sinon.assert.calledWith(mockRequest, { + url, + body: { + merchantIdentifier: worldpayMerchantId, + displayName: 'GOV.UK Pay', + initiative: 'web', + initiativeContext: merchantDomain + }, + method: 'post', + json: true, + cert: `-----BEGIN CERTIFICATE----- +${worldpayCertificate} +-----END CERTIFICATE-----`, + key: `-----BEGIN PRIVATE KEY----- +${worldpayKey} +-----END PRIVATE KEY-----` + }) + sinon.assert.calledWith(res.status, 200) + sinon.assert.calledWith(sendSpy, oldAppleResponseBody) + }) + + it('should return a payload for a Stripe payment if Merchant is valid', async () => { + const mockRequest = sinon.stub().yields(null, oldAppleResponse, oldAppleResponseBody) + const controller = getControllerWithMocks(mockRequest) + + const req = { + body: { + url, + paymentProvider: 'stripe' + } + } + await controller(req, res) + + sinon.assert.calledWith(mockRequest, { + url, + body: { + merchantIdentifier: stripeMerchantId, + displayName: 'GOV.UK Pay', + initiative: 'web', + initiativeContext: merchantDomain + }, + method: 'post', + json: true, + cert: `-----BEGIN CERTIFICATE----- +${stripeCertificate} +-----END CERTIFICATE-----`, + key: `-----BEGIN PRIVATE KEY----- +${stripeKey} +-----END PRIVATE KEY-----` + }) + sinon.assert.calledWith(res.status, 200) + sinon.assert.calledWith(sendSpy, oldAppleResponseBody) + }) + + it('should return 400 if no url is provided', async () => { + const mockRequest = sinon.stub().yields(null, oldAppleResponse, oldAppleResponseBody) + const controller = getControllerWithMocks(mockRequest) + + const req = { + body: { + paymentProvider: 'worldpay' + } + } + await controller(req, res) + sinon.assert.calledWith(res.sendStatus, 400) + }) + + it('should return a payload for a Sandbox payment if Merchant is valid', async () => { + const mockRequest = sinon.stub().yields(null, oldAppleResponse, oldAppleResponseBody) + const controller = getControllerWithMocks(mockRequest) + + const req = { + body: { + url, + paymentProvider: 'sandbox' + } + } + await controller(req, res) + + sinon.assert.calledWith(mockRequest, { + url, + body: { + merchantIdentifier: worldpayMerchantId, + displayName: 'GOV.UK Pay', + initiative: 'web', + initiativeContext: merchantDomain + }, + method: 'post', + json: true, + cert: `-----BEGIN CERTIFICATE----- +${worldpayCertificate} +-----END CERTIFICATE-----`, + key: `-----BEGIN PRIVATE KEY----- +${worldpayKey} +-----END PRIVATE KEY-----` + }) + sinon.assert.calledWith(res.status, 200) + sinon.assert.calledWith(sendSpy, oldAppleResponseBody) + }) + + it('should return 400 for unexpected payment provider', async () => { + const mockRequest = sinon.stub().yields(null, oldAppleResponse, oldAppleResponseBody) + const controller = getControllerWithMocks(mockRequest) + + const req = { + body: { + url, + paymentProvider: 'mystery' + } + } + await controller(req, res) + sinon.assert.calledWith(res.sendStatus, 400) + }) + + it('should return an error if Apple Pay returns an error', async () => { + const mockRequest = sinon.stub().yields(new Error(), oldAppleResponse, oldAppleResponseBody) + const controller = getControllerWithMocks(mockRequest) + + const req = { + body: { + url, + paymentProvider: 'worldpay' + } + } + await controller(req, res) + sinon.assert.calledWith(res.status, 500) + sinon.assert.calledWith(sendSpy, oldAppleResponseBody) + }) +})