Skip to content

Commit b646ce3

Browse files
committed
test: update e2e app with new capabilites
- add req.nextUrl.origin to connect-src (makes reporting work for every domain) - reporting endpoint with reporting type extraction and status codes
1 parent fb6ea5e commit b646ce3

3 files changed

Lines changed: 72 additions & 43 deletions

File tree

apps/e2e/pages/_middleware.ts

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,40 @@ import {
22
chain,
33
nextSafe,
44
strictDynamic,
5-
reporting
5+
reporting,
66
} from "@next-safe/middleware";
77

88
const isDev = process.env.NODE_ENV === "development";
9+
const reportOnly = !!process.env.CSP_REPORT_ONLY;
910

10-
const nextSafeMiddleware = nextSafe((req, res) => {
11+
const nextSafeMiddleware = nextSafe((req) => {
1112
return {
1213
isDev,
1314
contentSecurityPolicy: {
15+
reportOnly,
1416
"style-src": `'unsafe-inline'`,
17+
"connect-src": `'self' ${req.nextUrl.origin}`,
1518
},
1619
// customize as you need: https://trezy.gitbook.io/next-safe/usage/configuration
1720
};
1821
});
1922

20-
const vercelUrl = process.env.VERCEL_URL
21-
const reportEndpoint = vercelUrl ? `https://${vercelUrl}/api/reporting` : ""
22-
23-
const reportingMiddleware = reporting({
24-
csp: {
25-
reportUri: process.env.CSP_REPORT_URI || reportEndpoint || "https://example.com/csp-report-uri",
26-
reportSample: true
27-
},
28-
reportTo: {
29-
max_age: 1800,
30-
endpoints: [
31-
{
32-
url: process.env.CSP_REPORT_TO || reportEndpoint || "https://example.com/reporting-api",
33-
},
34-
],
35-
},
36-
23+
const reportingMiddleware = reporting((req) => {
24+
const nextApiReportEndpoint = `${req.nextUrl.origin}/api/reporting`;
25+
return {
26+
csp: {
27+
reportUri: process.env.CSP_REPORT_URI || nextApiReportEndpoint,
28+
reportSample: true,
29+
},
30+
reportTo: {
31+
max_age: 1800,
32+
endpoints: [
33+
{
34+
url: process.env.REPORT_TO_ENDPOINT_DEFAULT || nextApiReportEndpoint,
35+
},
36+
],
37+
},
38+
};
3739
});
3840

39-
export default chain(
40-
nextSafeMiddleware,
41-
strictDynamic(),
42-
reportingMiddleware
43-
);
41+
export default chain(nextSafeMiddleware, strictDynamic(), reportingMiddleware);

apps/e2e/pages/api/reporting.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1-
import type { NextApiHandler } from 'next';
1+
import type { NextApiHandler } from "next";
2+
import { extractReportingData } from "types/reporting";
23

34
const handler: NextApiHandler = (req, res) => {
4-
console.log('Reporting API data', { body: req.body, method: req.method })
5-
res.status(200).json({ message: 'logged reporting data'})
6-
}
7-
export default handler;
5+
const reportingData = extractReportingData(req.body);
6+
if (reportingData) {
7+
console.log(JSON.stringify(reportingData));
8+
// Accepted: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202
9+
res.status(202).end();
10+
return;
11+
}
12+
// Unprocessable Entity: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422
13+
res.status(422).end();
14+
};
15+
16+
export default handler;

apps/e2e/types/reporting.ts

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,12 @@ export type ReportingData = ReportToData | CspReportUriData;
5757

5858
// TODO: find out what's required and what's optional
5959
export const isReportToItem = (x: any): x is ReportToItem => {
60-
const requiredKeys = ["age", "type", "body"];
61-
return (
62-
typeof x === "object" &&
63-
Object.keys(x).filter((key) => key in requiredKeys).length ===
64-
requiredKeys.length
65-
);
60+
if (typeof x !== "object") {
61+
return false;
62+
}
63+
const requiredKeys = ["type", "body"];
64+
const itemKeys = Object.keys(x);
65+
return requiredKeys.every((key) => itemKeys.includes(key));
6666
};
6767

6868
export const isReportToPayload = (x: any): x is ReportToPayload => {
@@ -73,21 +73,43 @@ export const isCspReportUriPayload = (x: any): x is CspReportUriPayload => {
7373
return typeof x === "object" && !!x["csp-report"];
7474
};
7575

76-
export const extractReportingData = (
77-
data: unknown
78-
): ReportingData | undefined => {
79-
if (isReportToPayload(data)) {
80-
return {
81-
kind: "report-to",
82-
data,
83-
};
76+
const maybeArr = (x: string) => x.startsWith("[");
77+
const maybeObj = (x: string) => x.startsWith("{");
78+
79+
const deepJsonParse = (x: unknown) => {
80+
if (typeof x === "string" && (maybeArr(x) || maybeObj(x))) {
81+
try {
82+
return JSON.parse(x);
83+
} catch {
84+
return x;
85+
}
86+
}
87+
if (typeof x === "object") {
88+
return Object.fromEntries(
89+
Object.entries(x).map(([k, v]) => [k, deepJsonParse(v)])
90+
);
91+
}
92+
if (Array.isArray(x)) {
93+
return x.map(deepJsonParse);
8494
}
95+
return x;
96+
};
97+
98+
export const extractReportingData = (x: unknown): ReportingData | undefined => {
99+
const data = deepJsonParse(x);
85100
if (isCspReportUriPayload(data)) {
86101
const kind = "csp-report";
87102
return {
88103
kind,
89104
data: data[kind],
90105
};
91106
}
107+
if (isReportToPayload(data)) {
108+
return {
109+
kind: "report-to",
110+
data,
111+
};
112+
}
113+
92114
return undefined;
93115
};

0 commit comments

Comments
 (0)