diff --git a/app/controllers/my-services/post-index.controller.js b/app/controllers/my-services/post-index.controller.js index b6712224fa..e3b6c9574f 100644 --- a/app/controllers/my-services/post-index.controller.js +++ b/app/controllers/my-services/post-index.controller.js @@ -9,13 +9,15 @@ const validAccountId = (accountId, user) => { } module.exports = (req, res) => { - let newAccountId = _.get(req, 'body.gatewayAccountId') + const gatewayAccountId = req.body && req.body.gatewayAccountId + const gatewayAccountExternalId = req.body && req.body.gatewayAccountExternalId - if (validAccountId(newAccountId, req.user)) { - req.gateway_account.currentGatewayAccountId = newAccountId + if (validAccountId(gatewayAccountId, req.user)) { + req.gateway_account.currentGatewayAccountId = gatewayAccountId + req.gateway_account.currentGatewayAccountExternalId = gatewayAccountExternalId res.redirect(302, paths.dashboard.index) } else { - logger.warn(`Attempted to switch to invalid account ${newAccountId}`) + logger.warn(`Attempted to switch to invalid account ${gatewayAccountId}`) res.redirect(302, paths.serviceSwitcher.index) } } diff --git a/app/models/GatewayAccount.class.js b/app/models/GatewayAccount.class.js index fd3f0a65a5..203fc53e0b 100644 --- a/app/models/GatewayAccount.class.js +++ b/app/models/GatewayAccount.class.js @@ -23,6 +23,7 @@ class GatewayAccount { **/ constructor (gatewayAccountData) { this.id = gatewayAccountData.gateway_account_id + this.external_id = gatewayAccountData.external_id this.name = gatewayAccountData.service_name this.type = gatewayAccountData.type this.paymentProvider = gatewayAccountData.payment_provider @@ -39,7 +40,7 @@ class GatewayAccount { // until we have external ids for card accounts, the external id is the internal one return { id: this.id, - external_id: this.id, + external_id: this.external_id, payment_provider: this.paymentProvider, service_name: this.name, type: this.type diff --git a/app/routes.js b/app/routes.js index 4a8291823e..6353fdc6c9 100644 --- a/app/routes.js +++ b/app/routes.js @@ -7,6 +7,7 @@ const logger = require('./utils/logger')(__filename) const response = require('./utils/response.js').response const generateRoute = require('./utils/generate-route') const paths = require('./paths.js') +const accountUrls = require('./utils/gateway-account-urls') const userIsAuthorised = require('./middleware/user-is-authorised') const getServiceAndAccount = require('./middleware/get-service-and-gateway-account.middleware') @@ -488,6 +489,18 @@ module.exports.bind = function (app) { app.use(paths.account.root, account) app.all('*', (req, res) => { + const currentSessionAccountExternalId = req.gateway_account && req.gateway_account.currentGatewayAccountExternalId + if (accountUrls.isLegacyAccountsUrl(req.url) && currentSessionAccountExternalId) { + const upgradedPath = accountUrls.getUpgradedAccountStructureUrl(req.url, currentSessionAccountExternalId) + logger.info('Accounts URL utility upgraded a request to a legacy account URL', { + url: req.originalUrl, + redirected_url: upgradedPath, + session_has_user: !!req.user, + is_internal_user: req.user && req.user.internalUser + }) + res.redirect(upgradedPath) + return + } logger.info('Page not found', { url: req.originalUrl }) diff --git a/app/utils/flatten-nested-values.js b/app/utils/flatten-nested-values.js new file mode 100644 index 0000000000..53607facb3 --- /dev/null +++ b/app/utils/flatten-nested-values.js @@ -0,0 +1,12 @@ +'use strict' +function flattenNestedValues (target) { + return Object.values(target).reduce((aggregate, value) => { + const valueIsNestedObject = typeof value === 'object' && value !== null + if (valueIsNestedObject) { + return [ ...aggregate, ...flattenNestedValues(value) ] + } + return [ ...aggregate, value ] + }, []) +} + +module.exports = flattenNestedValues diff --git a/app/utils/flatten-nested-values.test.js b/app/utils/flatten-nested-values.test.js new file mode 100644 index 0000000000..bb5a57d1e7 --- /dev/null +++ b/app/utils/flatten-nested-values.test.js @@ -0,0 +1,19 @@ +const { expect } = require('chai') +const flattenNestedValues = require('./flatten-nested-values') +describe('flatten nested values utility', () => { + it('correctly flattens nested values', () => { + const nested = { + one: { + two: { + index: 'path-1', + secondPage: 'path-2' + }, + three: 'path-3' + }, + four: 'path-4' + } + const flat = flattenNestedValues(nested) + expect(flat.length).to.equal(4) + expect(flat).to.have.members(['path-1', 'path-2', 'path-3', 'path-4']) + }) +}) diff --git a/app/utils/gateway-account-urls.js b/app/utils/gateway-account-urls.js new file mode 100644 index 0000000000..815fc3d159 --- /dev/null +++ b/app/utils/gateway-account-urls.js @@ -0,0 +1,47 @@ +'use strict' +// check if a missed URL (404) is a URL that has been upgraded during the +// account URL structure change. When this utility is reporting few or no +// upgrades it can be removed +const urlJoin = require('url-join') +const paths = require('../paths') +const formattedPathFor = require('./replace-params-in-path') +const flattenNestedValues = require('./flatten-nested-values') + +// only flatten paths once given the singleton module export patten, these +// should never change after the server spins up +const allAccountPaths = flattenNestedValues(paths.account) +const templatedAccountPaths = allAccountPaths.filter((path) => path.includes(':')) + +const removeEmptyValues = (value) => !!value + +function isLegacyAccountsUrl (url) { + if (allAccountPaths.includes(url)) { + return true + } else { + // the path isn't directly in the list, check to see if it's a templated value + const numberOfUrlParts = url.split('/').filter(removeEmptyValues).length + return templatedAccountPaths.some((templatedPath) => { + const parts = templatedPath.split('/').filter(removeEmptyValues) + const matches = parts + + // remove variable sections + .filter((part) => !part.startsWith(':')) + + // ensure every part of the url structure is present in the url we're comparing against + .every((part) => url.includes(part)) + + // verify it matches and is not a subset (has less length) + return matches && parts.length === numberOfUrlParts + }) + } +} + +function getUpgradedAccountStructureUrl (url, gatewayAccountExternalId) { + const base = formattedPathFor(paths.account.root, gatewayAccountExternalId) + return urlJoin(base, url) +} + +module.exports = { + isLegacyAccountsUrl, + getUpgradedAccountStructureUrl +} diff --git a/app/utils/gateway-account-urls.js.test.js b/app/utils/gateway-account-urls.js.test.js new file mode 100644 index 0000000000..bb282cb7d0 --- /dev/null +++ b/app/utils/gateway-account-urls.js.test.js @@ -0,0 +1,16 @@ +const { expect } = require('chai') + +const accountsUrl = require('./gateway-account-urls') +describe('account URL checker', () => { + it('correctly identifies an original account URL', () => { + const url = '/billing-address' + const result = accountsUrl.isLegacyAccountsUrl(url) + expect(result).to.be.true //eslint-disable-line + }) + + it('correctly upgrades a URL to the account structure', () => { + const url = '/create-payment-link/manage/some-product-external-id/add-reporting-column/some-metadata-key' + const gatewayAccountExternalId = 'some-account-external-id' + expect(accountsUrl.getUpgradedAccountStructureUrl(url, gatewayAccountExternalId)).to.equal('/account/some-account-external-id/create-payment-link/manage/some-product-external-id/add-reporting-column/some-metadata-key') + }) +}) diff --git a/app/views/services/_service-switch.njk b/app/views/services/_service-switch.njk index cfb623c162..75b60ed984 100644 --- a/app/views/services/_service-switch.njk +++ b/app/views/services/_service-switch.njk @@ -1,7 +1,8 @@