Skip to content

Commit

Permalink
Merge pull request #574 from companieshouse/feature/IDVA5-1672-Implem…
Browse files Browse the repository at this point in the history
…ent-Content-Security-Policy

 Implement content security policy
  • Loading branch information
ttingle-ch authored Jan 7, 2025
2 parents ff62cac + a91e252 commit aac5cd3
Show file tree
Hide file tree
Showing 52 changed files with 145 additions and 70 deletions.
34 changes: 29 additions & 5 deletions package-lock.json

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

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,16 @@
"govuk_frontend_toolkit": "^9.0.1",
"govuk-elements-sass": "^3.1.3",
"govuk-frontend": "^4.7.0",
"helmet": "^8.0.0",
"http-errors": "^1.7.3",
"ioredis": "4.28.5",
"js-yaml": "^3.14.0",
"node-mocks-http": "^1.12.2",
"nocache": "^4.0.0",
"nunjucks": "^3.2.3",
"private-api-sdk-node": "github:companieshouse/private-api-sdk-node#1.0.45",
"tslib": "^2.0.3",
"uuid": "8.0.0",
"uuid": "^11.0.4",
"yargs": "15.3.1"
},
"devDependencies": {
Expand Down
15 changes: 14 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import cookieParser from "cookie-parser";
import { authenticationMiddleware } from "./middleware/authentication_middleware";
import { authenticationMiddlewareForSoleTrader } from "./middleware/authentication_middleware_sole_trader";
import { sessionMiddleware } from "./middleware/session_middleware";

import {
APPLICATION_NAME,
CDN_URL_CSS,
Expand All @@ -25,8 +24,15 @@ import { ErrorService } from "./services/errorService";
import { updateAcspAuthMiddleware } from "./middleware/update-acsp/update_acsp_authentication_middleware";
import { updateAcspBaseAuthenticationMiddleware } from "./middleware/update-acsp/update_acsp_base_authentication_middleware";
import { updateAcspIsOwnerMiddleware } from "./middleware/update-acsp/update_acsp_is_owner_middleware";
import helmet from "helmet";
import { v4 as uuidv4 } from "uuid";
import nocache from "nocache";
import { prepareCSPConfig } from "./middleware/content_security_policy_middleware_config";

const app = express();

const nonce: string = uuidv4();

const nunjucksEnv = nunjucks.configure([path.join(__dirname, "views"),
path.join(__dirname, "/../node_modules/govuk-frontend"),
path.join(__dirname, "/../../node_modules/govuk-frontend"),
Expand Down Expand Up @@ -59,6 +65,8 @@ app.use(express.static(path.join(__dirname, "/../assets/public")));

// Apply middleware
app.use(cookieParser());
app.use(nocache());
app.use(helmet(prepareCSPConfig(nonce)));
app.use(`^(?!(${BASE_URL}${HEALTHCHECK}|${BASE_URL}$|${BASE_URL}${ACCESSIBILITY_STATEMENT}))*`, sessionMiddleware);
app.use(`^(?!(${BASE_URL}${HEALTHCHECK}|${BASE_URL}$|${BASE_URL}${ACCESSIBILITY_STATEMENT})|(${BASE_URL}${SOLE_TRADER})|(${UPDATE_ACSP_DETAILS_BASE_URL}))*`, authenticationMiddleware);
app.use(`^(${BASE_URL}${SOLE_TRADER})*`, authenticationMiddlewareForSoleTrader);
Expand All @@ -68,6 +76,11 @@ app.use(UPDATE_ACSP_DETAILS_BASE_URL, updateAcspBaseAuthenticationMiddleware);
app.use(UPDATE_ACSP_DETAILS_BASE_URL, updateAcspAuthMiddleware);
app.use(UPDATE_ACSP_DETAILS_BASE_URL, updateAcspIsOwnerMiddleware);

app.use((req: Request, res: Response, next: NextFunction) => {
res.locals.nonce = nonce;
next();
});

// Company Auth redirect
// const companyAuthRegex = new RegExp(`^${HOME_URL}/.+`);
// app.use(companyAuthRegex, companyAuthenticationMiddleware);
Expand Down
31 changes: 31 additions & 0 deletions src/middleware/content_security_policy_middleware_config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { HelmetOptions } from "helmet";
import { CDN_HOST, PIWIK_URL, PIWIK_CHS_DOMAIN } from "../utils/properties";

export const prepareCSPConfig = (nonce: string) : HelmetOptions => {
const SELF = `'self'`;
const NONCE = `'nonce-${nonce}'`;
const ONE_YEAR_SECONDS = 31536000;

return {
contentSecurityPolicy: {
directives: {
upgradeInsecureRequests: null,
defaultSrc: [SELF],
fontSrc: [CDN_HOST],
imgSrc: [CDN_HOST],
styleSrc: [NONCE, CDN_HOST],
connectSrc: [SELF, PIWIK_URL],
formAction: [SELF, PIWIK_CHS_DOMAIN],
scriptSrc: [NONCE, CDN_HOST, PIWIK_URL],
objectSrc: [`'none'`]
}
},
referrerPolicy: {
policy: ["same-origin"]
},
hsts: {
maxAge: ONE_YEAR_SECONDS,
includeSubDomains: true
}
};
};
4 changes: 2 additions & 2 deletions src/middleware/session_middleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SessionMiddleware, SessionStore } from "@companieshouse/node-session-handler";
import Redis from "ioredis";
import { CACHE_SERVER, COOKIE_DOMAIN, COOKIE_NAME, COOKIE_SECRET, DEFAULT_SESSION_EXPIRATION } from "../utils/properties";
import { CACHE_SERVER, COOKIE_DOMAIN, COOKIE_NAME, COOKIE_SECRET, COOKIE_SECURE_ONLY, DEFAULT_SESSION_EXPIRATION } from "../utils/properties";

const redis = new Redis(CACHE_SERVER);
const sessionStore = new SessionStore(redis);
Expand All @@ -9,6 +9,6 @@ export const sessionMiddleware = SessionMiddleware({
cookieDomain: COOKIE_DOMAIN,
cookieName: COOKIE_NAME,
cookieSecret: COOKIE_SECRET,
cookieSecureFlag: undefined,
cookieSecureFlag: COOKIE_SECURE_ONLY !== "false",
cookieTimeToLiveInSeconds: parseInt(DEFAULT_SESSION_EXPIRATION, 10)
}, sessionStore, true);
4 changes: 4 additions & 0 deletions src/utils/properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ export const LOCALES_PATH = getEnvironmentVariable("LOCALES_PATH", "locales");

export const DEFAULT_SESSION_EXPIRATION = getEnvironmentValue("DEFAULT_SESSION_EXPIRATION", "3600");

export const COOKIE_SECURE_ONLY = getEnvironmentValue("COOKIE_SECURE_ONLY");

export const PIWIK_CHS_DOMAIN = getEnvironmentValue("PIWIK_CHS_DOMAIN", "*.chs.local");

// Matomo

export const PIWIK_URL = getEnvironmentValue("PIWIK_URL", "https://matomo.identity.aws.chdev.org/");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
<button class="govuk-button" id="save-continue-button">{{ i18n.SaveAndContinue}}</button>
</form>

<script>
<script nonce={{ nonce | dump | safe }}>
trackEventBasedOnPageTitle("different-address-id", "{{title}}", "select-option", "different-address");
trackEventBasedOnPageTitle("save-continue-button", "{{title}}", "click-button", "SAVE AND CONTINUE");
</script>
Expand Down
2 changes: 1 addition & 1 deletion src/views/common/aml-body-number/aml-body-number.njk
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
</fieldset>
</form>

<script>
<script nonce={{ nonce | dump | safe }}>
trackEventBasedOnPageTitle("save-continue-button", "{{title}}", "click-button", "SAVE AND CONTINUE - AML number");
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</p>
<p class="govuk-body">{{ i18n.askToRemoveYou }}</p>

<script>
<script nonce={{ nonce | dump | safe }}>
trackEventBasedOnPageTitle("authorised-agent-account-link-id", "{{title}}", "click-link", "authorised agent account link");
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{{ i18n.toCheckTheStatus}}
</p>

<script>
<script nonce={{ nonce | dump | safe }}>
trackEventBasedOnPageTitle("filings-link-id", "{{title}}", "click-link", "Your Filings link");
</script>

Expand Down
2 changes: 1 addition & 1 deletion src/views/common/check-aml-details/check-aml-details.njk
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
}) }}
</form>

