Skip to content

Commit

Permalink
feat(csp): handle nonce and trusted types (#113)
Browse files Browse the repository at this point in the history
* feat(csp): handle nonce and trusted types

* chore: add doc
  • Loading branch information
lsagetlethias committed Sep 21, 2023
1 parent 873ecb6 commit 2d7de88
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 2 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,35 @@ import { push } from "@socialgouv/matomo-next";
push(["trackEvent", "contact", "click phone"]);
```

### Content-Security-Policy
#### [Nonce](https://developer.mozilla.org/fr/docs/Web/HTML/Global_attributes/nonce)
If you use a `Content-Security-Policy` header with a `nonce` attribute, you can pass it to the `init` function to allow the script to be executed.

```js
init({
url: MATOMO_URL,
siteId: MATOMO_SITE_ID,
nonce: "123456789",
})
```

#### [Trusted Types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/trusted-types)
As the `matomo-next` injects a matomo script, if you use strict Trusted Types, you need to allow the `script` tag to be created by adding our policy name to your `trusted types` directive.

```
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types matomo-next;
```

You can set a custom policy name by passing it to the `init` function.

```js
init({
url: MATOMO_URL,
siteId: MATOMO_SITE_ID,
trustedPolicyName: "your-custom-policy-name",
})
```

### Extensibility

The function has three optional callback properties that allow for custom behavior to be added:
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"@socialgouv/eslint-config-react": "1.131.0",
"@socialgouv/eslint-config-typescript": "^1.97.3",
"@types/jest": "26.0.24",
"@types/trusted-types": "^2.0.4",
"eslint": "7.32.0",
"jest": "26.6.3",
"next": "11.1.3",
Expand Down
27 changes: 25 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { default as Router } from "next/router";

interface HTMLTrustedScriptElement extends Omit<HTMLScriptElement, "src"> {
src: TrustedScriptURL | string;
}

const isExcludedUrl = (url: string, patterns: RegExp[]): boolean => {
let excluded = false;
patterns.forEach((pattern) => {
Expand All @@ -20,6 +24,8 @@ interface InitSettings {
onRouteChangeStart?: (path: string) => void;
onRouteChangeComplete?: (path: string) => void;
onInitialization?: () => void;
nonce?: string;
trustedPolicyName?: string;
}

interface Dimensions {
Expand Down Expand Up @@ -58,6 +64,11 @@ const startsWith = (str: string, needle: string) => {
return str.substring(0, needle.length) === needle;
};

const trustedPolicyHooks: TrustedTypePolicyOptions = {
createScript: (s) => s,
createScriptURL: (s) => s,
};

// initialize the tracker
export function init({
url,
Expand All @@ -69,12 +80,19 @@ export function init({
onRouteChangeStart = undefined,
onRouteChangeComplete = undefined,
onInitialization = undefined,
nonce,
trustedPolicyName = "matomo-next",
}: InitSettings): void {
window._paq = window._paq !== null ? window._paq : [];
if (!url) {
console.warn("Matomo disabled, please provide matomo url");
return;
}

const sanitizer =
window.trustedTypes?.createPolicy(trustedPolicyName, trustedPolicyHooks) ??
trustedPolicyHooks;

let previousPath = "";
// order is important -_- so campaign are detected
const excludedUrl =
Expand Down Expand Up @@ -106,12 +124,17 @@ export function init({
* we rely on Router.pathname
*/

const scriptElement = document.createElement("script");
const scriptElement: HTMLTrustedScriptElement =
document.createElement("script");
const refElement = document.getElementsByTagName("script")[0];
if (nonce) {
scriptElement.setAttribute("nonce", nonce);
}
scriptElement.type = "text/javascript";
scriptElement.async = true;
scriptElement.defer = true;
scriptElement.src = `${url}/${jsTrackerFile}`;
const fullUrl = `${url}/${jsTrackerFile}`;
scriptElement.src = sanitizer.createScriptURL?.(fullUrl) ?? fullUrl;
if (refElement.parentNode) {
refElement.parentNode.insertBefore(scriptElement, refElement);
}
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,11 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff"
integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==

"@types/trusted-types@^2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.4.tgz#2b38784cd16957d3782e8e2b31c03bc1d13b4d65"
integrity sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ==

"@types/yargs-parser@*":
version "15.0.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
Expand Down

0 comments on commit 2d7de88

Please sign in to comment.