From ae2708163ce90807d7fb68c8f6f0eb44d17720c2 Mon Sep 17 00:00:00 2001 From: Jon Brighton Date: Thu, 16 May 2024 09:28:33 +0100 Subject: [PATCH] Bringing closer in line with `hmpps-typescript-template` (#619) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix docker build failing, and reduce image size (#50) * Remove use of semi colons before arrays (#51) * Remove use of semi-colons before arrays * Fix typos in README * WFP-610 update to npmv7 and fix some audit (#52) * WFP-610 update to npm 7 * WFP-610 update outdated dependencies * WFP-610 fixed some audit vulnerabilities * WFP-610 updated passport-oauth2 * WFP-610 upgrade to jest-junit 13 to bring in new ansi-regex (#54) * Upgrading dependencies (#55) * FIXBUILD: update ansi-regex subdependency (#56) * DT-2702: πŸ”¨ Use new generic service configuration (#57) * Update dependencies (#58) * Moving to use HMPPS header (#59) * Moving to use HMPPS header * Removing explicit reference to DPS * Fix path of unit test results that are uploaded as artifacts (#60) and properly indent "build" job (jobs should be an array of [name] to dictionary) Co-authored-by: Jon Brighton * DT-2814: πŸ› Fix cron timings for veracode (#61) * NN-3747 fixing json structure for the stubUserRoles call and populating the user directly and not from the request because passport isn't in the test stack (#62) * Bumping node version (#63) Also fixing open handle in test and bumping dependencies * DT-2796: πŸ”¨ Migrate dev to live context (#64) * Update dependencies and move to NPM v8 (#65) * Upgrading dependencies (#66) * Update modules and remove express-request-id (#67) * INC-163 Timeout Fix - Correctly sets the timeout for a HttpAgent (#69) * ⬆️ update dependencies and πŸ’„add no-only-tests linting rule for cypress (#70) * ⬆️ update dependencies * πŸ’„Add no-only-tests linting rule for cypress * SDI-60: πŸ”¨ Add global protect and petty france to allowlists (#71) * Update dependencies (#72) * DCS-1442 jquery-ui.css coep fix (#73) * Fixing docker caching issue (#74) Need to refer to build args before calling apt-get upgrade otherwise the set of packages are cached and not upgraded. Docker cannot cache anything in layers after a dynamic variable has been used Also bumping version of node and fixing test compilation issue * Setup prometheus metrics by default. (#75) This change sets up prometheus metrics to be available on port 3001, and with the helm chart changes they will automatically get scraped and be available for alerts and dashboards in grafana. The added metrics include: - General nodejs stats: memory use, gc etc - HTTP server requests: counters and timings of all served HTTP requests by the app. - HTTP client requests: counters and timings of all HTTP requests to other upstream APIs (as long as they are based off `restClient.ts`). - Upstream healthchecks: guages recording the status/health of each upstream service when the healthcheck is tested. This is all backported from the `manage-recalls-ui` app, please let me know what you think. :) * Bump minimist from 1.2.5 to 1.2.6 (#76) Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6. - [Release notes](https://github.com/substack/minimist/releases) - [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6) --- updated-dependencies: - dependency-name: minimist dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update dependencies (#77) * Audit fix (#79) * NN-4060: App Insights only works with bunyan v1 (#80) Co-authored-by: sp-watson * Update orb and dependencies (#82) * Stop metrics test from hitting a real service and occasionally timing out (#81) Co-authored-by: Jon Wyatt <> Co-authored-by: Andrew Lee <1517745+andrewrlee@users.noreply.github.com> * SDI-181: πŸ”§ Add jira notifications for new projects (#83) * Adding better redis error handling (#84) * SDI-181: πŸ”§ Use new cimg redis executor (#85) * SDI-181: πŸ”§ Use new cimg redis executor * SDI-181: πŸ”§ Improve docker ignore and use released orb instead * SDI-181: ⬆️ Bump node minor version * SLM-245 Restore cache prior to running up the app for the integration tests (#86) This caused an issue with our build where we received a segmentation fault as soon as the integration tests called the node app. Segmentation faults generally indicate an issue with one of the native C/C++ modules and it appears that one of these modules was relying on something we have stashed in the cache. * SDI-88: 🚨 Fix querystring warning (#87) * SDI-88: 🚨 Fix querystring warning * SDI-88: 🚨 Second attempt to querystring warning * SDI-88: πŸ› Fix cookie session down as a dependency (#88) * SDI-88: ♻️ Tidy up mocks and switch to multiplatform builds (#89) * Allow async get to take an array of strings for paths like original get method (#90) * SDI-182: ✨ Switch to using connection string instead (#91) * Update README.md (#92) * SDI-88: βœ… Add token verification integration tests (#94) * Minor "code smell" fixes suggested by Sonar Cloud (#95) * INC-567: Remove unnecessary type assertions * INC-567: Return resolved promise directly * SDI-211: πŸ”’οΈ Bump versions to fix security issues and cope with passport major upgrade (#96) * Updating dependencies (#97) * SDI-211: 🎨 Enforce trailing comma on functions too (#98) * SDI-211: 🎨 Enforce arrow parens (#99) * Bump dependencies (#100) * SDI-218: ⬆ Upgrade cypress to v10 (#101) * ⬆️ Update dependencies (#102) * Ignore false positive around nodemon (#103) * Ignore false positive around nodemon * Manage version of audit ci and provide full path to ignored dependency * Updating dependencies (#104) * Update dependencies (#105) * Create services container (#106) This allows passing through a container of wired up services through to route This approach has been used for the dps-shared and farsight projects and it leads to a testing approach that scales more naturally. Means you can pass through the services through to where they are needed and this can grow without changes propagating through the application Also extracted standard router into standalone middleware as the current approach relies on mutation and encourages making multiple copies of it. * SDI-265: ♻️ Minor improvements (#107) * SDI-265: 🚨 Add lint check for only (#108) * Update Jest to v28 and minor dependency updates (#109) * Remove duplicate
elements (#110) The govuk/template.njk which the layout.njk extends which these files use already includes a
element According to the HTML spec there should only be one
element present in the document at a time * Bumping dependencies and fixing page width (#111) * Bumping dependencies and fixing page width There seems to be a lot of variability in page width so going with something that seems most popular in HMPPS * Run tests in band Partially to fix tests hanging in circle, but also as test seems to run almost twice as fast (after clearing cache) * Add a `cspNonce` to the webSecurity setup (#112) Based on what I’ve seen elsewhere, this seems to now be a common approach to allow us to inline scripts, see: https://content-security-policy.com/nonce The GOV.UK frontend has now been updated to support the use of the `cspNonce` local - see: https://github.com/alphagov/govuk-frontend/commit/2e40d744af6e6e4213ebc47644982d4eb94422d4 So we no longer need to add the inline hash, which is vulnerable to if the code in the frontend template is changed. I’ve also removed the domain-specific overrides for jQuery scripts and styles, as we can use the nonce for this too. * Update dependencies 2022-08-22 (#113) * Update dependencies to fix check outdated flagging typescript (#114) * Update dependencies 2022-09-09 (#115) * Speeding up jest tests (#116) This speeds up the running of jest tests by enabling isolatedModules which has the effect of [disabling typechecking](https://kulshekhar.github.io/ts-jest/docs/getting-started/options/isolatedModules) It also drastically reduces memory usage, allowing for running tests in parallel locally at least. On my laptop this reduces the time to run the tests in this project from ~14 seconds to ~4 seconds. On larger projects the effect is much more pronounced, welcome-people-to-prison reduces build time from ~2mins, 20 seconds to ~25 seconds. In circle we still need to run in band but this is still significantly faster than before, in WPIP it reduces the build by over 1 min. Type checking is still available in the IDE, it is also part of the husky pre-commit hook and run by circle as part of the build, so the risk of type errors slipping through are very small. (We could possibly add a typechecking stage before running jest and it would still be much faster but not adding unless it becomes apparent that we need it ) It would be worth to re-assess this after jest 29 as there seems to be some fixes around a [memory issue](https://github.com/facebook/jest/issues/11956) that is part of node in versions > 16.10 NB: This will not work if type declaration (`d.ts`) files contain enums or any other constructs that generate javascript code. This seems to be a bit of an anti-pattern anyway (see [here](https://lukasbehal.com/2017-05-22-enums-in-declaration-files/)). Other tooling such as cypress will only allow you to import types from these files. * Removing colour from logs in production mode (#117) * Move ingress (#118) * Move ingress * CHange generic service to latest * SDI-345: ⬆️ Upgrade node and cypress (#119) * SDI-345: ⬆️ Upgrade node and cypress * SDI-345: ⬆️ Actually upgrade cypress * Fixing logging (#120) There was an issue where we weren't sending trace info to app insights. This is because appInsights needs to be imported before bunyan is imported so it can do its instrumentation magic. There was a related issue that obscured this. It was previously impossible to test app insights locally as dotenv wasn't set up correctly - it needed to happen before app insights is imported or app insights would prevent the app starting up. So this moves dotenv to dev dependencies and preloads it before running the app via start:dev. This removes some code that is only relevant for local development. It also means the application runs similar locally to how it would run in docker or kubernetes - it just expects the environment variables to be present. Also moving the app insights import so it's very apparent that it's the first thing that happens when the app starts. * Update dependencies 2022-09-28 (#121) * ⬆️Upgrade to latest helm chart versions (#122) * Adding badges (#123) * Adding badges * Update README.md * Updating node to v18 (#124) * Set helm timeout to 5 minutes (#125) * Use official redis image for docker-compose (#126) Which is suitable for arm64 and consistent with docker-compose-test which was updated with https://github.com/ministryofjustice/hmpps-template-typescript/pull/89 * Update Helm config to match Kotlin template (#127) * Update dependencies 2022-11-15 (#128) * Update dependencies 2022-11-16 (#129) * Update node images (#130) * SDI-476: ⬆ Bump versions (#131) * Update dependencies 2022-12-08 (#132) * Update dependencies 2022-12-19 (#133) * Bump jsonwebtoken from 8.5.1 to 9.0.0 (#136) Bumps [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) from 8.5.1 to 9.0.0. - [Release notes](https://github.com/auth0/node-jsonwebtoken/releases) - [Changelog](https://github.com/auth0/node-jsonwebtoken/blob/master/CHANGELOG.md) - [Commits](https://github.com/auth0/node-jsonwebtoken/compare/v8.5.1...v9.0.0) --- updated-dependencies: - dependency-name: jsonwebtoken dependency-type: direct:development ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix conflicting prettier / eslint rule (#135) In eslint, we ask for a trailing comma, while the prettier rules are set to `es5`. This causes issues if you have your IDE set up to fix on save, as one linter kicks in before the other, causing conflicting fixes. Co-authored-by: Andrew Lee <1517745+andrewrlee@users.noreply.github.com> * SDI-523: πŸ”’οΈ Fix / ignoresecurity issues (#138) * HEAT-41: use npm outdated job from HMPPS Orb; update other dependencies (#139) * Update dependencies 2023-01-24 (#140) * Update dependencies 2023-01-31 (#141) * Update dependencies 2023-02-01 (#142) * Configure Renovate (#144) * Add renovate.json * HEAT-52: source Renovate config from shared HMPPS repo * HEAT-52: tweak dependencies pinned by Renovate Inherit the ones from https://github.com/ministryofjustice/hmpps-renovate-config/blob/main/node.json * HEAT-52: manually bump Slack Orb as Renovate was complaining 'Can't find version matching 4.4.2 for slack' --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Neil Mendum * Update Helm release generic-service to v2.4.0 (#146) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update peter-evans/create-pull-request action to v4 (#148) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update actions/checkout action to v3 (#147) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Add .nvmrc file, Prettier support for Nunjucks and use SCSS (#143) * Add .nvmrc file with node version set to `18` Update npm engine version to `^9` Add `prettier-plugin-jinja-template` as dev dependency plus config Refactor `.sass` files to `.scss` for consistency * Add newline to .nvmrc --------- Co-authored-by: Neil Mendum * Revert build_multiplatform_docker because it causes the build to take over an hour (#149) See Slack discussion https://mojdt.slack.com/archives/C69NWE339/p1671529301455009?thread_ts=1671529075.740459&cid=C69NWE339 * Update dependency cypress to ^12.5.1 (#150) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * HEAT-52: reduce the size of the PR body by specifying prBodyTemplate (#152) This should help with GitHub integration in Slack * Update all non major NPM dependencies (#151) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update all non major NPM dependencies (#153) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update all non major NPM dependencies (#155) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update all non major NPM dependencies (#156) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update all non major NPM dependencies (#159) * Update all non major NPM dependencies * Reduce Renovate stabilityDays so that it raises fewer PRs --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Neil Mendum * Update Helm release generic-service to v2.5.0 (#161) * Update Helm release generic-service to v2.5.0 * Drop generic-service params no longer required --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Neil Mendum * Update node image and regenerate package-lock.json (#165) * Update hmpps-orb to v7.2.1 (#166) * Upgrade to connect-redis 7 and update other dependencies (#168) * Upgrade to connect-redis 7 and update other dependencies * Remove legacy mode * Fix npm prune warning * Upgrade to typescript 5 (#169) * Add HMPPS Auth URL to form-action CSP string (#170) Update the Content Security Policy to allow the HMPPS Auth URL as a possible form action target. Currently, if a 403 error occurs on a GET request, this will be captured by the error handling setup in errorHandler.ts, and the user will be redirected to the sign out URL, which then redirects to the HMPPS Auth URL. However, if a 403 error occurs on a POST request, this second redirect may not occur, and the user may, depending on their choice of browser, be frozen on the form page they just submitted. Due to CSP implementation details that vary between browsers, adding the HMPPS Auth URL to our form action targets allows this second redirect to occur as expected. * Update TypeScript etc 2023-04-03 (#174) * Removing unnecessary build (#172) All 3 processes: tsc, sass and copy-views are run by concurrently at start up anyway * Update dependencies 2023-04-12 (#177) * SDIT-738: ⚑️ Cache static resources for 1 hour (#178) * Update dependencies 2023-04-21 (#181) * Fix security vuln 2023-04-25 (#183) * Update Helm release generic-service to v2.6.2 (#182) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Neil Mendum * SDIT-760: πŸ”§ Upgrade redis to 7 (#186) * Update Helm release generic-service to v2.6.3 (#184) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Tie css cache to version of application (#188) At the moment the cache is linked to the start up time of pod, so get unnecessary cache misses for each pod in the cluster and also when pods restart This ties the cache to the git short hash of the deployment * Fix version not appearing in application insights (#190) * Fix version not appearing in application insights This previously relied on running a shell script to generate a file with a json payload in it. The code that read this file to extract out the version for the cache improvement and also setting the application version in app insights, was looking in the wrong location There was another location that looked up the file and read in the details for the health endpoint which was looking in the right place This change moves to reading the version and git reference into an env var in the docker file instead, which means we can centralise how this info is made available and remove the additional file management This should be a safe fix as the build info file was previously being generated from the docker build anyway - so the file should be available * Tidy up passing around application version * Update Helm release generic-prometheus-alerts to v1.3.2 (#189) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Add PreProd and Prod helm config (#193) As per Kotlin Template https://github.com/ministryofjustice/hmpps-template-kotlin/tree/main/helm_deploy * Update slack orb to v4.12.5 (#185) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update peter-evans/create-pull-request action to v5 (#175) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update all non major NPM dependencies (#176) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update Node.js to v18.16 (#191) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependencies 2023-06-07 (#196) * Do not retry POST requests by default (#197) It doesn't really make sense to retry non-idempotent calls Also moving sanitised error over to a real error rather than a object. Makes it a little bit easier to test these: 'expect(..).reject.throws' etc.. doesn't work if you don't have really errors * Adding changelog (#198) * Have `sanitisedError` always return an Error instance (#199) … for the same reasons as explained in https://github.com/ministryofjustice/hmpps-template-typescript/pull/197 * Update all non major NPM dependencies (#195) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update all non major NPM dependencies (#200) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Fix semver vuln (#202) * Update govuk-frontend to 4.7.0 (#205) * Update all non major NPM dependencies (#204) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * HEAT-82: Add productId and /info endpoint (#212) * HEAT-82: Add productId and /info endpoint * Update README and default value * Update values.yaml to point at README.md * Update README with dev portal URL (#213) * Update README with dev portal URL * Fix URL * Fix info endpoint test description (#214) * Update CHANGELOG.md (#216) * Fix linting, update modules, remove override (#219) * Move /info to health check block (#220) * Update dependencies 2023-09-05 (#226) * Update dependencies 2023-09-05 * Fix node version * Fix CircleCI workflows for cypress (#223) * Persist compiled stylesheets to workspace so that integration tests can load styles properly * Upload cypress screenshot and video artefacts from correct location * Fix cypress config and remove some vestigial code (#228) * Remove unused/vestigial integration test method * Remove deleted cypress config option * Update copyright date * Update readme (#229) * Update Helm release generic-prometheus-alerts to v1.3.3 (#224) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update redis Docker tag to v7.2 (#221) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update Node.js to v18.18 (#230) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * HEAT-106: Standardise endpoints (#231) * HEAT-106: Standardise endpoints * Fix e2e * Correct response * Update Dockerfile to pull through branch name * Amend output checks for int tests * Fix bugs and add Changelog * SDIT-1088: ✨ Get components to always return status even if failed (#232) * SDIT-1108: πŸ”§ Don't default build args (#233) * SDIT-1108: πŸ”§ Don't default build args * SDIT-1108: πŸ”§ Copy across args to env variables * SDIT-1108: πŸ”§ Add in docker compose build args and missing env vars * SDIT-1108: ♻️ Fix deprecated syntax version of ENV (#234) * SDIT-1108: ✨ Add in environment name to header (#235) * SDIT-1108: πŸ“ Add new environment name to changelog (#236) * NON-270: Improve REST client (#238) * Improve REST client typing information and add PATCH, PUT and DELETE methods allowing for query parameters as well as body payloads * Propagate user types into `res.locals` in request handlers * Update actions/checkout action to v4 (#225) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update all non major NPM dependencies (#210) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update Helm release generic-service to v2.6.5 (#237) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependencies 2023-10-17 (#243) * HAAR-1891: Update deprecated endpoints with new endpoints (#247) * HAAR-1891: Update deprecated endpoints with new (manage users api) endpoints * HAAR-1891: Update following PR comments * HAAR-1891: Added MANAGE_USERS_API_URL values. * HAAR-1891: Added MANAGE_USERS_API_URL values. * Update renovate.json (#248) …to prevent Node docker image from being updated beyond LTS * Move to Node 20 plus minor updates (#249) * Update CHANGELOG for node 20 change (#250) * Update CHANGELOG for node 20 change * Missed update link * Update jwt-decode module to version 4.0.0 (#252) * Update CHANGELOG.md (#253) * Added changelog for PR #247 (#254) * Update all non major NPM dependencies (#239) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * HAAR-1891: Add newly-included Manage Users api to health checks (#255) * Add newly-included Manage Users api to health checks * Update change log * Migrating project to use groups in allowlist (#257) * Updating generic service version to 2.8 * This PR migrates the project to use groups of IPs in their allowlist. By referring to groups to IP addresses, we can centralize the definition of groups of ip addresses. If these lists require changing in the future, we can change the definition once and future deploys across all services will automatically include these new IPs. 1 allowlist(s) have been detected that can be migrated. ## Allowlist: helm_deploy/hmpps-template-typescript/values.yaml ### New Groups The effect of applying this PR is as follows: - The following groups will be applied: `internal` - The size of the allowlist defined in this file will change: `8 => 0 (8 removed)` ### Added IPs The new Group membership will result in the following IPs being added to your allowlist by applying this PR: Merging this PR should not result in any additional IP addresses being added to the allowlist. ### Removed IPs The following IPs have been identified as unnecessary and will be removed by applying this PR: - health-kick (35.177.252.195/32) * SDIT-1223: ✨ Add in role_ prefix if not set by caller (#261) * SDIT-1223: ⬆️ Switch to latest wiremock (#262) * SDIT-1223: 🎨 Open chrome by default (#263) Co-authored-by: Steve Rendell <32732937+steverendell@users.noreply.github.com> * Update all non major NPM dependencies (#259) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * SDIT-1223: 🎨 Minor cypress improvements (#265) * SDIT-1248: πŸ”¨ Fix pushing JIRA deployment information (#266) * SDIT-1223: 🎨 Improve cypress healthcheck (#267) * SDIT-1223: 🎨 Rename breadcrumb to be lowercase (#268) * SDIT-1223: 🎨 Rename breadcrumb to be lowercase * Rename breadCrumb.njk to breadcrumb.njk * Update all non major NPM dependencies (#264) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependencies 2023-11-21 (#272) * Use in-memory token store when developing locally (#273) * Use in-memory token store when developing locally Removes the need for a local Redis container * Remove docker-compose dependency * Default to disabled when running locally * Rename to InMemoryTokenStore + explicitly create session MemoryStore * Remove getUserRoles as an api call and add as decoded from the token (#274) Co-authored-by: Andrew Lee <1517745+andrewrlee@users.noreply.github.com> * Requre user input when excucting rename script to ensure slack alert channels are set correctly. (#277) * fix rename project github workflow, correct inputs key. (#278) * prompt for user input if script is run manually/locally (#279) * PI-1717 Set session cookie name per-project (#280) * PI-1717 Set session cookie name per-project * Add `.session` suffix * Ensure product ID is set when bootstraping new projects (#281) * Update dependencies 2023-12-08 (#282) * HAAR-2061: Remove deprecated filed (#285) * HAAR-2061: 1 ) Removed staffId (userId has same info), 2) activeCaseLoadId : can be derived from 'me/caseloads' endpoint in 'nomis-user-roles-api' * HAAR-2061: Remove staffId from stub * Add execute permission back to rename-project.bash script (#286) * Remove jQueryUI, initialise moj frontend (#288) * Update Helm release generic-service to 2.9 (#284) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update CHANGELOG.md (#275) * Update CHANGELOG.md * Update CHANGELOG.md --------- Co-authored-by: nishanthmahasamudram <129746957+nishanthmahasamudram@users.noreply.github.com> Co-authored-by: Paul Solecki <51918433+psoleckimoj@users.noreply.github.com> * Update all non major NPM dependencies (#271) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependencies 2023-12-18 (#290) * Update the breadcrumb partial with display current page title toggle (#291) * Upgrade to GovUK 5 and MoJ 2.0 (#297) * Upgrade to GovUK 5 and MoJ 2.0 * Update sass command * SDIT-1411: πŸ”₯ Remove stub user roles as getUserRoles already removed (#298) * SDIT-1223: 🎨 Document timeout configuration options (#269) * SDIT-1411: πŸ”₯ Remove shiv as not used anymore (#299) * Changelog update 2024-01-10 (#300) * Update all non major NPM dependencies (#294) * Update all non major NPM dependencies * Update Node.js to v20.11.0 * Update dependency husky to v9 * Update MoJ frontend, dropping jQuery which is no longer required (#303) * Update MoJ frontend, dropping jQuery which is no longer required * Remove unused static assets route: `govuk_frontend_toolkit` is not currently included * Prevent renovate from updating `engines.node` and `engines.npm` in package.json because we’d usually want to stick to an LTS version rather than move to the latest * Update Nunjucks component paths (#305) `node_modules/govuk-frontend/dist/components/` was missing `govuk/` before `components/`, however we don't really need both the higher-level and lower-level paths here. For greater consistency, just using the higher-level path to enable a single clearly-namespaced route to each GOV.UK and MOJ Nunjucks component makes more sense. The lower-level path is often unused on projects that use this template Imports should therefore look like this: ```njk {% from "govuk/components/checkboxes/macro.njk" import govukCheckboxes %} ``` ... and not this: ```njk {% from "checkboxes/macro.njk" import govukCheckboxes %} ``` * Update all non major NPM dependencies (#304) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * SDIT-1450: πŸ› Fix manage details link to allow user to return (#307) * Update all non major NPM dependencies (#309) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update all non major NPM dependencies (#310) * Update all non major NPM dependencies * Use un-deprecated method to initialise husky [changed in v9](https://github.com/typicode/husky/releases/tag/v9.0.1) * heat-31 update the bootstrap repo name to hmpps-project-bootstrap (#312) * Update dependencies 2024-02-14 (#314) * Update peter-evans/create-pull-request action to v6 (#306) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependency lint-staged to ^15.2.2 (#311) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * PI-1789 added ability to get parameterised paths in app insights for FE (#315) * Move to Debian 12 / bookworm (#316) * Move to Debian 12 / bookworm * Fix PR number in Changelog * Update all non major NPM dependencies (#317) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependency nock to ^13.5.3 (#318) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update all non major NPM dependencies (#319) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Setting node version for security audit and outdate (#321) Currently defaults to node 16 * Update all non major NPM dependencies (#320) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update Helm release generic-prometheus-alerts to 1.4 (#322) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update all non major NPM dependencies (#323) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Add .DS_Store to .gitignore (#324) * add a lint-fix option (#326) * Update all non major NPM dependencies (#325) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update Helm release generic-service to v3 (#328) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * HMPPS Audit setup (#327) * HMPPS Audit setup * Bug fix * Minor improvements * Updated service acount name default value * Config updates * Update all non major NPM dependencies (#330) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update Helm release generic-service to 3.1 (#332) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Paul Solecki <51918433+psoleckimoj@users.noreply.github.com> * Update dependencies 2024-03-26 (#333) * Update dependencies 2024-03-26 * Forgotten package.json * Update dependency govuk-frontend to ^5.3.0 (#334) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update Node.js to v20.12.0 (#335) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependency prom-client to ^15.1.1 (#336) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Paul Solecki <51918433+psoleckimoj@users.noreply.github.com> * Update all non major NPM dependencies (#337) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update all non major NPM dependencies (#339) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Correcting the MOJ Compliant badge (#340) * Update all non major NPM dependencies (#341) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update all non major NPM dependencies (#342) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependency applicationinsights to v3 (#344) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependency eslint-plugin-cypress to v3 (#345) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Paul Solecki <51918433+psoleckimoj@users.noreply.github.com> * Update dependency superagent to v9 (#346) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependency supertest to v7 (#347) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update Helm release generic-service to 3.2 (#348) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update Helm release generic-prometheus-alerts to 1.5 (#349) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * SDIT-1682: ⬆️ Upgrade node and helm charts (#350) Co-authored-by: Paul Solecki <51918433+psoleckimoj@users.noreply.github.com> * Revert appinsights change (#353) * Update all non major NPM dependencies (#355) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update Helm release generic-prometheus-alerts to 1.6 (#351) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Using the user token to get user details (#352) * Using the user token to get user details * Removing manage-users-api env variables * Addressing comments * Update all non major NPM dependencies (#356) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update Node.js to v20.13.1 (#357) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Paul Solecki <51918433+psoleckimoj@users.noreply.github.com> * Update Helm release generic-prometheus-alerts to 1.7 (#358) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Remove cookie-session (#360) --------- Signed-off-by: dependabot[bot] Co-authored-by: Matt <34448412+mattops@users.noreply.github.com> Co-authored-by: Gareth.m.Davies Co-authored-by: markreesmoj <76954782+markreesmoj@users.noreply.github.com> Co-authored-by: Andrew Lee <1517745+andrewrlee@users.noreply.github.com> Co-authored-by: petergphillips Co-authored-by: ushkarev Co-authored-by: richardpopple Co-authored-by: Paul Solecki <51918433+psoleckimoj@users.noreply.github.com> Co-authored-by: Michael Willis Co-authored-by: Connor Glynn <66882795+connormaglynn@users.noreply.github.com> Co-authored-by: Louise N Co-authored-by: Darren Oakley Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sp-watson <77974320+sp-watson@users.noreply.github.com> Co-authored-by: sp-watson Co-authored-by: Jon Wyatt Co-authored-by: Mike Halma <58170926+mikehalmamoj@users.noreply.github.com> Co-authored-by: Richard James <44123869+richpjames@users.noreply.github.com> Co-authored-by: Stuart Harrison Co-authored-by: Neil Mendum Co-authored-by: carlov20 Co-authored-by: Neil Mendum Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: David Middleton <122619525+davidmiddletonmoj@users.noreply.github.com> Co-authored-by: Gareth.m.Davies Co-authored-by: bryangaledxw <94137563+bryangaledxw@users.noreply.github.com> Co-authored-by: ravmoj <104509282+ravmoj@users.noreply.github.com> Co-authored-by: Steve Rendell <32732937+steverendell@users.noreply.github.com> Co-authored-by: Marcus Aspin Co-authored-by: Tom Ridd Co-authored-by: nishanthmahasamudram <129746957+nishanthmahasamudram@users.noreply.github.com> Co-authored-by: BarryGeeMOJ <104000682+BarryGeeMOJ@users.noreply.github.com> Co-authored-by: Ynda Jas <40244233+yndajas@users.noreply.github.com> Co-authored-by: Babu Haridass <157376412+babuharidass11@users.noreply.github.com> Co-authored-by: Anthony Britton <105213050+anthony-britton-moj@users.noreply.github.com> Co-authored-by: William Falconer Co-authored-by: Lee Jacobson <125488090+leejacobson-moj@users.noreply.github.com> Co-authored-by: petergphillips <5099053+petergphillips@users.noreply.github.com> --- .circleci/config.yml | 21 ++- .eslintrc.json | 5 +- .gitignore | 2 + LICENSE | 2 +- docker-compose-test.yml | 9 +- feature.env | 2 + package-lock.json | 178 +++++++++++++++--- package.json | 17 +- server/app.ts | 4 +- server/config.ts | 6 + server/data/adjudicationsApiClient.test.ts | 2 - server/data/caseNotesApiClient.test.ts | 2 - server/data/complexityApiClient.test.ts | 2 - server/data/healthCheck.test.ts | 6 +- server/data/healthCheck.ts | 2 +- server/data/hmppsAuthClient.test.ts | 4 +- server/data/hmppsAuthClient.ts | 2 +- server/data/incentivesApiClient.test.ts | 2 - server/data/index.ts | 8 +- server/data/manageSocCasesApiClient.test.ts | 2 - server/data/manageUsersApiClient.test.ts | 2 - server/data/nonAssociationsApiClient.test.ts | 2 - server/data/pathfinderApiClient.test.ts | 2 - server/data/prisonApiClient.test.ts | 2 - .../prisonerProfileDeliusApiClient.test.ts | 2 - .../tokenStore/inMemoryTokenStore.test.ts | 19 ++ server/data/tokenStore/inMemoryTokenStore.ts | 17 ++ .../redisTokenStore.test.ts} | 4 +- .../redisTokenStore.ts} | 7 +- server/data/tokenStore/tokenStore.ts | 4 + server/data/whereaboutsClient.test.ts | 2 - server/interfaces/HmppsUser.ts | 37 +++- server/middleware/setUpWebSession.ts | 14 +- server/utils/azureAppInsights.ts | 1 - server/views/pages/error.njk | 2 +- server/views/partials/breadCrumb.njk | 32 ---- 36 files changed, 298 insertions(+), 129 deletions(-) create mode 100644 server/data/tokenStore/inMemoryTokenStore.test.ts create mode 100644 server/data/tokenStore/inMemoryTokenStore.ts rename server/data/{tokenStore.test.ts => tokenStore/redisTokenStore.test.ts} (94%) rename server/data/{tokenStore.ts => tokenStore/redisTokenStore.ts} (77%) create mode 100644 server/data/tokenStore/tokenStore.ts delete mode 100644 server/views/partials/breadCrumb.njk diff --git a/.circleci/config.yml b/.circleci/config.yml index f20ecb8bf..3f515b202 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,8 +1,8 @@ version: 2.1 orbs: - hmpps: ministryofjustice/hmpps@7 - slack: circleci/slack@4.4.2 + hmpps: ministryofjustice/hmpps@8 + slack: circleci/slack@4.12.5 parameters: alerts-slack-channel: @@ -26,7 +26,7 @@ jobs: - checkout - run: name: Update npm - command: 'sudo npm install -g npm@9' + command: 'sudo npm install -g npm@latest' - restore_cache: key: dependency-cache-{{ checksum "package-lock.json" }} - run: @@ -97,9 +97,8 @@ jobs: integration_test: executor: - name: hmpps/node_redis - node_tag: << pipeline.parameters.node-version >> - redis_tag: "6.2" + name: hmpps/node + tag: << pipeline.parameters.node-version >> steps: - checkout - attach_workspace: @@ -111,8 +110,7 @@ jobs: key: dependency-cache-{{ checksum "package-lock.json" }} - run: name: Get wiremock - command: curl -o wiremock.jar - https://repo1.maven.org/maven2/com/github/tomakehurst/wiremock-standalone/2.27.1/wiremock-standalone-2.27.1.jar + command: curl -o wiremock.jar https://repo1.maven.org/maven2/org/wiremock/wiremock-standalone/3.3.1/wiremock-standalone-3.3.1.jar - run: name: Run wiremock command: java -jar wiremock.jar --port 9091 @@ -160,6 +158,8 @@ workflows: name: deploy_dev env: "dev" jira_update: true + pipeline_id: <> + pipeline_number: <> context: hmpps-common-vars filters: branches: @@ -213,11 +213,14 @@ workflows: only: - main jobs: - - check_outdated: + - hmpps/npm_outdated: + slack_channel: << pipeline.parameters.alerts-slack-channel >> + node_tag: << pipeline.parameters.node-version >> context: - hmpps-common-vars - hmpps/npm_security_audit: slack_channel: << pipeline.parameters.alerts-slack-channel >> + node_tag: << pipeline.parameters.node-version >> context: - hmpps-common-vars - hmpps/trivy_latest_scan: diff --git a/.eslintrc.json b/.eslintrc.json index 32837b967..34d8ab5fd 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -67,7 +67,9 @@ "plugin:prettier/recommended" ], "rules": { + "no-shadow": "off", "dot-notation": "off", + "@typescript-eslint/no-shadow": ["error"], "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-use-before-define": 0, "class-methods-use-this": 0, @@ -134,6 +136,7 @@ "printWidth": 120, "semi": false } - ] + ], + "no-empty-function": ["error", { "allow": ["constructors", "arrowFunctions"] }] } } diff --git a/.gitignore b/.gitignore index a0e755501..5bddd0951 100644 --- a/.gitignore +++ b/.gitignore @@ -116,6 +116,7 @@ dist .pnp.* + assets/stylesheets/*.css .idea .vscode @@ -126,3 +127,4 @@ integration_tests/videos/ integration_tests/screenshots/ */*.iml **/Chart.lock +**/.DS_Store diff --git a/LICENSE b/LICENSE index c579c748d..d158ef802 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2021 Crown Copyright (Ministry of Justice) +Copyright (c) 2020-2023 Crown Copyright (Ministry of Justice) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 7a9c5dadd..a11224f22 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -1,17 +1,10 @@ version: '3.1' services: - redis: - image: 'redis:6.2' - networks: - - hmpps_int - ports: - - '6379:6379' - wiremock: image: wiremock/wiremock networks: - - hmpps_int + - hmpps_int container_name: wiremock restart: always ports: diff --git a/feature.env b/feature.env index db98e680a..8dd0d883b 100644 --- a/feature.env +++ b/feature.env @@ -2,12 +2,14 @@ PORT=3007 HMPPS_AUTH_URL=http://localhost:9091/auth TOKEN_VERIFICATION_API_URL=http://localhost:9091/verification TOKEN_VERIFICATION_ENABLED=true +REDIS_ENABLED=false NODE_ENV=development API_CLIENT_ID=clientid API_CLIENT_SECRET=clientsecret SYSTEM_CLIENT_ID=clientid SYSTEM_CLIENT_SECRET=clientsecret SYSTEM_PHASE=DEV +ENVIRONMENT_NAME=dev PRISON_API_URL=http://localhost:9091/prison PRISONER_SEARCH_API_URL=http://localhost:9091/prisonersearch diff --git a/package-lock.json b/package-lock.json index 9d4252ed5..eb761e9a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,27 +56,29 @@ "@types/http-errors": "^2.0.4", "@types/jest": "^29.5.12", "@types/jsonwebtoken": "^9.0.6", - "@types/node": "^20.12.7", + "@types/node": "^20.12.11", "@types/nunjucks": "^3.2.6", "@types/passport": "^1.0.16", - "@types/passport-oauth2": "^1.4.15", - "@types/superagent": "^8.1.6", + "@types/passport-oauth2": "^1.4.16", + "@types/superagent": "^8.1.7", "@types/supertest": "^6.0.2", "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^7.8.0", "@typescript-eslint/parser": "^7.8.0", "audit-ci": "^6.6.1", + "aws-sdk-client-mock": "^4.0.0", "concurrently": "^8.2.2", "cookie-session": "^2.1.0", - "cypress": "^13.8.1", + "cypress": "^13.9.0", "cypress-multi-reporters": "^1.6.4", "dotenv": "^16.4.5", "eslint": "^8.57.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-cypress": "^2.15.2", + "eslint-plugin-cypress": "^3.2.0", "eslint-plugin-import": "^2.29.1", + "eslint-plugin-no-only-tests": "^3.1.0", "eslint-plugin-prettier": "^5.1.3", "husky": "^9.0.11", "jest": "^29.7.0", @@ -89,7 +91,7 @@ "nodemon": "^3.1.0", "prettier": "^3.2.5", "prettier-plugin-jinja-template": "^1.4.0", - "sass": "^1.76.0", + "sass": "^1.77.0", "supertest": "^7.0.0", "ts-jest": "^29.1.2", "typescript": "^5.4.5" @@ -2373,6 +2375,32 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, "node_modules/@smithy/abort-controller": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.2.0.tgz", @@ -3139,9 +3167,9 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, "node_modules/@types/node": { - "version": "20.12.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", - "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", "dependencies": { "undici-types": "~5.26.4" } @@ -3171,9 +3199,9 @@ } }, "node_modules/@types/passport-oauth2": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.4.15.tgz", - "integrity": "sha512-9cUTP/HStNSZmhxXGuRrBJfEWzIEJRub2eyJu3CvkA+8HAMc9W3aKdFhVq+Qz1hi42qn+GvSAnz3zwacDSYWpw==", + "version": "1.4.16", + "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.4.16.tgz", + "integrity": "sha512-Sdr0rpAdkiidUOtyaapGgvXyMjqYlMTFHRy7gtJtzr0/ysEIa72N3j2FSHIRc14h29g1+dzDl8IW2WT2Mu29vQ==", "dev": true, "dependencies": { "@types/express": "*", @@ -3221,6 +3249,15 @@ "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.0.5.tgz", "integrity": "sha512-9Hp0ObzwwO57DpLFF0InUjUm/II8GmKAvzbefxQTihCb7KI6yc9yzf0nLc4mVdby5N4DRCgQM2wCup9KTieeww==" }, + "node_modules/@types/sinon": { + "version": "10.0.20", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.20.tgz", + "integrity": "sha512-2APKKruFNCAZgx3daAyACGzWuJ028VVCUDk6o2rw/Z4PXT0ogwdV4KUegW0MwVs0Zu59auPXbbuBJHF12Sx1Eg==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, "node_modules/@types/sinonjs__fake-timers": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", @@ -3240,9 +3277,9 @@ "dev": true }, "node_modules/@types/superagent": { - "version": "8.1.6", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.6.tgz", - "integrity": "sha512-yzBOv+6meEHSzV2NThYYOA6RtqvPr3Hbob9ZLp3i07SH27CrYVfm8CrF7ydTmidtelsFiKx2I4gZAiAOamGgvQ==", + "version": "8.1.7", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.7.tgz", + "integrity": "sha512-NmIsd0Yj4DDhftfWvvAku482PZum4DBW7U51OvS8gvOkDDY0WT1jsVyDV3hK+vplrsYw8oDwi9QxOM7U68iwww==", "dev": true, "dependencies": { "@types/cookiejar": "^2.1.5", @@ -4013,6 +4050,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/aws-sdk-client-mock": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/aws-sdk-client-mock/-/aws-sdk-client-mock-4.0.0.tgz", + "integrity": "sha512-/rxo+pzCFaUozK7TyCqo3GYwzdBGn9Ai6EsT8ytXDoUXlD/Q5hw9hj2lOkCAyubECzGJFHMmQg9GZ1GOGlN/qQ==", + "dev": true, + "dependencies": { + "@types/sinon": "^10.0.10", + "sinon": "^16.1.3", + "tslib": "^2.1.0" + } + }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -5197,9 +5245,9 @@ } }, "node_modules/cypress": { - "version": "13.8.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.8.1.tgz", - "integrity": "sha512-Uk6ovhRbTg6FmXjeZW/TkbRM07KPtvM5gah1BIMp4Y2s+i/NMxgaLw0+PbYTOdw1+egE0FP3mWRiGcRkjjmhzA==", + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.9.0.tgz", + "integrity": "sha512-atNjmYfHsvTuCaxTxLZr9xGoHz53LLui3266WWxXJHY7+N6OdwJdg/feEa3T+buez9dmUXHT1izCOklqG82uCQ==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -6008,15 +6056,15 @@ } }, "node_modules/eslint-plugin-cypress": { - "version": "2.15.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.15.2.tgz", - "integrity": "sha512-CtcFEQTDKyftpI22FVGpx8bkpKyYXBlNge6zSo0pl5/qJvBAnzaD76Vu2AsP16d6mTj478Ldn2mhgrWV+Xr0vQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-3.2.0.tgz", + "integrity": "sha512-HaxMz6BoU4ay+K4WrG9ZJC1NdX06FqSlAwtRDStjM0ORFT7zCNPNuRJ+kUPc17Rt2AMUBSqeD9L0zTR3uZhPpw==", "dev": true, "dependencies": { "globals": "^13.20.0" }, "peerDependencies": { - "eslint": ">= 3.2.1" + "eslint": ">=7" } }, "node_modules/eslint-plugin-import": { @@ -6097,6 +6145,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-3.1.0.tgz", "integrity": "sha512-Lf4YW/bL6Un1R6A76pRZyE1dl1vr31G/ev8UzIc/geCgFWyrKil8hVjYqWVKGB/UIGmb6Slzs9T0wNezdSVegw==", + "dev": true, "engines": { "node": ">=5.0.0" } @@ -8863,6 +8912,12 @@ "verror": "1.10.0" } }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true + }, "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -9459,6 +9514,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -10033,6 +10094,34 @@ "node": ">= 0.6" } }, + "node_modules/nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/nise/node_modules/path-to-regexp": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", + "dev": true + }, "node_modules/nocache": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/nocache/-/nocache-4.0.0.tgz", @@ -11368,9 +11457,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.76.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.76.0.tgz", - "integrity": "sha512-nc3LeqvF2FNW5xGF1zxZifdW3ffIz5aBb7I7tSvOoNu7z1RQ6pFt9MBuiPtjgaI62YWrM/txjWlOCFiGtf2xpw==", + "version": "1.77.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.1.tgz", + "integrity": "sha512-OMEyfirt9XEfyvocduUIOlUSkWOXS/LAt6oblR/ISXCTukyavjex+zQNm51pPCOiFKY1QpWvEH1EeCkgyV3I6w==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -11585,6 +11674,45 @@ "node": ">=10" } }, + "node_modules/sinon": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-16.1.3.tgz", + "integrity": "sha512-mjnWWeyxcAf9nC0bXcPmiDut+oE8HYridTNzBbF98AYVLmWwGRp2ISEpyhYflG1ifILT+eNn3BmKUJPxjXUPlA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^10.3.0", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.4", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", diff --git a/package.json b/package.json index ef7ab4e9e..acd096063 100644 --- a/package.json +++ b/package.json @@ -20,13 +20,14 @@ "start-feature:dev": "npm run build && 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\"", "record-build-info": "node ./bin/record-build-info", "lint": "eslint . --cache --max-warnings 0", + "lint-fix": "eslint . --cache --max-warnings 0 --fix", "typecheck": "tsc && tsc -p integration_tests", "test": "jest", "test:coverage": "jest --coverage", "test:ci": "jest --runInBand", "security_audit": "npx audit-ci --config audit-ci.json", "int-test": "cypress run --config video=false", - "int-test-ui": "cypress open", + "int-test-ui": "cypress open --e2e --browser chrome", "clean": "rm -rf dist build node_modules stylesheets", "rebuild": "npm run clean && npm i && npm run build" }, @@ -145,27 +146,29 @@ "@types/http-errors": "^2.0.4", "@types/jest": "^29.5.12", "@types/jsonwebtoken": "^9.0.6", - "@types/node": "^20.12.7", + "@types/node": "^20.12.11", "@types/nunjucks": "^3.2.6", "@types/passport": "^1.0.16", - "@types/passport-oauth2": "^1.4.15", - "@types/superagent": "^8.1.6", + "@types/passport-oauth2": "^1.4.16", + "@types/superagent": "^8.1.7", "@types/supertest": "^6.0.2", "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^7.8.0", "@typescript-eslint/parser": "^7.8.0", "audit-ci": "^6.6.1", + "aws-sdk-client-mock": "^4.0.0", "concurrently": "^8.2.2", "cookie-session": "^2.1.0", - "cypress": "^13.8.1", + "cypress": "^13.9.0", "cypress-multi-reporters": "^1.6.4", "dotenv": "^16.4.5", "eslint": "^8.57.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-cypress": "^2.15.2", + "eslint-plugin-cypress": "^3.2.0", "eslint-plugin-import": "^2.29.1", + "eslint-plugin-no-only-tests": "^3.1.0", "eslint-plugin-prettier": "^5.1.3", "husky": "^9.0.11", "jest": "^29.7.0", @@ -178,7 +181,7 @@ "nodemon": "^3.1.0", "prettier": "^3.2.5", "prettier-plugin-jinja-template": "^1.4.0", - "sass": "^1.76.0", + "sass": "^1.77.0", "supertest": "^7.0.0", "ts-jest": "^29.1.2", "typescript": "^5.4.5" diff --git a/server/app.ts b/server/app.ts index b9aaee035..7a83883ac 100755 --- a/server/app.ts +++ b/server/app.ts @@ -1,8 +1,9 @@ -import path from 'path' import express from 'express' +import path from 'path' import nunjucksSetup from './utils/nunjucksSetup' import errorHandler from './errorHandler' +import { appInsightsMiddleware } from './utils/azureAppInsights' import authorisationMiddleware from './middleware/authorisationMiddleware' import { metricsMiddleware } from './monitoring/metricsApp' @@ -23,7 +24,6 @@ import flashMessageMiddleware from './middleware/flashMessageMiddleware' import setUpEnvironmentName from './middleware/setUpEnvironmentName' import apiErrorMiddleware from './middleware/apiErrorMiddleware' import bannerMiddleware from './middleware/bannerMiddleware' -import { appInsightsMiddleware } from './utils/azureAppInsights' export default function createApp(services: Services): express.Application { const app = express() diff --git a/server/config.ts b/server/config.ts index 1ffeaa493..3dd1871cd 100755 --- a/server/config.ts +++ b/server/config.ts @@ -17,6 +17,7 @@ function get(name: string, fallback: T, options = { requireInProduction: fals const requiredInProduction = { requireInProduction: true } export class AgentConfig { + // Sets the working socket to timeout after timeout milliseconds of inactivity on the working socket. timeout: number constructor(timeout = 8000) { @@ -27,7 +28,11 @@ export class AgentConfig { export interface ApiConfig { url: string timeout: { + // sets maximum time to wait for the first byte to arrive from the server, but it does not limit how long the + // entire download can take. response: number + // sets a deadline for the entire request (including all uploads, redirects, server processing time) to complete. + // If the response isn't fully downloaded within that time, the request will be aborted. deadline: number } agent: AgentConfig @@ -42,6 +47,7 @@ export default { https: production, staticResourceCacheDuration: 20, redis: { + enabled: get('REDIS_ENABLED', 'false', requiredInProduction) === 'true', host: get('REDIS_HOST', 'localhost', requiredInProduction), port: parseInt(process.env.REDIS_PORT, 10) || 6379, password: process.env.REDIS_AUTH_TOKEN, diff --git a/server/data/adjudicationsApiClient.test.ts b/server/data/adjudicationsApiClient.test.ts index d0c1b53ac..17ffcf601 100644 --- a/server/data/adjudicationsApiClient.test.ts +++ b/server/data/adjudicationsApiClient.test.ts @@ -3,8 +3,6 @@ import config from '../config' import { adjudicationSummaryWithActiveMock } from './localMockData/miniSummaryMock' import AdjudicationsApiRestClient from './adjudicationsApiClient' -jest.mock('./tokenStore') - const token = { access_token: 'token-1', expires_in: 300 } describe('adjudicationsApiClient', () => { diff --git a/server/data/caseNotesApiClient.test.ts b/server/data/caseNotesApiClient.test.ts index ad940f834..c9e7d6680 100644 --- a/server/data/caseNotesApiClient.test.ts +++ b/server/data/caseNotesApiClient.test.ts @@ -7,8 +7,6 @@ import CaseNotesApiClient from './interfaces/caseNotesApi/caseNotesApiClient' import UpdateCaseNoteForm from './interfaces/caseNotesApi/UpdateCaseNoteForm' import CaseNote from './interfaces/caseNotesApi/CaseNote' -jest.mock('./tokenStore') - const token = { access_token: 'token-1', expires_in: 300 } describe('caseNotesApiClient', () => { diff --git a/server/data/complexityApiClient.test.ts b/server/data/complexityApiClient.test.ts index 6d3ac908c..955751d74 100644 --- a/server/data/complexityApiClient.test.ts +++ b/server/data/complexityApiClient.test.ts @@ -3,8 +3,6 @@ import config from '../config' import { complexityOfNeedHighMock } from './localMockData/complexityOfNeedMock' import ComplexityApiRestClient from './complexityApiClient' -jest.mock('./tokenStore') - const token = { access_token: 'token-1', expires_in: 300 } describe('complexityApiClient', () => { diff --git a/server/data/healthCheck.test.ts b/server/data/healthCheck.test.ts index 636221b7a..84649fe2d 100644 --- a/server/data/healthCheck.test.ts +++ b/server/data/healthCheck.test.ts @@ -24,7 +24,7 @@ describe('Service healthcheck', () => { fakeServiceApi.get('/ping').reply(200, 'pong') const output = await healthcheck() - expect(output).toEqual('OK') + expect(output).toEqual('UP') }) }) @@ -47,7 +47,7 @@ describe('Service healthcheck', () => { .reply(200, 'pong') const response = await healthcheck() - expect(response).toEqual('OK') + expect(response).toEqual('UP') }) it('Should retry twice if request times out', async () => { @@ -62,7 +62,7 @@ describe('Service healthcheck', () => { .reply(200, 'pong') const response = await healthcheck() - expect(response).toEqual('OK') + expect(response).toEqual('UP') }) it('Should fail if request times out three times', async () => { diff --git a/server/data/healthCheck.ts b/server/data/healthCheck.ts index 1eb2d920c..c42f2a3ce 100644 --- a/server/data/healthCheck.ts +++ b/server/data/healthCheck.ts @@ -34,7 +34,7 @@ export function serviceCheckFactory( logger.error(error.stack, `Error calling ${name}`) reject(error) } else if (result.status === 200) { - resolve('OK') + resolve('UP') } else { reject(result.status) } diff --git a/server/data/hmppsAuthClient.test.ts b/server/data/hmppsAuthClient.test.ts index 13ffbc676..7fdfeae9f 100644 --- a/server/data/hmppsAuthClient.test.ts +++ b/server/data/hmppsAuthClient.test.ts @@ -2,9 +2,9 @@ import nock from 'nock' import config from '../config' import { systemTokenBuilder } from './hmppsAuthClient' -import TokenStore from './tokenStore' +import TokenStore from './tokenStore/redisTokenStore' -jest.mock('./tokenStore') +jest.mock('./tokenStore/redisTokenStore') const tokenStore = new TokenStore(null) as jest.Mocked diff --git a/server/data/hmppsAuthClient.ts b/server/data/hmppsAuthClient.ts index 6541ea857..a19888227 100644 --- a/server/data/hmppsAuthClient.ts +++ b/server/data/hmppsAuthClient.ts @@ -1,10 +1,10 @@ import { URLSearchParams } from 'url' import superagent from 'superagent' -import type TokenStore from './tokenStore' import logger from '../../logger' import config from '../config' import generateOauthClientToken from '../authentication/clientCredentials' +import TokenStore from './tokenStore/tokenStore' const timeoutSpec = config.apis.hmppsAuth.timeout const hmppsAuthUrl = config.apis.hmppsAuth.url diff --git a/server/data/incentivesApiClient.test.ts b/server/data/incentivesApiClient.test.ts index cafb0b23c..526ad34b9 100644 --- a/server/data/incentivesApiClient.test.ts +++ b/server/data/incentivesApiClient.test.ts @@ -3,8 +3,6 @@ import config from '../config' import IncentivesApiRestClient from './incentivesApiClient' import { incentiveReviewsMock } from './localMockData/incentiveReviewsMock' -jest.mock('./tokenStore') - const token = { access_token: 'token-1', expires_in: 300 } describe('caseNotesApiClient', () => { diff --git a/server/data/index.ts b/server/data/index.ts index 31d4bee54..e7e9b937b 100644 --- a/server/data/index.ts +++ b/server/data/index.ts @@ -16,7 +16,6 @@ import PrisonApiRestClient from './prisonApiClient' import PrisonerSearchClient from './prisonerSearchClient' import { createRedisClient } from './redisClient' -import TokenStore from './tokenStore' import AdjudicationsApiRestClient from './adjudicationsApiClient' import NonAssociationsApiRestClient from './nonAssociationsApiClient' import ComponentApiRestClient from './componentApiClient' @@ -30,6 +29,9 @@ import RestrictedPatientApiRestClient from './restrictedPatientApiClient' import PrisonRegisterStore from './prisonRegisterStore/prisonRegisterStore' import CalculateReleaseDatesApiClient from './calculateReleaseDatesApiClient' import PrisonRegisterApiRestClient from './prisonRegisterApiClient' +import config from '../config' +import RedisTokenStore from './tokenStore/redisTokenStore' +import InMemoryTokenStore from './tokenStore/inMemoryTokenStore' initialiseAppInsights() buildAppInsightsClient(applicationInfo()) @@ -48,7 +50,9 @@ export const dataAccess = { adjudicationsApiClientBuilder: (token: string) => new AdjudicationsApiRestClient(token), prisonApiClientBuilder: (token: string) => new PrisonApiRestClient(token), prisonerSearchApiClientBuilder: (token: string) => new PrisonerSearchClient(token), - systemToken: systemTokenBuilder(new TokenStore(createRedisClient())), + systemToken: systemTokenBuilder( + config.redis.enabled ? new RedisTokenStore(createRedisClient()) : new InMemoryTokenStore(), + ), nonAssociationsApiClientBuilder: (token: string) => new NonAssociationsApiRestClient(token), componentApiClientBuilder: (token: string) => new ComponentApiRestClient(token), whereaboutsApiClientBuilder: (token: string) => new WhereaboutsRestApiClient(token), diff --git a/server/data/manageSocCasesApiClient.test.ts b/server/data/manageSocCasesApiClient.test.ts index 3e4730742..a81ed37b0 100644 --- a/server/data/manageSocCasesApiClient.test.ts +++ b/server/data/manageSocCasesApiClient.test.ts @@ -3,8 +3,6 @@ import config from '../config' import { ManageSocCasesApiClient } from './interfaces/manageSocCasesApi/manageSocCasesApiClient' import ManageSocCasesApiRestClient from './manageSocCasesApiClient' -jest.mock('./tokenStore') - const token = { access_token: 'token-1', expires_in: 300 } describe('manageSocCasesApiClient', () => { diff --git a/server/data/manageUsersApiClient.test.ts b/server/data/manageUsersApiClient.test.ts index 7bb7b6c4b..dfa07b994 100644 --- a/server/data/manageUsersApiClient.test.ts +++ b/server/data/manageUsersApiClient.test.ts @@ -3,8 +3,6 @@ import config from '../config' import ManageUsersApiRestClient from './manageUsersApiClient' import { ManageUsersApiClient } from './interfaces/manageUsersApi/manageUsersApiClient' -jest.mock('./tokenStore') - const token = { access_token: 'token-1', expires_in: 300 } describe('manageUsersApiClient', () => { diff --git a/server/data/nonAssociationsApiClient.test.ts b/server/data/nonAssociationsApiClient.test.ts index 2f94a1177..d64b25022 100644 --- a/server/data/nonAssociationsApiClient.test.ts +++ b/server/data/nonAssociationsApiClient.test.ts @@ -4,8 +4,6 @@ import { NonAssociationsApiClient } from './interfaces/nonAssociationsApi/nonAss import NonAssociationsApiRestClient from './nonAssociationsApiClient' import { prisonerNonAssociationsMock } from './localMockData/prisonerNonAssociationsMock' -jest.mock('./tokenStore') - const token = { access_token: 'token-1', expires_in: 300 } describe('nonAssociationsApiClient', () => { diff --git a/server/data/pathfinderApiClient.test.ts b/server/data/pathfinderApiClient.test.ts index c24acd2a5..f5cff8b84 100644 --- a/server/data/pathfinderApiClient.test.ts +++ b/server/data/pathfinderApiClient.test.ts @@ -3,8 +3,6 @@ import config from '../config' import PathfinderApiRestClient from './pathfinderApiClient' import { PathfinderApiClient } from './interfaces/pathfinderApi/pathfinderApiClient' -jest.mock('./tokenStore') - const token = { access_token: 'token-1', expires_in: 300 } describe('pathfinderApiClient', () => { diff --git a/server/data/prisonApiClient.test.ts b/server/data/prisonApiClient.test.ts index cb20b3181..8b3964a42 100644 --- a/server/data/prisonApiClient.test.ts +++ b/server/data/prisonApiClient.test.ts @@ -55,8 +55,6 @@ import { pagedVisitsMock } from './localMockData/pagedVisitsWithVisitors' import { visitPrisonsMock } from './localMockData/visitPrisons' import { pagedActivePrisonApiAlertsMock } from './localMockData/pagedAlertsMock' -jest.mock('./tokenStore') - const token = { access_token: 'token-1', expires_in: 300 } describe('prisonApiClient', () => { diff --git a/server/data/prisonerProfileDeliusApiClient.test.ts b/server/data/prisonerProfileDeliusApiClient.test.ts index 84f10f072..ff5e7cfa3 100644 --- a/server/data/prisonerProfileDeliusApiClient.test.ts +++ b/server/data/prisonerProfileDeliusApiClient.test.ts @@ -5,8 +5,6 @@ import { PrisonerProfileDeliusApiClient } from './interfaces/deliusApi/prisonerP import { communityManagerMock } from './localMockData/communityManagerMock' import probationDocuments from './localMockData/deliusApi/probationDocuments' -jest.mock('./tokenStore') - const token = { access_token: 'token-1', expires_in: 300 } describe('prisonerProfileDeliusApiClient', () => { diff --git a/server/data/tokenStore/inMemoryTokenStore.test.ts b/server/data/tokenStore/inMemoryTokenStore.test.ts new file mode 100644 index 000000000..eb9b2fac4 --- /dev/null +++ b/server/data/tokenStore/inMemoryTokenStore.test.ts @@ -0,0 +1,19 @@ +import TokenStore from './inMemoryTokenStore' + +describe('inMemoryTokenStore', () => { + let tokenStore: TokenStore + + beforeEach(() => { + tokenStore = new TokenStore() + }) + + it('Can store and retrieve token', async () => { + await tokenStore.setToken('user-1', 'token-1', 10) + expect(await tokenStore.getToken('user-1')).toBe('token-1') + }) + + it('Expires token', async () => { + await tokenStore.setToken('user-2', 'token-2', -1) + expect(await tokenStore.getToken('user-2')).toBe(null) + }) +}) diff --git a/server/data/tokenStore/inMemoryTokenStore.ts b/server/data/tokenStore/inMemoryTokenStore.ts new file mode 100644 index 000000000..2b6a1b440 --- /dev/null +++ b/server/data/tokenStore/inMemoryTokenStore.ts @@ -0,0 +1,17 @@ +import TokenStore from './tokenStore' + +export default class InMemoryTokenStore implements TokenStore { + map = new Map() + + public async setToken(key: string, token: string, durationSeconds: number): Promise { + this.map.set(key, { token, expiry: new Date(Date.now() + durationSeconds * 1000) }) + return Promise.resolve() + } + + public async getToken(key: string): Promise { + if (!this.map.has(key) || this.map.get(key).expiry.getTime() < Date.now()) { + return Promise.resolve(null) + } + return Promise.resolve(this.map.get(key).token) + } +} diff --git a/server/data/tokenStore.test.ts b/server/data/tokenStore/redisTokenStore.test.ts similarity index 94% rename from server/data/tokenStore.test.ts rename to server/data/tokenStore/redisTokenStore.test.ts index 56b6104b2..ef98d6cbc 100644 --- a/server/data/tokenStore.test.ts +++ b/server/data/tokenStore/redisTokenStore.test.ts @@ -1,5 +1,5 @@ -import { RedisClient } from './redisClient' -import TokenStore from './tokenStore' +import { RedisClient } from '../redisClient' +import TokenStore from './redisTokenStore' const redisClient = { get: jest.fn(), diff --git a/server/data/tokenStore.ts b/server/data/tokenStore/redisTokenStore.ts similarity index 77% rename from server/data/tokenStore.ts rename to server/data/tokenStore/redisTokenStore.ts index d0f43a2bc..52032f703 100644 --- a/server/data/tokenStore.ts +++ b/server/data/tokenStore/redisTokenStore.ts @@ -1,8 +1,9 @@ -import type { RedisClient } from './redisClient' +import type { RedisClient } from '../redisClient' -import logger from '../../logger' +import logger from '../../../logger' +import TokenStore from './tokenStore' -export default class TokenStore { +export default class RedisTokenStore implements TokenStore { private readonly prefix = 'systemToken:' constructor(private readonly client: RedisClient) { diff --git a/server/data/tokenStore/tokenStore.ts b/server/data/tokenStore/tokenStore.ts new file mode 100644 index 000000000..60f3a2827 --- /dev/null +++ b/server/data/tokenStore/tokenStore.ts @@ -0,0 +1,4 @@ +export default interface TokenStore { + setToken(key: string, token: string, durationSeconds: number): Promise + getToken(key: string): Promise +} diff --git a/server/data/whereaboutsClient.test.ts b/server/data/whereaboutsClient.test.ts index ed4c63368..49744e4bf 100644 --- a/server/data/whereaboutsClient.test.ts +++ b/server/data/whereaboutsClient.test.ts @@ -6,8 +6,6 @@ import { courtLocationsMock } from './localMockData/courtLocationsMock' import { appointmentMock } from './localMockData/appointmentMock' import { videoLinkBookingMock } from './localMockData/videoLinkBookingMock' -jest.mock('./tokenStore') - const token = { access_token: 'token-1', expires_in: 300 } describe('whereaboutsClient', () => { diff --git a/server/interfaces/HmppsUser.ts b/server/interfaces/HmppsUser.ts index 3a4099a63..68330479e 100644 --- a/server/interfaces/HmppsUser.ts +++ b/server/interfaces/HmppsUser.ts @@ -1,8 +1,11 @@ import CaseLoad from '../data/interfaces/prisonApi/CaseLoad' import { Role } from '../data/enums/role' -export type AuthSource = 'nomis' | 'delius' | 'external' +export type AuthSource = 'nomis' | 'delius' | 'external' | 'azuread' +/** + * These are the details that all user types share. + */ export interface BaseUser { authSource: AuthSource username: string @@ -14,6 +17,14 @@ export interface BaseUser { backLink?: string } +/** + * Prison users are those that have a user account in NOMIS. + * HMPPS Auth automatically grants these users a `ROLE_PRISON` role. + * Prison users have an additional numerical staffId. The userId is + * a stringified version of the staffId. Some teams may need to separately + * retrieve the user case load (which prisons that a prison user has access + * to) and store it here, an example can be found in `hmpps-prisoner-profile`. + */ export interface PrisonUser extends BaseUser { authSource: 'nomis' staffId: number @@ -21,12 +32,34 @@ export interface PrisonUser extends BaseUser { caseLoads: CaseLoad[] } +/** + * Probation users are those that have a user account in nDelius. + * HMPPS Auth automatically grants these users a `ROLE_PROBATION` role. + */ export interface ProbationUser extends BaseUser { authSource: 'delius' } +/** + * External users are those that have a user account in our External Users + * database. These accounts are created for users that need access to HMPPS + * services but have neither NOMIS nor nDelius access. + */ export interface ExternalUser extends BaseUser { authSource: 'external' } -export type HmppsUser = PrisonUser | ProbationUser | ExternalUser +/** + * AzureAD users are those that have a justice.gov.uk email address and + * an account in MoJ's Azure AD (now called Entra ID) tenant. HMPPS Auth + * will normally check to see if there is a Prison/Probation/External + * user with the same email address and request that the user to pick one + * to use to access the service, however if there is no match, it is + * possible that a user of this type could attempt to access the service, + * and would have no user roles associated. + */ +export interface AzureADUser extends BaseUser { + authSource: 'azuread' +} + +export type HmppsUser = PrisonUser | ProbationUser | ExternalUser | AzureADUser diff --git a/server/middleware/setUpWebSession.ts b/server/middleware/setUpWebSession.ts index a3b4a99e8..3f2f8877b 100644 --- a/server/middleware/setUpWebSession.ts +++ b/server/middleware/setUpWebSession.ts @@ -1,5 +1,5 @@ import { v4 as uuidv4 } from 'uuid' -import session from 'express-session' +import session, { MemoryStore, Store } from 'express-session' import RedisStore from 'connect-redis' import express, { Router } from 'express' import { createRedisClient } from '../data/redisClient' @@ -7,13 +7,19 @@ import config from '../config' import logger from '../../logger' export default function setUpWebSession(): Router { - const client = createRedisClient() - client.connect().catch((err: Error) => logger.error(`Error connecting to Redis`, err)) + let store: Store + if (config.redis.enabled) { + const client = createRedisClient() + client.connect().catch((err: Error) => logger.error(`Error connecting to Redis`, err)) + store = new RedisStore({ client }) + } else { + store = new MemoryStore() + } const router = express.Router() router.use( session({ - store: new RedisStore({ client }), + store, name: 'hmpps-prisoner-profile', cookie: { secure: config.https, sameSite: 'lax', maxAge: config.session.expiryMinutes * 60 * 1000 }, secret: config.session.secret, diff --git a/server/utils/azureAppInsights.ts b/server/utils/azureAppInsights.ts index 2b9c40e51..251c8f562 100644 --- a/server/utils/azureAppInsights.ts +++ b/server/utils/azureAppInsights.ts @@ -30,7 +30,6 @@ export function buildAppInsightsClient({ applicationName, buildNumber }: Applica defaultClient.context.tags['ai.application.ver'] = buildNumber defaultClient.addTelemetryProcessor(addUserDataToRequests) defaultClient.addTelemetryProcessor(parameterisePaths) - return defaultClient } return null diff --git a/server/views/pages/error.njk b/server/views/pages/error.njk index 27127fd8d..db29f7e96 100755 --- a/server/views/pages/error.njk +++ b/server/views/pages/error.njk @@ -1,6 +1,6 @@ {% extends "../partials/layout.njk" %} -{% set pageTitle = "Error" %} +{% set pageTitle = applicationName + " - Error" %} {% set mainClasses = "app-container govuk-body" %} {% block content %} diff --git a/server/views/partials/breadCrumb.njk b/server/views/partials/breadCrumb.njk deleted file mode 100644 index affaa72df..000000000 --- a/server/views/partials/breadCrumb.njk +++ /dev/null @@ -1,32 +0,0 @@ -{% from "govuk/components/breadcrumbs/macro.njk" import govukBreadcrumbs %} - -{% macro breadCrumb(pageTitle, breadCrumbList) %} - - {% set rows = [ { - text: "Home", - href: '/' - } ] - %} - - {% for item in breadCrumbList %} - {% set rows = (rows.push( - { - text: item.title, - href: item.href - } - ), rows) %} - {% endfor %} - - {% set completedRows = (rows.push( - { - text: pageTitle - } - ), rows) %} - -{{ govukBreadcrumbs({ - collapseOnMobile: true, - items: completedRows, - classes: "govuk-!-display-none-print" -}) }} - -{% endmacro %}