Skip to content

Commit 9370199

Browse files
committed
feat: handle nonce and trusted types (CSP) in next app-dir
1 parent c880d43 commit 9370199

File tree

21 files changed

+233
-148
lines changed

21 files changed

+233
-148
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ A few projects that use `@codegouvfr/react-dsfr`.
110110

111111
- https://code.gouv.fr/sill
112112
- https://immersion-facile.beta.gouv.fr/
113+
- https://egapro.travail.gouv.fr/
114+
- https://maisondelautisme.gouv.fr/
113115
- https://refugies.info/fr
114116
- https://www.mediateur-public.fr/
115117
- https://signal.conso.gouv.fr/

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@
107107
"remixicon": "^3.2.0",
108108
"storybook-dark-mode": "^1.1.2",
109109
"ts-node": "^10.9.1",
110-
"tss-react": "^4.9.0",
110+
"tss-react": "^4.9.1",
111111
"type-route": "^1.0.1",
112112
"typescript": "^4.9.1",
113113
"vitest": "^0.24.3"

src/next-appdir/DsfrHead.tsx

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,25 @@ export function DsfrHead(props: DsfrHeadProps) {
5555
<link rel="apple-touch-icon" href={getAssetUrl(AppleTouchIcon)} />
5656
<link rel="icon" href={getAssetUrl(FaviconSvg)} type="image/svg+xml" />
5757
<link rel="shortcut icon" href={getAssetUrl(FaviconIco)} type="image/x-icon" />
58-
{isProduction && (
59-
<script
60-
nonce={nonce}
61-
dangerouslySetInnerHTML={{
62-
"__html": getScriptToRunAsap({
63-
defaultColorScheme,
64-
nonce,
65-
trustedTypesPolicyName
66-
})
67-
}}
68-
/>
69-
)}
58+
<script
59+
suppressHydrationWarning
60+
nonce={nonce}
61+
dangerouslySetInnerHTML={{
62+
"__html": getScriptToRunAsap({
63+
defaultColorScheme,
64+
nonce,
65+
trustedTypesPolicyName
66+
})
67+
}}
68+
/>
69+
<script
70+
suppressHydrationWarning
71+
key="nonce-setter"
72+
nonce={nonce}
73+
dangerouslySetInnerHTML={{
74+
__html: `window.ssrNonce = "${nonce}";`
75+
}}
76+
/>
7077
</>
7178
);
7279
}

src/next-appdir/zz_internal/start.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,22 @@ import { isBrowser } from "../../tools/isBrowser";
88
let isAfterFirstEffect = false;
99
const actions: (() => void)[] = [];
1010

11-
export function startReactDsfr(params: {
11+
export async function startReactDsfr(params: {
1212
defaultColorScheme: DefaultColorScheme;
1313
/** Default: false */
1414
verbose?: boolean;
1515
/** Default: <a /> */
1616
Link?: (props: RegisteredLinkProps & { children: ReactNode }) => ReturnType<React.FC>;
17-
nonce?: string;
17+
checkNonce?: boolean;
1818
trustedTypesPolicyName?: string;
1919
}) {
20-
const { defaultColorScheme, verbose = false, Link, nonce, trustedTypesPolicyName } = params;
20+
const {
21+
defaultColorScheme,
22+
verbose = false,
23+
Link,
24+
checkNonce,
25+
trustedTypesPolicyName
26+
} = params;
2127

2228
setDefaultColorSchemeClientSide({ defaultColorScheme });
2329

@@ -39,7 +45,7 @@ export function startReactDsfr(params: {
3945
}
4046
}
4147
},
42-
nonce,
48+
checkNonce,
4349
trustedTypesPolicyName
4450
});
4551
}

src/spa.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ export function startReactDsfr(params: {
1515
Link?: (props: RegisteredLinkProps & { children: ReactNode }) => ReturnType<React.FC>;
1616
/** Default: ()=> "fr" */
1717
useLang?: () => string;
18-
nonce?: string;
18+
checkNonce?: boolean;
1919
trustedTypesPolicyName?: string;
2020
}) {
2121
const {
2222
defaultColorScheme,
2323
verbose = false,
2424
Link,
2525
useLang,
26-
nonce,
26+
checkNonce,
2727
trustedTypesPolicyName
2828
} = params;
2929

@@ -39,7 +39,7 @@ export function startReactDsfr(params: {
3939
defaultColorScheme,
4040
verbose,
4141
"nextParams": undefined,
42-
nonce,
42+
checkNonce,
4343
trustedTypesPolicyName
4444
});
4545
}

src/start.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ type Params = {
1313
registerEffectAction: (effect: () => void) => void;
1414
}
1515
| undefined;
16-
nonce?: string;
16+
checkNonce?: boolean;
1717
trustedTypesPolicyName?: string;
1818
};
1919

2020
let isStarted = false;
2121

2222
export async function start(params: Params) {
23-
const { defaultColorScheme, verbose, nextParams, nonce, trustedTypesPolicyName } = params;
23+
const { defaultColorScheme, verbose, nextParams, checkNonce, trustedTypesPolicyName } = params;
2424

2525
assert(isBrowser);
2626

@@ -38,7 +38,7 @@ export async function start(params: Params) {
3838
"doPersistDarkModePreferenceWithCookie":
3939
nextParams === undefined ? false : nextParams.doPersistDarkModePreferenceWithCookie,
4040
registerEffectAction,
41-
nonce,
41+
checkNonce,
4242
trustedTypesPolicyName
4343
});
4444

