Repository navigation
Expand file tree
/
Copy pathnext.config.mjs
More file actions
60 lines (56 loc) · 2.75 KB
/
Copy pathnext.config.mjs
File metadata and controls
60 lines (56 loc) · 2.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
const isDev = process.env.NODE_ENV === "development";
/**
* Content-Security-Policy.
*
* Everything talks to Firebase straight from the browser, so the allowances
* below are what sign-in and Firestore actually need — narrow them and Google
* sign-in breaks:
* - script-src apis.google.com / gstatic.com: the auth helper scripts
* - connect-src googleapis.com: identity toolkit, token refresh, Firestore
* - frame-src firebaseapp.com / accounts.google.com: the sign-in popup and
* the auth iframe it hands back to
* - img-src googleusercontent.com: member avatars from Google profiles
* - www.google.com: reCAPTCHA, once App Check has a site key
*
* 'unsafe-inline' in script-src is Next's hydration payload, which ships as
* inline <script> tags; removing it needs a nonce and a custom document. The
* policy is still worth having without it — it pins which origins may serve
* script at all, and frame-ancestors 'none' blocks clickjacking of the
* delete-group and settle-up buttons.
*/
const csp = [
"default-src 'self'",
`script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ""} https://apis.google.com https://www.gstatic.com https://www.google.com`,
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https://*.googleusercontent.com https://www.gstatic.com",
"font-src 'self' data:",
"connect-src 'self' https://*.googleapis.com https://*.firebaseio.com wss://*.firebaseio.com https://*.firebaseapp.com",
"frame-src https://*.firebaseapp.com https://accounts.google.com https://www.google.com",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
"object-src 'none'",
"upgrade-insecure-requests",
].join("; ");
const securityHeaders = [
{ key: "Content-Security-Policy", value: csp },
// frame-ancestors already covers this for current browsers; kept for old ones.
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=(), payment=()" },
{ key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains" },
];
/** @type {import("next").NextConfig} */
const nextConfig = {
// The end-to-end tests build a demo-mode copy of the app into its own
// folder (see playwright.config.mjs), so it can't clobber a real build.
distDir: process.env.NEXT_DIST_DIR || ".next",
// No "X-Powered-By: Next.js": it tells a visitor nothing they need, and
// tells a scanner which framework's advisories to try.
poweredByHeader: false,
async headers() {
return [{ source: "/:path*", headers: securityHeaders }];
},
};
export default nextConfig;