Skip to content

Commit 540536c

Browse files
YooLCDkirjs
authored andcommitted
fix(http): add CSP nonce support to JsonpClientBackend
Add support for CSP nonces in JsonpClientBackend by injecting the CSP_NONCE token. This ensures that dynamically created script tags for JSONP requests include the required nonce attribute to comply with strict Content Security Policies. (cherry picked from commit 39e382a)
1 parent f603d47 commit 540536c

3 files changed

Lines changed: 47 additions & 0 deletions

File tree

packages/common/http/src/jsonp.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {DOCUMENT} from '../../index';
1010
import {
11+
CSP_NONCE,
1112
EnvironmentInjector,
1213
Inject,
1314
inject,
@@ -94,6 +95,7 @@ export class JsonpClientBackend implements HttpBackend {
9495
* A resolved promise that can be used to schedule microtasks in the event handlers.
9596
*/
9697
private readonly resolvedPromise = Promise.resolve();
98+
private readonly nonce = inject(CSP_NONCE, {optional: true});
9799

98100
constructor(
99101
private callbackMap: JsonpCallbackContext,
@@ -149,6 +151,12 @@ export class JsonpClientBackend implements HttpBackend {
149151
const node = this.document.createElement('script');
150152
node.src = url;
151153

154+
// Set the nonce for Content Security Policy compatibility. Without this,
155+
// JSONP requests will be blocked by strict-dynamic CSP policies.
156+
if (this.nonce) {
157+
node.setAttribute('nonce', this.nonce);
158+
}
159+
152160
// A JSONP request requires waiting for multiple callbacks. These variables
153161
// are closed over and track state across those callbacks.
154162

packages/common/http/test/jsonp_mock.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ export class MockScriptElement {
2525
remove() {
2626
this.ownerDocument.removeNode(this);
2727
}
28+
29+
private attrs: Record<string, string> = {};
30+
31+
setAttribute(name: string, value: string): void {
32+
this.attrs[name] = value;
33+
}
34+
35+
getAttribute(name: string): string | null {
36+
return this.attrs.hasOwnProperty(name) ? this.attrs[name] : null;
37+
}
2838
}
2939

3040
export class MockDocument {

packages/common/http/test/jsonp_spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import {DOCUMENT} from '../..';
10+
import {CSP_NONCE} from '@angular/core';
1011
import {HttpHeaders} from '../src/headers';
1112
import {
1213
JSONP_ERR_HEADERS_NOT_SUPPORTED,
@@ -43,6 +44,7 @@ describe('JsonpClientBackend', () => {
4344
JsonpClientBackend,
4445
{provide: JsonpCallbackContext, useValue: {}},
4546
{provide: DOCUMENT, useValue: mockDoc},
47+
{provide: CSP_NONCE, useValue: null},
4648
],
4749
});
4850
backend = TestBed.inject(JsonpClientBackend);
@@ -98,6 +100,33 @@ describe('JsonpClientBackend', () => {
98100
// executing.
99101
expect(document.mock!.ownerDocument).not.toEqual(document);
100102
});
103+
describe('CSP nonce', () => {
104+
it('sets nonce attribute on script element when CSP_NONCE token is provided', (done) => {
105+
TestBed.resetTestingModule();
106+
const mockDoc = new MockDocument();
107+
TestBed.configureTestingModule({
108+
providers: [
109+
JsonpClientBackend,
110+
{provide: JsonpCallbackContext, useValue: {}},
111+
{provide: DOCUMENT, useValue: mockDoc},
112+
{provide: CSP_NONCE, useValue: 'test-nonce-123'},
113+
],
114+
});
115+
const nonceBackend = TestBed.inject(JsonpClientBackend);
116+
nonceBackend.handle(SAMPLE_REQ).subscribe();
117+
118+
expect(mockDoc.mock!.getAttribute('nonce')).toBe('test-nonce-123');
119+
done();
120+
});
121+
122+
it('does not set nonce attribute when CSP_NONCE token is not provided', (done) => {
123+
backend.handle(SAMPLE_REQ).subscribe();
124+
125+
expect(document.mock!.getAttribute('nonce')).toBeNull();
126+
done();
127+
});
128+
});
129+
101130
describe('throws an error', () => {
102131
it('when request method is not JSONP', () =>
103132
expect(() => backend.handle(SAMPLE_REQ.clone<never>({method: 'GET'}))).toThrowError(

0 commit comments

Comments
 (0)