src/useIsDark/client.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,14 @@ export function startClientSideIsDarkLogic(params: {
9898
registerEffectAction: (action: () => void) => void;
9999
doPersistDarkModePreferenceWithCookie: boolean;
100100
colorSchemeExplicitlyProvidedAsParameter: ColorScheme | "system";
101-
nonce?: string;
101+
checkNonce?: boolean;
102102
trustedTypesPolicyName?: string;
103103
}) {
104104
const {
105105
doPersistDarkModePreferenceWithCookie,
106106
registerEffectAction,
107107
colorSchemeExplicitlyProvidedAsParameter,
108-
nonce,
108+
checkNonce,
109109
trustedTypesPolicyName = "react-dsfr"
110110
} = params;
111111

@@ -119,8 +119,7 @@ export function startClientSideIsDarkLogic(params: {
119119
return {
120120
"clientSideIsDark": isDarkFromHtmlAttribute,
121121
"ssrWasPerformedWithIsDark":
122-
((window as any).ssrWasPerformedWithIsDark as boolean | undefined) ??
123-
isDarkFromHtmlAttribute
122+
window.ssrWasPerformedWithIsDark ?? isDarkFromHtmlAttribute
124123
};
125124
}
126125

@@ -234,13 +233,17 @@ export function startClientSideIsDarkLogic(params: {
234233

235234
{
236235
const setRootColorScheme = (isDark: boolean) => {
236+
const nonce = window.ssrNonce;
237+
if (checkNonce && !nonce) {
238+
return;
239+
}
237240
document.getElementById(rootColorSchemeStyleTagId)?.remove();
238241

239242
const element = document.createElement("style");
240243

241244
element.id = rootColorSchemeStyleTagId;
242245

243-
if (nonce !== undefined) {
246+
if (nonce) {
244247
element.setAttribute("nonce", nonce);
245248
}
246249

src/useIsDark/scriptToRunAsap.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ type GetScriptToRunAsap = (props: {
88
trustedTypesPolicyName?: string;
99
}) => string;
1010

11+
declare global {
12+
interface Window {
13+
ssrWasPerformedWithIsDark?: boolean;
14+
ssrNonce?: string;
15+
}
16+
}
17+
1118
// TODO enhance to use DOMPurify with trustedTypes
1219
export const getScriptToRunAsap: GetScriptToRunAsap = ({
1320
defaultColorScheme,
@@ -17,7 +24,7 @@ export const getScriptToRunAsap: GetScriptToRunAsap = ({
1724
{
1825
1926
window.ssrWasPerformedWithIsDark = "${defaultColorScheme}" === "dark";
20-
const sanitizer = typeof trustedTypes !== "undefined" ? trustedTypes.createPolicy("${trustedTypesPolicyName}", { createHTML: s => s }) : {
27+
const sanitizer = typeof trustedTypes !== "undefined" ? trustedTypes.createPolicy("${trustedTypesPolicyName}-asap", { createHTML: s => s }) : {
2128
createHTML: s => s,
2229
};
2330

test/integration/cra/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
"react-scripts": "5.0.1",
99
"type-route": "^0.7.2",
1010
"@mui/material": "^5.13.3",
11-
"tss-react": "^4.8.6",
1211
"@emotion/react": "^11.11.0",
1312
"@emotion/styled": "^11.11.0",
1413
"@mui/icons-material": "^5.10.16",
@@ -50,4 +49,4 @@
5049
"last 1 safari version"
5150
]
5251
}
53-
}
52+
}

test/integration/cra/yarn.lock

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,7 +1103,7 @@
11031103
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
11041104

11051105
"@codegouvfr/react-dsfr@file:../../../dist":
1106-
version "0.58.9"
1106+
version "0.75.8"
11071107
dependencies:
11081108
tsafe "^1.6.3"
11091109

@@ -1268,7 +1268,7 @@
12681268
source-map "^0.5.7"
12691269
stylis "4.2.0"
12701270

1271-
"@emotion/cache@*", "@emotion/cache@^11.11.0":
1271+
"@emotion/cache@^11.11.0":
12721272
version "11.11.0"
12731273
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff"
12741274
integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==
@@ -1310,7 +1310,7 @@
13101310
"@emotion/weak-memoize" "^0.3.1"
13111311
hoist-non-react-statics "^3.3.1"
13121312

1313-
"@emotion/serialize@*", "@emotion/serialize@^1.1.2":
1313+
"@emotion/serialize@^1.1.2":
13141314
version "1.1.2"
13151315
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.2.tgz#017a6e4c9b8a803bd576ff3d52a0ea6fa5a62b51"
13161316
integrity sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==
@@ -1348,7 +1348,7 @@
13481348
resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963"
13491349
integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==
13501350

1351-
"@emotion/utils@*", "@emotion/utils@^1.2.1":
1351+
"@emotion/utils@^1.2.1":
13521352
version "1.2.1"
13531353
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4"
13541354
integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==
@@ -8540,15 +8540,6 @@ tslib@^2.0.3:
85408540
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
85418541
integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
85428542

8543-
tss-react@^4.8.6:
8544-
version "4.8.6"
8545-
resolved "https://registry.yarnpkg.com/tss-react/-/tss-react-4.8.6.tgz#c1d9ede44474e3119bb21ef6ef7ae4057cbee751"
8546-
integrity sha512-+ucvy+SLFUUxd3zA3QS9Q7bo5FerR8VIUOHieyvYYMoBqtpVinnOA0aTOSXcSdl4lqjFc/9gNA5x0B5iIWk7hA==
8547-
dependencies:
8548-
"@emotion/cache" "*"
8549-
"@emotion/serialize" "*"
8550-
"@emotion/utils" "*"
8551-
85528543
tsutils@^3.21.0:
85538544
version "3.21.0"
85548545
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"

0 commit comments

Comments
 (0)