From 9f4bbc9fa98dcccb42769bb3465eb6a34b6fdba9 Mon Sep 17 00:00:00 2001 From: Oswald Quek Date: Tue, 31 Dec 2024 14:43:23 +0000 Subject: [PATCH] PP-12395: Change api key name --- .../settings/api-keys/api-keys.controller.js | 11 ++- .../change-name/change-name.controller.js | 36 +++++++ .../change-name.controller.test.js | 96 +++++++++++++++++++ .../create/create-api-key.controller.js | 12 +-- .../create/create-api-key.controller.test.js | 2 +- .../settings/api-keys/validations.js | 11 +++ app/models/Token.class.js | 6 ++ app/paths.js | 3 +- app/services/api-keys.service.js | 5 + app/simplified-account-routes.js | 2 + .../settings/api-keys/index.njk | 2 +- 11 files changed, 171 insertions(+), 15 deletions(-) create mode 100644 app/controllers/simplified-account/settings/api-keys/change-name/change-name.controller.js create mode 100644 app/controllers/simplified-account/settings/api-keys/change-name/change-name.controller.test.js create mode 100644 app/controllers/simplified-account/settings/api-keys/validations.js diff --git a/app/controllers/simplified-account/settings/api-keys/api-keys.controller.js b/app/controllers/simplified-account/settings/api-keys/api-keys.controller.js index f09b55ed8..1d8655ffd 100644 --- a/app/controllers/simplified-account/settings/api-keys/api-keys.controller.js +++ b/app/controllers/simplified-account/settings/api-keys/api-keys.controller.js @@ -7,11 +7,18 @@ async function get (req, res) { const activeKeys = await apiKeysService.getActiveKeys(req.account.id) return response(req, res, 'simplified-account/settings/api-keys/index', { accountType: req.account.type, - activeKeys, - createApiKeyLink: formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.apiKeys.create, req.service.externalId, req.account.type), + activeKeys: activeKeys.map(x => { + const token = x.toJson() + token.changeNameLink = formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.apiKeys.changeName, + req.service.externalId, req.account.type, x.tokenLink) + return token + }), + createApiKeyLink: formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.apiKeys.create, + req.service.externalId, req.account.type), showRevokedKeysLink: '#' }) } module.exports = { get } module.exports.createApiKey = require('./create/create-api-key.controller') +module.exports.changeName = require('./change-name/change-name.controller') diff --git a/app/controllers/simplified-account/settings/api-keys/change-name/change-name.controller.js b/app/controllers/simplified-account/settings/api-keys/change-name/change-name.controller.js new file mode 100644 index 000000000..dbb3ec9bb --- /dev/null +++ b/app/controllers/simplified-account/settings/api-keys/change-name/change-name.controller.js @@ -0,0 +1,36 @@ +const { response } = require('@utils/response') +const formatSimplifiedAccountPathsFor = require('../../../../../utils/simplified-account/format/format-simplified-account-paths-for') +const paths = require('@root/paths') +const { changeApiKeyName } = require('@services/api-keys.service') +const DESCRIPTION_VALIDATION = require('@controllers/simplified-account/settings/api-keys/validations') +const { validationResult } = require('express-validator') +const formatValidationErrors = require('@utils/simplified-account/format/format-validation-errors') + +function get (req, res) { + return response(req, res, 'simplified-account/settings/api-keys/api-key-name', { + backLink: formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.apiKeys.index, req.service.externalId, req.account.type) + }) +} + +async function post (req, res) { + await Promise.all(DESCRIPTION_VALIDATION.map(validation => validation.run(req))) + const errors = validationResult(req) + + if (!errors.isEmpty()) { + const formattedErrors = formatValidationErrors(errors) + return response(req, res, 'simplified-account/settings/api-keys/api-key-name', { + errors: { + summary: formattedErrors.errorSummary, + formErrors: formattedErrors.formErrors + }, + backLink: formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.apiKeys.index, req.service.externalId, req.account.type) + }) + } + + const tokenLink = req.params.tokenLink + const description = req.body.description + await changeApiKeyName(tokenLink, description) + res.redirect(formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.apiKeys.index, req.service.externalId, req.account.type)) +} + +module.exports = { get, post } diff --git a/app/controllers/simplified-account/settings/api-keys/change-name/change-name.controller.test.js b/app/controllers/simplified-account/settings/api-keys/change-name/change-name.controller.test.js new file mode 100644 index 000000000..e8cf86353 --- /dev/null +++ b/app/controllers/simplified-account/settings/api-keys/change-name/change-name.controller.test.js @@ -0,0 +1,96 @@ +const ControllerTestBuilder = require('@test/test-helpers/simplified-account/controllers/ControllerTestBuilder.class') +const sinon = require('sinon') +const { expect } = require('chai') +const formatSimplifiedAccountPathsFor = require('@utils/simplified-account/format/format-simplified-account-paths-for') +const paths = require('@root/paths') + +const ACCOUNT_TYPE = 'live' +const SERVICE_ID = 'service-id-123abc' +const mockResponse = sinon.spy() +const apiKeysService = { + changeApiKeyName: sinon.stub().resolves() +} +const { + req, + res, + nextRequest, + call +} = new ControllerTestBuilder('@controllers/simplified-account/settings/api-keys/change-name/change-name.controller') + .withServiceExternalId(SERVICE_ID) + .withAccountType(ACCOUNT_TYPE) + .withStubs({ + '@utils/response': { response: mockResponse }, + '@services/api-keys.service': apiKeysService + }) + .build() + +describe('Controller: settings/api-keys/change-name', () => { + describe('get', () => { + before(() => { + call('get') + }) + + it('should call the response method', () => { + expect(mockResponse).to.have.been.calledOnce // eslint-disable-line + }) + + it('should pass req, res, template path and context to the response method', () => { + expect(mockResponse).to.have.been.calledWith(req, res, 'simplified-account/settings/api-keys/api-key-name', + { backLink: formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.apiKeys.index, SERVICE_ID, ACCOUNT_TYPE) }) + }) + }) + + describe('post', () => { + describe('a valid description', () => { + before(() => { + nextRequest({ + body: { + description: 'a test api key' + }, + params: { + tokenLink: '123-456-abc' + } + }) + call('post') + }) + + it('should submit values to the api keys service', () => { + expect(apiKeysService.changeApiKeyName).to.have.been.calledWith('123-456-abc', 'a test api key') + }) + + it('should redirect to the api keys index page', () => { + expect(res.redirect.calledOnce).to.be.true // eslint-disable-line + expect(res.redirect.args[0][0]).to.include( + formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.apiKeys.index, SERVICE_ID, ACCOUNT_TYPE) + ) + }) + }) + + describe('an invalid description', () => { + before(() => { + nextRequest({ + body: { + description: '' + }, + params: { + tokenLink: '123-456-abc' + } + }) + call('post') + }) + + it('should not call apiKeysService.changeApiKeyName', () => { + sinon.assert.notCalled(apiKeysService.changeApiKeyName) + }) + + it('should pass req, res, template path and context to the response method', () => { + expect(mockResponse.calledOnce).to.be.true // eslint-disable-line + expect(mockResponse.args[0][2]).to.equal('simplified-account/settings/api-keys/api-key-name') + expect(mockResponse.args[0][3].errors.summary[0].text).to.equal('Name must not be empty') + expect(mockResponse.args[0][3].errors.formErrors.description).to.equal('Name must not be empty') + expect(mockResponse.args[0][3].backLink).to.equal( + formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.apiKeys.index, SERVICE_ID, ACCOUNT_TYPE)) + }) + }) + }) +}) diff --git a/app/controllers/simplified-account/settings/api-keys/create/create-api-key.controller.js b/app/controllers/simplified-account/settings/api-keys/create/create-api-key.controller.js index ee0d1999e..a20145e4d 100644 --- a/app/controllers/simplified-account/settings/api-keys/create/create-api-key.controller.js +++ b/app/controllers/simplified-account/settings/api-keys/create/create-api-key.controller.js @@ -4,6 +4,7 @@ const paths = require('@root/paths') const { TOKEN_SOURCE, createApiKey } = require('@services/api-keys.service') const { body, validationResult } = require('express-validator') const formatValidationErrors = require('@utils/simplified-account/format/format-validation-errors') +const DESCRIPTION_VALIDATION = require('@controllers/simplified-account/settings/api-keys/validations') async function get (req, res) { return response(req, res, 'simplified-account/settings/api-keys/api-key-name', { @@ -11,17 +12,8 @@ async function get (req, res) { }) } -const descriptionValidation = [ - body('description') - .trim() - .notEmpty() - .withMessage('Name must not be empty') - .isLength({ max: 50 }) - .withMessage('Name must be 50 characters or fewer') -] - async function post (req, res) { - await Promise.all(descriptionValidation.map(validation => validation.run(req))) + await Promise.all(DESCRIPTION_VALIDATION.map(validation => validation.run(req))) const errors = validationResult(req) if (!errors.isEmpty()) { diff --git a/app/controllers/simplified-account/settings/api-keys/create/create-api-key.controller.test.js b/app/controllers/simplified-account/settings/api-keys/create/create-api-key.controller.test.js index 126c8d65b..8f93bc961 100644 --- a/app/controllers/simplified-account/settings/api-keys/create/create-api-key.controller.test.js +++ b/app/controllers/simplified-account/settings/api-keys/create/create-api-key.controller.test.js @@ -28,7 +28,7 @@ const { }) .build() -describe('Controller: settings/create-api-key', () => { +describe('Controller: settings/api-keys/create', () => { describe('get', () => { before(() => { call('get') diff --git a/app/controllers/simplified-account/settings/api-keys/validations.js b/app/controllers/simplified-account/settings/api-keys/validations.js new file mode 100644 index 000000000..578677aa0 --- /dev/null +++ b/app/controllers/simplified-account/settings/api-keys/validations.js @@ -0,0 +1,11 @@ +const { body } = require('express-validator') +const DESCRIPTION_VALIDATION = [ + body('description') + .trim() + .notEmpty() + .withMessage('Name must not be empty') + .isLength({ max: 50 }) + .withMessage('Name must be 50 characters or fewer') +] + +module.exports = DESCRIPTION_VALIDATION diff --git a/app/models/Token.class.js b/app/models/Token.class.js index 51e670e94..52bd50baa 100644 --- a/app/models/Token.class.js +++ b/app/models/Token.class.js @@ -19,6 +19,11 @@ class Token { return this } + withTokenLink (tokenLink) { + this.tokenLink = tokenLink + return this + } + toJson () { return { ...this.description && { description: this.description }, @@ -37,6 +42,7 @@ class Token { .withCreatedBy(data?.created_by) .withIssuedDate(data?.issued_date) .withLastUsed(data?.last_used) + .withTokenLink(data?.token_link) } } diff --git a/app/paths.js b/app/paths.js index d42807f04..63555849f 100644 --- a/app/paths.js +++ b/app/paths.js @@ -229,7 +229,8 @@ module.exports = { }, apiKeys: { index: '/settings/api-keys', - create: '/settings/api-keys/create' + create: '/settings/api-keys/create', + changeName: '/settings/api-keys/change-name/:tokenLink' }, webhooks: { index: '/settings/webhooks' diff --git a/app/services/api-keys.service.js b/app/services/api-keys.service.js index ebb396ab5..0550b0c6d 100644 --- a/app/services/api-keys.service.js +++ b/app/services/api-keys.service.js @@ -38,7 +38,12 @@ const getActiveKeys = async (gatewayAccountId) => { return publicAuthData.tokens.map(tokenData => Token.fromJson(tokenData)) } +const changeApiKeyName = async (tokenLink, description) => { + await publicAuthClient.updateToken({ payload: { token_link: tokenLink, description } }) +} + module.exports = { + changeApiKeyName, createApiKey, getActiveKeys, TOKEN_SOURCE diff --git a/app/simplified-account-routes.js b/app/simplified-account-routes.js index 8ee7d8e12..168907e53 100644 --- a/app/simplified-account-routes.js +++ b/app/simplified-account-routes.js @@ -77,6 +77,8 @@ simplifiedAccount.get(paths.simplifiedAccount.settings.cardTypes.index, permissi simplifiedAccount.get(paths.simplifiedAccount.settings.apiKeys.index, permission('tokens-active:read'), serviceSettingsController.apiKeys.get) simplifiedAccount.get(paths.simplifiedAccount.settings.apiKeys.create, permission('tokens:create'), serviceSettingsController.apiKeys.createApiKey.get) simplifiedAccount.post(paths.simplifiedAccount.settings.apiKeys.create, permission('tokens:create'), serviceSettingsController.apiKeys.createApiKey.post) +simplifiedAccount.get(paths.simplifiedAccount.settings.apiKeys.changeName, permission('tokens:update'), serviceSettingsController.apiKeys.changeName.get) +simplifiedAccount.post(paths.simplifiedAccount.settings.apiKeys.changeName, permission('tokens:update'), serviceSettingsController.apiKeys.changeName.post) // stripe details const stripeDetailsPath = paths.simplifiedAccount.settings.stripeDetails diff --git a/app/views/simplified-account/settings/api-keys/index.njk b/app/views/simplified-account/settings/api-keys/index.njk index 89b1e2c83..5c902b810 100644 --- a/app/views/simplified-account/settings/api-keys/index.njk +++ b/app/views/simplified-account/settings/api-keys/index.njk @@ -49,7 +49,7 @@ actions: { items: [ { - href: '#', + href: key.changeNameLink, text: 'Change name' }, {