<script>
<script nonce={{ nonce | dump | safe }}>
trackEventBasedOnPageTitle("save-continue-button", "{{title}}", "click-button", "SAVE AND CONTINUE - Check AML details");
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
}) }}
</form>

<script>
<script nonce={{ nonce | dump | safe }}>
trackEventBasedOnPageTitle("save-continue-button", "{{title}}", "click-button", "CONFIRM AND CONTINUE - address correspondence confirm");
trackEventBasedOnPageTitle("edit-address-link", "{{title}}", "click-link", "EDIT ADDRESS - edit correspondence address");
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@

<button class="govuk-button" id="save-continue-button">{{ i18n.SaveAndContinue }}</button>
</form>
<script>
<script nonce={{ nonce | dump | safe }}>
trackEventBasedOnPageTitle("save-continue-button", "{{title}}", "click-button", "SAVE AND CONTINUE");
</script>
{% endblock main_content %}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
</p>
</form>

<script>
<script nonce={{ nonce | dump | safe }}>
trackEventBasedOnPageTitle("find-address-id", "{{title}}", "click-button", "FIND ADDRESS");
trackEventBasedOnPageTitle("manual-address-id", "{{title}}", "click-link", "Enter address manually");
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
<a href= {{ correspondenceAddressManualLink }} class="govuk-link" id ="manual-address-id">{{ i18n.correspondenceLookUpAddressManuallyBtn }}</a>
</p>

