Security scan updates
@@ -185,8 +185,7 @@ def _csp(config):
|
|||||||
def create_app(application):
|
def create_app(application):
|
||||||
@application.after_request
|
@application.after_request
|
||||||
def add_security_headers(response):
|
def add_security_headers(response):
|
||||||
# Add Cross-Origin-Embedder-Policy header
|
response.headers['Cross-Origin-Embedder-Policy'] = 'credentialless'
|
||||||
response.headers["Cross-Origin-Embedder-Policy"] = "credentialless"
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@application.context_processor
|
@application.context_processor
|
||||||
@@ -274,14 +273,11 @@ def create_app(application):
|
|||||||
content_security_policy=_csp(application.config),
|
content_security_policy=_csp(application.config),
|
||||||
content_security_policy_nonce_in=["style-src", "script-src"],
|
content_security_policy_nonce_in=["style-src", "script-src"],
|
||||||
permissions_policy={
|
permissions_policy={
|
||||||
"accelerometer": "()",
|
"accelerometer": "(self \"https://www.youtube-nocookie.com\")",
|
||||||
"ambient-light-sensor": "()",
|
"autoplay": "(self \"https://www.youtube-nocookie.com\")",
|
||||||
"autoplay": "()",
|
|
||||||
"battery": "()",
|
|
||||||
"camera": "()",
|
"camera": "()",
|
||||||
"document-domain": "()",
|
|
||||||
"geolocation": "()",
|
"geolocation": "()",
|
||||||
"gyroscope": "()",
|
"gyroscope": "(self \"https://www.youtube-nocookie.com\")",
|
||||||
"local-fonts": "()",
|
"local-fonts": "()",
|
||||||
"magnetometer": "()",
|
"magnetometer": "()",
|
||||||
"microphone": "()",
|
"microphone": "()",
|
||||||
|
|||||||
@@ -25,30 +25,31 @@
|
|||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
};
|
};
|
||||||
ListEntry.optionalAttributes = ['aria-describedby'];
|
ListEntry.optionalAttributes = ['aria-describedby'];
|
||||||
ListEntry.prototype.entryTemplate = Hogan.compile(
|
ListEntry.prototype.renderEntry = function(data) {
|
||||||
'<div class="list-entry">' +
|
return `
|
||||||
'<label for="{{{id}}}" class="govuk-input--numbered__label">' +
|
<div class="list-entry">
|
||||||
'<span class="usa-sr-only">{{listItemName}} number </span>{{number}}.' +
|
<label for="${data.id}" class="govuk-input--numbered__label">
|
||||||
'</label>' +
|
<span class="usa-sr-only">${data.listItemName} number </span>${data.number}.
|
||||||
'<input' +
|
</label>
|
||||||
' name="{{name}}"' +
|
<input
|
||||||
' id="{{id}}"' +
|
name="${data.name}"
|
||||||
' {{#value}}value="{{value}}{{/value}}"' +
|
id="${data.id}"
|
||||||
' {{{sharedAttributes}}}' +
|
${data.value ? `value="${data.value}"` : ''}
|
||||||
'/>' +
|
${data.sharedAttributes}
|
||||||
'{{#button}}' +
|
/>
|
||||||
'<button type="button" class="usa-button input-list__button--remove">' +
|
${data.button ? `
|
||||||
'Remove<span class="usa-sr-only"> {{listItemName}} number {{number}}</span>' +
|
<button type="button" class="usa-button input-list__button--remove">
|
||||||
'</button>' +
|
Remove<span class="usa-sr-only"> ${data.listItemName} number ${data.number}</span>
|
||||||
'{{/button}}' +
|
</button>
|
||||||
'</div>'
|
` : ''}
|
||||||
);
|
</div>
|
||||||
ListEntry.prototype.addButtonTemplate = Hogan.compile(
|
`;
|
||||||
'<button type="button" class="usa-button input-list__button--add">Add another {{listItemName}} ({{entriesLeft}} remaining)</button>'
|
};
|
||||||
);
|
ListEntry.prototype.renderAddButton = function(data) {
|
||||||
|
return `<button type="button" class="usa-button input-list__button--add">Add another ${data.listItemName} (${data.entriesLeft} remaining)</button>`;
|
||||||
|
};
|
||||||
ListEntry.prototype.getSharedAttributes = function () {
|
ListEntry.prototype.getSharedAttributes = function () {
|
||||||
var $inputs = this.$wrapper.find('input'),
|
var $inputs = this.$wrapper.find('input'),
|
||||||
attributeTemplate = Hogan.compile(' {{name}}="{{value}}"'),
|
|
||||||
generatedAttributes = ['id', 'name', 'value'],
|
generatedAttributes = ['id', 'name', 'value'],
|
||||||
attributes = [],
|
attributes = [],
|
||||||
attrIdx,
|
attrIdx,
|
||||||
@@ -68,7 +69,7 @@
|
|||||||
while (attrIdx--) {
|
while (attrIdx--) {
|
||||||
// prevent duplicates
|
// prevent duplicates
|
||||||
if ($.inArray(elmAttrs[attrIdx].name, existingAttributes) === -1) {
|
if ($.inArray(elmAttrs[attrIdx].name, existingAttributes) === -1) {
|
||||||
attrStr += attributeTemplate.render({ 'name': elmAttrs[attrIdx].name, 'value': elmAttrs[attrIdx].value });
|
attrStr += ` ${elmAttrs[attrIdx].name}="${elmAttrs[attrIdx].value}"`;
|
||||||
existingAttributes.push(elmAttrs[attrIdx].name);
|
existingAttributes.push(elmAttrs[attrIdx].name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,10 +189,10 @@
|
|||||||
if (entryNumber > 1) {
|
if (entryNumber > 1) {
|
||||||
dataObj.button = true;
|
dataObj.button = true;
|
||||||
}
|
}
|
||||||
this.$wrapper.append(this.entryTemplate.render(dataObj));
|
this.$wrapper.append(this.renderEntry(dataObj));
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
if (this.entries.length < this.maxEntries) {
|
if (this.entries.length < this.maxEntries) {
|
||||||
this.$wrapper.append(this.addButtonTemplate.render({
|
this.$wrapper.append(this.renderAddButton({
|
||||||
'listItemName' : this.listItemName,
|
'listItemName' : this.listItemName,
|
||||||
'entriesLeft' : (this.maxEntries - this.entries.length)
|
'entriesLeft' : (this.maxEntries - this.entries.length)
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -3,65 +3,70 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var Modules = global.GOVUK.Modules;
|
var Modules = global.GOVUK.Modules;
|
||||||
var Hogan = global.Hogan;
|
|
||||||
|
|
||||||
// Object holding all the states for the component's HTML
|
// Template functions for rendering component states
|
||||||
let states = {
|
let renderStates = {
|
||||||
'initial': Hogan.compile(`
|
'initial': function(data) {
|
||||||
{{#showNowAsDefault}}
|
return `
|
||||||
|
${data.showNowAsDefault ? `
|
||||||
|
<div class="radio-select__column margin-y-2">
|
||||||
|
<div class="usa-radio">
|
||||||
|
<input class="usa-radio__input" checked="checked" id="${data.name}-0" name="${data.name}" type="radio" value="">
|
||||||
|
<label class="usa-radio__label" for="${data.name}-0">Now</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
<div class="radio-select__column margin-y-2">
|
<div class="radio-select__column margin-y-2">
|
||||||
<div class="usa-radio">
|
${data.categories.map(category =>
|
||||||
<input class="usa-radio__input" checked="checked" id="{{name}}-0" name="{{name}}" type="radio" value="">
|
`<input type='button' class='usa-button usa-button--outline radio-select__button--category' aria-expanded="false" value='${category}' />`
|
||||||
<label class="usa-radio__label" for="{{name}}-0">Now</label>
|
).join('')}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{{/showNowAsDefault}}
|
`;
|
||||||
<div class="radio-select__column margin-y-2">
|
},
|
||||||
{{#categories}}
|
'choose': function(data) {
|
||||||
<input type='button' class='usa-button usa-button--outline radio-select__button--category' aria-expanded="false" value='{{.}}' />
|
return `
|
||||||
{{/categories}}
|
${data.showNowAsDefault ? `
|
||||||
</div>
|
<div class="radio-select__column margin-y-2">
|
||||||
`),
|
<div class="usa-radio">
|
||||||
'choose': Hogan.compile(`
|
<input class="usa-radio__input" checked="checked" id="${data.name}-0" name="${data.name}" type="radio" value="">
|
||||||
{{#showNowAsDefault}}
|
<label class="usa-radio__label" for="${data.name}-0">Now</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
<div class="radio-select__column margin-y-2">
|
<div class="radio-select__column margin-y-2">
|
||||||
<div class="usa-radio">
|
${data.choices.map(choice => `
|
||||||
<input class="usa-radio__input" checked="checked" id="{{name}}-0" name="{{name}}" type="radio" value="">
|
<div class="usa-radio js-option">
|
||||||
<label class="usa-radio__label" for="{{name}}-0">Now</label>
|
<input class="usa-radio__input" type="radio" value="${choice.value}" id="${choice.id}" name="${data.name}" />
|
||||||
</div>
|
<label class="usa-radio__label" for="${choice.id}">${choice.label}</label>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
<input type='button' class='usa-button usa-button--outline radio-select__button--done margin-top-4' aria-expanded='true' value='Back to select a new time' />
|
||||||
</div>
|
</div>
|
||||||
{{/showNowAsDefault}}
|
`;
|
||||||
<div class="radio-select__column margin-y-2">
|
},
|
||||||
{{#choices}}
|
'chosen': function(data) {
|
||||||
<div class="usa-radio js-option">
|
return `
|
||||||
<input class="usa-radio__input" type="radio" value="{{value}}" id="{{id}}" name="{{name}}" />
|
${data.showNowAsDefault ? `
|
||||||
<label class="usa-radio__label" for="{{id}}">{{label}}</label>
|
<div class="radio-select__column margin-y-2">
|
||||||
|
<div class="usa-radio">
|
||||||
|
<input class="usa-radio__input" id="${data.name}-0" name="${data.name}" type="radio" value="">
|
||||||
|
<label class="usa-radio__label" for="${data.name}-0">Now</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/choices}}
|
` : ''}
|
||||||
<input type='button' class='usa-button usa-button--outline radio-select__button--done margin-top-4' aria-expanded='true' value='Back to select a new time' />
|
|
||||||
</div>
|
|
||||||
`),
|
|
||||||
'chosen': Hogan.compile(`
|
|
||||||
{{#showNowAsDefault}}
|
|
||||||
<div class="radio-select__column margin-y-2">
|
<div class="radio-select__column margin-y-2">
|
||||||
<div class="usa-radio">
|
${data.choices.map(choice => `
|
||||||
<input class="usa-radio__input" id="{{name}}-0" name="{{name}}" type="radio" value="">
|
<div class="usa-radio">
|
||||||
<label class="usa-radio__label" for="{{name}}-0">Now</label>
|
<input class="usa-radio__input" checked="checked" type="radio" value="${choice.value}" id="${choice.id}" name="${data.name}" />
|
||||||
</div>
|
<label class="usa-radio__label" for="${choice.id}">${choice.label}</label>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
</div>
|
</div>
|
||||||
{{/showNowAsDefault}}
|
<div class="radio-select__column margin-y-2">
|
||||||
<div class="radio-select__column margin-y-2">
|
<input type='button' class='usa-button usa-button--outline radio-select__button--reset' aria-expanded='false' value='Choose a different time' />
|
||||||
{{#choices}}
|
</div>
|
||||||
<div class="usa-radio">
|
`;
|
||||||
<input class="usa-radio__input" checked="checked" type="radio" value="{{value}}" id="{{id}}" name="{{name}}" />
|
}
|
||||||
<label class="usa-radio__label" for="{{id}}">{{label}}</label>
|
|
||||||
</div>
|
|
||||||
{{/choices}}
|
|
||||||
</div>
|
|
||||||
<div class="radio-select__column margin-y-2">
|
|
||||||
<input type='button' class='usa-button usa-button--outline radio-select__button--reset' aria-expanded='false' value='Choose a different time' />
|
|
||||||
</div>
|
|
||||||
`)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let shiftFocus = function(elementToFocus, component) {
|
let shiftFocus = function(elementToFocus, component) {
|
||||||
@@ -80,7 +85,7 @@
|
|||||||
|
|
||||||
let $component = $(component);
|
let $component = $(component);
|
||||||
let render = (state, data) => {
|
let render = (state, data) => {
|
||||||
$component.html(states[state].render(data));
|
$component.html(renderStates[state](data));
|
||||||
};
|
};
|
||||||
// store array of all options in component
|
// store array of all options in component
|
||||||
let choices = $('label', $component).toArray().map(function(element) {
|
let choices = $('label', $component).toArray().map(function(element) {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<p>Notify.gov is an easy-to-use, web-based platform. It requires no technical expertise or system integration — users
|
<p>Notify.gov is an easy-to-use, web-based platform. It requires no technical expertise or system integration — users
|
||||||
can create an account and get started within minutes. We take the security and privacy of messaging data seriously
|
can create an account and get started within minutes. We take the security and privacy of messaging data seriously
|
||||||
by minimizing data retention and using modern encryption methods.</p>
|
by minimizing data retention and using modern encryption methods.</p>
|
||||||
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/fkcy_xbo0Bw?si=qNo94BwL4WCeusy5&start=2" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> <h2 class="padding-bottom-3">Product Highlights</h2>
|
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/fkcy_xbo0Bw?si=qNo94BwL4WCeusy5&start=2" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen credentialless></iframe> <h2 class="padding-bottom-3">Product Highlights</h2>
|
||||||
{% set product_highlights = [
|
{% set product_highlights = [
|
||||||
{
|
{
|
||||||
"svg_src": "#send",
|
"svg_src": "#send",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 325 KiB After Width: | Height: | Size: 325 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 314 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 227 KiB After Width: | Height: | Size: 222 KiB |
|
Before Width: | Height: | Size: 314 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 314 KiB After Width: | Height: | Size: 158 KiB |
@@ -44,7 +44,6 @@ const javascripts = () => {
|
|||||||
.pipe(buffer())
|
.pipe(buffer())
|
||||||
.pipe(
|
.pipe(
|
||||||
plugins.addSrc.prepend([
|
plugins.addSrc.prepend([
|
||||||
paths.npm + 'hogan.js/dist/hogan-3.0.2.js',
|
|
||||||
paths.npm + 'jquery/dist/jquery.min.js',
|
paths.npm + 'jquery/dist/jquery.min.js',
|
||||||
paths.npm + 'query-command-supported/dist/queryCommandSupported.min.js',
|
paths.npm + 'query-command-supported/dist/queryCommandSupported.min.js',
|
||||||
paths.npm + 'textarea-caret/index.js',
|
paths.npm + 'textarea-caret/index.js',
|
||||||
|
|||||||
49
package-lock.json
generated
@@ -18,7 +18,6 @@
|
|||||||
"govuk_frontend_toolkit": "^9.0.1",
|
"govuk_frontend_toolkit": "^9.0.1",
|
||||||
"govuk-frontend": "2.13.0",
|
"govuk-frontend": "2.13.0",
|
||||||
"gulp-merge": "^0.1.1",
|
"gulp-merge": "^0.1.1",
|
||||||
"hogan": "1.0.2",
|
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"morphdom": "^2.7.7",
|
"morphdom": "^2.7.7",
|
||||||
"playwright": "^1.54.1",
|
"playwright": "^1.54.1",
|
||||||
@@ -4404,11 +4403,6 @@
|
|||||||
"sass-embedded-linux-x64": "^1.89.0"
|
"sass-embedded-linux-x64": "^1.89.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/abbrev": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
|
||||||
},
|
|
||||||
"node_modules/accepts": {
|
"node_modules/accepts": {
|
||||||
"version": "1.3.8",
|
"version": "1.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||||
@@ -8856,26 +8850,6 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hogan": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/hogan/-/hogan-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-2RV7G4f+Rt9YIYDu01r6pgZvP+XhrXi/JKlXd4b+vRybXk94ui4PQjbh/lFaH8gQtxCygy/WKkqmpm0IyZysJA==",
|
|
||||||
"dependencies": {
|
|
||||||
"hogan.js": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/hogan.js": {
|
|
||||||
"version": "3.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz",
|
|
||||||
"integrity": "sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==",
|
|
||||||
"dependencies": {
|
|
||||||
"mkdirp": "0.3.0",
|
|
||||||
"nopt": "1.0.10"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"hulk": "bin/hulk"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/homedir-polyfill": {
|
"node_modules/homedir-polyfill": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
|
||||||
@@ -11489,15 +11463,6 @@
|
|||||||
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
|
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/mkdirp": {
|
|
||||||
"version": "0.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz",
|
|
||||||
"integrity": "sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==",
|
|
||||||
"deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)",
|
|
||||||
"engines": {
|
|
||||||
"node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/morphdom": {
|
"node_modules/morphdom": {
|
||||||
"version": "2.7.7",
|
"version": "2.7.7",
|
||||||
"resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.7.tgz",
|
"resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.7.tgz",
|
||||||
@@ -11593,20 +11558,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/nopt": {
|
|
||||||
"version": "1.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
|
|
||||||
"integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
|
|
||||||
"dependencies": {
|
|
||||||
"abbrev": "1"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"nopt": "bin/nopt.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/normalize-path": {
|
"node_modules/normalize-path": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
"govuk_frontend_toolkit": "^9.0.1",
|
"govuk_frontend_toolkit": "^9.0.1",
|
||||||
"govuk-frontend": "2.13.0",
|
"govuk-frontend": "2.13.0",
|
||||||
"gulp-merge": "^0.1.1",
|
"gulp-merge": "^0.1.1",
|
||||||
"hogan": "1.0.2",
|
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"morphdom": "^2.7.7",
|
"morphdom": "^2.7.7",
|
||||||
"playwright": "^1.54.1",
|
"playwright": "^1.54.1",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
"""Simple tests to verify ZAP security fixes"""
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@@ -24,11 +23,23 @@ def test_no_duplicate_form_action(client_request, mocker, mock_get_service_and_o
|
|||||||
assert count == 1
|
assert count == 1
|
||||||
|
|
||||||
|
|
||||||
def test_cross_origin_embedder_policy_exists(client_request, mocker, mock_get_service_and_organization_counts):
|
def test_cross_origin_embedder_policy_set_to_credentialless(client_request, mocker, mock_get_service_and_organization_counts):
|
||||||
"""Check that Cross-Origin-Embedder-Policy header is present"""
|
"""Check that Cross-Origin-Embedder-Policy is set to 'credentialless' for YouTube compatibility"""
|
||||||
mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user")
|
mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user")
|
||||||
client_request.logout()
|
client_request.logout()
|
||||||
response = client_request.get_response('.index')
|
response = client_request.get_response('.index')
|
||||||
|
|
||||||
assert 'Cross-Origin-Embedder-Policy' in response.headers
|
assert response.headers.get('Cross-Origin-Embedder-Policy') == 'credentialless'
|
||||||
assert response.headers['Cross-Origin-Embedder-Policy'] == 'credentialless'
|
|
||||||
|
|
||||||
|
def test_permissions_policy_allows_youtube_features(client_request, mocker, mock_get_service_and_organization_counts):
|
||||||
|
"""Check that Permissions-Policy allows necessary features for YouTube embeds"""
|
||||||
|
mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user")
|
||||||
|
client_request.logout()
|
||||||
|
response = client_request.get_response('.index')
|
||||||
|
|
||||||
|
permissions_policy = response.headers.get('Permissions-Policy', '')
|
||||||
|
|
||||||
|
assert 'accelerometer=(self "https://www.youtube-nocookie.com")' in permissions_policy
|
||||||
|
assert 'autoplay=(self "https://www.youtube-nocookie.com")' in permissions_policy
|
||||||
|
assert 'gyroscope=(self "https://www.youtube-nocookie.com")' in permissions_policy
|
||||||
|
|||||||
20
urls.js
@@ -19,17 +19,17 @@ const routes = {
|
|||||||
authenticated: [
|
authenticated: [
|
||||||
{
|
{
|
||||||
label: 'SMS Template Preview',
|
label: 'SMS Template Preview',
|
||||||
path: '/services/e6de050d-bd56-4f49-b44b-cb789c4f79fd/send/a4e530e7-cae2-4e24-a1e9-752141a0b58e/one-off/step-0',
|
path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/send/9c2b7a55-8785-4dc6-84d6-eb0e0615590d/one-off/step-0',
|
||||||
},
|
},
|
||||||
// Pages with govuk buttons that need testing
|
// Pages with govuk buttons that need testing
|
||||||
{
|
{
|
||||||
label: 'API Keys',
|
label: 'API Keys',
|
||||||
path: '/services/e6de050d-bd56-4f49-b44b-cb789c4f79fd/api/keys',
|
path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/api/keys',
|
||||||
},
|
},
|
||||||
// Pages to test radio buttons before converting govukRadios to USWDS
|
// Pages to test radio buttons before converting govukRadios to USWDS
|
||||||
{
|
{
|
||||||
label: 'API Keys Create',
|
label: 'API Keys Create',
|
||||||
path: '/services/e6de050d-bd56-4f49-b44b-cb789c4f79fd/api/keys/create',
|
path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/api/keys/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Change User Auth',
|
label: 'Change User Auth',
|
||||||
@@ -41,31 +41,31 @@ const routes = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Service Settings',
|
label: 'Service Settings',
|
||||||
path: '/services/e6de050d-bd56-4f49-b44b-cb789c4f79fd/service-settings',
|
path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/service-settings',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Service Send Files By Email',
|
label: 'Service Send Files By Email',
|
||||||
path: '/services/e6de050d-bd56-4f49-b44b-cb789c4f79fd/service-settings/send-files-by-email',
|
path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/service-settings/send-files-by-email',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Service SMS Prefix',
|
label: 'Service SMS Prefix',
|
||||||
path: '/services/e6de050d-bd56-4f49-b44b-cb789c4f79fd/service-settings/sms-prefix',
|
path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/service-settings/sms-prefix',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Send One Off Step 2',
|
label: 'Send One Off Step 2',
|
||||||
path: '/services/e6de050d-bd56-4f49-b44b-cb789c4f79fd/send/a4e530e7-cae2-4e24-a1e9-752141a0b58e/one-off/step-2',
|
path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/send/9c2b7a55-8785-4dc6-84d6-eb0e0615590d/one-off/step-2',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Choose Template',
|
label: 'Choose Template',
|
||||||
path: '/services/e6de050d-bd56-4f49-b44b-cb789c4f79fd/templates',
|
path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/templates',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Team Members',
|
label: 'Team Members',
|
||||||
path: '/services/e6de050d-bd56-4f49-b44b-cb789c4f79fd/users',
|
path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/users',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Invite User',
|
label: 'Invite User',
|
||||||
path: '/services/e6de050d-bd56-4f49-b44b-cb789c4f79fd/users/invite',
|
path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/users/invite',
|
||||||
},
|
},
|
||||||
// Platform admin pages with checkboxes
|
// Platform admin pages with checkboxes
|
||||||
{
|
{
|
||||||
|
|||||||