Skip to content

Commit 813ea33

Browse files
authored
fix: replace assert syntax to be forwards compatible (#133)
* fix: replace assert syntax with eval to be forwards compatible * test: update syntax to new cli output * chore: auto format test file * chore: ts ignore annotations and cleanup * chore: inline comment explaining approach
1 parent 907fafc commit 813ea33

File tree

4 files changed

+87
-63
lines changed

4 files changed

+87
-63
lines changed

src/__csp-nonce.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
/* eslint-disable */
2-
// @ts-ignore
1+
// @ts-ignore cannot find module
32
import type { Config, Context } from "netlify:edge";
4-
// @ts-ignore
3+
// @ts-ignore cannot find module
54
import { csp } from "https://deno.land/x/[email protected]/src/index-embedded-wasm.ts";
6-
// @ts-ignore
7-
import inputs from "./__csp-nonce-inputs.json" assert { type: "json" };
5+
6+
// Using `import ... with ...` syntax directly fails due to the node 18 type-checking we're running on this file,
7+
// but this syntax works fine in deno 1.46.3 and 2.x which is what the functions are bundled and run with.
8+
// We're able to sneak by the node syntax issues by using this `await import(...)` syntax instead of a direct import statement.
9+
// @ts-ignore top-level await
10+
const { default: inputs } = await import("./__csp-nonce-inputs.json", {
11+
// @ts-ignore 'with' syntax
12+
with: { type: "json" },
13+
});
814

915
type Params = {
1016
reportOnly: boolean;
@@ -21,7 +27,7 @@ type Params = {
2127
};
2228
const params = inputs as Params;
2329
params.reportUri = params.reportUri || "/.netlify/functions/__csp-violations";
24-
// @ts-ignore
30+
// @ts-ignore Netlify
2531
params.distribution = Netlify.env.get("CSP_NONCE_DISTRIBUTION");
2632

2733
params.strictDynamic = params.strictDynamic ?? true;

src/__csp-violations.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
/* eslint-disable */
2-
// @ts-ignore
1+
// @ts-ignore async missing await
32
const handler = async (event) => {
43
try {
54
const { "csp-report": cspReport } = JSON.parse(event.body);
65
if (cspReport) {
76
console.log(JSON.stringify(cspReport));
87
}
9-
} catch (err) {
8+
} catch {
109
// ...the sound of silence
1110
}
1211
return {

tests/integration/helpers.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,15 @@ export const serve = async ({
5050

5151
if (
5252
message.includes(
53-
`◈ Server now ready on http://localhost:${port.toString()}`
53+
`Local dev server ready: http://localhost:${port.toString()}`
5454
)
5555
) {
5656
hasServerStarted = true;
5757
}
5858

59-
if (message.includes("Loaded edge function ")) {
59+
if (message.includes("Loaded edge function ")) {
6060
const match =
61-
/ Loaded edge function (?<name>[\w-]+)/.exec(message)?.groups
61+
/Loaded edge function (?<name>[\w-]+)/.exec(message)?.groups
6262
?.name ?? null;
6363

6464
if (match !== null && functionsReady.has(match)) {

tests/integration/test.test.ts

Lines changed: 70 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,21 @@ import { readFile } from "node:fs/promises";
55
import { afterAll, beforeAll, expect, it, describe, beforeEach } from "vitest";
66
import { serve } from "./helpers";
77

8-
98
function isCheerioAPIShape(val) {
10-
const props = ['contains', 'extract', 'html', 'merge', 'parseHTML', 'root', 'text', 'xml', 'load', '_root', '_options', 'fn'];
9+
const props = [
10+
"contains",
11+
"extract",
12+
"html",
13+
"merge",
14+
"parseHTML",
15+
"root",
16+
"text",
17+
"xml",
18+
"load",
19+
"_root",
20+
"_options",
21+
"fn",
22+
];
1123
for (const prop of props) {
1224
if (!Object.hasOwn(val, prop)) {
1325
return false;
@@ -17,16 +29,18 @@ function isCheerioAPIShape(val) {
1729
}
1830
expect.addSnapshotSerializer({
1931
serialize($, config, indentation, depth, refs, printer) {
20-
const elements = $('script[nonce]:not([nonce=""]),link[rel="preload"][as="script"][nonce]:not([nonce=""])');
32+
const elements = $(
33+
'script[nonce]:not([nonce=""]),link[rel="preload"][as="script"][nonce]:not([nonce=""])'
34+
);
2135
for (const element of elements) {
22-
element.attribs.nonce = "<placeholder_for_snapshop_test>"
36+
element.attribs.nonce = "<placeholder_for_snapshop_test>";
2337
}
24-
return $.html()
38+
return $.html();
2539
},
2640
test(val) {
27-
return isCheerioAPIShape(val)
41+
return isCheerioAPIShape(val);
2842
},
29-
})
43+
});
3044

3145
let baseURL: string;
3246

@@ -39,7 +53,7 @@ beforeAll(async () => {
3953
});
4054

4155
afterAll(async () => {
42-
await cleanup()
56+
await cleanup();
4357
});
4458

4559
describe("GET /", function () {
@@ -122,16 +136,16 @@ describe("GET /", function () {
122136
?.find((v) => v.startsWith("'nonce-"))
123137
?.slice("'nonce-".length, -1)!;
124138

125-
const elements = $("link[rel=\"preload\"][as=\"script\"]");
139+
const elements = $('link[rel="preload"][as="script"]');
126140
for (const element of elements) {
127141
expect(element.attribs.nonce).to.eql(nonce);
128142
}
129143
});
130144
});
131145

132-
it('html snapshot with filtered nonce', async () => {
133-
expect(cheerio.load(await response.text())).toMatchSnapshot()
134-
})
146+
it("html snapshot with filtered nonce", async () => {
147+
expect(cheerio.load(await response.text())).toMatchSnapshot();
148+
});
135149
});
136150

137151
describe("POST /", function () {
@@ -141,11 +155,11 @@ describe("POST /", function () {
141155
method: "POST",
142156
});
143157
});
144-
158+
145159
it("__csp-nonce edge function was not invoked", () => {
146160
expect(response.headers.has("x-debug-csp-nonce")).to.eql(false);
147161
});
148-
162+
149163
it("responds with original content-security-policy header", () => {
150164
expect(response.headers.get("content-security-policy")).to.eql(
151165
"img-src 'self' blob: data:; script-src 'sha256-/Cb4VxgL2aVP0MVDvbP0DgEOUv+MeNQmZX4yXHkn/c0='"
@@ -158,74 +172,75 @@ describe("GET /main.css", function () {
158172
beforeAll(async () => {
159173
response = await fetch(new URL(`/main.css`, baseURL));
160174
});
161-
175+
162176
it("__csp-nonce edge function was not invoked", () => {
163177
expect(response.headers.has("x-debug-csp-nonce")).to.eql(false);
164178
});
165-
179+
166180
it("responds with a 200 status", () => {
167181
expect(response.status).to.eql(200);
168182
});
169-
183+
170184
it("responds without a content-security-policy header", () => {
171185
expect(response.headers.has("content-security-policy")).to.eql(false);
172186
});
173187
});
174188

175-
176189
describe("Origin response has non-html content-type", () => {
177190
let response: Response;
178191
beforeAll(async () => {
179192
response = await fetch(new URL(`/hello`, baseURL));
180193
});
181-
194+
182195
it("__csp-nonce edge function was invoked", () => {
183196
expect(response.headers.get("x-debug-csp-nonce")).to.eql("invoked");
184197
});
185-
198+
186199
it("responds with a 200 status", () => {
187200
expect(response.status).to.eql(200);
188201
});
189-
202+
190203
it("responds without a content-security-policy header", () => {
191204
expect(response.headers.has("content-security-policy")).to.eql(false);
192205
});
193-
206+
194207
describe("body", () => {
195208
it("has has the original response body unmodified", async () => {
196-
const actual = Buffer.from(await response.arrayBuffer())
197-
const expected = await readFile(new URL("../../site/hello", import.meta.url))
198-
expect(actual).to.eql(expected)
209+
const actual = Buffer.from(await response.arrayBuffer());
210+
const expected = await readFile(
211+
new URL("../../site/hello", import.meta.url)
212+
);
213+
expect(actual).to.eql(expected);
199214
});
200215
});
201-
})
216+
});
202217

203218
describe("Origin response has html content-type but binary contents in body", () => {
204219
let response: Response;
205220
beforeAll(async () => {
206221
response = await fetch(new URL(`/i-am-really-a-png-file.html`, baseURL));
207222
});
208-
223+
209224
it("__csp-nonce edge function was invoked", () => {
210225
expect(response.headers.get("x-debug-csp-nonce")).to.eql("invoked");
211226
});
212-
227+
213228
it("responds with a 200 status", () => {
214229
expect(response.status).to.eql(200);
215230
});
216-
231+
217232
it("responds with a content-security-policy header", () => {
218233
expect(response.headers.has("content-security-policy")).to.eql(true);
219234
});
220-
235+
221236
describe("content-security-policy header", () => {
222237
let csp: Map<string, string[]>;
223238
beforeAll(async () => {
224239
csp = parseContentSecurityPolicy(
225240
response.headers.get("content-security-policy") || ""
226241
);
227242
});
228-
243+
229244
it("has correct img-src directive", () => {
230245
expect(csp.get("img-src")).to.eql(["'self'", "blob:", "data:"]);
231246
});
@@ -249,42 +264,44 @@ describe("Origin response has html content-type but binary contents in body", ()
249264
]);
250265
});
251266
});
252-
267+
253268
describe("body", () => {
254269
it("has has the original response body unmodified", async () => {
255-
const actual = Buffer.from(await response.arrayBuffer())
256-
const expected = await readFile(new URL("../../site/i-am-really-a-png-file.html", import.meta.url))
257-
expect(actual).to.eql(expected)
270+
const actual = Buffer.from(await response.arrayBuffer());
271+
const expected = await readFile(
272+
new URL("../../site/i-am-really-a-png-file.html", import.meta.url)
273+
);
274+
expect(actual).to.eql(expected);
258275
});
259276
});
260-
})
277+
});
261278

262279
describe("Origin response has html content-type but non-html text contents in body", () => {
263280
let response: Response;
264281
beforeAll(async () => {
265282
response = await fetch(new URL(`/i-am-really-a-json-file.html`, baseURL));
266283
}, 15000);
267-
284+
268285
it("__csp-nonce edge function was invoked", () => {
269286
expect(response.headers.get("x-debug-csp-nonce")).to.eql("invoked");
270287
});
271-
288+
272289
it("responds with a 200 status", () => {
273290
expect(response.status).to.eql(200);
274291
});
275-
292+
276293
it("responds with a content-security-policy header", () => {
277294
expect(response.headers.has("content-security-policy")).to.eql(true);
278295
});
279-
296+
280297
describe("content-security-policy header", () => {
281298
let csp: Map<string, string[]>;
282299
beforeAll(async () => {
283300
csp = parseContentSecurityPolicy(
284301
response.headers.get("content-security-policy") || ""
285302
);
286303
});
287-
304+
288305
it("has correct img-src directive", () => {
289306
expect(csp.get("img-src")).to.eql(["'self'", "blob:", "data:"]);
290307
});
@@ -308,15 +325,17 @@ describe("Origin response has html content-type but non-html text contents in bo
308325
]);
309326
});
310327
});
311-
328+
312329
describe("body", () => {
313330
it("has has the original response body unmodified", async () => {
314-
const actual = Buffer.from(await response.arrayBuffer())
315-
const expected = await readFile(new URL("../../site/i-am-really-a-json-file.html", import.meta.url))
316-
expect(actual).to.eql(expected)
331+
const actual = Buffer.from(await response.arrayBuffer());
332+
const expected = await readFile(
333+
new URL("../../site/i-am-really-a-json-file.html", import.meta.url)
334+
);
335+
expect(actual).to.eql(expected);
317336
});
318337
});
319-
})
338+
});
320339

321340
// Really large HTML file
322341
describe("GET /whatwg", function () {
@@ -393,14 +412,14 @@ describe("GET /whatwg", function () {
393412
?.find((v) => v.startsWith("'nonce-"))
394413
?.slice("'nonce-".length, -1)!;
395414

396-
const elements = $("link[rel=\"preload\"][as=\"script\"]");
415+
const elements = $('link[rel="preload"][as="script"]');
397416
for (const element of elements) {
398417
expect(element.attribs.nonce).to.eql(nonce);
399418
}
400419
});
401420
});
402421

403-
it('html snapshot with filtered nonce', async () => {
404-
expect(cheerio.load(await response.text())).toMatchSnapshot()
405-
})
406-
});
422+
it("html snapshot with filtered nonce", async () => {
423+
expect(cheerio.load(await response.text())).toMatchSnapshot();
424+
});
425+
});

0 commit comments

Comments
 (0)