<script>
<script nonce={{ nonce | dump | safe }}>
trackEventBasedOnPageTitle("manual-address-id", "{{title}}", "click-link", "Enter address manually");
trackEventBasedOnPageTitle("save-continue-button", "{{title}}", "click-button", "SAVE AND CONTINUE - Address Correspondence");
</script>
Expand Down
6 changes: 4 additions & 2 deletions src/views/common/index/home.njk
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,20 @@
</p>
<div
class="ch-info-panel"
nonce={{ nonce | dump | safe }}
style="max-width: 630px; width: 100%; background-color: #f3f2f1; color: #000000; margin: 16px 0; padding: 2em;">
<h2 class="govuk-heading-m">{{ i18n.verifyYourIdentity }}</h2>
<div class="govuk-panel__body">
<p class="govuk-body">
{{ i18n.identifyBeforeYouCanUse }}
</p>
<p class="govuk-body" style="display: flex; align-items: center;">
<img src="{{ cdnHost }}/images/icon/govuk-ons-icon-arrow-circle-medium.png" alt="" height="40" width="40" style="max-width:100%; margin-top: 5px; margin-right: 0px;">
<img src="{{ cdnHost }}/images/icon/govuk-ons-icon-arrow-circle-medium.png" alt="" height="40" width="40" nonce={{ nonce | dump | safe }} style="max-width:100%; margin-top: 5px; margin-right: 0px;">
<strong>
<a
class="govuk-link govuk-link--no-visited-state"
href="/v7/verify-identity-prototype"
nonce={{ nonce | dump | safe }}
style="margin-left:10px;"
id="verify-id">
{{ i18n.verifyIdentityLink }}
Expand Down Expand Up @@ -112,7 +114,7 @@
</p>
</form>

