Skip to content

Commit 5674c64

Browse files
alan-agius4alxhub
authored andcommitted
fix(platform-server): add nonce attribute to event record script (angular#55495)
This commit fixes an issue where the nonce attribute was not added when `CSP_NONCE` token was provided. PR Close angular#55495
1 parent 5948193 commit 5674c64

File tree

3 files changed

+46
-6
lines changed

3 files changed

+46
-6
lines changed

packages/platform-server/src/transfer_state.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,17 @@ export const TRANSFER_STATE_SERIALIZATION_PROVIDERS: Provider[] = [
2121
];
2222

2323
/** TODO: Move this to a utils folder and convert to use SafeValues. */
24-
export function createScript(doc: Document, textContent: string) {
24+
export function createScript(
25+
doc: Document,
26+
textContent: string,
27+
nonce: string | null,
28+
): HTMLScriptElement {
2529
const script = doc.createElement('script');
2630
script.textContent = textContent;
31+
if (nonce) {
32+
script.setAttribute('nonce', nonce);
33+
}
34+
2735
return script;
2836
}
2937

@@ -39,7 +47,11 @@ function serializeTransferStateFactory(doc: Document, appId: string, transferSto
3947
return;
4048
}
4149

42-
const script = createScript(doc, content);
50+
const script = createScript(
51+
doc,
52+
content,
53+
null /** nonce is not required for 'application/json' */,
54+
);
4355
script.id = appId + '-state';
4456
script.setAttribute('type', 'application/json');
4557

packages/platform-server/src/utils.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
ɵIS_HYDRATION_DOM_REUSE_ENABLED as IS_HYDRATION_DOM_REUSE_ENABLED,
2020
ɵSSR_CONTENT_INTEGRITY_MARKER as SSR_CONTENT_INTEGRITY_MARKER,
2121
ɵwhenStable as whenStable,
22+
CSP_NONCE,
2223
} from '@angular/core';
2324

2425
import {PlatformState} from './platform_state';
@@ -96,13 +97,14 @@ function insertEventRecordScript(
9697
appId: string,
9798
doc: Document,
9899
eventTypesToBeReplayed: Set<string>,
99-
) {
100+
nonce: string | null,
101+
): void {
100102
const events = Array.from(eventTypesToBeReplayed);
101103
// This is defined in packages/core/primitives/event-dispatch/contract_binary.ts
102104
const replayScript = `window.__jsaction_bootstrap('ngContracts', document.body, ${JSON.stringify(
103105
appId,
104106
)}, ${JSON.stringify(events)});`;
105-
const script = createScript(doc, replayScript);
107+
const script = createScript(doc, replayScript, nonce);
106108
doc.body.insertBefore(script, doc.body.firstChild);
107109
}
108110

@@ -113,12 +115,17 @@ async function _render(platformRef: PlatformRef, applicationRef: ApplicationRef)
113115
await whenStable(applicationRef);
114116

115117
const platformState = platformRef.injector.get(PlatformState);
116-
if (applicationRef.injector.get(IS_HYDRATION_DOM_REUSE_ENABLED, false)) {
118+
if (environmentInjector.get(IS_HYDRATION_DOM_REUSE_ENABLED, false)) {
117119
const doc = platformState.getDocument();
118120
appendSsrContentIntegrityMarker(doc);
119121
const eventTypesToBeReplayed = annotateForHydration(applicationRef, doc);
120122
if (eventTypesToBeReplayed) {
121-
insertEventRecordScript(environmentInjector.get(APP_ID), doc, eventTypesToBeReplayed);
123+
insertEventRecordScript(
124+
environmentInjector.get(APP_ID),
125+
doc,
126+
eventTypesToBeReplayed,
127+
environmentInjector.get(CSP_NONCE, null),
128+
);
122129
} else {
123130
// No events to replay, we should remove inlined event dispatch script
124131
// (which was injected by the build process) from the HTML.

packages/platform-server/test/event_replay_spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,27 @@ describe('event replay', () => {
105105
expect(ssrContents).toContain('<div jsaction="click:"><div jsaction="blur:"></div></div>');
106106
});
107107

108+
it(`should add 'nonce' attribute to event record script when 'ngCspNonce' is provided`, async () => {
109+
@Component({
110+
standalone: true,
111+
selector: 'app',
112+
template: `
113+
<div (click)="onClick()">
114+
<div (blur)="onClick()"></div>
115+
</div>
116+
`,
117+
})
118+
class SimpleComponent {
119+
onClick() {}
120+
}
121+
122+
const doc = `<html><head></head><body><app ngCspNonce="{{nonce}}"></app></body></html>`;
123+
const html = await ssr(SimpleComponent, {doc});
124+
expect(getAppContents(html)).toContain(
125+
'<script nonce="{{nonce}}">window.__jsaction_bootstrap',
126+
);
127+
});
128+
108129
describe('event dispatch script', () => {
109130
it('should not be present on a page if there are no events to replay', async () => {
110131
@Component({

0 commit comments

Comments
 (0)