diff --git a/helm_deploy/hmpps-challenge-support-intervention-plan-ui/values.yaml b/helm_deploy/hmpps-challenge-support-intervention-plan-ui/values.yaml index 46e05546..6ef8bc11 100644 --- a/helm_deploy/hmpps-challenge-support-intervention-plan-ui/values.yaml +++ b/helm_deploy/hmpps-challenge-support-intervention-plan-ui/values.yaml @@ -6,6 +6,7 @@ generic-service: # serviceAccountName: "hmpps-challenge-support-intervention-plan-ui" replicaCount: 4 + serviceAccountName: hmpps-csip-ui image: repository: quay.io/hmpps/hmpps-challenge-support-intervention-plan-ui @@ -54,9 +55,9 @@ generic-service: csip-ui-sentry: SENTRY_DSN: "SENTRY_DSN" SENTRY_LOADER_SCRIPT_ID: "SENTRY_LOADER_SCRIPT_ID" - #sqs-hmpps-audit-secret: - # AUDIT_SQS_QUEUE_URL: "sqs_queue_url" - # AUDIT_SQS_QUEUE_NAME: "sqs_queue_name" + sqs-hmpps-audit-secret: + AUDIT_SQS_QUEUE_URL: "sqs_queue_url" + AUDIT_SQS_QUEUE_NAME: "sqs_queue_name" allowlist: groups: diff --git a/helm_deploy/values-dev.yaml b/helm_deploy/values-dev.yaml index c47ad67f..94baf0b6 100644 --- a/helm_deploy/values-dev.yaml +++ b/helm_deploy/values-dev.yaml @@ -22,8 +22,11 @@ generic-service: DPS_HOME_PAGE_URL: "https://digital-dev.prison.service.justice.gov.uk" PRISONER_PROFILE_URL: "https://prisoner-dev.digital.prison.service.justice.gov.uk" ENVIRONMENT_NAME: DEV # Phase banner tag label - AUDIT_ENABLED: "false" + AUDIT_ENABLED: "true" SENTRY_ENVIRONMENT: DEV + AUDIT_SQS_REGION: "eu-west-2" + AUDIT_SERVICE_NAME: "hmpps-challenge-support-intervention-plan-ui" + generic-prometheus-alerts: alertSeverity: move-and-improve-alerts-non-prod diff --git a/helm_deploy/values-preprod.yaml b/helm_deploy/values-preprod.yaml index 9160628c..411d9c2d 100644 --- a/helm_deploy/values-preprod.yaml +++ b/helm_deploy/values-preprod.yaml @@ -24,6 +24,8 @@ generic-service: ENVIRONMENT_NAME: PRE-PRODUCTION # Phase banner tag label AUDIT_ENABLED: "false" SENTRY_ENVIRONMENT: PRE-PRODUCTION + AUDIT_SQS_REGION: "eu-west-2" + AUDIT_SERVICE_NAME: "hmpps-challenge-support-intervention-plan-ui" generic-prometheus-alerts: alertSeverity: move-and-improve-alerts-non-prod diff --git a/helm_deploy/values-prod.yaml b/helm_deploy/values-prod.yaml index 5c77daca..c817e4ce 100644 --- a/helm_deploy/values-prod.yaml +++ b/helm_deploy/values-prod.yaml @@ -18,6 +18,8 @@ generic-service: PRISONER_PROFILE_URL: "https://prisoner.digital.prison.service.justice.gov.uk" AUDIT_ENABLED: "false" SENTRY_ENVIRONMENT: PRODUCTION + AUDIT_SQS_REGION: "eu-west-2" + AUDIT_SERVICE_NAME: "hmpps-challenge-support-intervention-plan-ui" generic-prometheus-alerts: alertSeverity: move-and-improve-alerts diff --git a/server/@types/express/index.d.ts b/server/@types/express/index.d.ts index a581b9f0..bd36636c 100644 --- a/server/@types/express/index.d.ts +++ b/server/@types/express/index.d.ts @@ -174,6 +174,14 @@ export declare global { validationErrors?: fieldErrors digitalPrisonServicesUrl: string breadcrumbs: Breadcrumbs + auditEvent: { + pageNameSuffix: string + who: string + subjectId?: string + subjectType?: string + correlationId?: string + details?: Record + } prisoner?: PrisonerSummary feComponentsMeta?: { activeCaseLoad: CaseLoad diff --git a/server/app.ts b/server/app.ts index 4c60ff32..9c67f748 100755 --- a/server/app.ts +++ b/server/app.ts @@ -5,6 +5,7 @@ import dpsComponents from '@ministryofjustice/hmpps-connect-dps-components' import * as Sentry from '@sentry/node' // @ts-expect-error Import untyped middleware for cypress coverage import cypressCoverage from '@cypress/code-coverage/middleware/express' +import { validate } from 'uuid' import nunjucksSetup from './utils/nunjucksSetup' import errorHandler from './errorHandler' import { appInsightsMiddleware } from './utils/azureAppInsights' @@ -53,6 +54,41 @@ export default function createApp(services: Services): express.Application { app.use(setUpStaticResources()) nunjucksSetup(app) app.use(setUpAuthentication()) + app.use((req, res, next) => { + const hasJourneyId = validate(req.originalUrl.split('/')[1]) + res.locals.auditEvent = { + pageNameSuffix: hasJourneyId + ? `${req.originalUrl.split('/')[1]}/${req.originalUrl.replace(/\?.*/, '').split('/').slice(2).join('/')}` // JOURNEYID_PAGE + : req.originalUrl.replace(/\?.*/, ''), // PAGE + who: res.locals.user.username, + correlationId: req.id, + } + + res.prependOnceListener('close', async () => { + await services.auditService.logPageView( + req.originalUrl, + req.journeyData, + req.query, + res.locals.auditEvent, + `ACCESS_ATTEMPT_`, + ) + }) + + type resRenderCb = (view: string, options?: object, callback?: (err: Error, html: string) => void) => void + const resRender = res.render as resRenderCb + res.render = (view: string, options?) => { + resRender.call(res, view, options, async (err: Error, html: string) => { + if (err) { + res.status(500).send(err) + return + } + await services.auditService.logPageView(req.originalUrl, req.journeyData, req.query, res.locals.auditEvent) + res.send(html) + }) + } + next() + }) + app.use(authorisationMiddleware()) app.use(setUpCsrf()) app.use(setUpCurrentUser()) diff --git a/server/middleware/insertJourneyIdentifier.ts b/server/middleware/insertJourneyIdentifier.ts index e13af470..9bcb81e9 100644 --- a/server/middleware/insertJourneyIdentifier.ts +++ b/server/middleware/insertJourneyIdentifier.ts @@ -7,6 +7,7 @@ export default function insertJourneyIdentifier() { if (!validate(uuid)) { return res.redirect(`${req.baseUrl}/${uuidV4()}${req.url}`) } + res.locals.auditEvent.pageNameSuffix = `${req.url.split('/')[1]}/${req.url.replace(/\?.*/, '').split('/').slice(2).join('/')}` // JOURNEYID_PAGE return next() } } diff --git a/server/middleware/populatePrisonerSummary.ts b/server/middleware/populatePrisonerSummary.ts index c8ba4c25..5c5c69c7 100644 --- a/server/middleware/populatePrisonerSummary.ts +++ b/server/middleware/populatePrisonerSummary.ts @@ -4,6 +4,8 @@ export default function populatePrisonerSummary(): RequestHandler { return async (req, res, next) => { if (req.journeyData.prisoner) { res.locals['prisoner'] = req.journeyData.prisoner + res.locals.auditEvent.subjectId = req.journeyData.prisoner.prisonerNumber + res.locals.auditEvent.subjectType = req.journeyData.prisoner.prisonerNumber } next() } diff --git a/server/routes/index.ts b/server/routes/index.ts index b3a2b376..ab8ce9a4 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -1,5 +1,4 @@ import { type RequestHandler, Router } from 'express' - import asyncMiddleware from '../middleware/asyncMiddleware' import type { Services } from '../services' import insertJourneyIdentifier from '../middleware/insertJourneyIdentifier' @@ -15,9 +14,6 @@ export default function routes(services: Services): Router { const controller = new HomePageController(services.csipApiService) const get = (path: string | string[], handler: RequestHandler) => router.get(path, asyncMiddleware(handler)) - // TODO: determine whether we need to implement audit service - // import { Page } from '../services/auditService' - // await services.auditService.logPageView(Page.EXAMPLE_PAGE, { who: res.locals.user.username, correlationId: req.id }) get('/', controller.GET) router.use('/csip-records/:recordUuid', CsipRecordRoutes(services)) @@ -27,6 +23,7 @@ export default function routes(services: Services): Router { ) router.use(insertJourneyIdentifier()) + router.use( redirectCheckAnswersMiddleware([ /on-behalf-of$/, @@ -46,6 +43,7 @@ export default function routes(services: Services): Router { /record-review\/next-review-date$/, ]), ) + router.use(journeyStateMachine()) router.use('/:journeyId', JourneyRoutes(services)) diff --git a/server/services/auditService.test.ts b/server/services/auditService.test.ts index 71a9c1aa..eb254370 100644 --- a/server/services/auditService.test.ts +++ b/server/services/auditService.test.ts @@ -1,4 +1,4 @@ -import AuditService, { Page } from './auditService' +import AuditService from './auditService' import HmppsAuditClient from '../data/hmppsAuditClient' jest.mock('../data/hmppsAuditClient') @@ -41,19 +41,22 @@ describe('Audit service', () => { describe('logPageView', () => { it('sends page view event audit message using audit client', async () => { - await auditService.logPageView(Page.EXAMPLE_PAGE, { - who: 'user1', - subjectId: 'subject123', - subjectType: 'exampleType', - correlationId: 'request123', - details: { extraDetails: 'example' }, - }) + await auditService.logPageView( + '/csip-records/0192d2fb-6920-749f-86fb-f0c6c2deaec8', + {}, + { extraDetails: 'example' }, + { + pageNameSuffix: 'EXAMPLE_PAGE', + who: 'user1', + correlationId: 'request123', + }, + ) expect(hmppsAuditClient.sendMessage).toHaveBeenCalledWith({ what: 'PAGE_VIEW_EXAMPLE_PAGE', who: 'user1', - subjectId: 'subject123', - subjectType: 'exampleType', + subjectId: '0192d2fb-6920-749f-86fb-f0c6c2deaec8', + subjectType: '0192d2fb-6920-749f-86fb-f0c6c2deaec8', correlationId: 'request123', details: { extraDetails: 'example' }, }) diff --git a/server/services/auditService.ts b/server/services/auditService.ts index cabc1993..1f209336 100644 --- a/server/services/auditService.ts +++ b/server/services/auditService.ts @@ -1,4 +1,6 @@ +import { Request, Response } from 'express' import HmppsAuditClient, { AuditEvent } from '../data/hmppsAuditClient' +import { JourneyData } from '../@types/express' export enum Page { EXAMPLE_PAGE = 'EXAMPLE_PAGE', @@ -19,10 +21,24 @@ export default class AuditService { await this.hmppsAuditClient.sendMessage(event) } - async logPageView(page: Page, eventDetails: PageViewEventDetails) { + async logPageView( + requestUrl: string, + journeyData: Partial, + query: Request['query'], + auditEvent: Response['locals']['auditEvent'], + pagePrefix: string = '', + ) { + const { pageNameSuffix, ...auditEventProperties } = auditEvent + + const csipFromUrl = requestUrl.includes('csip-record') ? requestUrl.split('/').filter(Boolean)[1] : undefined + const csipIdInRequest = csipFromUrl || journeyData?.csipRecord?.recordUuid + const event: AuditEvent = { - ...eventDetails, - what: `PAGE_VIEW_${page}`, + ...auditEventProperties, + ...(query ? { details: query } : {}), + ...(csipIdInRequest ? { subjectId: csipIdInRequest } : {}), + ...(csipIdInRequest ? { subjectType: csipIdInRequest } : {}), + what: `PAGE_VIEW_${pagePrefix + pageNameSuffix}`, } await this.hmppsAuditClient.sendMessage(event) }