diff --git a/.circleci/config.yml b/.circleci/config.yml index 3834e9d2..ad7f2f02 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,7 +18,7 @@ parameters: node-version: type: string - default: 20.8-browsers + default: 20.10-browsers jobs: build: diff --git a/Dockerfile b/Dockerfile index f843f8f1..28655040 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Stage: base image -FROM node:20.8-bullseye-slim as base +FROM node:20.10-bullseye-slim as base ARG BUILD_NUMBER ARG GIT_REF diff --git a/assets/js/govukFrontendInit.js b/assets/js/govukFrontendInit.js index 31889785..ee1be487 100644 --- a/assets/js/govukFrontendInit.js +++ b/assets/js/govukFrontendInit.js @@ -1 +1,3 @@ -window.GOVUKFrontend.initAll() +import { initAll } from '/assets/govuk/govuk-frontend.min.js' + +initAll() diff --git a/assets/js/initMOJFilterPage.js b/assets/js/initMOJFilterPage.js index 9a14625f..9900f834 100644 --- a/assets/js/initMOJFilterPage.js +++ b/assets/js/initMOJFilterPage.js @@ -1,3 +1,4 @@ +window.MOJFrontend.initAll() new MOJFrontend.FilterToggleButton({ bigModeMediaQuery: '(min-width: 48.063em)', startHidden: true, diff --git a/assets/scss/application-ie8.scss b/assets/scss/application-ie8.scss deleted file mode 100644 index 1faa305b..00000000 --- a/assets/scss/application-ie8.scss +++ /dev/null @@ -1,7 +0,0 @@ -$govuk-global-styles: true; -$path: "/assets/images/"; - -@import 'govuk/all-ie8'; - -@import './components/header-bar'; -@import 'assets/scss/local'; diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml new file mode 100644 index 00000000..1c357412 --- /dev/null +++ b/docker-compose-dev.yml @@ -0,0 +1,45 @@ +version: '3.1' +services: + + redis: + image: 'redis:7.2' + networks: + - hmpps + container_name: redis + environment: + - ALLOW_EMPTY_PASSWORD=yes + ports: + - '6379:6379' + + hmpps-auth: + image: quay.io/hmpps/hmpps-auth:latest + networks: + - hmpps + container_name: hmpps-auth + ports: + - "9090:8080" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/auth/health"] + environment: + - SPRING_PROFILES_ACTIVE=dev + - APPLICATION_AUTHENTICATION_UI_ALLOWLIST=0.0.0.0/0 + + manage-users-api: + image: quay.io/hmpps/hmpps-manage-users-api:latest + networks: + - hmpps + container_name: manage-users-api_mhaa + depends_on: + - hmpps-auth + ports: + - "9091:8080" + healthcheck: + test: [ "CMD", "curl", "-f", "http://localhost:8080/health" ] + environment: + - SERVER_PORT=8080 + - SPRING_PROFILES_ACTIVE=dev + - HMPPS_AUTH_ENDPOINT_URL=http://hmpps-auth:8080/auth + - EXTERNAL_USERS_ENDPOINT_URL=http://hmpps-external-users-api:8080 + +networks: + hmpps: diff --git a/helm_deploy/hmpps-authorization/values.yaml b/helm_deploy/hmpps-authorization/values.yaml index 94b4e031..31b521ad 100644 --- a/helm_deploy/hmpps-authorization/values.yaml +++ b/helm_deploy/hmpps-authorization/values.yaml @@ -1,5 +1,6 @@ generic-service: nameOverride: hmpps-authorization + serviceAccountName: hmpps-authorization productId: DPS017 replicaCount: 4 @@ -34,6 +35,9 @@ generic-service: REDIS_TLS_ENABLED: "true" TOKEN_VERIFICATION_ENABLED: "true" APPLICATIONINSIGHTS_CONNECTION_STRING: "InstrumentationKey=$(APPINSIGHTS_INSTRUMENTATIONKEY);IngestionEndpoint=https://northeurope-0.in.applicationinsights.azure.com/;LiveEndpoint=https://northeurope.livediagnostics.monitor.azure.com/" + AUDIT_SQS_REGION: "eu-west-2" + AUDIT_SERVICE_NAME: "hmpps-authorization" + # Pre-existing kubernetes secrets to load as environment variables in the deployment. # namespace_secrets: diff --git a/integration_tests/e2e/add-base-client.cy.ts b/integration_tests/e2e/add-base-client.cy.ts index a14e3e1f..8b40efb0 100644 --- a/integration_tests/e2e/add-base-client.cy.ts +++ b/integration_tests/e2e/add-base-client.cy.ts @@ -60,8 +60,6 @@ context('Add client page', () => { it('User can see base-client form inputs', () => { addBaseClientDetailsPage.baseClientIdInput().should('be.visible') - addBaseClientDetailsPage.baseClientServiceRadioButton().should('exist') - addBaseClientDetailsPage.baseClientPersonalRadioButton().should('exist') addBaseClientDetailsPage.baseClientAccessTokenValidityDropdown().should('be.visible') addBaseClientDetailsPage.baseClientApprovedScopesInput().should('be.visible') }) diff --git a/integration_tests/e2e/edit-base-client-deployment.cy.ts b/integration_tests/e2e/edit-base-client-deployment.cy.ts index 7c6e5d2c..35e6ac9f 100644 --- a/integration_tests/e2e/edit-base-client-deployment.cy.ts +++ b/integration_tests/e2e/edit-base-client-deployment.cy.ts @@ -22,6 +22,7 @@ context('Edit base client deployment details page', () => { it('User can see contact details form inputs', () => { editBaseClientDeploymentDetailsPage.baseClientSummaryList().should('be.visible') + editBaseClientDeploymentDetailsPage.deploymentTypeRadios().should('be.visible') editBaseClientDeploymentDetailsPage.deploymentTeamInput().should('be.visible') editBaseClientDeploymentDetailsPage.deploymentTeamContactInput().should('be.visible') editBaseClientDeploymentDetailsPage.deploymentTeamSlackInput().should('be.visible') diff --git a/integration_tests/e2e/edit-base-client-details.cy.ts b/integration_tests/e2e/edit-base-client-details.cy.ts index 6d4dc139..7627dfac 100644 --- a/integration_tests/e2e/edit-base-client-details.cy.ts +++ b/integration_tests/e2e/edit-base-client-details.cy.ts @@ -22,8 +22,6 @@ context('Edit base client details page', () => { it('User can see base-client form inputs', () => { editBaseClientDetailsPage.baseClientIdInput().should('exist') - editBaseClientDetailsPage.baseClientServiceRadioButton().should('exist') - editBaseClientDetailsPage.baseClientPersonalRadioButton().should('exist') editBaseClientDetailsPage.baseClientAccessTokenValidityDropdown().should('be.visible') editBaseClientDetailsPage.baseClientApprovedScopesInput().should('be.visible') }) diff --git a/integration_tests/mockApis/manageUsersApi.ts b/integration_tests/mockApis/manageUsersApi.ts index 8ebefcc0..02dd86d0 100644 --- a/integration_tests/mockApis/manageUsersApi.ts +++ b/integration_tests/mockApis/manageUsersApi.ts @@ -1,8 +1,6 @@ -import { Response } from 'superagent' - import { stubFor } from './wiremock' -const stubUser = (name: string) => +const stubUser = (name: string = 'john smith') => stubFor({ request: { method: 'GET', @@ -14,7 +12,6 @@ const stubUser = (name: string) => 'Content-Type': 'application/json;charset=UTF-8', }, jsonBody: { - staffId: 231232, username: 'USER1', active: true, name, @@ -22,22 +19,18 @@ const stubUser = (name: string) => }, }) -const stubUserRoles = () => +const ping = () => stubFor({ request: { method: 'GET', - urlPattern: '/manage-users-api/users/me/roles', + urlPattern: '/manage-users-api/health/ping', }, response: { status: 200, - headers: { - 'Content-Type': 'application/json;charset=UTF-8', - }, - jsonBody: [{ roleCode: 'ROLE_CLIENT_CREDENTIALS' }], }, }) export default { - stubManageUser: (name = 'john smith'): Promise<[Response, Response]> => - Promise.all([stubUser(name), stubUserRoles()]), + stubManageUser: stubUser, + stubManageUsersPing: ping, } diff --git a/integration_tests/pages/addBaseClientDetails.ts b/integration_tests/pages/addBaseClientDetails.ts index 59bc3e21..73928c61 100644 --- a/integration_tests/pages/addBaseClientDetails.ts +++ b/integration_tests/pages/addBaseClientDetails.ts @@ -7,12 +7,6 @@ export default class AddBaseClientDetailsPage extends Page { baseClientIdInput = (): PageElement => cy.get('[data-qa="base-client-id-input"]') - baseClientServiceRadioButton = (): PageElement => cy.get('[data-qa="base-client-service-radio"]') - - baseClientPersonalRadioButton = (): PageElement => cy.get('[data-qa="base-client-personal-radio"]') - - baseClientTypeRadios = (): PageElement => cy.get('[data-qa="base-client-type-radios"]') - baseClientAccessTokenValidityDropdown = (): PageElement => cy.get('[data-qa="base-client-access-token-validity-dropdown"]') diff --git a/integration_tests/pages/editBaseClientDeploymentDetails.ts b/integration_tests/pages/editBaseClientDeploymentDetails.ts index a35a42b0..78469a81 100644 --- a/integration_tests/pages/editBaseClientDeploymentDetails.ts +++ b/integration_tests/pages/editBaseClientDeploymentDetails.ts @@ -7,6 +7,12 @@ export default class EditBaseClientDeploymentDetailsPage extends Page { baseClientSummaryList = (): PageElement => cy.get('[data-qa="base-client-summary-list"]') + deploymentServiceRadioButton = (): PageElement => cy.get('[data-qa="deployment-service-radio"]') + + deploymentPersonalRadioButton = (): PageElement => cy.get('[data-qa="deployment-personal-radio"]') + + deploymentTypeRadios = (): PageElement => cy.get('[data-qa="deployment-type-radios"]') + deploymentTeamInput = (): PageElement => cy.get('[data-qa="deployment-team-input"]') deploymentTeamContactInput = (): PageElement => cy.get('[data-qa="deployment-team-contact-input"]') diff --git a/integration_tests/pages/editBaseClientDetails.ts b/integration_tests/pages/editBaseClientDetails.ts index 1a60c992..dce87c5c 100644 --- a/integration_tests/pages/editBaseClientDetails.ts +++ b/integration_tests/pages/editBaseClientDetails.ts @@ -7,12 +7,6 @@ export default class EditBaseClientDetailsPage extends Page { baseClientIdInput = (): PageElement => cy.get('[data-qa="base-client-id-input"]') - baseClientServiceRadioButton = (): PageElement => cy.get('[data-qa="base-client-service-radio"]') - - baseClientPersonalRadioButton = (): PageElement => cy.get('[data-qa="base-client-personal-radio"]') - - baseClientTypeRadios = (): PageElement => cy.get('[data-qa="base-client-type-radios"]') - baseClientAccessTokenValidityDropdown = (): PageElement => cy.get('[data-qa="base-client-access-token-validity-dropdown"]') diff --git a/package-lock.json b/package-lock.json index a02bcd2e..3713e069 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,12 @@ "version": "0.0.1", "license": "MIT", "dependencies": { + "@aws-sdk/client-sqs": "^3.410.0", "@faker-js/faker": "^8.0.0", - "@ministryofjustice/frontend": "^1.8.0", + "@ministryofjustice/frontend": "^2.0.0", + "@ministryofjustice/hmpps-audit-client": "^1.0.18", "agentkeepalive": "^4.5.0", - "applicationinsights": "^2.9.0", + "applicationinsights": "^2.9.2", "body-parser": "^1.20.2", "bunyan": "^1.8.15", "bunyan-format": "^0.2.1", @@ -20,11 +22,12 @@ "connect-flash": "^0.1.1", "connect-redis": "^7.1.0", "csurf": "^1.11.0", + "date-fns": "^2.30.0", "express": "^4.18.2", - "express-prom-bundle": "^6.6.0", + "express-prom-bundle": "^7.0.0", "express-session": "^1.17.3", "fishery": "^2.2.2", - "govuk-frontend": "^4.7.0", + "govuk-frontend": "^5.0.0", "helmet": "^7.0.0", "http-errors": "^2.0.0", "jquery": "^3.7.1", @@ -33,8 +36,8 @@ "nunjucks": "^3.2.4", "passport": "^0.6.0", "passport-oauth2": "^1.7.0", - "prom-client": "^15.0.0", - "redis": "^4.6.10", + "prom-client": "^15.1.0", + "redis": "^4.6.12", "superagent": "^8.1.2", "url-value-parser": "^2.2.0", "uuid": "^9.0.1" @@ -52,29 +55,29 @@ "@types/http-errors": "^2.0.3", "@types/jest": "^29.5.6", "@types/jsonwebtoken": "^9.0.4", - "@types/node": "^20.8.9", + "@types/node": "^20.10.7", "@types/nunjucks": "^3.2.5", "@types/passport": "^1.0.14", "@types/passport-oauth2": "^1.4.14", - "@types/superagent": "^4.1.20", - "@types/supertest": "^2.0.15", + "@types/superagent": "^8.1.1", + "@types/supertest": "^6.0.2", "@types/uuid": "^9.0.6", - "@typescript-eslint/eslint-plugin": "^6.9.0", - "@typescript-eslint/parser": "^6.9.0", + "@typescript-eslint/eslint-plugin": "^6.18.1", + "@typescript-eslint/parser": "^6.18.1", "audit-ci": "^6.6.1", "concurrently": "^8.2.2", "cookie-session": "^2.0.0", - "cypress": "^13.3.3", + "cypress": "^13.6.2", "cypress-multi-reporters": "^1.6.4", "dotenv": "^16.3.1", - "eslint": "^8.52.0", + "eslint": "^8.56.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^9.0.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-cypress": "^2.15.1", - "eslint-plugin-import": "^2.29.0", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-no-only-tests": "^3.1.0", - "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-prettier": "^5.1.2", "husky": "^8.0.3", "jest": "^29.7.0", "jest-html-reporter": "^3.10.2", @@ -84,9 +87,9 @@ "mocha-junit-reporter": "^2.2.1", "nock": "^13.3.6", "nodemon": "^3.0.1", - "prettier": "^3.0.3", - "prettier-plugin-jinja-template": "^1.3.1", - "sass": "^1.69.5", + "prettier": "^3.1.1", + "prettier-plugin-jinja-template": "^1.3.2", + "sass": "^1.69.7", "supertest": "^6.3.3", "ts-jest": "^29.1.1", "typescript": "^5.3.3" @@ -116,6 +119,575 @@ "node": ">=6.0.0" } }, + "node_modules/@aws-crypto/crc32": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", + "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", + "dependencies": { + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/crc32/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-crypto/ie11-detection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", + "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", + "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", + "dependencies": { + "@aws-crypto/ie11-detection": "^3.0.0", + "@aws-crypto/sha256-js": "^3.0.0", + "@aws-crypto/supports-web-crypto": "^3.0.0", + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", + "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", + "dependencies": { + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", + "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-crypto/util": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", + "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/util/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-sdk/client-sqs": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sqs/-/client-sqs-3.489.0.tgz", + "integrity": "sha512-vpEjdqXiuHnpvc0hvgsYRJFAUHPVi3SScyNFbQAvGhAnCgx1dP2c02dHMjYauESOxRd4m+iaDy3HXUKlPhNEbQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.489.0", + "@aws-sdk/core": "3.485.0", + "@aws-sdk/credential-provider-node": "3.489.0", + "@aws-sdk/middleware-host-header": "3.489.0", + "@aws-sdk/middleware-logger": "3.489.0", + "@aws-sdk/middleware-recursion-detection": "3.489.0", + "@aws-sdk/middleware-sdk-sqs": "3.489.0", + "@aws-sdk/middleware-user-agent": "3.489.0", + "@aws-sdk/region-config-resolver": "3.489.0", + "@aws-sdk/types": "3.489.0", + "@aws-sdk/util-endpoints": "3.489.0", + "@aws-sdk/util-user-agent-browser": "3.489.0", + "@aws-sdk/util-user-agent-node": "3.489.0", + "@smithy/config-resolver": "^2.0.23", + "@smithy/core": "^1.2.2", + "@smithy/fetch-http-handler": "^2.3.2", + "@smithy/hash-node": "^2.0.18", + "@smithy/invalid-dependency": "^2.0.16", + "@smithy/md5-js": "^2.0.18", + "@smithy/middleware-content-length": "^2.0.18", + "@smithy/middleware-endpoint": "^2.3.0", + "@smithy/middleware-retry": "^2.0.26", + "@smithy/middleware-serde": "^2.0.16", + "@smithy/middleware-stack": "^2.0.10", + "@smithy/node-config-provider": "^2.1.9", + "@smithy/node-http-handler": "^2.2.2", + "@smithy/protocol-http": "^3.0.12", + "@smithy/smithy-client": "^2.2.1", + "@smithy/types": "^2.8.0", + "@smithy/url-parser": "^2.0.16", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.1", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.24", + "@smithy/util-defaults-mode-node": "^2.0.32", + "@smithy/util-endpoints": "^1.0.8", + "@smithy/util-middleware": "^2.0.9", + "@smithy/util-retry": "^2.0.9", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.489.0.tgz", + "integrity": "sha512-SZPXiYnByYnd3Vy0qY/PnWD2e9JA3Lwi000Tyz+ZQvjK9emH0B6aeWaxFZ7W4jscJVwQVc5kgvRPsJi5zY3w1w==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.485.0", + "@aws-sdk/middleware-host-header": "3.489.0", + "@aws-sdk/middleware-logger": "3.489.0", + "@aws-sdk/middleware-recursion-detection": "3.489.0", + "@aws-sdk/middleware-user-agent": "3.489.0", + "@aws-sdk/region-config-resolver": "3.489.0", + "@aws-sdk/types": "3.489.0", + "@aws-sdk/util-endpoints": "3.489.0", + "@aws-sdk/util-user-agent-browser": "3.489.0", + "@aws-sdk/util-user-agent-node": "3.489.0", + "@smithy/config-resolver": "^2.0.23", + "@smithy/core": "^1.2.2", + "@smithy/fetch-http-handler": "^2.3.2", + "@smithy/hash-node": "^2.0.18", + "@smithy/invalid-dependency": "^2.0.16", + "@smithy/middleware-content-length": "^2.0.18", + "@smithy/middleware-endpoint": "^2.3.0", + "@smithy/middleware-retry": "^2.0.26", + "@smithy/middleware-serde": "^2.0.16", + "@smithy/middleware-stack": "^2.0.10", + "@smithy/node-config-provider": "^2.1.9", + "@smithy/node-http-handler": "^2.2.2", + "@smithy/protocol-http": "^3.0.12", + "@smithy/smithy-client": "^2.2.1", + "@smithy/types": "^2.8.0", + "@smithy/url-parser": "^2.0.16", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.1", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.24", + "@smithy/util-defaults-mode-node": "^2.0.32", + "@smithy/util-endpoints": "^1.0.8", + "@smithy/util-retry": "^2.0.9", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.489.0.tgz", + "integrity": "sha512-AAQ9+oEJPIPHXWtQL7ahZCKata+d+vZMXpQp92st7KzgmcgsUBdDTBOH0ImN8LXwZwIMAzfn98wWf4s1xtqUeg==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.485.0", + "@aws-sdk/credential-provider-node": "3.489.0", + "@aws-sdk/middleware-host-header": "3.489.0", + "@aws-sdk/middleware-logger": "3.489.0", + "@aws-sdk/middleware-recursion-detection": "3.489.0", + "@aws-sdk/middleware-user-agent": "3.489.0", + "@aws-sdk/region-config-resolver": "3.489.0", + "@aws-sdk/types": "3.489.0", + "@aws-sdk/util-endpoints": "3.489.0", + "@aws-sdk/util-user-agent-browser": "3.489.0", + "@aws-sdk/util-user-agent-node": "3.489.0", + "@smithy/config-resolver": "^2.0.23", + "@smithy/core": "^1.2.2", + "@smithy/fetch-http-handler": "^2.3.2", + "@smithy/hash-node": "^2.0.18", + "@smithy/invalid-dependency": "^2.0.16", + "@smithy/middleware-content-length": "^2.0.18", + "@smithy/middleware-endpoint": "^2.3.0", + "@smithy/middleware-retry": "^2.0.26", + "@smithy/middleware-serde": "^2.0.16", + "@smithy/middleware-stack": "^2.0.10", + "@smithy/node-config-provider": "^2.1.9", + "@smithy/node-http-handler": "^2.2.2", + "@smithy/protocol-http": "^3.0.12", + "@smithy/smithy-client": "^2.2.1", + "@smithy/types": "^2.8.0", + "@smithy/url-parser": "^2.0.16", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.1", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.24", + "@smithy/util-defaults-mode-node": "^2.0.32", + "@smithy/util-endpoints": "^1.0.8", + "@smithy/util-middleware": "^2.0.9", + "@smithy/util-retry": "^2.0.9", + "@smithy/util-utf8": "^2.0.2", + "fast-xml-parser": "4.2.5", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.485.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.485.0.tgz", + "integrity": "sha512-Yvi80DQcbjkYCft471ClE3HuetuNVqntCs6eFOomDcrJaqdOFrXv2kJAxky84MRA/xb7bGlDGAPbTuj1ICputg==", + "dependencies": { + "@smithy/core": "^1.2.2", + "@smithy/protocol-http": "^3.0.12", + "@smithy/signature-v4": "^2.0.0", + "@smithy/smithy-client": "^2.2.1", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.489.0.tgz", + "integrity": "sha512-5PqYsx9G5SB2tqPT9/z/u0EkF6D4wP6HTMWQs+DfMdmwXihrqQAgeYaTtV3KbXqb88p6sfacwxhUvE6+Rm494w==", + "dependencies": { + "@aws-sdk/types": "3.489.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.489.0.tgz", + "integrity": "sha512-lB5yufriHMzraQaAlsVKgzXKLGhRHt+ybgcVD+SIegw0QwabWL2va8h1KuRUGqEOUFH6BNTCx9HnI+uH5EadVA==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.489.0", + "@aws-sdk/credential-provider-process": "3.489.0", + "@aws-sdk/credential-provider-sso": "3.489.0", + "@aws-sdk/credential-provider-web-identity": "3.489.0", + "@aws-sdk/types": "3.489.0", + "@smithy/credential-provider-imds": "^2.0.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.489.0.tgz", + "integrity": "sha512-HXjYjG5oqQflLOSkxjDTfWOeE5UX3CvPhcvexZLen8TWyI7azIT81PjFVLq5CJdnFaoeVRxvhp/DIgL7RrNivw==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.489.0", + "@aws-sdk/credential-provider-ini": "3.489.0", + "@aws-sdk/credential-provider-process": "3.489.0", + "@aws-sdk/credential-provider-sso": "3.489.0", + "@aws-sdk/credential-provider-web-identity": "3.489.0", + "@aws-sdk/types": "3.489.0", + "@smithy/credential-provider-imds": "^2.0.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.489.0.tgz", + "integrity": "sha512-3vKQYJZ5cZYjy0870CPmbmKRBgATw2xCygxhn4m4UDCjOXVXcGUtYD51DMWsvBo3S0W8kH+FIJV4yuEDMFqLFQ==", + "dependencies": { + "@aws-sdk/types": "3.489.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.489.0.tgz", + "integrity": "sha512-tN+7q7xKA4VZmVSMolStvBd8UeHf43kt3TR/tTfqaSvOQR1hKUrDyVgg2rTdyXWxyQPy1O3rtwMKPsorhc/BTA==", + "dependencies": { + "@aws-sdk/client-sso": "3.489.0", + "@aws-sdk/token-providers": "3.489.0", + "@aws-sdk/types": "3.489.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.489.0.tgz", + "integrity": "sha512-mjIuE2Wg1H/ds0nXQ/7vfusEDudmdd8YzKZI1y5O4n60iZZtyB2RNIECtvLMx1EQAKclidY7/06qQkArrGau5Q==", + "dependencies": { + "@aws-sdk/types": "3.489.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.489.0.tgz", + "integrity": "sha512-Cl7HJ1jhOfllwf0CRx1eB4ypRGMqdGKWpc0eSTXty7wWSvCdMZUhwfjQqu2bIOIlgYxg/gFu6TVmVZ6g4O8PlA==", + "dependencies": { + "@aws-sdk/types": "3.489.0", + "@smithy/protocol-http": "^3.0.12", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.489.0.tgz", + "integrity": "sha512-+EVDnWese61MdImcBNAgz/AhTcIZJaska/xsU3GWU9CP905x4a4qZdB7fExFMDu1Jlz5pJqNteFYYHCFMJhHfg==", + "dependencies": { + "@aws-sdk/types": "3.489.0", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.489.0.tgz", + "integrity": "sha512-m4rU+fTzziQcu9DKjRNZ4nQlXENEd2ZnJblJV4ONdWqqEjbmOgOj3P6aCCQlJdIbzuNvX1FBOZ5tY59ZpERo7Q==", + "dependencies": { + "@aws-sdk/types": "3.489.0", + "@smithy/protocol-http": "^3.0.12", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-sqs": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.489.0.tgz", + "integrity": "sha512-0rk/Ps5/aKbH7siyrASmmMy7RQ0qszeFj54+srBnjHqvPcZVUJ1CRWb1voKeta4PzDF3ffbCNiQ8XZKMF5fvMQ==", + "dependencies": { + "@aws-sdk/types": "3.489.0", + "@smithy/types": "^2.8.0", + "@smithy/util-hex-encoding": "^2.0.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.489.0.tgz", + "integrity": "sha512-M54Cv2fAN3GGgdfUjLtZ4wFUIrfM/ivbXv4DgpcNsacEQ2g4H+weQgKp41X7XZW8MWAzl+k1zJaryK69RYNQkQ==", + "dependencies": { + "@aws-sdk/types": "3.489.0", + "@aws-sdk/util-endpoints": "3.489.0", + "@smithy/protocol-http": "^3.0.12", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.489.0.tgz", + "integrity": "sha512-UvrnB78XTz9ddby7mr0vuUHn2MO3VTjzaIu+GQhyedMGQU0QlIQrYOlzbbu4LC5rL1O8FxFLUxRe/AAjgwyuGw==", + "dependencies": { + "@aws-sdk/types": "3.489.0", + "@smithy/node-config-provider": "^2.1.9", + "@smithy/types": "^2.8.0", + "@smithy/util-config-provider": "^2.1.0", + "@smithy/util-middleware": "^2.0.9", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.489.0.tgz", + "integrity": "sha512-hSgjB8CMQoA8EIQ0ripDjDtbBcWDSa+7vSBYPIzksyknaGERR/GUfGXLV2dpm5t17FgFG6irT5f3ZlBzarL8Dw==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/middleware-host-header": "3.489.0", + "@aws-sdk/middleware-logger": "3.489.0", + "@aws-sdk/middleware-recursion-detection": "3.489.0", + "@aws-sdk/middleware-user-agent": "3.489.0", + "@aws-sdk/region-config-resolver": "3.489.0", + "@aws-sdk/types": "3.489.0", + "@aws-sdk/util-endpoints": "3.489.0", + "@aws-sdk/util-user-agent-browser": "3.489.0", + "@aws-sdk/util-user-agent-node": "3.489.0", + "@smithy/config-resolver": "^2.0.23", + "@smithy/fetch-http-handler": "^2.3.2", + "@smithy/hash-node": "^2.0.18", + "@smithy/invalid-dependency": "^2.0.16", + "@smithy/middleware-content-length": "^2.0.18", + "@smithy/middleware-endpoint": "^2.3.0", + "@smithy/middleware-retry": "^2.0.26", + "@smithy/middleware-serde": "^2.0.16", + "@smithy/middleware-stack": "^2.0.10", + "@smithy/node-config-provider": "^2.1.9", + "@smithy/node-http-handler": "^2.2.2", + "@smithy/property-provider": "^2.0.0", + "@smithy/protocol-http": "^3.0.12", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/smithy-client": "^2.2.1", + "@smithy/types": "^2.8.0", + "@smithy/url-parser": "^2.0.16", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.1", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.24", + "@smithy/util-defaults-mode-node": "^2.0.32", + "@smithy/util-endpoints": "^1.0.8", + "@smithy/util-retry": "^2.0.9", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.489.0.tgz", + "integrity": "sha512-kcDtLfKog/p0tC4gAeqJqWxAiEzfe2LRPnKamvSG2Mjbthx4R/alE2dxyIq/wW+nvRv0fqR3OD5kD1+eVfdr/w==", + "dependencies": { + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.489.0.tgz", + "integrity": "sha512-uGyG1u84ATX03mf7bT4xD9XD/vlYJGD5+RxMN/UpzeTfzXfh+jvCQWbOQ44z8ttFJWYQQqrLxkfpF/JgvALzLA==", + "dependencies": { + "@aws-sdk/types": "3.489.0", + "@smithy/types": "^2.8.0", + "@smithy/util-endpoints": "^1.0.8", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.465.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.465.0.tgz", + "integrity": "sha512-f+QNcWGswredzC1ExNAB/QzODlxwaTdXkNT5cvke2RLX8SFU5pYk6h4uCtWC0vWPELzOfMfloBrJefBzlarhsw==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.489.0.tgz", + "integrity": "sha512-85B9KMsuMpAZauzWQ16r52ZBAHYnznW6BVitnBglsibN7oJKn10Hggt4QGuRhvQFCxQ8YhvBl7r+vQGFO4hxIw==", + "dependencies": { + "@aws-sdk/types": "3.489.0", + "@smithy/types": "^2.8.0", + "bowser": "^2.11.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.489.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.489.0.tgz", + "integrity": "sha512-CYdkBHig8sFNc0dv11Ni9WXvZQHeI5+z77OrDHKkbidFx/V4BDTuwZw4K1vWg62pzFOEfzunJFiULRcDZWJR3w==", + "dependencies": { + "@aws-sdk/types": "3.489.0", + "@smithy/node-config-provider": "^2.1.9", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/util-utf8-browser": { + "version": "3.259.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", + "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", + "dependencies": { + "tslib": "^2.3.1" + } + }, "node_modules/@azure/abort-controller": { "version": "1.1.0", "license": "MIT", @@ -761,7 +1333,6 @@ }, "node_modules/@babel/runtime": { "version": "7.22.6", - "dev": true, "license": "MIT", "dependencies": { "regenerator-runtime": "^0.13.11" @@ -944,9 +1515,10 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -966,9 +1538,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", - "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1457,10 +2029,11 @@ "license": "MIT" }, "node_modules/@ministryofjustice/frontend": { - "version": "1.8.0", - "license": "MIT", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ministryofjustice/frontend/-/frontend-2.0.0.tgz", + "integrity": "sha512-JbYtMKjbR3fe8l5y/cgWXHFPJbLg2y0nlRuibM6Vdn9WYWayB8VJkoCoeAh04Jz20QBhzL4lag7KpcP7xQjbqw==", "dependencies": { - "govuk-frontend": "^3.0.0 || ^4.0.0", + "govuk-frontend": "^5.0.0", "moment": "^2.27.0" }, "engines": { @@ -1470,6 +2043,38 @@ "jquery": "^3.6.0" } }, + "node_modules/@ministryofjustice/hmpps-audit-client": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/@ministryofjustice/hmpps-audit-client/-/hmpps-audit-client-1.0.20.tgz", + "integrity": "sha512-uFxWludo1aH8moaFIq+rCIUDcR+kOvkDvUqDG4J1B3NRafGG0A2RUgxRmFCLpd9La9xp8kc9NB2XorOpscv1Hg==", + "dependencies": { + "@aws-sdk/client-sqs": "^3.410.0", + "ajv": "^8.12.0", + "ajv-formats": "^2.1.1", + "bunyan": "^1.8.15", + "bunyan-format": "^0.2.1" + } + }, + "node_modules/@ministryofjustice/hmpps-audit-client/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@ministryofjustice/hmpps-audit-client/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/@nicolo-ribaudo/semver-v6": { "version": "6.3.3", "dev": true, @@ -1518,16 +2123,17 @@ } }, "node_modules/@opentelemetry/core": { - "version": "1.17.0", - "license": "Apache-2.0", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.19.0.tgz", + "integrity": "sha512-w42AukJh3TP8R0IZZOVJVM/kMWu8g+lm4LzT70WtuKqhwq7KVhcDzZZuZinWZa6TtQCl7Smt2wolEYzpHabOgw==", "dependencies": { - "@opentelemetry/semantic-conventions": "1.17.0" + "@opentelemetry/semantic-conventions": "1.19.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.7.0" + "@opentelemetry/api": ">=1.0.0 <1.8.0" } }, "node_modules/@opentelemetry/instrumentation": { @@ -1548,53 +2154,49 @@ } }, "node_modules/@opentelemetry/resources": { - "version": "1.17.0", - "license": "Apache-2.0", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.19.0.tgz", + "integrity": "sha512-RgxvKuuMOf7nctOeOvpDjt2BpZvZGr9Y0vf7eGtY5XYZPkh2p7e2qub1S2IArdBMf9kEbz0SfycqCviOu9isqg==", "dependencies": { - "@opentelemetry/core": "1.17.0", - "@opentelemetry/semantic-conventions": "1.17.0" + "@opentelemetry/core": "1.19.0", + "@opentelemetry/semantic-conventions": "1.19.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.7.0" + "@opentelemetry/api": ">=1.0.0 <1.8.0" } }, "node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.17.0", - "license": "Apache-2.0", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.19.0.tgz", + "integrity": "sha512-+IRvUm+huJn2KqfFW3yW/cjvRwJ8Q7FzYHoUNx5Fr0Lws0LxjMJG1uVB8HDpLwm7mg5XXH2M5MF+0jj5cM8BpQ==", "dependencies": { - "@opentelemetry/core": "1.17.0", - "@opentelemetry/resources": "1.17.0", - "@opentelemetry/semantic-conventions": "1.17.0" + "@opentelemetry/core": "1.19.0", + "@opentelemetry/resources": "1.19.0", + "@opentelemetry/semantic-conventions": "1.19.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.7.0" + "@opentelemetry/api": ">=1.0.0 <1.8.0" } }, "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.17.0", - "license": "Apache-2.0", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.19.0.tgz", + "integrity": "sha512-14jRpC8f5c0gPSwoZ7SbEJni1PqI+AhAE8m1bMz6v+RPM4OlP1PT2UHBJj5Qh/ALLPjhVU/aZUK3YyjTUqqQVg==", "engines": { "node": ">=14" } }, - "node_modules/@pkgr/utils": { - "version": "2.4.2", + "node_modules/@pkgr/core": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.0.tgz", + "integrity": "sha512-Zwq5OCzuwJC2jwqmpEQt7Ds1DTi6BWSwoGkbb1n9pO3hzb35BoJELx7c0T23iDkBGkh2e7tvOtjF3tr3OaQHDQ==", "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "fast-glob": "^3.3.0", - "is-glob": "^4.0.3", - "open": "^9.1.0", - "picocolors": "^1.0.0", - "tslib": "^2.6.0" - }, "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -1610,8 +2212,9 @@ } }, "node_modules/@redis/client": { - "version": "1.5.11", - "license": "MIT", + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.13.tgz", + "integrity": "sha512-epkUM9D0Sdmt93/8Ozk43PNjLi36RZzG+d/T1Gdu5AI8jvghonTeLYV69WVWdilvFo+PYxbP0TZ0saMvr6nscQ==", "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -1626,8 +2229,9 @@ "license": "ISC" }, "node_modules/@redis/graph": { - "version": "1.1.0", - "license": "MIT", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", "peerDependencies": { "@redis/client": "^1.0.0" } @@ -1640,8 +2244,9 @@ } }, "node_modules/@redis/search": { - "version": "1.1.5", - "license": "MIT", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.6.tgz", + "integrity": "sha512-mZXCxbTYKBQ3M2lZnEddwEAks0Kc7nauire8q20oA0oA/LoA+E/b5Y5KZn232ztPb1FkIGqo12vh3Lf+Vw5iTw==", "peerDependencies": { "@redis/client": "^1.0.0" } @@ -1658,20 +2263,560 @@ "dev": true, "license": "MIT" }, - "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.0.16.tgz", + "integrity": "sha512-4foO7738k8kM9flMHu3VLabqu7nPgvIj8TB909S0CnKx0YZz/dcDH3pZ/4JHdatfxlZdKF1JWOYCw9+v3HVVsw==", + "dependencies": { + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "2.0.23", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.0.23.tgz", + "integrity": "sha512-XakUqgtP2YY8Mi+Nlif5BiqJgWdvfxJafSpOSQeCOMizu+PUhE4fBQSy6xFcR+eInrwVadaABNxoJyGUMn15ew==", + "dependencies": { + "@smithy/node-config-provider": "^2.1.9", + "@smithy/types": "^2.8.0", + "@smithy/util-config-provider": "^2.1.0", + "@smithy/util-middleware": "^2.0.9", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-1.2.2.tgz", + "integrity": "sha512-uLjrskLT+mWb0emTR5QaiAIxVEU7ndpptDaVDrTwwhD+RjvHhjIiGQ3YL5jKk1a5VSDQUA2RGkXvJ6XKRcz6Dg==", + "dependencies": { + "@smithy/middleware-endpoint": "^2.3.0", + "@smithy/middleware-retry": "^2.0.26", + "@smithy/middleware-serde": "^2.0.16", + "@smithy/protocol-http": "^3.0.12", + "@smithy/smithy-client": "^2.2.1", + "@smithy/types": "^2.8.0", + "@smithy/util-middleware": "^2.0.9", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.1.5.tgz", + "integrity": "sha512-VfvE6Wg1MUWwpTZFBnUD7zxvPhLY8jlHCzu6bCjlIYoWgXCDzZAML76IlZUEf45nib3rjehnFgg0s1rgsuN/bg==", + "dependencies": { + "@smithy/node-config-provider": "^2.1.9", + "@smithy/property-provider": "^2.0.17", + "@smithy/types": "^2.8.0", + "@smithy/url-parser": "^2.0.16", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.0.16.tgz", + "integrity": "sha512-umYh5pdCE9GHgiMAH49zu9wXWZKNHHdKPm/lK22WYISTjqu29SepmpWNmPiBLy/yUu4HFEGJHIFrDWhbDlApaw==", + "dependencies": { + "@aws-crypto/crc32": "3.0.0", + "@smithy/types": "^2.8.0", + "@smithy/util-hex-encoding": "^2.0.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.3.2.tgz", + "integrity": "sha512-O9R/OlnAOTsnysuSDjt0v2q6DcSvCz5cCFC/CFAWWcLyBwJDeFyGTCTszgpQTb19+Fi8uRwZE5/3ziAQBFeDMQ==", + "dependencies": { + "@smithy/protocol-http": "^3.0.12", + "@smithy/querystring-builder": "^2.0.16", + "@smithy/types": "^2.8.0", + "@smithy/util-base64": "^2.0.1", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.0.18.tgz", + "integrity": "sha512-gN2JFvAgnZCyDN9rJgcejfpK0uPPJrSortVVVVWsru9whS7eQey6+gj2eM5ln2i6rHNntIXzal1Fm9XOPuoaKA==", + "dependencies": { + "@smithy/types": "^2.8.0", + "@smithy/util-buffer-from": "^2.0.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.0.16.tgz", + "integrity": "sha512-apEHakT/kmpNo1VFHP4W/cjfeP9U0x5qvfsLJubgp7UM/gq4qYp0GbqdE7QhsjUaYvEnrftRqs7+YrtWreV0wA==", + "dependencies": { + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz", + "integrity": "sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-2.0.18.tgz", + "integrity": "sha512-bHwZ8/m6RbERQdVW5rJ2LzeW8qxfXv6Q/S7Fiudhso4pWRrksqLx3nsGZw7bmqqfN4zLqkxydxSa9+4c7s5zxg==", + "dependencies": { + "@smithy/types": "^2.8.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.0.18.tgz", + "integrity": "sha512-ZJ9uKPTfxYheTKSKYB+GCvcj+izw9WGzRLhjn8n254q0jWLojUzn7Vw0l4R/Gq7Wdpf/qmk/ptD+6CCXHNVCaw==", + "dependencies": { + "@smithy/protocol-http": "^3.0.12", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.3.0.tgz", + "integrity": "sha512-VsOAG2YQ8ykjSmKO+CIXdJBIWFo6AAvG6Iw95BakBTqk66/4BI7XyqLevoNSq/lZ6NgZv24sLmrcIN+fLDWBCg==", + "dependencies": { + "@smithy/middleware-serde": "^2.0.16", + "@smithy/node-config-provider": "^2.1.9", + "@smithy/shared-ini-file-loader": "^2.2.8", + "@smithy/types": "^2.8.0", + "@smithy/url-parser": "^2.0.16", + "@smithy/util-middleware": "^2.0.9", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.0.26.tgz", + "integrity": "sha512-Qzpxo0U5jfNiq9iD38U3e2bheXwvTEX4eue9xruIvEgh+UKq6dKuGqcB66oBDV7TD/mfoJi9Q/VmaiqwWbEp7A==", + "dependencies": { + "@smithy/node-config-provider": "^2.1.9", + "@smithy/protocol-http": "^3.0.12", + "@smithy/service-error-classification": "^2.0.9", + "@smithy/smithy-client": "^2.2.1", + "@smithy/types": "^2.8.0", + "@smithy/util-middleware": "^2.0.9", + "@smithy/util-retry": "^2.0.9", + "tslib": "^2.5.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-retry/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.0.16.tgz", + "integrity": "sha512-5EAd4t30pcc4M8TSSGq7q/x5IKrxfXR5+SrU4bgxNy7RPHQo2PSWBUco9C+D9Tfqp/JZvprRpK42dnupZafk2g==", + "dependencies": { + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.0.10.tgz", + "integrity": "sha512-I2rbxctNq9FAPPEcuA1ntZxkTKOPQFy7YBPOaD/MLg1zCvzv21CoNxR0py6J8ZVC35l4qE4nhxB0f7TF5/+Ldw==", + "dependencies": { + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.1.9.tgz", + "integrity": "sha512-tUyW/9xrRy+s7RXkmQhgYkAPMpTIF8izK4orhHjNFEKR3QZiOCbWB546Y8iB/Fpbm3O9+q0Af9rpywLKJOwtaQ==", + "dependencies": { + "@smithy/property-provider": "^2.0.17", + "@smithy/shared-ini-file-loader": "^2.2.8", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.2.2.tgz", + "integrity": "sha512-XO58TO/Eul/IBQKFKaaBtXJi0ItEQQCT+NI4IiKHCY/4KtqaUT6y/wC1EvDqlA9cP7Dyjdj7FdPs4DyynH3u7g==", + "dependencies": { + "@smithy/abort-controller": "^2.0.16", + "@smithy/protocol-http": "^3.0.12", + "@smithy/querystring-builder": "^2.0.16", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.0.17.tgz", + "integrity": "sha512-+VkeZbVu7qtQ2DjI48Qwaf9fPOr3gZIwxQpuLJgRRSkWsdSvmaTCxI3gzRFKePB63Ts9r4yjn4HkxSCSkdWmcQ==", + "dependencies": { + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.0.12.tgz", + "integrity": "sha512-Xz4iaqLiaBfbQpB9Hgi3VcZYbP7xRDXYhd8XWChh4v94uw7qwmvlxdU5yxzfm6ACJM66phHrTbS5TVvj5uQ72w==", + "dependencies": { + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.0.16.tgz", + "integrity": "sha512-Q/GsJT0C0mijXMRs7YhZLLCP5FcuC4797lYjKQkME5CZohnLC4bEhylAd2QcD3gbMKNjCw8+T2I27WKiV/wToA==", + "dependencies": { + "@smithy/types": "^2.8.0", + "@smithy/util-uri-escape": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.0.16.tgz", + "integrity": "sha512-c4ueAuL6BDYKWpkubjrQthZKoC3L5kql5O++ovekNxiexRXTlLIVlCR4q3KziOktLIw66EU9SQljPXd/oN6Okg==", + "dependencies": { + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.0.9.tgz", + "integrity": "sha512-0K+8GvtwI7VkGmmInPydM2XZyBfIqLIbfR7mDQ+oPiz8mIinuHbV6sxOLdvX1Jv/myk7XTK9orgt3tuEpBu/zg==", + "dependencies": { + "@smithy/types": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.2.8.tgz", + "integrity": "sha512-E62byatbwSWrtq9RJ7xN40tqrRKDGrEL4EluyNpaIDvfvet06a/QC58oHw2FgVaEgkj0tXZPjZaKrhPfpoU0qw==", + "dependencies": { + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.0.19.tgz", + "integrity": "sha512-nwc3JihdM+kcJjtORv/n7qRHN2Kfh7S2RJI2qr8pz9UcY5TD8rSCRGQ0g81HgyS3jZ5X9U/L4p014P3FonBPhg==", + "dependencies": { + "@smithy/eventstream-codec": "^2.0.16", + "@smithy/is-array-buffer": "^2.0.0", + "@smithy/types": "^2.8.0", + "@smithy/util-hex-encoding": "^2.0.0", + "@smithy/util-middleware": "^2.0.9", + "@smithy/util-uri-escape": "^2.0.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.2.1.tgz", + "integrity": "sha512-SpD7FLK92XV2fon2hMotaNDa2w5VAy5/uVjP9WFmjGSgWM8pTPVkHcDl1yFs5Z8LYbij0FSz+DbCBK6i+uXXUA==", + "dependencies": { + "@smithy/middleware-endpoint": "^2.3.0", + "@smithy/middleware-stack": "^2.0.10", + "@smithy/protocol-http": "^3.0.12", + "@smithy/types": "^2.8.0", + "@smithy/util-stream": "^2.0.24", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.8.0.tgz", + "integrity": "sha512-h9sz24cFgt/W1Re22OlhQKmUZkNh244ApgRsUDYinqF8R+QgcsBIX344u2j61TPshsTz3CvL6HYU1DnQdsSrHA==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.0.16.tgz", + "integrity": "sha512-Wfz5WqAoRT91TjRy1JeLR0fXtkIXHGsMbgzKFTx7E68SrZ55TB8xoG+vm11Ru4gheFTMXjAjwAxv1jQdC+pAQA==", + "dependencies": { + "@smithy/querystring-parser": "^2.0.16", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.0.1.tgz", + "integrity": "sha512-DlI6XFYDMsIVN+GH9JtcRp3j02JEVuWIn/QOZisVzpIAprdsxGveFed0bjbMRCqmIFe8uetn5rxzNrBtIGrPIQ==", + "dependencies": { + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.0.1.tgz", + "integrity": "sha512-NXYp3ttgUlwkaug4bjBzJ5+yIbUbUx8VsSLuHZROQpoik+gRkIBeEG9MPVYfvPNpuXb/puqodeeUXcKFe7BLOQ==", + "dependencies": { + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.1.0.tgz", + "integrity": "sha512-/li0/kj/y3fQ3vyzn36NTLGmUwAICb7Jbe/CsWCktW363gh1MOcpEcSO3mJ344Gv2dqz8YJCLQpb6hju/0qOWw==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz", + "integrity": "sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==", + "dependencies": { + "@smithy/is-array-buffer": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.1.0.tgz", + "integrity": "sha512-S6V0JvvhQgFSGLcJeT1CBsaTR03MM8qTuxMH9WPCCddlSo2W0V5jIHimHtIQALMLEDPGQ0ROSRr/dU0O+mxiQg==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "2.0.24", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.24.tgz", + "integrity": "sha512-TsP5mBuLgO2C21+laNG2nHYZEyUdkbGURv2tHvSuQQxLz952MegX95uwdxOY2jR2H4GoKuVRfdJq7w4eIjGYeg==", + "dependencies": { + "@smithy/property-provider": "^2.0.17", + "@smithy/smithy-client": "^2.2.1", + "@smithy/types": "^2.8.0", + "bowser": "^2.11.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "2.0.32", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.32.tgz", + "integrity": "sha512-d0S33dXA2cq1NyorVMroMrEtqKMr3MlyLITcfTBf9pXiigYiPMOtbSI7czHIfDbuVuM89Cg0urAgpt73QV9mPQ==", + "dependencies": { + "@smithy/config-resolver": "^2.0.23", + "@smithy/credential-provider-imds": "^2.1.5", + "@smithy/node-config-provider": "^2.1.9", + "@smithy/property-provider": "^2.0.17", + "@smithy/smithy-client": "^2.2.1", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.0.8.tgz", + "integrity": "sha512-l8zVuyZZ61IzZBYp5NWvsAhbaAjYkt0xg9R4xUASkg5SEeTT2meHOJwJHctKMFUXe4QZbn9fR2MaBYjP2119+w==", + "dependencies": { + "@smithy/node-config-provider": "^2.1.9", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz", + "integrity": "sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.0.9.tgz", + "integrity": "sha512-PnCnBJ07noMX1lMDTEefmxSlusWJUiLfrme++MfK5TD0xz8NYmakgoXy5zkF/16zKGmiwOeKAztWT/Vjk1KRIQ==", + "dependencies": { + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.0.9.tgz", + "integrity": "sha512-46BFWe9RqB6g7f4mxm3W3HlqknqQQmWHKlhoqSFZuGNuiDU5KqmpebMbvC3tjTlUkqn4xa2Z7s3Hwb0HNs5scw==", + "dependencies": { + "@smithy/service-error-classification": "^2.0.9", + "@smithy/types": "^2.8.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "2.0.24", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.0.24.tgz", + "integrity": "sha512-hRpbcRrOxDriMVmbya+Mv77VZVupxRAsfxVDKS54XuiURhdiwCUXJP0X1iJhHinuUf6n8pBF0MkG9C8VooMnWw==", + "dependencies": { + "@smithy/fetch-http-handler": "^2.3.2", + "@smithy/node-http-handler": "^2.2.2", + "@smithy/types": "^2.8.0", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-buffer-from": "^2.0.0", + "@smithy/util-hex-encoding": "^2.0.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz", + "integrity": "sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==", "dependencies": { - "type-detect": "4.0.8" + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/@smithy/util-utf8": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.2.tgz", + "integrity": "sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA==", "dependencies": { - "@sinonjs/commons": "^3.0.0" + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" } }, "node_modules/@tootallnate/once": { @@ -1720,7 +2865,6 @@ }, "node_modules/@types/body-parser": { "version": "1.19.2", - "dev": true, "license": "MIT", "dependencies": { "@types/connect": "*", @@ -1756,7 +2900,6 @@ }, "node_modules/@types/connect": { "version": "3.4.35", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -1782,9 +2925,10 @@ } }, "node_modules/@types/cookiejar": { - "version": "2.1.2", - "dev": true, - "license": "MIT" + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true }, "node_modules/@types/csurf": { "version": "1.11.4", @@ -1796,9 +2940,9 @@ } }, "node_modules/@types/express": { - "version": "4.17.17", - "dev": true, - "license": "MIT", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -1808,7 +2952,6 @@ }, "node_modules/@types/express-serve-static-core": { "version": "4.17.35", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -1837,8 +2980,7 @@ "node_modules/@types/http-errors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", - "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==", - "dev": true + "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==" }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", @@ -1872,15 +3014,16 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", - "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/json5": { "version": "0.0.29", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true }, "node_modules/@types/jsonwebtoken": { "version": "9.0.4", @@ -1896,16 +3039,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true + }, "node_modules/@types/mime": { "version": "1.3.2", - "dev": true, "license": "MIT" }, "node_modules/@types/node": { - "version": "20.8.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", - "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", - "dev": true, + "version": "20.10.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.8.tgz", + "integrity": "sha512-f8nQs3cLxbAFc00vEU59yf9UyGUftkPaLGfvbVOIDdx2i1b8epBqj2aNGyP19fiyXWvlmZ7qC1XLjAzw/OKIeA==", "dependencies": { "undici-types": "~5.26.4" } @@ -1946,23 +3093,20 @@ }, "node_modules/@types/qs": { "version": "6.9.7", - "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.4", - "dev": true, "license": "MIT" }, "node_modules/@types/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, "node_modules/@types/send": { "version": "0.17.1", - "dev": true, "license": "MIT", "dependencies": { "@types/mime": "^1", @@ -1971,7 +3115,6 @@ }, "node_modules/@types/serve-static": { "version": "1.15.2", - "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", @@ -1999,22 +3142,24 @@ "license": "MIT" }, "node_modules/@types/superagent": { - "version": "4.1.20", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.20.tgz", - "integrity": "sha512-GfpwJgYSr3yO+nArFkmyqv3i0vZavyEG5xPd/o95RwpKYpsOKJYI5XLdxLpdRbZI3YiGKKdIOFIf/jlP7A0Jxg==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.1.tgz", + "integrity": "sha512-YQyEXA4PgCl7EVOoSAS3o0fyPFU6erv5mMixztQYe1bqbWmmn8c+IrqoxjQeZe4MgwXikgcaZPiI/DsbmOVlzA==", "dev": true, "dependencies": { - "@types/cookiejar": "*", + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", "@types/node": "*" } }, "node_modules/@types/supertest": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.15.tgz", - "integrity": "sha512-jUCZZ/TMcpGzoSaed9Gjr8HCf3HehExdibyw3OHHEL1als1KmyzcOZZH4MjbObI8TkWsEr7bc7gsW0WTDni+qQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.2.tgz", + "integrity": "sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==", "dev": true, "dependencies": { - "@types/superagent": "*" + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" } }, "node_modules/@types/uuid": { @@ -2046,16 +3191,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.1.tgz", - "integrity": "sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.18.1.tgz", + "integrity": "sha512-nISDRYnnIpk7VCFrGcu1rnZfM1Dh9LRHnfgdkjcbi/l7g16VYRri3TjXi9Ir4lOZSw5N/gnV/3H7jIPQ8Q4daA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/type-utils": "6.9.1", - "@typescript-eslint/utils": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/type-utils": "6.18.1", + "@typescript-eslint/utils": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -2081,15 +3226,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz", - "integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.18.1.tgz", + "integrity": "sha512-zct/MdJnVaRRNy9e84XnVtRv9Vf91/qqe+hZJtKanjojud4wAVy/7lXxJmMyX6X6J+xc6c//YEWvpeif8cAhWA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/typescript-estree": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/typescript-estree": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", "debug": "^4.3.4" }, "engines": { @@ -2109,13 +3254,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.9.1.tgz", - "integrity": "sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz", + "integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1" + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -2126,13 +3271,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.9.1.tgz", - "integrity": "sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.18.1.tgz", + "integrity": "sha512-wyOSKhuzHeU/5pcRDP2G2Ndci+4g653V43gXTpt4nbyoIOAASkGDA9JIAgbQCdCkcr1MvpSYWzxTz0olCn8+/Q==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.9.1", - "@typescript-eslint/utils": "6.9.1", + "@typescript-eslint/typescript-estree": "6.18.1", + "@typescript-eslint/utils": "6.18.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -2153,9 +3298,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.1.tgz", - "integrity": "sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz", + "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -2166,16 +3311,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.1.tgz", - "integrity": "sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz", + "integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -2192,18 +3338,42 @@ } } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/utils": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.9.1.tgz", - "integrity": "sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz", + "integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/typescript-estree": "6.18.1", "semver": "^7.5.4" }, "engines": { @@ -2218,12 +3388,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.1.tgz", - "integrity": "sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz", + "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/types": "6.18.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -2279,8 +3449,9 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -2319,8 +3490,9 @@ }, "node_modules/ajv": { "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2332,6 +3504,42 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/ansi-colors": { "version": "4.1.3", "dev": true, @@ -2408,23 +3616,23 @@ } }, "node_modules/applicationinsights": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-2.9.0.tgz", - "integrity": "sha512-W90WNjtvZ10GUInpkyNM0xBGe2qRYChHhdb44SE5KU7hXtCZLxs3IZjWw1gJINQem0qGAgtZlxrVvKPj5SlTbQ==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-2.9.2.tgz", + "integrity": "sha512-wlDiD7v0BQNM8oNzsf9C836R5ze25u+CuCEZsbA5xMIXYYBxkqkWE/mo9GFJM7rsKaiGqpxEwWmePHKD2Lwy2w==", "dependencies": { "@azure/core-auth": "^1.5.0", "@azure/core-rest-pipeline": "1.10.1", "@azure/core-util": "1.2.0", "@azure/opentelemetry-instrumentation-azure-sdk": "^1.0.0-beta.5", "@microsoft/applicationinsights-web-snippet": "^1.0.1", - "@opentelemetry/api": "^1.4.1", - "@opentelemetry/core": "^1.15.2", - "@opentelemetry/sdk-trace-base": "^1.15.2", - "@opentelemetry/semantic-conventions": "^1.15.2", + "@opentelemetry/api": "^1.7.0", + "@opentelemetry/core": "^1.19.0", + "@opentelemetry/sdk-trace-base": "^1.19.0", + "@opentelemetry/semantic-conventions": "^1.19.0", "cls-hooked": "^4.2.2", "continuation-local-storage": "^3.2.1", "diagnostic-channel": "1.1.1", - "diagnostic-channel-publishers": "1.0.7" + "diagnostic-channel-publishers": "1.0.8" }, "engines": { "node": ">=8.0.0" @@ -2438,6 +3646,14 @@ } } }, + "node_modules/applicationinsights/node_modules/@opentelemetry/api": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.7.0.tgz", + "integrity": "sha512-AdY5wvN0P2vXBi3b29hxZgSFvdhdxPB9+f0B6s//P9Q8nibRWeA3cHm8UmLpio9ABigkVHJ5NMPk+Mz8VCCyrw==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/arch": { "version": "2.2.0", "dev": true, @@ -2823,14 +4039,6 @@ "tweetnacl": "^0.14.3" } }, - "node_modules/big-integer": { - "version": "1.6.51", - "dev": true, - "license": "Unlicense", - "engines": { - "node": ">=0.6" - } - }, "node_modules/binary-extensions": { "version": "2.2.0", "devOptional": true, @@ -2886,16 +4094,10 @@ "version": "2.0.0", "license": "MIT" }, - "node_modules/bplist-parser": { - "version": "0.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "big-integer": "^1.6.44" - }, - "engines": { - "node": ">= 5.10.0" - } + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -3014,20 +4216,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bundle-name": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "run-applescript": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/bunyan": { "version": "1.8.15", "engines": [ @@ -3751,9 +4939,9 @@ } }, "node_modules/cypress": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.4.0.tgz", - "integrity": "sha512-KeWNC9xSHG/ewZURVbaQsBQg2mOKw4XhjJZFKjWbEjgZCdxpPXLpJnfq5Jns1Gvnjp6AlnIfpZfWFlDgVKXdWQ==", + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.2.tgz", + "integrity": "sha512-TW3bGdPU4BrfvMQYv1z3oMqj71YI4AlgJgnrycicmPZAXtvywVFZW9DAToshO65D97rCWfG/kqMFsYB6Kp91gQ==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -3846,228 +5034,82 @@ }, "node_modules/date-fns": { "version": "2.30.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, - "node_modules/dateformat": { - "version": "3.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/dayjs": { - "version": "1.11.9", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.3.4", - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dedent": { - "version": "1.5.1", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-browser": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-name": "^3.0.0", - "default-browser-id": "^3.0.0", - "execa": "^7.1.1", - "titleize": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "bplist-parser": "^0.2.0", - "untildify": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/execa": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/default-browser/node_modules/get-stream": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/human-signals": { - "version": "4.3.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/default-browser/node_modules/is-stream": { - "version": "3.0.0", - "dev": true, "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=0.11" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/date-fns" } }, - "node_modules/default-browser/node_modules/mimic-fn": { - "version": "4.0.0", + "node_modules/dateformat": { + "version": "3.0.2", "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "*" } }, - "node_modules/default-browser/node_modules/npm-run-path": { - "version": "5.1.0", + "node_modules/dayjs": { + "version": "1.11.9", "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.3.4", "license": "MIT", "dependencies": { - "path-key": "^4.0.0" + "ms": "2.1.2" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/default-browser/node_modules/onetime": { - "version": "6.0.0", + "node_modules/decamelize": { + "version": "4.0.0", "dev": true, "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, + "peer": true, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/default-browser/node_modules/path-key": { - "version": "4.0.0", + "node_modules/dedent": { + "version": "1.5.1", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } } }, - "node_modules/default-browser/node_modules/strip-final-newline": { - "version": "3.0.0", + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, "node_modules/define-data-property": { @@ -4083,17 +5125,6 @@ "node": ">= 0.4" } }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/define-properties": { "version": "1.2.0", "dev": true, @@ -4149,14 +5180,16 @@ }, "node_modules/diagnostic-channel": { "version": "1.1.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-1.1.1.tgz", + "integrity": "sha512-r2HV5qFkUICyoaKlBEpLKHjxMXATUf/l+h8UZPGBHGLy4DDiY2sOLcIctax4eRnTw5wH2jTMExLntGPJ8eOJxw==", "dependencies": { "semver": "^7.5.3" } }, "node_modules/diagnostic-channel-publishers": { - "version": "1.0.7", - "license": "MIT", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/diagnostic-channel-publishers/-/diagnostic-channel-publishers-1.0.8.tgz", + "integrity": "sha512-HmSm9hXxSPxA9BaLGY98QU1zsdjeCk113KjAYGPCen1ZP6mhVaTPzHd6UYv5r21DnWANi+f+NyPOHruGT9jpqQ==", "peerDependencies": { "diagnostic-channel": "*" } @@ -4443,15 +5476,15 @@ } }, "node_modules/eslint": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", - "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.52.0", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -4614,9 +5647,9 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "dependencies": { "array-includes": "^3.1.7", @@ -4635,7 +5668,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -4680,23 +5713,24 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz", - "integrity": "sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.2.tgz", + "integrity": "sha512-dhlpWc9vOwohcWmClFcA+HjlvUpuyynYs0Rf+L/P6/0iQE6vlHW9l5bkfzN62/Stm9fbq8ku46qzde76T1xlSg==", "dev": true, "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.5" + "synckit": "^0.8.6" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/prettier" + "url": "https://opencollective.com/eslint-plugin-prettier" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", + "eslint-config-prettier": "*", "prettier": ">=3.0.0" }, "peerDependenciesMeta": { @@ -4736,8 +5770,9 @@ }, "node_modules/espree": { "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -4928,17 +5963,20 @@ } }, "node_modules/express-prom-bundle": { - "version": "6.6.0", - "license": "MIT", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/express-prom-bundle/-/express-prom-bundle-7.0.0.tgz", + "integrity": "sha512-VwVaCyGBGHkHdecpTqRdW1Jm2fXK8weCUKjGjNWorc9g4M+cZ3xoj+N9uQzfRWfIPXJG5QOaiAziOIalQzMwgA==", "dependencies": { + "@types/express": "^4.17.21", + "express": "^4.18.2", "on-finished": "^2.3.0", "url-value-parser": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "peerDependencies": { - "prom-client": ">=12.0.0" + "prom-client": ">=15.0.0" } }, "node_modules/express-session": { @@ -5099,8 +6137,8 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-diff": { "version": "1.3.0", @@ -5147,6 +6185,27 @@ "version": "2.1.1", "license": "MIT" }, + "node_modules/fast-xml-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", + "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "funding": [ + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + }, + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.15.0", "dev": true, @@ -5618,8 +6677,9 @@ } }, "node_modules/govuk-frontend": { - "version": "4.7.0", - "license": "MIT", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/govuk-frontend/-/govuk-frontend-5.0.0.tgz", + "integrity": "sha512-3WSfvQ+3kw/q/m8jrq/t8XnMUA8D2r0uhGyZaDbIh1gWTJBQzJBHbHiKYI9nc9ixIXdCFsc9RozkgEm57a795g==", "engines": { "node": ">= 4.2.0" } @@ -5872,8 +6932,9 @@ }, "node_modules/import-fresh": { "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, - "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -6077,20 +7138,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-docker": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "devOptional": true, @@ -6130,23 +7177,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-installed-globally": { "version": "0.4.0", "dev": true, @@ -6318,31 +7348,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-wsl/node_modules/is-docker": { - "version": "2.2.1", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isarray": { "version": "2.0.5", "dev": true, @@ -7088,8 +8093,9 @@ }, "node_modules/json-schema-traverse": { "version": "0.4.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -8553,23 +9559,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/open": { - "version": "9.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "default-browser": "^4.0.0", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.3", "dev": true, @@ -8643,8 +9632,9 @@ }, "node_modules/parent-module": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -8892,9 +9882,10 @@ } }, "node_modules/prettier": { - "version": "3.0.3", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", + "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", "dev": true, - "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -8917,9 +9908,9 @@ } }, "node_modules/prettier-plugin-jinja-template": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-jinja-template/-/prettier-plugin-jinja-template-1.3.1.tgz", - "integrity": "sha512-fw/AvJ4nTYwqnPX3rtIZ9vEJGJIpyD/b4tRnJS4bqsEtxHW7HzUm0qRWNXZOq8UxMsxiWRtKR0l0zn+lKZKRWQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-jinja-template/-/prettier-plugin-jinja-template-1.3.2.tgz", + "integrity": "sha512-ROglSKajIA4TRxM5othKfhc+dZrWYaVoJW/0EHlN0WwCXS1FmF/2mtfDkJW4wSTBqu7re1svsYMAu+oMGf6T9Q==", "dev": true, "peerDependencies": { "prettier": "^3.0.0" @@ -8969,9 +9960,9 @@ } }, "node_modules/prom-client": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.0.0.tgz", - "integrity": "sha512-UocpgIrKyA2TKLVZDSfm8rGkL13C19YrQBAiG3xo3aDFWcHedxRxI3z+cIcucoxpSO0h5lff5iv/SXoxyeopeA==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.0.tgz", + "integrity": "sha512-cCD7jLTqyPdjEPBo/Xk4Iu8jxjuZgZJ3e/oET3L+ZwOuap/7Cw3dH/TJSsZKs1TQLZ2IHpIlRAKw82ef06kmMw==", "dependencies": { "@opentelemetry/api": "^1.4.0", "tdigest": "^0.1.1" @@ -9037,7 +10028,6 @@ }, "node_modules/punycode": { "version": "2.3.0", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9156,23 +10146,20 @@ } }, "node_modules/redis": { - "version": "4.6.10", - "license": "MIT", - "workspaces": [ - "./packages/*" - ], + "version": "4.6.12", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.12.tgz", + "integrity": "sha512-41Xuuko6P4uH4VPe5nE3BqXHB7a9lkFL0J29AlxKaIfD6eWO8VO/5PDF9ad2oS+mswMsfFxaM5DlE3tnXT+P8Q==", "dependencies": { "@redis/bloom": "1.2.0", - "@redis/client": "1.5.11", - "@redis/graph": "1.1.0", + "@redis/client": "1.5.13", + "@redis/graph": "1.1.1", "@redis/json": "1.0.6", - "@redis/search": "1.1.5", + "@redis/search": "1.1.6", "@redis/time-series": "1.0.5" } }, "node_modules/regenerator-runtime": { "version": "0.13.11", - "dev": true, "license": "MIT" }, "node_modules/regexp.prototype.flags": { @@ -9207,6 +10194,14 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-in-the-middle": { "version": "7.2.0", "license": "MIT", @@ -9261,8 +10256,9 @@ }, "node_modules/resolve-from": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -9327,61 +10323,6 @@ "version": "1.2.0", "license": "MIT" }, - "node_modules/run-applescript": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/execa": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/run-applescript/node_modules/get-stream": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/human-signals": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "dev": true, @@ -9456,9 +10397,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.69.5", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz", - "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", + "version": "1.69.7", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz", + "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -9904,6 +10845,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "node_modules/superagent": { "version": "8.1.2", "license": "MIT", @@ -9970,12 +10916,13 @@ } }, "node_modules/synckit": { - "version": "0.8.5", + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", "dev": true, - "license": "MIT", "dependencies": { - "@pkgr/utils": "^2.3.1", - "tslib": "^2.5.0" + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -10027,17 +10974,6 @@ "dev": true, "license": "MIT" }, - "node_modules/titleize": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/tmp": { "version": "0.2.1", "dev": true, @@ -10183,9 +11119,10 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, - "license": "MIT", "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -10195,8 +11132,9 @@ }, "node_modules/tsconfig-paths/node_modules/json5": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, - "license": "MIT", "dependencies": { "minimist": "^1.2.0" }, @@ -10206,15 +11144,17 @@ }, "node_modules/tsconfig-paths/node_modules/strip-bom": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/tslib": { - "version": "2.6.0", - "license": "0BSD" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsscmp": { "version": "1.0.6", @@ -10390,8 +11330,7 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/universalify": { "version": "2.0.0", @@ -10447,8 +11386,8 @@ }, "node_modules/uri-js": { "version": "4.4.1", - "dev": true, - "license": "BSD-2-Clause", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dependencies": { "punycode": "^2.1.0" } diff --git a/package.json b/package.json index c5cb8107..4250fee5 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "prepare": "husky install", "copy-views": "cp -R server/views dist/server/", - "compile-sass": "sass --quiet-deps --no-source-map --load-path=node_modules/govuk-frontend --load-path=node_modules/@ministryofjustice/frontend --load-path=. assets/scss/application.scss:./assets/stylesheets/application.css assets/scss/application-ie8.scss:./assets/stylesheets/application-ie8.css --style compressed", + "compile-sass": "sass --quiet-deps --no-source-map --load-path=node_modules/govuk-frontend/dist --load-path=node_modules/@ministryofjustice/frontend --load-path=. assets/scss/application.scss:./assets/stylesheets/application.css --style compressed", "watch-ts": "tsc -w", "watch-views": "nodemon --watch server/views -e html,njk -x npm run copy-views", "watch-node": "DEBUG=gov-starter-server* nodemon -r dotenv/config --watch dist/ dist/server.js | bunyan -o short", @@ -20,8 +20,8 @@ "start-feature:dev": "concurrently -k -p \"[{name}]\" -n \"Views,TypeScript,Node,Sass\" -c \"yellow.bold,cyan.bold,green.bold,blue.bold\" \"npm run watch-views\" \"npm run watch-ts\" \"npm run watch-node-feature\" \"npm run watch-sass\"", "lint": "eslint . --cache --max-warnings 0", "typecheck": "tsc && tsc -p integration_tests", - "test": "jest", - "test:ci": "jest --runInBand", + "test": "TZ=Europe/London jest", + "test:ci": "TZ=Europe/London jest --runInBand", "security_audit": "npx audit-ci --config audit-ci.json", "int-test": "cypress run --config video=false", "int-test-ui": "cypress open", @@ -91,10 +91,12 @@ ] }, "dependencies": { + "@aws-sdk/client-sqs": "^3.410.0", "@faker-js/faker": "^8.0.0", - "@ministryofjustice/frontend": "^1.8.0", + "@ministryofjustice/frontend": "^2.0.0", + "@ministryofjustice/hmpps-audit-client": "^1.0.18", "agentkeepalive": "^4.5.0", - "applicationinsights": "^2.9.0", + "applicationinsights": "^2.9.2", "body-parser": "^1.20.2", "bunyan": "^1.8.15", "bunyan-format": "^0.2.1", @@ -102,11 +104,12 @@ "connect-flash": "^0.1.1", "connect-redis": "^7.1.0", "csurf": "^1.11.0", + "date-fns": "^2.30.0", "express": "^4.18.2", - "express-prom-bundle": "^6.6.0", + "express-prom-bundle": "^7.0.0", "express-session": "^1.17.3", "fishery": "^2.2.2", - "govuk-frontend": "^4.7.0", + "govuk-frontend": "^5.0.0", "helmet": "^7.0.0", "http-errors": "^2.0.0", "jquery": "^3.7.1", @@ -115,8 +118,8 @@ "nunjucks": "^3.2.4", "passport": "^0.6.0", "passport-oauth2": "^1.7.0", - "prom-client": "^15.0.0", - "redis": "^4.6.10", + "prom-client": "^15.1.0", + "redis": "^4.6.12", "superagent": "^8.1.2", "url-value-parser": "^2.2.0", "uuid": "^9.0.1" @@ -134,29 +137,29 @@ "@types/http-errors": "^2.0.3", "@types/jest": "^29.5.6", "@types/jsonwebtoken": "^9.0.4", - "@types/node": "^20.8.9", + "@types/node": "^20.10.7", "@types/nunjucks": "^3.2.5", "@types/passport": "^1.0.14", "@types/passport-oauth2": "^1.4.14", - "@types/superagent": "^4.1.20", - "@types/supertest": "^2.0.15", + "@types/superagent": "^8.1.1", + "@types/supertest": "^6.0.2", "@types/uuid": "^9.0.6", - "@typescript-eslint/eslint-plugin": "^6.9.0", - "@typescript-eslint/parser": "^6.9.0", + "@typescript-eslint/eslint-plugin": "^6.18.1", + "@typescript-eslint/parser": "^6.18.1", "audit-ci": "^6.6.1", "concurrently": "^8.2.2", "cookie-session": "^2.0.0", - "cypress": "^13.3.3", + "cypress": "^13.6.2", "cypress-multi-reporters": "^1.6.4", "dotenv": "^16.3.1", - "eslint": "^8.52.0", + "eslint": "^8.56.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^9.0.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-cypress": "^2.15.1", - "eslint-plugin-import": "^2.29.0", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-no-only-tests": "^3.1.0", - "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-prettier": "^5.1.2", "husky": "^8.0.3", "jest": "^29.7.0", "jest-html-reporter": "^3.10.2", @@ -166,9 +169,9 @@ "mocha-junit-reporter": "^2.2.1", "nock": "^13.3.6", "nodemon": "^3.0.1", - "prettier": "^3.0.3", - "prettier-plugin-jinja-template": "^1.3.1", - "sass": "^1.69.5", + "prettier": "^3.1.1", + "prettier-plugin-jinja-template": "^1.3.2", + "sass": "^1.69.7", "supertest": "^6.3.3", "ts-jest": "^29.1.1", "typescript": "^5.3.3" diff --git a/server/@types/audit-client/index.d.ts b/server/@types/audit-client/index.d.ts new file mode 100644 index 00000000..a75dcae3 --- /dev/null +++ b/server/@types/audit-client/index.d.ts @@ -0,0 +1 @@ +declare module '@ministryofjustice/hmpps-audit-client' diff --git a/server/audit/baseClientAudit.ts b/server/audit/baseClientAudit.ts new file mode 100644 index 00000000..6551454e --- /dev/null +++ b/server/audit/baseClientAudit.ts @@ -0,0 +1,43 @@ +import { v4 as uuidv4 } from 'uuid' +import { auditService } from '@ministryofjustice/hmpps-audit-client' +import { BaseClientEvent } from './baseClientEvent' +import config from '../config' +import logger from '../../logger' + +const BASE_CLIENT_SUBJECT_TYPE = 'BASE_CLIENT_ID' + +export const sendBaseClientEvent = async ( + baseClientEvent: BaseClientEvent, + baseClientId: string, + details: Record, + username: string, + correlationId: string, +) => { + if (!config.apis.audit.enabled) { + logger.info(`${baseClientEvent} - ${baseClientId} - ${JSON.stringify(details)}`) + } else { + await auditService.sendAuditMessage({ + action: baseClientEvent, + who: username, + subjectId: baseClientId, + subjectType: BASE_CLIENT_SUBJECT_TYPE, + correlationId, + details: JSON.stringify(details), + }) + } +} + +const baseClientAudit = (username: string): BaseClientAuditFunction => { + const correlationId = uuidv4() + return async (baseClientEvent: BaseClientEvent, baseClientId?: string, details?: Record) => { + await sendBaseClientEvent(baseClientEvent, baseClientId, details, username, correlationId) + } +} + +export default baseClientAudit + +export type BaseClientAuditFunction = ( + baseClientEvent: BaseClientEvent, + baseClientId?: string, + details?: Record, +) => Promise diff --git a/server/audit/baseClientEvent.ts b/server/audit/baseClientEvent.ts new file mode 100644 index 00000000..574b4326 --- /dev/null +++ b/server/audit/baseClientEvent.ts @@ -0,0 +1,19 @@ +// eslint-disable-next-line no-shadow,import/prefer-default-export +export enum BaseClientEvent { + LIST_BASE_CLIENTS = 'LIST_BASE_CLIENTS', + LIST_BASE_CLIENTS_FAILURE = 'LIST_BASE_CLIENTS_FAILURE', + VIEW_BASE_CLIENT = 'VIEW_BASE_CLIENT', + VIEW_BASE_CLIENT_FAILURE = 'VIEW_BASE_CLIENT_FAILURE', + CREATE_BASE_CLIENT = 'CREATE_BASE_CLIENT', + CREATE_BASE_CLIENT_FAILURE = 'CREATE_BASE_CLIENT_FAILURE', + UPDATE_BASE_CLIENT = 'UPDATE_BASE_CLIENT', + UPDATE_BASE_CLIENT_FAILURE = 'UPDATE_BASE_CLIENT_FAILURE', + UPDATE_BASE_CLIENT_DEPLOYMENT = 'UPDATE_BASE_CLIENT_DEPLOYMENT', + UPDATE_BASE_CLIENT_DEPLOYMENT_FAILURE = 'UPDATE_BASE_CLIENT_DEPLOYMENT_FAILURE', + CREATE_CLIENT = 'CREATE_CLIENT', + CREATE_CLIENT_FAILURE = 'CREATE_CLIENT_FAILURE', + DELETE_CLIENT = 'DELETE_CLIENT', + DELETE_CLIENT_FAILURE = 'DELETE_CLIENT_FAILURE', + VIEW_CLIENT_SECRETS = 'VIEW_CLIENT_SECRETS', + VIEW_CLIENT_SECRETS_FAILURE = 'VIEW_CLIENT_SECRETS_FAILURE', +} diff --git a/server/config.ts b/server/config.ts index 57c9d478..f610f1e5 100755 --- a/server/config.ts +++ b/server/config.ts @@ -48,6 +48,12 @@ export default { expiryMinutes: Number(get('WEB_SESSION_TIMEOUT_IN_MINUTES', 120)), }, apis: { + audit: { + enabled: get('AUDIT_ENABLED', 'false') === 'true', + region: get('AUDIT_SQS_REGION', 'eu-west-2', requiredInProduction), + queueUrl: get('AUDIT_SQS_QUEUE_URL', 'http://localhost:4566/000000000000/mainQueue', requiredInProduction), + serviceName: get('AUDIT_SERVICE_NAME', 'authorization-ui', requiredInProduction), + }, hmppsAuth: { url: get('HMPPS_AUTH_URL', 'http://localhost:9090/auth', requiredInProduction), externalUrl: get('HMPPS_AUTH_EXTERNAL_URL', get('HMPPS_AUTH_URL', 'http://localhost:9090/auth')), diff --git a/server/controllers/baseClientController.test.ts b/server/controllers/baseClientController.test.ts index 35934d56..4023b6d4 100644 --- a/server/controllers/baseClientController.test.ts +++ b/server/controllers/baseClientController.test.ts @@ -10,6 +10,8 @@ import createUserToken from '../testutils/createUserToken' import viewBaseClientPresenter from '../views/presenters/viewBaseClientPresenter' import nunjucksUtils from '../views/helpers/nunjucksUtils' import editBaseClientPresenter from '../views/presenters/editBaseClientPresenter' +import { BaseClientEvent } from '../audit/baseClientEvent' +import * as baseClientAudit from '../audit/baseClientAudit' describe('BaseClientController', () => { const token = createUserToken(['ADMIN']) @@ -20,6 +22,9 @@ describe('BaseClientController', () => { let baseClientController: BaseClientController beforeEach(() => { + jest.resetAllMocks() + jest.spyOn(baseClientAudit, 'sendBaseClientEvent').mockResolvedValue() + request = createMock() response = createMock({ locals: { @@ -55,11 +60,48 @@ describe('BaseClientController', () => { // AND the list of base clients is retrieved from the base client service expect(baseClientService.listBaseClients).toHaveBeenCalledWith(token) }) + + it('audits the view attempt', async () => { + // GIVEN a list of base clients + const baseClients = baseClientFactory.buildList(3) + baseClientService.listBaseClients.mockResolvedValue(baseClients) + + // WHEN the index page is requested + await baseClientController.displayBaseClients()(request, response, next) + + // THEN a view base clients audit event is sent + expect(baseClientAudit.sendBaseClientEvent).toHaveBeenCalledWith( + BaseClientEvent.LIST_BASE_CLIENTS, + undefined, + undefined, + undefined, + expect.any(String), + ) + }) + + it('audits the view failure', async () => { + // GIVEN an error will be thrown + baseClientService.listBaseClients.mockRejectedValue(404) + + // WHEN the index page is requested + try { + await baseClientController.displayBaseClients()(request, response, next) + } catch (e) { + // THEN a view base clients failure audit event is sent + expect(baseClientAudit.sendBaseClientEvent).toHaveBeenCalledWith( + BaseClientEvent.LIST_BASE_CLIENTS_FAILURE, + undefined, + undefined, + undefined, + expect.any(String), + ) + } + }) }) describe('view base client', () => { - it('renders the main view of a base clients', async () => { - // GIVEN a list of base clients + it('renders the main view of a base client', async () => { + // GIVEN a base client const baseClient = baseClientFactory.build() const clients = clientFactory.buildList(3) baseClientService.getBaseClient.mockResolvedValue(baseClient) @@ -80,6 +122,49 @@ describe('BaseClientController', () => { // AND the base client is retrieved from the base client service expect(baseClientService.getBaseClient).toHaveBeenCalledWith(token, baseClient.baseClientId) }) + + it('audits the view attempt', async () => { + // GIVEN a base client + const baseClient = baseClientFactory.build() + const clients = clientFactory.buildList(3) + baseClientService.getBaseClient.mockResolvedValue(baseClient) + baseClientService.listClientInstances.mockResolvedValue(clients) + + // WHEN the base client page is requested + request = createMock({ params: { baseClientId: baseClient.baseClientId } }) + await baseClientController.displayBaseClient()(request, response, next) + + // THEN a view base client audit event is sent + expect(baseClientAudit.sendBaseClientEvent).toHaveBeenCalledWith( + BaseClientEvent.VIEW_BASE_CLIENT, + baseClient.baseClientId, + undefined, + undefined, + expect.any(String), + ) + }) + + it('audits the view failure', async () => { + // GIVEN an error will be thrown + const baseClientId = '1234' + baseClientService.getBaseClient.mockRejectedValue(404) + baseClientService.listClientInstances.mockRejectedValue(404) + + // WHEN the base client page is requested + try { + request = createMock({ params: { baseClientId } }) + await baseClientController.displayBaseClient()(request, response, next) + } catch (e) { + // THEN a view base clients failure audit event is sent + expect(baseClientAudit.sendBaseClientEvent).toHaveBeenCalledWith( + BaseClientEvent.VIEW_BASE_CLIENT_FAILURE, + baseClientId, + undefined, + undefined, + expect.any(String), + ) + } + }) }) describe('create base client', () => { @@ -105,6 +190,7 @@ describe('BaseClientController', () => { // THEN the enter client details page is rendered with client credentials selected expect(response.render).toHaveBeenCalledWith('pages/new-base-client-details.njk', { grant: 'client-credentials', + presenter: editBaseClientPresenter(null), ...nunjucksUtils, }) }) @@ -119,6 +205,7 @@ describe('BaseClientController', () => { // THEN the enter client details page is rendered with authorisation code selected expect(response.render).toHaveBeenCalledWith('pages/new-base-client-details.njk', { grant: 'authorization-code', + presenter: editBaseClientPresenter(null), ...nunjucksUtils, }) }) @@ -147,6 +234,15 @@ describe('BaseClientController', () => { 'pages/new-base-client-details.njk', expect.objectContaining({ errorMessage: { text: expectedError } }), ) + + // AND the fail is audited + expect(baseClientAudit.sendBaseClientEvent).toHaveBeenCalledWith( + BaseClientEvent.CREATE_BASE_CLIENT_FAILURE, + expect.any(String), + expect.objectContaining({ error: expectedError }), + undefined, + expect.any(String), + ) }) it('if validation fails because id exists then render the details screen with error message', async () => { @@ -164,6 +260,15 @@ describe('BaseClientController', () => { 'pages/new-base-client-details.njk', expect.objectContaining({ errorMessage: { text: expectedError } }), ) + + // AND the fail is audited + expect(baseClientAudit.sendBaseClientEvent).toHaveBeenCalledWith( + BaseClientEvent.CREATE_BASE_CLIENT_FAILURE, + expect.any(String), + expect.objectContaining({ error: expectedError }), + undefined, + expect.any(String), + ) }) it('if success renders the secrets screen', async () => { @@ -181,8 +286,76 @@ describe('BaseClientController', () => { expect(response.render).toHaveBeenCalledWith( 'pages/new-base-client-success.njk', expect.objectContaining({ secrets }), + expect.anything(), ) }) + + it('audits the create attempt', async () => { + // GIVEN the service returns success and a set of secrets + const secrets = clientSecretsFactory.build() + baseClientService.addBaseClient.mockResolvedValue(secrets) + + // WHEN it is posted + await baseClientController.createBaseClient()(request, response, next) + + // THEN the attempt is audited + expect(baseClientAudit.sendBaseClientEvent).toHaveBeenCalledWith( + BaseClientEvent.CREATE_BASE_CLIENT, + undefined, + undefined, + undefined, + expect.any(String), + ) + }) + + it('audits the secrets viewing attempt if successful', async () => { + // GIVEN the service returns success and a set of secrets + const secrets = clientSecretsFactory.build() + // set request body to include baseClientId + request = createMock({ body: { baseClientId: 'abcd' } }) + baseClientService.addBaseClient.mockResolvedValue(secrets) + baseClientService.getBaseClient.mockRejectedValue({ status: 404 }) + + // WHEN it is posted + await baseClientController.createBaseClient()(request, response, next) + + // THEN the attempt is audited + expect(baseClientAudit.sendBaseClientEvent).toHaveBeenCalledWith( + BaseClientEvent.VIEW_CLIENT_SECRETS, + expect.any(String), + { clientId: expect.any(String) }, + undefined, + expect.any(String), + ) + }) + + it('audits the secrets viewing attempt if fail', async () => { + // GIVEN the post is properly set up + const secrets = clientSecretsFactory.build() + request = createMock({ body: { baseClientId: 'abcd' } }) + baseClientService.addBaseClient.mockResolvedValue(secrets) + baseClientService.getBaseClient.mockRejectedValue({ status: 404 }) + // but the render fails + response.render.mockImplementation( + (view: string, options: object, callback?: (err: Error, html: string) => void) => { + callback(new Error('Test error'), '') + }, + ) + + // WHEN it is posted + try { + await baseClientController.createBaseClient()(request, response, next) + } catch (e) { + // THEN the view attempt is audited + expect(baseClientAudit.sendBaseClientEvent).toHaveBeenCalledWith( + BaseClientEvent.VIEW_CLIENT_SECRETS_FAILURE, + undefined, + undefined, + undefined, + expect.any(String), + ) + } + }) }) }) @@ -226,6 +399,54 @@ describe('BaseClientController', () => { // AND the user is redirected to the view base client page expect(response.redirect).toHaveBeenCalledWith(`/base-clients/${baseClient.baseClientId}`) }) + + it('audits the update attempt', async () => { + // GIVEN the service will return without an error + const baseClient = baseClientFactory.build() + request = createMock({ + params: { baseClientId: baseClient.baseClientId }, + body: { baseClientId: baseClient.baseClientId }, + }) + baseClientService.getBaseClient.mockResolvedValue(baseClient) + baseClientService.updateBaseClient.mockResolvedValue(new Response()) + + // WHEN it is posted + await baseClientController.updateBaseClientDetails()(request, response, next) + + // THEN the attempt is audited + expect(baseClientAudit.sendBaseClientEvent).toHaveBeenCalledWith( + BaseClientEvent.UPDATE_BASE_CLIENT, + baseClient.baseClientId, + undefined, + undefined, + expect.any(String), + ) + }) + + it('audits a failed update attempt', async () => { + // GIVEN the service will return without an error + const baseClient = baseClientFactory.build() + request = createMock({ + params: { baseClientId: baseClient.baseClientId }, + body: { baseClientId: baseClient.baseClientId }, + }) + baseClientService.getBaseClient.mockResolvedValue(baseClient) + baseClientService.updateBaseClient.mockRejectedValue(404) + + // WHEN it is posted + try { + await baseClientController.updateBaseClientDetails()(request, response, next) + } catch (e) { + // THEN the attempt is audited + expect(baseClientAudit.sendBaseClientEvent).toHaveBeenCalledWith( + BaseClientEvent.UPDATE_BASE_CLIENT_FAILURE, + baseClient.baseClientId, + undefined, + undefined, + expect.any(String), + ) + } + }) }) describe('update base client deployment', () => { @@ -265,6 +486,54 @@ describe('BaseClientController', () => { // AND the user is redirected to the view base client page expect(response.redirect).toHaveBeenCalledWith(`/base-clients/${baseClient.baseClientId}`) }) + + it('audits the update attempt', async () => { + // GIVEN the service will return without an error + const baseClient = baseClientFactory.build() + request = createMock({ + params: { baseClientId: baseClient.baseClientId }, + body: { baseClientId: baseClient.baseClientId }, + }) + baseClientService.getBaseClient.mockResolvedValue(baseClient) + baseClientService.updateBaseClientDeployment.mockResolvedValue(new Response()) + + // WHEN it is posted + await baseClientController.updateBaseClientDeployment()(request, response, next) + + // THEN the attempt is audited + expect(baseClientAudit.sendBaseClientEvent).toHaveBeenCalledWith( + BaseClientEvent.UPDATE_BASE_CLIENT_DEPLOYMENT, + baseClient.baseClientId, + undefined, + undefined, + expect.any(String), + ) + }) + + it('audits a failed update attempt', async () => { + // GIVEN the service will return without an error + const baseClient = baseClientFactory.build() + request = createMock({ + params: { baseClientId: baseClient.baseClientId }, + body: { baseClientId: baseClient.baseClientId }, + }) + baseClientService.getBaseClient.mockResolvedValue(baseClient) + baseClientService.updateBaseClientDeployment.mockRejectedValue(404) + + // WHEN it is posted + try { + await baseClientController.updateBaseClientDeployment()(request, response, next) + } catch (e) { + // THEN the attempt is audited + expect(baseClientAudit.sendBaseClientEvent).toHaveBeenCalledWith( + BaseClientEvent.UPDATE_BASE_CLIENT_DEPLOYMENT_FAILURE, + baseClient.baseClientId, + undefined, + undefined, + expect.any(String), + ) + } + }) }) describe('create client instance', () => { @@ -284,8 +553,53 @@ describe('BaseClientController', () => { expect(response.render).toHaveBeenCalledWith( 'pages/new-base-client-success.njk', expect.objectContaining({ secrets }), + expect.anything(), + ) + }) + + it('audits the update attempt', async () => { + // GIVEN the service returns success and a set of secrets + const baseClient = baseClientFactory.build() + baseClientService.getBaseClient.mockResolvedValue(baseClient) + request = createMock({ body: { baseClientId: baseClient.baseClientId } }) + + const secrets = clientSecretsFactory.build() + baseClientService.addClientInstance.mockResolvedValue(secrets) + + // WHEN it is posted + await baseClientController.createClientInstance()(request, response, next) + + expect(baseClientAudit.sendBaseClientEvent).toHaveBeenCalledWith( + BaseClientEvent.CREATE_CLIENT, + undefined, + undefined, + undefined, + expect.any(String), ) }) + + it('audits a failed update attempt', async () => { + // GIVEN the service returns success and a set of secrets + const baseClient = baseClientFactory.build() + baseClientService.getBaseClient.mockResolvedValue(baseClient) + request = createMock({ body: { baseClientId: baseClient.baseClientId } }) + + baseClientService.addClientInstance.mockRejectedValue(400) + + try { + // WHEN it is posted + await baseClientController.createClientInstance()(request, response, next) + } catch (e) { + // THEN the failed attempt is audited + expect(baseClientAudit.sendBaseClientEvent).toHaveBeenCalledWith( + BaseClientEvent.CREATE_CLIENT_FAILURE, + undefined, + undefined, + undefined, + expect.any(String), + ) + } + }) }) describe('delete client instance', () => { @@ -392,6 +706,61 @@ describe('BaseClientController', () => { const expectedURL = `/base-clients/${baseClient.baseClientId}/clients/${client.clientId}/delete?error=clientIdMismatch` expect(response.redirect).toHaveBeenCalledWith(expectedURL) }) + + it('audits the delete attempt', async () => { + // GIVEN a base client + const baseClient = baseClientFactory.build({ baseClientId: 'abcd' }) + const clients = clientFactory.buildList(2) + const client = clients[0] + baseClientService.getBaseClient.mockResolvedValue(baseClient) + baseClientService.listClientInstances.mockResolvedValue(clients) + + // WHEN a delete request is made + request = createMock({ + params: { baseClientId: baseClient.baseClientId, clientId: client.clientId }, + query: { error: null }, + body: { confirm: client.clientId }, + }) + await baseClientController.deleteClientInstance()(request, response, next) + + // THEN the delete attempt is audited + expect(baseClientAudit.sendBaseClientEvent).toHaveBeenCalledWith( + BaseClientEvent.DELETE_CLIENT, + baseClient.baseClientId, + { clientId: client.clientId }, + undefined, + expect.any(String), + ) + }) + + it('audits a failed update attempt', async () => { + // GIVEN a base client + const baseClient = baseClientFactory.build({ baseClientId: 'abcd' }) + const clients = clientFactory.buildList(2) + const client = clients[0] + baseClientService.getBaseClient.mockResolvedValue(baseClient) + baseClientService.listClientInstances.mockResolvedValue(clients) + baseClientService.deleteClientInstance.mockRejectedValue(404) + + // WHEN a delete request is made + request = createMock({ + params: { baseClientId: baseClient.baseClientId, clientId: client.clientId }, + query: { error: null }, + body: { confirm: client.clientId }, + }) + try { + await baseClientController.deleteClientInstance()(request, response, next) + } catch (e) { + // THEN the failed attempt is audited + expect(baseClientAudit.sendBaseClientEvent).toHaveBeenCalledWith( + BaseClientEvent.DELETE_CLIENT_FAILURE, + baseClient.baseClientId, + { clientId: client.clientId }, + undefined, + expect.any(String), + ) + } + }) }) }) }) diff --git a/server/controllers/baseClientController.ts b/server/controllers/baseClientController.ts index aef6a3dd..f9bebb5b 100644 --- a/server/controllers/baseClientController.ts +++ b/server/controllers/baseClientController.ts @@ -1,102 +1,113 @@ -import { RequestHandler } from 'express' +import { RequestHandler, Response } from 'express' import { BaseClientService } from '../services' import listBaseClientsPresenter from '../views/presenters/listBaseClientsPresenter' import viewBaseClientPresenter from '../views/presenters/viewBaseClientPresenter' import nunjucksUtils from '../views/helpers/nunjucksUtils' import { mapCreateBaseClientForm, mapEditBaseClientDeploymentForm, mapEditBaseClientDetailsForm } from '../mappers' -import { BaseClient } from '../interfaces/baseClientApi/baseClient' +import { BaseClient, BaseClientListFilter, ClientSecrets } from '../interfaces/baseClientApi/baseClient' import editBaseClientPresenter from '../views/presenters/editBaseClientPresenter' import mapFilterForm from '../mappers/forms/mapFilterForm' +import { GrantTypes } from '../data/enums/grantTypes' +import { kebab } from '../utils/utils' +import baseClientAudit, { BaseClientAuditFunction } from '../audit/baseClientAudit' +import { BaseClientEvent } from '../audit/baseClientEvent' +import { Client } from '../interfaces/baseClientApi/client' export default class BaseClientController { constructor(private readonly baseClientService: BaseClientService) {} public displayBaseClients(): RequestHandler { return async (req, res) => { - const userToken = res.locals.user.token - const baseClients = await this.baseClientService.listBaseClients(userToken) - - const presenter = listBaseClientsPresenter(baseClients) - - res.render('pages/base-clients.njk', { - presenter, - }) + const { token, username } = res.locals.user + const audit = baseClientAudit(username) + await audit(BaseClientEvent.LIST_BASE_CLIENTS) + + try { + const baseClients = await this.baseClientService.listBaseClients(token) + this.renderBaseClientsPage(res, baseClients) + } catch (e) { + await audit(BaseClientEvent.LIST_BASE_CLIENTS_FAILURE) + throw e + } } } public filterBaseClients(): RequestHandler { return async (req, res) => { - const userToken = res.locals.user.token + const { token, username } = res.locals.user const filter = mapFilterForm(req) - - const baseClients = await this.baseClientService.listBaseClients(userToken) - - const presenter = listBaseClientsPresenter(baseClients, filter) - - res.render('pages/base-clients.njk', { - presenter, - }) + const audit = baseClientAudit(username) + await audit(BaseClientEvent.LIST_BASE_CLIENTS) + + try { + const baseClients = await this.baseClientService.listBaseClients(token) + this.renderBaseClientsPage(res, baseClients, filter) + } catch (e) { + await audit(BaseClientEvent.LIST_BASE_CLIENTS_FAILURE) + throw e + } } } public displayBaseClient(): RequestHandler { return async (req, res) => { - const userToken = res.locals.user.token + const { token, username } = res.locals.user const { baseClientId } = req.params - const baseClient = await this.baseClientService.getBaseClient(userToken, baseClientId) - const clients = await this.baseClientService.listClientInstances(userToken, baseClient) - - const presenter = viewBaseClientPresenter(baseClient, clients) - res.render('pages/base-client.njk', { - baseClient, - presenter, - ...nunjucksUtils, - }) + const audit = baseClientAudit(username) + + await audit(BaseClientEvent.VIEW_BASE_CLIENT, baseClientId) + try { + const baseClient = await this.baseClientService.getBaseClient(token, baseClientId) + const clients = await this.baseClientService.listClientInstances(token, baseClient) + this.renderBaseClientPage(res, baseClient, clients) + } catch (e) { + await audit(BaseClientEvent.VIEW_BASE_CLIENT_FAILURE, baseClientId) + throw e + } } } public displayNewBaseClient(): RequestHandler { return async (req, res) => { const { grant } = req.query - if (!(grant === 'client-credentials' || grant === 'authorization-code')) { + if (!(grant === kebab(GrantTypes.ClientCredentials) || grant === kebab(GrantTypes.AuthorizationCode))) { res.render('pages/new-base-client-grant.njk') return } - res.render('pages/new-base-client-details.njk', { grant, ...nunjucksUtils }) + res.render('pages/new-base-client-details.njk', { + grant, + presenter: editBaseClientPresenter(null), + ...nunjucksUtils, + }) } } public createBaseClient(): RequestHandler { return async (req, res, next) => { - const userToken = res.locals.user.token - const baseClient = mapCreateBaseClientForm(req) - - // Simple validation - const error = await this.validateCreateBaseClient(userToken, baseClient) - if (error) { - res.render('pages/new-base-client-details.njk', { - errorMessage: { text: error }, - grant: baseClient.grantType, - baseClient, - presenter: editBaseClientPresenter(baseClient), - ...nunjucksUtils, - }) - return + const { token, username } = res.locals.user + const audit = baseClientAudit(username) + + await audit(BaseClientEvent.CREATE_BASE_CLIENT) + try { + const baseClient = mapCreateBaseClientForm(req) + + const error = await this.validateCreateBaseClient(token, baseClient) + if (error) { + await audit(BaseClientEvent.CREATE_BASE_CLIENT_FAILURE, '', { error }) + this.renderCreateBaseClientErrorPage(res, error, baseClient) + return + } + + const secrets = await this.baseClientService.addBaseClient(token, baseClient) + await this.renderSecretsPage(res, baseClient, secrets, audit) + } catch (e) { + await audit(BaseClientEvent.CREATE_BASE_CLIENT_FAILURE) + throw e } - - // Create base client - const secrets = await this.baseClientService.addBaseClient(userToken, baseClient) - - // Display success page - res.render('pages/new-base-client-success.njk', { - title: `Client has been added`, - baseClientId: baseClient.baseClientId, - secrets, - }) } } - async validateCreateBaseClient(userToken: string, baseClient: BaseClient) { + async validateCreateBaseClient(token: string, baseClient: BaseClient) { // if baseClient.baseClientId is null or empty string, throw error if (!baseClient.baseClientId) { return 'This field is required' @@ -104,7 +115,7 @@ export default class BaseClientController { // if baseClient.baseClientId is not unique, throw error try { - await this.baseClientService.getBaseClient(userToken, baseClient.baseClientId) + await this.baseClientService.getBaseClient(token, baseClient.baseClientId) return 'A base client with this ID already exists' } catch (e) { return '' @@ -113,9 +124,9 @@ export default class BaseClientController { public displayEditBaseClient(): RequestHandler { return async (req, res) => { - const userToken = res.locals.user.token + const { token } = res.locals.user const { baseClientId } = req.params - const baseClient = await this.baseClientService.getBaseClient(userToken, baseClientId) + const baseClient = await this.baseClientService.getBaseClient(token, baseClientId) const presenter = editBaseClientPresenter(baseClient) res.render('pages/edit-base-client-details.njk', { @@ -128,28 +139,28 @@ export default class BaseClientController { public updateBaseClientDetails(): RequestHandler { return async (req, res, next) => { - const userToken = res.locals.user.token + const { token, username } = res.locals.user const { baseClientId } = req.params + const audit = baseClientAudit(username) + await audit(BaseClientEvent.UPDATE_BASE_CLIENT, baseClientId) - // get current values - const baseClient = await this.baseClientService.getBaseClient(userToken, baseClientId) - - // map form values to updated base client - const updatedClient = mapEditBaseClientDetailsForm(baseClient, req) - - // update base client - await this.baseClientService.updateBaseClient(userToken, updatedClient) - - // return to view base client page - res.redirect(`/base-clients/${baseClientId}`) + try { + const baseClient = await this.baseClientService.getBaseClient(token, baseClientId) + const updatedClient = mapEditBaseClientDetailsForm(baseClient, req) + await this.baseClientService.updateBaseClient(token, updatedClient) + res.redirect(`/base-clients/${baseClientId}`) + } catch (e) { + await audit(BaseClientEvent.UPDATE_BASE_CLIENT_FAILURE, baseClientId) + throw e + } } } public displayEditBaseClientDeployment(): RequestHandler { return async (req, res) => { - const userToken = res.locals.user.token + const { token } = res.locals.user const { baseClientId } = req.params - const baseClient = await this.baseClientService.getBaseClient(userToken, baseClientId) + const baseClient = await this.baseClientService.getBaseClient(token, baseClientId) res.render('pages/edit-base-client-deployment.njk', { baseClient, @@ -159,94 +170,157 @@ export default class BaseClientController { public updateBaseClientDeployment(): RequestHandler { return async (req, res, next) => { - const userToken = res.locals.user.token + const { token, username } = res.locals.user const { baseClientId } = req.params + const audit = baseClientAudit(username) + await audit(BaseClientEvent.UPDATE_BASE_CLIENT_DEPLOYMENT, baseClientId) - // get current values - const baseClient = await this.baseClientService.getBaseClient(userToken, baseClientId) - - // map form values to updated base client - const updatedClient = mapEditBaseClientDeploymentForm(baseClient, req) - - // update base client - await this.baseClientService.updateBaseClientDeployment(userToken, updatedClient) - - // return to view base client page - res.redirect(`/base-clients/${baseClientId}`) + try { + const baseClient = await this.baseClientService.getBaseClient(token, baseClientId) + const updatedClient = mapEditBaseClientDeploymentForm(baseClient, req) + await this.baseClientService.updateBaseClientDeployment(token, updatedClient) + res.redirect(`/base-clients/${baseClientId}`) + } catch (e) { + await audit(BaseClientEvent.UPDATE_BASE_CLIENT_DEPLOYMENT_FAILURE, baseClientId) + throw e + } } } public createClientInstance(): RequestHandler { return async (req, res, next) => { - const userToken = res.locals.user.token + const { token, username } = res.locals.user const { baseClientId } = req.params + const audit = baseClientAudit(username) - // get base client - const baseClient = await this.baseClientService.getBaseClient(userToken, baseClientId) - - // Create base client - const secrets = await this.baseClientService.addClientInstance(userToken, baseClient) + try { + await audit(BaseClientEvent.CREATE_CLIENT, baseClientId) + const baseClient = await this.baseClientService.getBaseClient(token, baseClientId) + const secrets = await this.baseClientService.addClientInstance(token, baseClient) - // Display success page - res.render('pages/new-base-client-success.njk', { - title: `Client has been added`, - baseClientId: baseClient.baseClientId, - secrets, - }) + await this.renderSecretsPage(res, baseClient, secrets, audit) + } catch (e) { + await audit(BaseClientEvent.CREATE_CLIENT_FAILURE, baseClientId) + throw e + } } } public displayDeleteClientInstance(): RequestHandler { return async (req, res, next) => { - const userToken = res.locals.user.token + const { token } = res.locals.user const { baseClientId, clientId } = req.params - const error = req.query.error === 'clientIdMismatch' ? 'Client ID does not match' : null - // get base client - const baseClient = await this.baseClientService.getBaseClient(userToken, baseClientId) - const clients = await this.baseClientService.listClientInstances(userToken, baseClient) + const baseClient = await this.baseClientService.getBaseClient(token, baseClientId) + const clients = await this.baseClientService.listClientInstances(token, baseClient) + const error = req.query.error === 'clientIdMismatch' ? 'Client ID does not match' : null - // Display delete confirmation page - res.render('pages/delete-client-instance.njk', { - baseClient, - clientId, - isLastClient: clients.length === 1, - error, - }) + this.renderDeleteConfirmationPage(res, baseClient, clientId, clients, error) } } public deleteClientInstance(): RequestHandler { return async (req, res, next) => { - const userToken = res.locals.user.token + const { token, username } = res.locals.user const { baseClientId, clientId } = req.params - - // check client id matches - if (req.body.confirm !== clientId) { - res.redirect(`/base-clients/${baseClientId}/clients/${clientId}/delete?error=clientIdMismatch`) - return + const audit = baseClientAudit(username) + await audit(BaseClientEvent.DELETE_CLIENT, baseClientId, { clientId }) + + try { + if (req.body.confirm !== clientId) { + await audit(BaseClientEvent.DELETE_CLIENT_FAILURE) + res.redirect(`/base-clients/${baseClientId}/clients/${clientId}/delete?error=clientIdMismatch`) + return + } + + const baseClient = await this.baseClientService.getBaseClient(token, baseClientId) + const clients = await this.baseClientService.listClientInstances(token, baseClient) + const client = clients.find(c => c.clientId === clientId) + + if (!client) { + res.redirect(`/base-clients/${baseClientId}/clients/${clientId}/delete?error=clientNotFound`) + return + } + + await this.baseClientService.deleteClientInstance(token, client) + this.redirectAfterDeletion(res, clients, baseClientId) + } catch (e) { + await audit(BaseClientEvent.DELETE_CLIENT_FAILURE, baseClientId, { clientId }) + throw e } + } + } - // get base client - const baseClient = await this.baseClientService.getBaseClient(userToken, baseClientId) - const clients = await this.baseClientService.listClientInstances(userToken, baseClient) - const client = clients.find(c => c.clientId === clientId) + private renderBaseClientsPage(res: Response, baseClients: BaseClient[], filter?: BaseClientListFilter) { + const presenter = listBaseClientsPresenter(baseClients, filter) + res.render('pages/base-clients.njk', { + presenter, + }) + } - // check client exists - if (!client) { - res.redirect(`/base-clients/${baseClientId}/clients/${clientId}/delete?error=clientNotFound`) - return - } + private async renderSecretsPage( + res: Response, + baseClient: BaseClient, + secrets: ClientSecrets, + audit: BaseClientAuditFunction, + ) { + await audit(BaseClientEvent.VIEW_CLIENT_SECRETS, baseClient.baseClientId, { clientId: secrets.clientId }) + res.render( + 'pages/new-base-client-success.njk', + { + title: `Client has been added`, + baseClientId: baseClient.baseClientId, + secrets, + }, + (err, html) => { + if (err) { + audit(BaseClientEvent.VIEW_CLIENT_SECRETS_FAILURE, baseClient.baseClientId, { clientId: secrets.clientId }) + } else { + res.send(html) + } + }, + ) + } - // delete client - await this.baseClientService.deleteClientInstance(userToken, client) + private renderCreateBaseClientErrorPage(res: Response, error: string, baseClient: BaseClient) { + res.render('pages/new-base-client-details.njk', { + errorMessage: { text: error }, + grant: baseClient.grantType, + baseClient, + presenter: editBaseClientPresenter(baseClient), + ...nunjucksUtils, + }) + } - // return to view base client screen (or home screen if last client deleted) - if (clients.length === 1) { - res.redirect(`/`) - } else { - res.redirect(`/base-clients/${baseClientId}`) - } + private renderBaseClientPage(res: Response, baseClient: BaseClient, clients: Client[]) { + const presenter = viewBaseClientPresenter(baseClient, clients) + res.render('pages/base-client.njk', { + baseClient, + presenter, + ...nunjucksUtils, + }) + } + + private renderDeleteConfirmationPage( + res: Response, + baseClient: BaseClient, + clientId: string, + clients: Client[], + error: string | null, + ): void { + res.render('pages/delete-client-instance.njk', { + baseClient, + clientId, + isLastClient: clients.length === 1, + error, + }) + } + + private redirectAfterDeletion(res: Response, clients: Client[], baseClientId: string): void { + if (clients.length === 1) { + res.redirect(`/`) + } else { + res.redirect(`/base-clients/${baseClientId}`) } } } diff --git a/server/data/enums/clientTypes.ts b/server/data/enums/clientTypes.ts new file mode 100644 index 00000000..d49ead67 --- /dev/null +++ b/server/data/enums/clientTypes.ts @@ -0,0 +1,5 @@ +// eslint-disable-next-line no-shadow,import/prefer-default-export +export enum ClientType { + Personal = 'personal', + Service = 'service', +} diff --git a/server/data/enums/grantTypes.ts b/server/data/enums/grantTypes.ts new file mode 100644 index 00000000..68c49d21 --- /dev/null +++ b/server/data/enums/grantTypes.ts @@ -0,0 +1,5 @@ +// eslint-disable-next-line no-shadow,import/prefer-default-export +export enum GrantTypes { + ClientCredentials = 'client_credentials', + AuthorizationCode = 'authorization_code', +} diff --git a/server/data/enums/hostingTypes.ts b/server/data/enums/hostingTypes.ts new file mode 100644 index 00000000..1cba823a --- /dev/null +++ b/server/data/enums/hostingTypes.ts @@ -0,0 +1,5 @@ +// eslint-disable-next-line no-shadow,import/prefer-default-export +export enum HostingType { + Cloud = 'cloudplatform', + Other = 'other', +} diff --git a/server/data/localMockData/baseClientsResponseMock.ts b/server/data/localMockData/baseClientsResponseMock.ts index 37958b39..dfc13897 100644 --- a/server/data/localMockData/baseClientsResponseMock.ts +++ b/server/data/localMockData/baseClientsResponseMock.ts @@ -43,10 +43,11 @@ export const getBaseClientResponseMock: GetBaseClientResponse = { validDays: 1, accessTokenValidityMinutes: 60, deployment: { + clientType: 'service', team: 'deployment team', teamContact: 'deployment team contact', teamSlack: 'deployment team slack', - hosting: 'deployment hosting', + hosting: 'other', namespace: 'deployment namespace', deployment: 'deployment deployment', secretName: 'deployment secret name', diff --git a/server/data/manageUsersApiClient.test.ts b/server/data/manageUsersApiClient.test.ts index 136ee0ec..575bdfd2 100644 --- a/server/data/manageUsersApiClient.test.ts +++ b/server/data/manageUsersApiClient.test.ts @@ -34,16 +34,4 @@ describe('manageUsersApiClient', () => { expect(output).toEqual(response) }) }) - - describe('getUserRoles', () => { - it('should return data from api', async () => { - fakeManageUsersApiClient - .get('/users/me/roles') - .matchHeader('authorization', `Bearer ${token.access_token}`) - .reply(200, [{ roleCode: 'role1' }, { roleCode: 'role2' }]) - - const output = await manageUsersApiClient.getUserRoles(token.access_token) - expect(output).toEqual(['role1', 'role2']) - }) - }) }) diff --git a/server/data/manageUsersApiClient.ts b/server/data/manageUsersApiClient.ts index 41019fdf..30a7e7cf 100644 --- a/server/data/manageUsersApiClient.ts +++ b/server/data/manageUsersApiClient.ts @@ -28,10 +28,4 @@ export default class ManageUsersApiClient { logger.info('Getting user details: calling HMPPS Manage Users Api') return ManageUsersApiClient.restClient(token).get({ path: '/users/me' }) } - - getUserRoles(token: string): Promise { - return ManageUsersApiClient.restClient(token) - .get({ path: '/users/me/roles' }) - .then(roles => roles.map(role => role.roleCode)) - } } diff --git a/server/interfaces/baseClientApi/baseClient.ts b/server/interfaces/baseClientApi/baseClient.ts index 4e7421ff..072b31b3 100644 --- a/server/interfaces/baseClientApi/baseClient.ts +++ b/server/interfaces/baseClientApi/baseClient.ts @@ -1,11 +1,12 @@ export interface BaseClient { baseClientId: string - clientType: string accessTokenValidity: number scopes: string[] grantType: string audit: string count: number + lastAccessed: string + expired: boolean clientCredentials: ClientCredentialsDetails authorisationCode: AuthorisationCodeDetails service: ServiceDetails @@ -32,7 +33,8 @@ interface ServiceDetails { contact: string status: string } -interface DeploymentDetails { +export interface DeploymentDetails { + clientType: string team: string teamContact: string teamSlack: string diff --git a/server/interfaces/baseClientApi/baseClientResponse.ts b/server/interfaces/baseClientApi/baseClientResponse.ts index dcc90aa5..fb165b65 100644 --- a/server/interfaces/baseClientApi/baseClientResponse.ts +++ b/server/interfaces/baseClientApi/baseClientResponse.ts @@ -23,6 +23,7 @@ export interface GetBaseClientResponse { validDays?: number accessTokenValidityMinutes?: number deployment: { + clientType: string team: string teamContact: string teamSlack: string diff --git a/server/mappers/baseClientApi/getBaseClient.ts b/server/mappers/baseClientApi/getBaseClient.ts index 7aa7f714..a540c535 100644 --- a/server/mappers/baseClientApi/getBaseClient.ts +++ b/server/mappers/baseClientApi/getBaseClient.ts @@ -1,13 +1,16 @@ import { GetBaseClientResponse } from '../../interfaces/baseClientApi/baseClientResponse' -import { BaseClient } from '../../interfaces/baseClientApi/baseClient' +import { BaseClient, DeploymentDetails } from '../../interfaces/baseClientApi/baseClient' +import { GrantTypes } from '../../data/enums/grantTypes' +import { ClientType } from '../../data/enums/clientTypes' +import { HostingType } from '../../data/enums/hostingTypes' +import { snake } from '../../utils/utils' export default (response: GetBaseClientResponse): BaseClient => { return { baseClientId: response.clientId, - clientType: 'SERVICE', accessTokenValidity: response.accessTokenValidityMinutes ? response.accessTokenValidityMinutes * 60 : 0, scopes: response.scopes ? response.scopes : [], - grantType: 'client_credentials', + grantType: GrantTypes.ClientCredentials, audit: response.jiraNumber ? response.jiraNumber : '', count: 1, clientCredentials: { @@ -27,7 +30,7 @@ export default (response: GetBaseClientResponse): BaseClient => { contact: '', status: '', }, - deployment: response.deployment, + deployment: getDeployment(response), config: { allowedIPs: response.ips ? response.ips : [], expiryDate: response.validDays @@ -36,3 +39,40 @@ export default (response: GetBaseClientResponse): BaseClient => { }, } as BaseClient } + +const getClientType = (response: GetBaseClientResponse): string => { + if (!response.deployment || !response.deployment.clientType) { + return '' + } + + const clientType = snake(response.deployment.clientType) + if (clientType === ClientType.Personal || clientType === ClientType.Service) { + return clientType + } + return '' +} + +const getHostingType = (response: GetBaseClientResponse): string => { + if (!response.deployment || !response.deployment.hosting) { + return '' + } + + const hostingType = snake(response.deployment.hosting) + if (hostingType === HostingType.Cloud || hostingType === HostingType.Other) { + return hostingType + } + + return '' +} + +const getDeployment = (response: GetBaseClientResponse): DeploymentDetails => { + if (!response.deployment) { + return null + } + + const { deployment } = response + deployment.hosting = getHostingType(response) + deployment.clientType = getClientType(response) + + return deployment +} diff --git a/server/mappers/baseClientApi/listBaseClients.ts b/server/mappers/baseClientApi/listBaseClients.ts index db7e37cd..d6425252 100644 --- a/server/mappers/baseClientApi/listBaseClients.ts +++ b/server/mappers/baseClientApi/listBaseClients.ts @@ -1,6 +1,6 @@ import { ListBaseClientsResponse } from '../../interfaces/baseClientApi/baseClientResponse' import { BaseClient } from '../../interfaces/baseClientApi/baseClient' -import { multiSeparatorSplit } from '../../utils/utils' +import { multiSeparatorSplit, snake } from '../../utils/utils' export default (response: ListBaseClientsResponse): BaseClient[] => { const { clients } = response @@ -11,12 +11,13 @@ export default (response: ListBaseClientsResponse): BaseClient[] => { client => ({ baseClientId: client.baseClientId, - clientType: client.clientType, accessTokenValidity: 24000, scopes: [], - grantType: client.grantType, + grantType: snake(client.grantType), audit: '', count: client.count ? client.count : 0, + lastAccessed: client.lastAccessed ? client.lastAccessed : '', + expired: client.expired, clientCredentials: { authorities: multiSeparatorSplit(client.roles, [' ', ',', '\n']), databaseUserName: '', @@ -35,6 +36,7 @@ export default (response: ListBaseClientsResponse): BaseClient[] => { status: '', }, deployment: { + clientType: snake(client.clientType), team: client.teamName || '', teamContact: '', teamSlack: '', diff --git a/server/mappers/baseClientApi/updateBaseClientDeployment.ts b/server/mappers/baseClientApi/updateBaseClientDeployment.ts index 175b9f0b..450093e2 100644 --- a/server/mappers/baseClientApi/updateBaseClientDeployment.ts +++ b/server/mappers/baseClientApi/updateBaseClientDeployment.ts @@ -1,13 +1,14 @@ import { BaseClient } from '../../interfaces/baseClientApi/baseClient' import { UpdateBaseClientDeploymentRequest } from '../../interfaces/baseClientApi/baseClientRequestBody' +import { apiEnum } from '../../utils/utils' export default (baseClient: BaseClient): UpdateBaseClientDeploymentRequest => { return { - clientType: baseClient.clientType, + clientType: apiEnum(baseClient.deployment.clientType.toUpperCase()), team: baseClient.deployment.team, teamContact: baseClient.deployment.teamContact, teamSlack: baseClient.deployment.teamSlack, - hosting: baseClient.deployment.hosting, + hosting: apiEnum(baseClient.deployment.hosting), namespace: baseClient.deployment.namespace, deployment: baseClient.deployment.deployment, secretName: baseClient.deployment.secretName, diff --git a/server/mappers/forms/mapCreateBaseClientForm.ts b/server/mappers/forms/mapCreateBaseClientForm.ts index 148071b2..989fd934 100644 --- a/server/mappers/forms/mapCreateBaseClientForm.ts +++ b/server/mappers/forms/mapCreateBaseClientForm.ts @@ -1,6 +1,6 @@ import type { Request } from 'express' import { BaseClient } from '../../interfaces/baseClientApi/baseClient' -import { getAccessTokenValiditySeconds, getDayOfExpiry, multiSeparatorSplit } from '../../utils/utils' +import { getAccessTokenValiditySeconds, getDayOfExpiry, multiSeparatorSplit, snake } from '../../utils/utils' export default (request: Request): BaseClient => { // valid days is calculated from expiry date @@ -12,12 +12,13 @@ export default (request: Request): BaseClient => { return { baseClientId: data.baseClientId, - clientType: data.clientType, accessTokenValidity: accessTokenValiditySeconds, scopes: multiSeparatorSplit(data.approvedScopes, [',', '\r\n', '\n']), audit: data.audit, count: 1, - grantType: data.grant, + lastAccessed: '', + expired: false, + grantType: snake(data.grant), clientCredentials: { authorities: multiSeparatorSplit(data.authorities, [',', '\r\n', '\n']), databaseUserName: data.databaseUserName, @@ -36,6 +37,7 @@ export default (request: Request): BaseClient => { status: '', }, deployment: { + clientType: '', team: '', teamContact: '', teamSlack: '', diff --git a/server/mappers/forms/mapEditBaseClientDeploymentForm.test.ts b/server/mappers/forms/mapEditBaseClientDeploymentForm.test.ts index f119d32d..f6a56d4e 100644 --- a/server/mappers/forms/mapEditBaseClientDeploymentForm.test.ts +++ b/server/mappers/forms/mapEditBaseClientDeploymentForm.test.ts @@ -2,6 +2,7 @@ import type { Request } from 'express' import { createMock } from '@golevelup/ts-jest' import { baseClientFactory } from '../../testutils/factories' import { mapEditBaseClientDeploymentForm } from '../index' +import { HostingType } from '../../data/enums/hostingTypes' const formRequest = (form: Record) => { return createMock({ body: form }) @@ -26,7 +27,7 @@ describe('mapEditBaseClientDeploymentForm', () => { team: 'team', teamContact: 'contact', teamSlack: 'slack', - hosting: 'CLOUDPLATFORM', + hosting: HostingType.Cloud, namespace: 'b', deployment: 'c', secretName: 'd', @@ -42,7 +43,7 @@ describe('mapEditBaseClientDeploymentForm', () => { expect(update.deployment.team).toEqual('team') expect(update.deployment.teamContact).toEqual('contact') expect(update.deployment.teamSlack).toEqual('slack') - expect(update.deployment.hosting).toEqual('CLOUDPLATFORM') + expect(update.deployment.hosting).toEqual(HostingType.Cloud) expect(update.deployment.namespace).toEqual('b') expect(update.deployment.deployment).toEqual('c') expect(update.deployment.secretName).toEqual('d') diff --git a/server/mappers/forms/mapEditBaseClientDeploymentForm.ts b/server/mappers/forms/mapEditBaseClientDeploymentForm.ts index d52baa0e..8c1ae56d 100644 --- a/server/mappers/forms/mapEditBaseClientDeploymentForm.ts +++ b/server/mappers/forms/mapEditBaseClientDeploymentForm.ts @@ -1,15 +1,17 @@ import type { Request } from 'express' import { BaseClient } from '../../interfaces/baseClientApi/baseClient' +import { snake } from '../../utils/utils' export default (baseClient: BaseClient, request: Request): BaseClient => { const data = request.body return { ...baseClient, deployment: { + clientType: snake(data.clientType), team: data.team, teamContact: data.teamContact, teamSlack: data.teamSlack, - hosting: data.hosting, + hosting: snake(data.hosting), namespace: data.namespace, deployment: data.deployment, secretName: data.secretName, diff --git a/server/mappers/forms/mapEditBaseClientDetailsForm.test.ts b/server/mappers/forms/mapEditBaseClientDetailsForm.test.ts index 24ac0644..9e7a3eed 100644 --- a/server/mappers/forms/mapEditBaseClientDetailsForm.test.ts +++ b/server/mappers/forms/mapEditBaseClientDetailsForm.test.ts @@ -95,7 +95,6 @@ describe('mapEditBaseClientDetailsForm', () => { // and given an edit request with all fields populated const request = formRequest({ baseClientId: detailedBaseClient.baseClientId, - clientType: 'request clientType', approvedScopes: 'requestscope1,requestscope2', audit: 'request audit', grant: 'request grant', @@ -111,10 +110,9 @@ describe('mapEditBaseClientDetailsForm', () => { // then the client details are updated expect(update.baseClientId).toEqual(detailedBaseClient.baseClientId) - expect(update.clientType).toEqual('request clientType') expect(update.scopes).toEqual(['requestscope1', 'requestscope2']) expect(update.audit).toEqual('request audit') - expect(update.grantType).toEqual('request grant') + expect(update.grantType).toEqual('request_grant') expect(update.clientCredentials.authorities).toEqual(['requestauthority1', 'requestauthority2']) expect(update.clientCredentials.databaseUserName).toEqual('request databaseUsername') expect(update.config.allowedIPs).toEqual(['requestallowedIP1', 'requestallowedIP2']) diff --git a/server/mappers/forms/mapEditBaseClientDetailsForm.ts b/server/mappers/forms/mapEditBaseClientDetailsForm.ts index 2a4491ec..695a7496 100644 --- a/server/mappers/forms/mapEditBaseClientDetailsForm.ts +++ b/server/mappers/forms/mapEditBaseClientDetailsForm.ts @@ -1,6 +1,6 @@ import type { Request } from 'express' import { BaseClient } from '../../interfaces/baseClientApi/baseClient' -import { getAccessTokenValiditySeconds, getDayOfExpiry, multiSeparatorSplit } from '../../utils/utils' +import { getAccessTokenValiditySeconds, getDayOfExpiry, multiSeparatorSplit, snake } from '../../utils/utils' export default (baseClient: BaseClient, request: Request): BaseClient => { const data = request.body @@ -11,11 +11,10 @@ export default (baseClient: BaseClient, request: Request): BaseClient => { return { ...baseClient, - clientType: data.clientType, accessTokenValidity: accessTokenValiditySeconds, scopes: multiSeparatorSplit(data.approvedScopes, [',', '\r\n', '\n']), audit: data.audit, - grantType: data.grant, + grantType: snake(data.grant), clientCredentials: { authorities: multiSeparatorSplit(data.authorities, [',', '\r\n', '\n']), databaseUserName: data.databaseUsername, diff --git a/server/mappers/forms/mapFilterForm.ts b/server/mappers/forms/mapFilterForm.ts index 8f1103e2..2d42a021 100644 --- a/server/mappers/forms/mapFilterForm.ts +++ b/server/mappers/forms/mapFilterForm.ts @@ -1,16 +1,22 @@ import { Request } from 'express' import { BaseClientListFilter } from '../../interfaces/baseClientApi/baseClient' +import { GrantTypes } from '../../data/enums/grantTypes' +import { ClientType } from '../../data/enums/clientTypes' +import { snake } from '../../utils/utils' export default (request: Request): BaseClientListFilter => { // valid days is calculated from expiry date const data = request.body + const grantTypes = data.grantType ? data.grantType.map(snake) : [] + const clientTypes = data.clientType ? data.clientType.map(snake) : [] + return { roleSearch: data.role.trim(), - clientCredentials: data.grantType ? data.grantType.includes('client-credentials') : true, - authorisationCode: data.grantType ? data.grantType.includes('authorization-code') : true, - serviceClientType: data.clientType ? data.clientType.includes('service') : true, - personalClientType: data.clientType ? data.clientType.includes('personal') : true, - blankClientType: data.clientType ? data.clientType.includes('blank') : true, + clientCredentials: grantTypes.includes(GrantTypes.ClientCredentials), + authorisationCode: grantTypes.includes(GrantTypes.AuthorizationCode), + serviceClientType: clientTypes.includes(ClientType.Service), + personalClientType: clientTypes.includes(ClientType.Personal), + blankClientType: clientTypes.includes('blank'), } } diff --git a/server/middleware/setUpStaticResources.ts b/server/middleware/setUpStaticResources.ts index e486d25c..e8e8f563 100644 --- a/server/middleware/setUpStaticResources.ts +++ b/server/middleware/setUpStaticResources.ts @@ -17,8 +17,8 @@ export default function setUpStaticResources(): Router { '/assets', '/assets/stylesheets', '/assets/js', - '/node_modules/govuk-frontend/govuk/assets', - '/node_modules/govuk-frontend', + '/node_modules/govuk-frontend/dist/govuk/assets', + '/node_modules/govuk-frontend/dist', '/node_modules/@ministryofjustice/frontend/moj/assets', '/node_modules/@ministryofjustice/frontend', '/node_modules/jquery/dist', diff --git a/server/services/userService.test.ts b/server/services/userService.test.ts index 06691890..e4bde202 100644 --- a/server/services/userService.test.ts +++ b/server/services/userService.test.ts @@ -1,10 +1,9 @@ import UserService from './userService' import ManageUsersApiClient, { type User } from '../data/manageUsersApiClient' +import createUserToken from '../testutils/createUserToken' jest.mock('../data/manageUsersApiClient') -const token = 'some token' - describe('User service', () => { let manageUsersApiClient: jest.Mocked let userService: UserService @@ -16,6 +15,7 @@ describe('User service', () => { }) it('Retrieves and formats user name', async () => { + const token = createUserToken([]) manageUsersApiClient.getUser.mockResolvedValue({ name: 'john smith' } as User) const result = await userService.getUser(token) @@ -23,7 +23,17 @@ describe('User service', () => { expect(result.displayName).toEqual('John Smith') }) + it('Retrieves and formats roles', async () => { + const token = createUserToken(['ROLE_ONE', 'ROLE_TWO']) + manageUsersApiClient.getUser.mockResolvedValue({ name: 'john smith' } as User) + + const result = await userService.getUser(token) + + expect(result.roles).toEqual(['ONE', 'TWO']) + }) + it('Propagates error', async () => { + const token = createUserToken([]) manageUsersApiClient.getUser.mockRejectedValue(new Error('some error')) await expect(userService.getUser(token)).rejects.toEqual(new Error('some error')) diff --git a/server/services/userService.ts b/server/services/userService.ts index 6705cb70..bdd902fe 100644 --- a/server/services/userService.ts +++ b/server/services/userService.ts @@ -1,9 +1,11 @@ +import { jwtDecode } from 'jwt-decode' import { convertToTitleCase } from '../utils/utils' import type { User } from '../data/manageUsersApiClient' import ManageUsersApiClient from '../data/manageUsersApiClient' export interface UserDetails extends User { displayName: string + roles: string[] } export default class UserService { @@ -11,6 +13,11 @@ export default class UserService { async getUser(token: string): Promise { const user = await this.manageUsersApiClient.getUser(token) - return { ...user, displayName: convertToTitleCase(user.name) } + return { ...user, roles: this.getUserRoles(token), displayName: convertToTitleCase(user.name) } + } + + getUserRoles(token: string): string[] { + const { authorities: roles = [] } = jwtDecode(token) as { authorities?: string[] } + return roles.map(role => role.substring(role.indexOf('_') + 1)) } } diff --git a/server/testutils/factories/baseClient.ts b/server/testutils/factories/baseClient.ts index 770751f2..f5cc579a 100644 --- a/server/testutils/factories/baseClient.ts +++ b/server/testutils/factories/baseClient.ts @@ -1,14 +1,16 @@ import { Factory } from 'fishery' import { faker } from '@faker-js/faker' import { BaseClient } from '../../interfaces/baseClientApi/baseClient' +import { HostingType } from '../../data/enums/hostingTypes' export default Factory.define(() => ({ baseClientId: faker.string.uuid(), - clientType: 'SERVICE', accessTokenValidity: 3600, scopes: ['read', 'write'], grantType: 'client_credentials', audit: 'audit notes', + lastAccessed: '2021-01-01T00:00:00.000Z', + expired: false, count: 1, clientCredentials: { authorities: ['ROLE_CLIENT_CREDENTIALS'], @@ -28,10 +30,11 @@ export default Factory.define(() => ({ status: 'ACTIVE', }, deployment: { + clientType: 'SERVICE', team: `${faker.word.adjective()} ${faker.word.adverb()} ${faker.word.noun()}`, teamContact: `${faker.person.firstName()} ${faker.person.lastName()}`, teamSlack: `${faker.internet.url()}`, - hosting: 'CLOUD PLATFORM', + hosting: HostingType.Cloud, namespace: 'namespace', deployment: '', secretKey: 'secretKey', diff --git a/server/testutils/factories/responses/getBaseClientResponse.ts b/server/testutils/factories/responses/getBaseClientResponse.ts index f2b7449b..41ceea17 100644 --- a/server/testutils/factories/responses/getBaseClientResponse.ts +++ b/server/testutils/factories/responses/getBaseClientResponse.ts @@ -12,6 +12,7 @@ export default Factory.define(() => ({ validDays: 1, accessTokenValidityMinutes: 60, deployment: { + clientType: 'SERVICE', team: 'deployment team', teamContact: 'deployment team contact', teamSlack: 'deployment team slack', diff --git a/server/utils/nunjucksSetup.ts b/server/utils/nunjucksSetup.ts index c4f8a671..9f994f53 100644 --- a/server/utils/nunjucksSetup.ts +++ b/server/utils/nunjucksSetup.ts @@ -31,8 +31,8 @@ export default function nunjucksSetup(app: express.Express, applicationInfo: App const njkEnv = nunjucks.configure( [ path.join(__dirname, '../../server/views'), - 'node_modules/govuk-frontend/', - 'node_modules/govuk-frontend/components/', + 'node_modules/govuk-frontend/dist/', + 'node_modules/govuk-frontend/dist/components/', 'node_modules/@ministryofjustice/frontend/', 'node_modules/@ministryofjustice/frontend/moj/components/', ], diff --git a/server/utils/utils.test.ts b/server/utils/utils.test.ts index c162bff3..dd4681a4 100644 --- a/server/utils/utils.test.ts +++ b/server/utils/utils.test.ts @@ -1,4 +1,17 @@ -import { convertToTitleCase, dayDiff, daysRemaining, initialiseName, multiSeparatorSplit, offsetDate } from './utils' +import { + apiEnum, + convertToTitleCase, + dayDiff, + daysRemaining, + initialiseName, + kebab, + multiSeparatorSplit, + offsetDate, + snake, + dateTimeFormat, + dateFormat, + dateTimeFormatFromString, +} from './utils' describe('convert to title case', () => { it.each([ @@ -75,3 +88,70 @@ describe('days remaining', () => { expect(daysRemaining(expiryDate)).toEqual(expected) }) }) + +describe('snake', () => { + it.each([ + ['Null', null, null], + ['Empty string', '', ''], + ['Spaced', 'one two three', 'one_two_three'], + ['Kebab', 'one-two-three', 'one_two_three'], + ['Capitalised', 'One Two THREE', 'one_two_three'], + ['Extra spaces', ' one two three ', 'one_two_three'], + ['Mixed', 'one-two Three ', 'one_two_three'], + ])('%s snake', (_: string, a: string, expected: string) => { + expect(snake(a)).toEqual(expected) + }) +}) + +describe('kebab', () => { + it.each([ + ['Null', null, null], + ['Empty string', '', ''], + ['Spaced', 'one two three', 'one-two-three'], + ['Snake', 'one_two_three', 'one-two-three'], + ['Capitalised', 'One Two THREE', 'one-two-three'], + ['Extra spaces', ' one two three ', 'one-two-three'], + ['Mixed', 'one_two Three ', 'one-two-three'], + ])('%s kebab', (_: string, a: string, expected: string) => { + expect(kebab(a)).toEqual(expected) + }) +}) + +describe('api enum', () => { + it.each([ + ['Null', null, null], + ['Mixed case', 'OneTwoThree', 'ONETWOTHREE'], + ['Spaced', 'One two three', 'ONE_TWO_THREE'], + ])('%s apiEnum', (_: string, a: string, expected: string) => { + expect(apiEnum(a)).toEqual(expected) + }) +}) + +describe('dateFormat', () => { + it.each([ + ['a date with single digits', new Date(2020, 0, 1), '01-01-2020'], + ['a date', new Date(2020, 11, 31), '31-12-2020'], + ])('handles %s: %s -> %s', (_inputType: string, input: Date, expectedOutput: string) => { + expect(dateFormat(input)).toEqual(expectedOutput) + }) +}) + +describe('dateTimeFormat', () => { + it.each([ + ['a date time with single digits', new Date(2020, 0, 1, 0, 0), '01-01-2020 00:00'], + ['a date in winter', new Date(2020, 11, 31, 23, 59), '31-12-2020 23:59'], + ['a date time in British Summer time', new Date(2020, 7, 31, 12, 0), '31-08-2020 12:00'], + ])('handles %s: %s -> %s', (_inputType: string, input: Date, expectedOutput: string) => { + expect(dateTimeFormat(input)).toEqual(expectedOutput) + }) +}) + +describe('dateTimeStringFormat', () => { + it.each([ + ['a date time with single digits', '2020-01-01T00:00:00.000Z', '01-01-2020 00:00'], + ['a zulu date time in winter', '2020-12-31T23:59:00.000Z', '31-12-2020 23:59'], + ['a zulu date time during British Summer Time', '2020-08-31T12:00:00.000Z', '31-08-2020 13:00'], + ])('handles %s: %s -> %s', (_inputType: string, input: string, expectedOutput: string) => { + expect(dateTimeFormatFromString(input)).toEqual(expectedOutput) + }) +}) diff --git a/server/utils/utils.ts b/server/utils/utils.ts index cb18e838..c9b1472f 100644 --- a/server/utils/utils.ts +++ b/server/utils/utils.ts @@ -1,3 +1,6 @@ +import { format } from 'date-fns' // eslint-disable-line import/no-duplicates +import { enGB } from 'date-fns/locale' // eslint-disable-line import/no-duplicates + const properCase = (word: string): string => word.length >= 1 ? word[0].toUpperCase() + word.toLowerCase().slice(1) : word @@ -35,6 +38,24 @@ export const multiSeparatorSplit = (str: string, separators: string[]): string[] return value.split(firstSeparator) } +export const snake = (str: string): string => { + if (!str) return str + let value = str.trim().toLowerCase() + value = value.replace(/ /g, '_') + value = value.replace(/-/g, '_') + return value +} + +export const kebab = (str: string): string => { + if (!str) return str + return snake(str).replace(/_/g, '-') +} + +export const apiEnum = (str: string): string => { + if (!str) return str + return snake(str).toUpperCase() +} + export const dayDiff = (fromDate: Date, toDate: Date) => { const diff = toDate.getTime() - fromDate.getTime() return Math.floor(diff / (1000 * 60 * 60 * 24)) @@ -81,3 +102,34 @@ export const getAccessTokenValiditySeconds = (accessTokenValidity: string, custo } return parseIntWithDefault(accessTokenValidity, 0) } + +export const dateFormat = (date: Date): string => { + // dd-mm-yyyy + return format(date, 'dd-MM-yyyy', { locale: enGB }) +} + +export const dateTimeFormat = (date: Date): string => { + // dd-mm-yyyy hh:mm + return format(date, 'dd-MM-yyyy HH:mm', { locale: enGB }) +} + +export const dateFormatFromString = (date: string): string => { + // return null if date is null + if (!date || Number.isNaN(Date.parse(date))) { + return '' + } + + // dd-mm-yyyy + return format(new Date(date), 'dd-MM-yyyy', { locale: enGB }) +} + +export const dateTimeFormatFromString = (date: string): string => { + // return null if date is null + if (!date || Number.isNaN(Date.parse(date))) { + return '' + } + + // dd-mm-yyyy hh:mm for GB locale + + return format(new Date(date), 'dd-MM-yyyy HH:mm', { locale: enGB }) +} diff --git a/server/views/helpers/nunjucksUtils.ts b/server/views/helpers/nunjucksUtils.ts index 90fc2785..a1067808 100644 --- a/server/views/helpers/nunjucksUtils.ts +++ b/server/views/helpers/nunjucksUtils.ts @@ -1,3 +1,5 @@ +import { kebab, snake, dateFormat, dateTimeFormat } from '../../utils/utils' + const isBlank = (str: string): boolean => { return !str || /^\s*$/.test(str) } @@ -19,6 +21,7 @@ const sentenceCase = (sentence: string | null): string => { } const capitalize = (word: string): string => { + if (!word) return word return word.length >= 1 ? word[0].toUpperCase() + word.toLowerCase().slice(1) : word } @@ -42,4 +45,8 @@ export default { sentenceCase, capitalize, capitalCase, + snake, + kebab, + dateFormat, + dateTimeFormat, } diff --git a/server/views/pages/base-client.njk b/server/views/pages/base-client.njk index b98901b5..10d1d3df 100644 --- a/server/views/pages/base-client.njk +++ b/server/views/pages/base-client.njk @@ -83,13 +83,6 @@ text: baseClient.baseClientId }], rows: [ - [ - { - text: "Client type" - },{ - text: capitalize(baseClient.clientType) - } - ], [ { text: "Access token validity" @@ -322,6 +315,13 @@ text: "" }], rows: [ + [ + { + text: "Client type" + },{ + text: capitalize(baseClient.deployment.clientType) + } + ], [ { text: "Team" diff --git a/server/views/pages/edit-base-client-deployment.njk b/server/views/pages/edit-base-client-deployment.njk index 64c412df..41e697ef 100644 --- a/server/views/pages/edit-base-client-deployment.njk +++ b/server/views/pages/edit-base-client-deployment.njk @@ -56,6 +56,37 @@
+ {{ govukRadios({ + classes: "govuk-radios--inline", + name: "clientType", + fieldset: { + legend: { + text: "Client type", + isPageHeading: false + } + }, + items: [ + { + value: "SERVICE", + text: "Service", + attributes: { + "data-qa": "deployment-service-radio" + } + }, + { + value: "PERSONAL", + text: "Personal", + attributes: { + "data-qa": "deployment-personal-radio" + } + } + ], + value: baseClient.clientType, + attributes: { + "data-qa": "deployment-type-radios" + } + }) }} + {{ govukInput({ label: { text: "Team" diff --git a/server/views/pages/edit-base-client-details.njk b/server/views/pages/edit-base-client-details.njk index abd0bdeb..f73e6da5 100644 --- a/server/views/pages/edit-base-client-details.njk +++ b/server/views/pages/edit-base-client-details.njk @@ -50,37 +50,6 @@ } }) }} - {{ govukRadios({ - classes: "govuk-radios--inline", - name: "clientType", - fieldset: { - legend: { - text: "Client type", - isPageHeading: false - } - }, - items: [ - { - value: "SERVICE", - text: "Service", - attributes: { - "data-qa": "base-client-service-radio" - } - }, - { - value: "PERSONAL", - text: "Personal", - attributes: { - "data-qa": "base-client-personal-radio" - } - } - ], - value: baseClient.clientType, - attributes: { - "data-qa": "base-client-type-radios" - } - }) }} - {{ govukSelect({ id: "access-token-validity", name: "accessTokenValidity", diff --git a/server/views/pages/new-base-client-details.njk b/server/views/pages/new-base-client-details.njk index 99f99664..7dce8a75 100644 --- a/server/views/pages/new-base-client-details.njk +++ b/server/views/pages/new-base-client-details.njk @@ -47,37 +47,6 @@ } }) }} - {{ govukRadios({ - classes: "govuk-radios--inline", - name: "clientType", - fieldset: { - legend: { - text: "Client type", - isPageHeading: false - } - }, - items: [ - { - value: "SERVICE", - text: "Service", - attributes: { - "data-qa": "base-client-service-radio" - } - }, - { - value: "PERSONAL", - text: "Personal", - attributes: { - "data-qa": "base-client-personal-radio" - } - } - ], - value: baseClient.clientType, - attributes: { - "data-qa": "base-client-type-radios" - } - }) }} - {{ govukSelect({ id: "access-token-validity", name: "accessTokenValidity", diff --git a/server/views/partials/layout.njk b/server/views/partials/layout.njk index 948b3121..2aa91ff1 100644 --- a/server/views/partials/layout.njk +++ b/server/views/partials/layout.njk @@ -1,15 +1,7 @@ {% extends "govuk/template.njk" %} {% block head %} - - - - - - + {% block pageScripts %}{% endblock %} {% endblock %} diff --git a/server/views/presenters/editBaseClientPresenter.ts b/server/views/presenters/editBaseClientPresenter.ts index 9b102edb..c8e71c09 100644 --- a/server/views/presenters/editBaseClientPresenter.ts +++ b/server/views/presenters/editBaseClientPresenter.ts @@ -2,6 +2,15 @@ import { BaseClient } from '../../interfaces/baseClientApi/baseClient' import { daysRemaining } from '../../utils/utils' export default (baseClient: BaseClient) => { + if (!baseClient) { + return { + accessTokenValidityDropdown: '', + accessTokenValidityText: '', + daysRemaining: 0, + expiry: false, + } + } + return { accessTokenValidityDropdown: getAccessTokenValidityDropdown(baseClient.accessTokenValidity), accessTokenValidityText: getAccessTokenValidityTextbox(baseClient.accessTokenValidity), diff --git a/server/views/presenters/listBaseClientsPresenter.ts b/server/views/presenters/listBaseClientsPresenter.ts index 642526f6..f9a0dba7 100644 --- a/server/views/presenters/listBaseClientsPresenter.ts +++ b/server/views/presenters/listBaseClientsPresenter.ts @@ -1,5 +1,7 @@ import { BaseClient, BaseClientListFilter } from '../../interfaces/baseClientApi/baseClient' -import { convertToTitleCase } from '../../utils/utils' +import { convertToTitleCase, dateFormatFromString, snake } from '../../utils/utils' +import { GrantTypes } from '../../data/enums/grantTypes' +import { ClientType } from '../../data/enums/clientTypes' const indexTableHead = () => { return [ @@ -45,13 +47,6 @@ const indexTableHead = () => { 'aria-sort': 'none', }, }, - { - text: 'Secret updated', - classes: 'app-custom-class', - attributes: { - 'aria-sort': 'none', - }, - }, { text: 'Last accessed', classes: 'app-custom-class', @@ -83,7 +78,7 @@ const indexTableRows = (data: BaseClient[], filter?: BaseClientListFilter) => { html: item.count > 1 ? `${item.count}` : '', }, { - text: item.clientType ? convertToTitleCase(item.clientType) : '', + text: item.deployment && item.deployment.clientType ? convertToTitleCase(item.deployment.clientType) : '', }, { text: item.deployment.team, @@ -95,13 +90,10 @@ const indexTableRows = (data: BaseClient[], filter?: BaseClientListFilter) => { html: item.clientCredentials.authorities.join('
'), }, { - text: '2023/09/01 12:00:00', - }, - { - text: '2023/09/01 12:00:00', + text: dateFormatFromString(item.lastAccessed), }, { - text: '', + html: item.expired ? `Expired` : '', }, ]) } @@ -119,21 +111,22 @@ export const filterBaseClient = (baseClient: BaseClient, filter: BaseClientListF } } - const grantType = baseClient.grantType ? baseClient.grantType.trim().toLowerCase() : '' - const clientType = baseClient.clientType ? baseClient.clientType.trim().toLowerCase() : '' + const grantType = baseClient.grantType ? snake(baseClient.grantType) : '' + const clientType = + baseClient.deployment && baseClient.deployment.clientType ? snake(baseClient.deployment.clientType) : '' - if (grantType === 'client_credentials' && !filter.clientCredentials) { + if (grantType === GrantTypes.ClientCredentials && !filter.clientCredentials) { return false } - if (grantType === 'authorisation_code' && !filter.authorisationCode) { + if (grantType === GrantTypes.AuthorizationCode && !filter.authorisationCode) { return false } - if (clientType === 'personal' && !filter.personalClientType) { + if (clientType === ClientType.Personal && !filter.personalClientType) { return false } - if (clientType === 'service' && !filter.serviceClientType) { + if (clientType === ClientType.Service && !filter.serviceClientType) { return false } diff --git a/server/views/presenters/viewBaseClientPresenter.test.ts b/server/views/presenters/viewBaseClientPresenter.test.ts index 76878f10..4cb16ac3 100644 --- a/server/views/presenters/viewBaseClientPresenter.test.ts +++ b/server/views/presenters/viewBaseClientPresenter.test.ts @@ -10,25 +10,25 @@ describe('viewBaseClientPresenter', () => { const clients = [ clientFactory.build({ clientId: 'clientIdA', - created: new Date('2020-01-01'), - accessed: new Date('2020-01-02'), + created: new Date('2020-01-01T01:02:03.456Z'), + accessed: new Date('2020-01-02T01:02:03.456Z'), }), clientFactory.build({ clientId: 'clientIdB', - created: new Date('2020-02-01'), - accessed: new Date('2020-02-02'), + created: new Date('2020-02-01T01:02:03.456Z'), + accessed: new Date('2020-02-02T01:02:03.456Z'), }), ] // When we map to a presenter const presenter = viewBaseClientPresenter(baseClient, clients) - // Then the dates are formatted as DD/MM/YYYY - const expectedCreated = ['01/01/2020', '01/02/2020'] + // Then the dates are formatted as DD-MM-YYYY hh:mm + const expectedCreated = ['01-01-2020 01:02', '01-02-2020 01:02'] const actualCreated = presenter.clientsTable.map(row => row[1].html) expect(expectedCreated).toEqual(actualCreated) - const expectedAccessed = ['02/01/2020', '02/02/2020'] + const expectedAccessed = ['02-01-2020 01:02', '02-02-2020 01:02'] const actualAccessed = presenter.clientsTable.map(row => row[2].html) expect(expectedAccessed).toEqual(actualAccessed) }) diff --git a/server/views/presenters/viewBaseClientPresenter.ts b/server/views/presenters/viewBaseClientPresenter.ts index e0443f72..1e8da82e 100644 --- a/server/views/presenters/viewBaseClientPresenter.ts +++ b/server/views/presenters/viewBaseClientPresenter.ts @@ -1,6 +1,6 @@ import { BaseClient } from '../../interfaces/baseClientApi/baseClient' import { Client } from '../../interfaces/baseClientApi/client' -import { daysRemaining } from '../../utils/utils' +import { dateTimeFormat, daysRemaining } from '../../utils/utils' export default (baseClient: BaseClient, clients: Client[]) => { return { @@ -9,10 +9,10 @@ export default (baseClient: BaseClient, clients: Client[]) => { text: item.clientId, }, { - html: item.created.toLocaleDateString('en-GB'), + html: item.created ? dateTimeFormat(item.created) : '', }, { - html: item.accessed ? item.accessed.toLocaleDateString('en-GB') : '', + html: item.accessed ? dateTimeFormat(item.accessed) : '', }, { html: `delete`,