<script>
<script nonce={{ nonce | dump | safe }}>
function trackStartNowEvent() {
trackEventBasedOnPageTitle("start-now", "{{matomoPageTitle}}", "click-button", "START NOW");
_paq.push(['trackGoal', "{{ PIWIK_REGISTRATION_START_GOAL_ID }}"]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
}) }}
</form>

<script>
<script nonce={{ nonce | dump | safe }}>
trackEventBasedOnPageTitle("name-of-the-business-option-id", "{{title}}", "select-option", "name-of-the-business");
trackEventBasedOnPageTitle("your-name-option-id", "{{title}}", "select-option", "your-name");
trackEventBasedOnPageTitle("both-option-id", "{{title}}", "select-option", "both");
Expand Down
2 changes: 1 addition & 1 deletion src/views/common/name/capture-name.njk
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
{% endif %}
</form>

<script>
<script nonce={{ nonce | dump | safe }}>
trackEventBasedOnPageTitle("continue-button-id", "{{title}}", "click-button", "CONTINUE - UPDATENAME");
trackEventBasedOnPageTitle("cancel-id", "{{title}}", "click-link", "CANCEL - UPDATENAME");
trackEventBasedOnPageTitle("save-continue-button", "{{title}}", "click-button", "SAVE AND CONTINUE - NAME");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
}) }}
</form>

<script type="text/javascript">
<script type="text/javascript" nonce={{ nonce | dump | safe }}>
function continueOnClick () {
const selectedOption = document.querySelector('input[name="otherTypeOfBusinessRadio"]:checked');
if (selectedOption){
Expand All @@ -67,7 +67,7 @@
}
</script>

<script>
<script nonce={{ nonce | dump | safe }}>
trackEventBasedOnPageTitle("UNINCORPORATED", "{{title}}", "select-option", "Unincorporated Entity");
trackEventBasedOnPageTitle("CORPORATE_BODY", "{{title}}", "select-option", "Corporate Body");
trackEventBasedOnPageTitle("save-continue-button", "{{title}}", "click-button", "SAVE AND CONTINUE - OTHER TYPE");
Expand Down
2 changes: 1 addition & 1 deletion src/views/common/payment-failed/payment-failed.njk
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
{{ i18n.paymentFailedCompletePayment }}
</p>

<script>
<script nonce={{ nonce | dump | safe }}>
trackEventBasedOnPageTitle("your-filings-link-id", "{{title}}", "click-link", "Your Filings link");
</script>

Expand Down
2 changes: 1 addition & 1 deletion src/views/common/saved-application/saved-application.njk
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
<button class="govuk-button" id="save-continue-button" >{{ i18n.SaveAndContinue }}</button>
</form>

<script>
<script nonce={{ nonce | dump | safe }}>
trackEventBasedOnPageTitle("yes-id", "{{title}}", "select-option", "savedApplication-yes");
trackEventBasedOnPageTitle("no-id", "{{title}}", "select-option", "savedApplication-no");
trackEventBasedOnPageTitle("save-continue-button", "{{title}}", "click-button", "SAVE AND CONTINUE");
Expand Down
2 changes: 1 addition & 1 deletion src/views/common/sector-you-work-in/sector-you-work-in.njk
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
}) }}
</form>

<script>
<script nonce={{ nonce | dump | safe }}>
let selectedRadio = null;
// Function to track Matomo analytics based on the selected radio button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
}) }}
</form>

<script>
<script nonce={{ nonce | dump | safe }}>
document.querySelectorAll('input[type="checkbox"][id^="aml"]').forEach(function (checkbox) {
trackEventBasedOnPageTitle(checkbox.id, "{{title}}","select-option", checkbox.value);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
{{ i18n.moreInformation }}
</p>

<script>
<script nonce={{ nonce | dump | safe }}>
trackEventBasedOnPageTitle("guidance-link", "{{title}}", "click-link", "Read the guidance about applying to register as an authorised agent");
</script>

Expand Down
Loading

0 comments on commit aac5cd3

Please sign in to comment.