Skip to content

Commit

Permalink
NFDIV-4298 (#3912)
Browse files Browse the repository at this point in the history
* NFDIV-4298 starting

* NFDIV-4298 starting

* NFDIV-4298 starting

* NFDIV-4298 try mocking token

* NFDIV-4298 system user fix

* NFDIV-4298 prettier fix

* NFDIV-4298 fortify label ability for pr

* NFDIV-4298 import

* NFDIV-4298 fortify fixes will need to test the updated redirect with token works

* NFDIV-4298 healthcheck failing so removing that last user hmcts to try

* NFDIV-4298 healthcheck failing

* NFDIV-4298 fixing my buggy changes

* NFDIV-4298 fixing my buggy changes

* NFDIV-4298 merge master

* NFDIV-4298 errors missing gradle wrapper

* NFDIV-4298 errors missing gradle wrapper

* NFDIV-4298

* Delete src/test/java/.gitignore

* NFDIV-4298 try to update uppy version to see if this one behaves with fortify

* NFDIV-4298 try to update uppy version to see if this one behaves with fortify

* NFDIV-4298 new errors after upgrading uppy, needed to add pluralise and result can be undefined so needed to add a check for that

* NFDIV-4298

* NFDIV-4298 pluralize fix

* NFDIV-4298 think wdio/sauce-service latest might be to blame for fortify issue so downgrading

* NFDIV-4559

* NFDIV-4298 update from master

* NFDIV-4298 lock file

* NFDIV-4298 fix bad merge

* NFDIV-4298 fix fortify issue after merge

---------

Co-authored-by: adamg-hmcts <[email protected]>
  • Loading branch information
DelythJustice and adamg-hmcts authored Dec 16, 2024
1 parent 7c9cd25 commit ab06db0
Show file tree
Hide file tree
Showing 23 changed files with 360 additions and 257 deletions.
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# ---- Base image ----
FROM hmctspublic.azurecr.io/base/node:20-alpine as base
FROM hmctspublic.azurecr.io/base/node:20-alpine AS base
USER root
RUN corepack enable
COPY --chown=hmcts:hmcts . .
USER hmcts
# ---- Build image ----
FROM base as build
FROM base AS build
RUN yarn --version && yarn install
RUN yarn build:prod

# ---- Runtime image ----
FROM build as runtime
FROM build AS runtime
RUN rm -rf webpack/ webpack.config.js
COPY --from=build $WORKDIR/src/main ./src/main
RUN yarn build:ts
Expand Down
9 changes: 8 additions & 1 deletion Jenkinsfile_CNP
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@Library("Infrastructure")

import uk.gov.hmcts.contino.AppPipelineConfig
import uk.gov.hmcts.contino.GithubAPI

def type = "nodejs"
def product = "nfdiv"
Expand Down Expand Up @@ -33,6 +34,10 @@ def branchesToSync = ['demo', 'perftest', 'ithc']
def pipelineConf = new AppPipelineConfig()
pipelineConf.vaultSecrets = secrets

def checkForFortifyLabel(branch_name) {
return new GithubAPI(this).getLabelsbyPattern(branch_name, "fortify").contains("fortify")
}

withPipeline(type, product, component) {
disableLegacyDeployment()
loadVaultSecrets(secrets)
Expand All @@ -41,7 +46,9 @@ withPipeline(type, product, component) {
afterAlways('build') {
yarnBuilder.yarn('build')
}

if(checkForFortifyLabel(env.BRANCH_NAME)) {
enableFortifyScan()
}
afterAlways('functionalTest:aat') {
steps.archiveArtifacts allowEmptyArchive: true, artifacts: './functional-output/functional/reports/Functional test report.html'
}
Expand Down
5 changes: 4 additions & 1 deletion Jenkinsfile_nightly
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ withNightlyPipeline(type, product, component) {
enableCrossBrowserTest()
enableFullFunctionalTest()
loadVaultSecrets(secrets)

enableFortifyScan()
before('crossBrowserTest') {
yarnBuilder.smokeTest()
}
Expand All @@ -63,4 +63,7 @@ withNightlyPipeline(type, product, component) {
afterAlways('fullFunctionalTest') {
steps.archiveArtifacts allowEmptyArchive: true, artifacts: 'functional-output/functional/reports/**/*'
}
afterAlways('fortify-scan') {
steps.archiveArtifacts allowEmptyArchive: true, artifacts: '**/Fortify Scan/**/*'
}
}
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ Run the application by executing the following command:
```bash
yarn start:docker
```
or start:docker:civil `to force FORCE_CIVIL_MODE true`

This will start the frontend container exposing the application's port `3001`.

Expand Down Expand Up @@ -228,8 +229,10 @@ There is a configuration section related with those headers, where you can speci
Here's an example setup:

```json
"security": {
"referrerPolicy": "origin",
{
"security": {
"referrerPolicy": "origin"
}
}
```

Expand All @@ -239,6 +242,12 @@ Make sure you have those values set correctly for your application.

The application exposes a health endpoint [https://localhost:3001/health](https://localhost:3001/health), created with the use of [Nodejs Healthcheck](https://github.com/hmcts/nodejs-healthcheck) library. This endpoint is defined in [health.ts](src/main/routes/health.ts) file. Make sure you adjust it correctly in your application. In particular, remember to replace the sample check with checks specific to your frontend app, e.g. the ones verifying the state of each service it depends on.

### Fortify check new code
To scan latest code on local
fortify-client.properties will read variables set in env FORTIFY_CLIENT_PASSWORD and FORTIFY_CLIENT_USERNAME
these need to be your valid token and login, exporting them in your ~/.zshrc file is handy

and then run gradle fortifyScan
## Migrating backend field changes

Once you have created a NFDIV-Case-API Pull Request with the case definition changes, update `CCD_URL` in [values.yaml](charts/nfdiv-frontend/values.yaml) and `services.case.url` in [default.yaml](config/default.yaml) so that the CCD Data Store is pointing at the Preview version deployed as part of your No Fault Divorce Case API pull request.
Expand Down
2 changes: 0 additions & 2 deletions config/custom-environment-variables.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ port: PORT
session:
redis:
host: REDIS_HOST
e2e:
userTestPassword: TEST_PASSWORD
appInsights:
instrumentationKey: APPINSIGHTS_KEY
webchat:
Expand Down
2 changes: 1 addition & 1 deletion config/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ services:
clientID: 'divorce'
clientSecret: 'NEED TO INSERT SECRET'
systemUsername: 'dummy_user'
systemPassword: 'dummy_password'
systemPassword:
caching: false
case:
url: 'http://ccd-data-store-api-aat.service.core-compute-aat.internal'
Expand Down
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@
"@types/session-file-store": "1.2.5",
"@types/toobusy-js": "0.5.4",
"@types/uuid": "10.0.0",
"@uppy/core": "3.13.1",
"@uppy/drop-target": "2.1.0",
"@uppy/file-input": "3.1.2",
"@uppy/progress-bar": "3.1.1",
"@uppy/xhr-upload": "3.6.8",
"@uppy/core": "4.2.3",
"@uppy/drop-target": "3.0.1",
"@uppy/file-input": "4.0.3",
"@uppy/progress-bar": "4.0.1",
"@uppy/xhr-upload": "4.2.2",
"applicationinsights": "2.9.6",
"autobind-decorator": "2.4.0",
"axios": "1.7.7",
Expand Down
59 changes: 40 additions & 19 deletions src/main/app/auth/user/oidc.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios, { AxiosRequestHeaders, AxiosResponse, AxiosStatic } from 'axios';
import jwt from 'jsonwebtoken';

import { APPLICANT_2_SIGN_IN_URL, CALLBACK_URL, SIGN_IN_URL } from '../../../steps/urls';
import { UserDetails } from '../../controller/AppRequest';

import { OidcResponse, getRedirectUrl, getSystemUser, getUserDetails } from './oidc';

Expand All @@ -13,8 +13,25 @@ jest.mock('config');
const mockedConfig = config as jest.Mocked<typeof config>;
const mockedAxios = axios as jest.Mocked<AxiosStatic>;

const token =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0QHRlc3QuY29tIiwiZ2l2ZW5fbmFtZSI6IkpvaG4iLCJmYW1pbHlfbmFtZSI6IkRvcmlhbiIsInVpZCI6IjEyMyIsInJvbGVzIjpbImNpdGl6ZW4iXX0.rxjx6XsSNNYavVppwKAqWiNWT_GxN4vjVzdLRe6q14I';
const mockSecret = 'mock-secret';
const mockPayload = {
uid: '123',
id: '123',
sub: '[email protected]',
email: '[email protected]',
given_name: 'John',
family_name: 'Dorian',
roles: ['citizen'],
};
const mockSystemPayload = {
uid: '456',
sub: 'user-email',
name: 'System',
roles: ['caseworker-divorce-systemupdate', 'caseworker-caa', 'caseworker', 'caseworker-divorce'],
};
// Generate a mock JWT for testing
const mockToken = jwt.sign(mockPayload, mockSecret, { expiresIn: '1h' });
const mockSystemToken = jwt.sign(mockSystemPayload, mockSecret, { expiresIn: '1h' });

describe('getRedirectUrl', () => {
test('should create a valid URL to redirect to the login screen', () => {
Expand All @@ -36,16 +53,16 @@ describe('getRedirectUrl', () => {

describe('getUserDetails', () => {
test('should exchange a code for a token and decode a JWT to get the user details', async () => {
mockedAxios.post.mockResolvedValue({
mockedAxios.post.mockResolvedValueOnce({
data: {
access_token: token,
id_token: token,
id_token: mockToken,
access_token: 'token',
},
});
} as AxiosResponse);

const result = await getUserDetails('http://localhost', '123', CALLBACK_URL);
expect(result).toStrictEqual({
accessToken: token,
accessToken: 'token',
email: '[email protected]',
givenName: 'John',
familyName: 'Dorian',
Expand All @@ -62,26 +79,30 @@ describe('getUserDetails', () => {
});

describe('getSystemUser', () => {
const getSystemUserTestToken =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0QHRlc3QuY29tIiwiZ2l2ZW5fbmFtZSI6IkpvaG4iLCJmYW1pbHlfbmFtZSI6IkRvcmlhbiIsInVpZCI6IjEyMyIsInJvbGVzIjpbImNhc2V3b3JrZXItZGl2b3JjZS1zeXN0ZW11cGRhdGUiLCJjYXNld29ya2VyLWNhYSIsImNhc2V3b3JrZXIiLCJjYXNld29ya2VyLWRpdm9yY2UiXX0.NDab3XAV8NWQTuuxBQ9mpwTIdw4KMWWiJ37Dp3EHG7s';

const accessTokenResponse: AxiosResponse<OidcResponse> = {
status: 200,
data: {
id_token: getSystemUserTestToken,
access_token: getSystemUserTestToken,
id_token: mockSystemToken,
access_token: 'systemUserTestToken',
},
statusText: 'wsssw',
headers: { test: 'now' },
config: { headers: [] as unknown as AxiosRequestHeaders },
};

const expectedGetSystemUserResponse: UserDetails = {
accessToken: getSystemUserTestToken,
email: '[email protected]',
givenName: 'John',
familyName: 'Dorian',
id: '123',
const expectedGetSystemUserResponse: {
givenName: undefined;
familyName: undefined;
roles: string[];
id: string;
accessToken: string;
email: string;
} = {
email: 'user-email',
accessToken: 'systemUserTestToken',
id: '456',
givenName: undefined,
familyName: undefined,
roles: ['caseworker-divorce-systemupdate', 'caseworker-caa', 'caseworker', 'caseworker-divorce'],
};

Expand Down
7 changes: 7 additions & 0 deletions src/main/app/controller/PostController.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// noinspection TypeScriptValidateTypes

import config from 'config';
import { set } from 'lodash';

import { mockRequest } from '../../../test/unit/utils/mockRequest';
import { mockResponse } from '../../../test/unit/utils/mockResponse';
import { FormContent } from '../../app/form/Form';
Expand All @@ -18,6 +23,8 @@ import { PostController } from './PostController';

import Mock = jest.Mock;

set(config, 'services.idam.systemPassword', 'DUMMY_VALUE_REPLACE');

const getNextStepUrlMock = jest.spyOn(steps, 'getNextStepUrl');

describe('PostController', () => {
Expand Down
20 changes: 14 additions & 6 deletions src/main/assets/js/upload-manager/FileUploadEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,27 @@ export class FileUploadEvents {

const result = await uppy.upload();
location.hash = '#';
if (result.successful[0]?.response?.body) {
if (result && result.successful?.[0]?.response?.body) {
uploadedFiles.add(Object.values(result.successful[0].response.body) as []);
}

const uploadInfo = uppy.getState();
if (result.failed.length || !result.successful.length || uploadInfo.info?.[0]?.message) {
if (result?.failed?.length || !result?.successful?.length || uploadInfo.info?.[0]?.message) {
return this.onError({ name: 'Upload error', ...(uploadInfo.info ? uploadInfo.info[0] : new Error()) });
}

uploadGroupEl?.scrollIntoView({ block: 'center' });
if (uploadGroupEl) {
uploadGroupEl.scrollIntoView({ block: 'center' });
uploadGroupEl.classList.add('uploaded');
uploadGroupEl.addEventListener(
'animationend',
() => {
uploadGroupEl.classList.remove('uploaded');
},
{ once: true }
);
}
uploadProcessEl?.classList.remove('govuk-!-margin-top-5');

uploadGroupEl?.classList.add('uploaded');
uploadGroupEl?.addEventListener('animationend', () => uploadGroupEl.classList.remove('uploaded'), { once: true });
uploadGroupEl?.focus();
};

Expand Down
4 changes: 4 additions & 0 deletions src/main/assets/js/upload-manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const initUploadManager = (): void => {
location.hash = '';

const chooseFilePhoto = language === SupportedLanguages.Cy ? 'Dewiswch ffeil' : 'Choose a file';
const pluralize = (count: number): number => {
return count === 1 ? 0 : 1;
};

const uppy = new Uppy({
restrictions: {
Expand All @@ -43,6 +46,7 @@ const initUploadManager = (): void => {
strings: {
chooseFiles: chooseFilePhoto,
},
pluralize, // Uses the adjusted `pluralize` function
},
})
.use(DropTarget, { target: document.body })
Expand Down
29 changes: 23 additions & 6 deletions src/main/modules/oidc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class OidcMiddleware {
const protocol = app.locals.developmentMode ? 'http://' : 'https://';
const port = app.locals.developmentMode ? `:${config.get('port')}` : '';
const { errorHandler } = app.locals;

const safeListedUrls = ['https://manage-case.aat.platform.hmcts.net/', 'https://manage-case.platform.hmcts.net/'];
app.get([SIGN_IN_URL, APPLICANT_2_SIGN_IN_URL], (req, res) =>
res.redirect(getRedirectUrl(`${protocol}${res.locals.host}${port}`, req.path))
);
Expand All @@ -44,9 +44,7 @@ export class OidcMiddleware {
req.session.user.roles.includes('caseworker') ||
req.session.user.roles.includes('caseworker-divorce-solicitor')
) {
const redirectUrl = app.locals.developmentMode
? 'https://manage-case.aat.platform.hmcts.net/'
: 'https://manage-case.platform.hmcts.net/';
const redirectUrl = app.locals.developmentMode ? safeListedUrls[0] : safeListedUrls[1];
res.redirect(redirectUrl);
}
res.locals.isLoggedIn = true;
Expand Down Expand Up @@ -121,8 +119,27 @@ export class OidcMiddleware {
}
if (config.get('services.case.checkDivCases') && (await req.locals.api.hasInProgressDivorceCase())) {
logger.info(`UserID ${req.session.user.id} being redirected to old divorce`);
const token = encodeURIComponent(req.session.user.accessToken);
return res.redirect(config.get('services.decreeNisi.url') + `/authenticated?__auth-token=${token}`);
const axios = require('axios');

const token = req.session.user.accessToken;
const decreeNisiUrl = config.get('services.decreeNisi.url') + '/authenticated';
axios
.post(
decreeNisiUrl,
{},
{
headers: {
Authorization: `Bearer ${token}`,
},
}
)
.then(() => {
res.redirect(decreeNisiUrl);
})
.catch(error => {
console.error('Error authenticating with Old Divorce service:', error);
res.status(500).send('Internal Server Error');
});
}
if (isLinkingUrl(req.path)) {
return next();
Expand Down
Loading

0 comments on commit ab06db0

Please sign in to comment.