-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnext.config.js
More file actions
386 lines (356 loc) · 12.1 KB
/
next.config.js
File metadata and controls
386 lines (356 loc) · 12.1 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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
import withBundleAnalyzer from "@next/bundle-analyzer";
// import packageJson from "./package.json" assert { type: "json" };
// const { version } = packageJson;
// Extract domain from URL (remove protocol and path)
const getDomainFromUrl = (url) => {
if (!url) return null;
return new URL(url).hostname;
};
const isProd = process.env.NEXT_PUBLIC_ENV === "production";
const isDev = process.env.NEXT_PUBLIC_ENV === "development";
const isDebugMode = process.env.NEXT_PUBLIC_DEBUG === "true";
// Feature flag to enable/disable security headers (CSP, etc.)
const ENABLE_SECURITY_HEADERS =
process.env.NEXT_PUBLIC_ENABLE_SECURITY_HEADERS === "true";
/**
* Check if the build is running inside a Docker container to generate the standalone build.
* This envoirnment variable can be passed in the Dockerfile.
*/
const isDockerBuild = process.env.DOCKER_BUILD === "true";
// Domain Constants for CSP organized by directive type
const SELF = "'self'";
// Create array of dynamic domains, filtering out null values
const EKO_DOMAINS = [
getDomainFromUrl(process.env.NEXT_PUBLIC_API_BASE_URL),
getDomainFromUrl(process.env.NEXT_PUBLIC_CONNECT_WIDGET_URL),
].filter(Boolean);
// --- CSP Domains organized by directive type ---
// Domains for script-src directive (JavaScript execution)
const SCRIPT_SRC_DOMAINS = [
SELF,
"'unsafe-inline'", // Required for inline scripts (GTM, etc.)
"'wasm-unsafe-eval'", // Required for wasm in mobile browsers & web-view
"data:", // Required for data: URIs in scripts
"*.eko.in", // Eko platform scripts
"accounts.google.com", // Google authentication scripts
"www.google-analytics.com", // Google Analytics scripts
"cdnjs.cloudflare.com", // CDN scripts
"cdn.jsdelivr.net", // CDN scripts (MediaPipe, etc.)
"www.youtube.com", // YouTube embed scripts
...EKO_DOMAINS,
...(isDev ? ["'unsafe-eval'"] : []),
].filter(Boolean);
// Domains for style-src directive (CSS styles)
const STYLE_SRC_DOMAINS = [
SELF,
"'unsafe-inline'", // Required for inline styles
"accounts.google.com", // Google authentication styles
];
// Domains for img-src directive (images)
const IMG_SRC_DOMAINS = [
SELF,
"blob:", // Required for blob URLs
"data:", // Required for data: URIs
"*.eko.in", // Eko platform images
"files.eko.co.in", // Eko file server
"img.youtube.com", // YouTube thumbnails
];
// Domains for media (audio/video) can be added to img-src if needed
const MEDIA_SRC_DOMAINS = [
SELF,
"blob:",
"data:",
"*.eko.in",
"files.eko.co.in",
"*.youtube.com",
];
// Domains for font-src directive (fonts)
const FONT_SRC_DOMAINS = [SELF];
// Domains for connect-src directive (XHR, fetch, WebSocket)
const CONNECT_SRC_DOMAINS = [
SELF,
"data:", // Required for media (camera) stream capture
"*.eko.in", // Eko platform APIs
"files.eko.co.in", // Eko file server
"*.digitaloceanspaces.com", // DigitalOcean CDN
"www.youtube.com", // YouTube API
"api.cloud.copilotkit.ai", // CopilotKit API
"cdn.jsdelivr.net", // CDN for MediaPipe WASM/model files
...EKO_DOMAINS,
].filter(Boolean);
// Domains for frame-src directive (iframes)
const FRAME_SRC_DOMAINS = [
SELF,
"accounts.google.com", // Google authentication iframe
"www.bing.com", // Bing maps iframe
"zfrmz.in", // Zoho forms iframe
"forms.zohopublic.in", // Zoho public forms iframe
"www.youtube.com", // YouTube embed iframe
...EKO_DOMAINS,
].filter(Boolean);
// Domains for base-uri directive (base tag restrictions)
const BASE_URI_DOMAINS = [SELF, ...EKO_DOMAINS];
// Content Security Policy (CSP) for multi-tenant environment
// NOTE: 'unsafe-inline' in script-src is required due to inline <Script> usage (e.g., Google Tag Manager). To improve security, migrate all inline scripts to use a nonce/hash or load as external files. See pages/_app.tsx for GTM example. Remove 'unsafe-inline' if/when all inline scripts are eliminated.
const cspHeaders = [
// Only allow resources from the same origin by default. Blocks all external sources unless explicitly allowed below.
`default-src ${SELF}`,
// Allow JavaScript execution from trusted domains. 'unsafe-inline' is present due to inline <Script> usage (e.g., Google Tag Manager).
`script-src ${SCRIPT_SRC_DOMAINS.join(" ")}`,
// Allow styles from self and trusted domains. 'unsafe-inline' allows inline styles (required for some libraries).
`style-src ${STYLE_SRC_DOMAINS.join(" ")}`,
// Allow images from self, blob, data, and trusted domains (e.g., for logos and static assets).
`img-src ${IMG_SRC_DOMAINS.join(" ")}`,
// Allow media (audio/video) from self, blob, data, and trusted domains.
`media-src ${MEDIA_SRC_DOMAINS.join(" ")}`,
// Allow fonts from self only.
`font-src ${FONT_SRC_DOMAINS.join(" ")}`,
// Allow XHR/fetch/WebSocket connections to trusted domains.
`connect-src ${CONNECT_SRC_DOMAINS.join(" ")}`,
// Allow embedding widgets and trusted iframes. Blocks other external iframes.
`frame-src ${FRAME_SRC_DOMAINS.join(" ")}`,
// Block all plugins and object/embed elements for security.
"object-src 'none'",
// Allow base URI to be set to self or trusted domains. Prevents base tag abuse from other origins.
`base-uri ${BASE_URI_DOMAINS.join(" ")}`,
// Only allow forms to be submitted to self. Blocks data exfiltration via forms.
"form-action 'self'",
// Prevent the site from being embedded in any iframe. Protects against clickjacking.
"frame-ancestors 'none'",
].join("; ");
// Security headers for all routes (applied in Next.js custom headers)
// These headers help mitigate common web vulnerabilities and enforce best practices.
const securityHeaders = [
{
key: "X-DNS-Prefetch-Control",
value: "on", // Enables DNS prefetching for faster external resource loading.
},
{
key: "X-Frame-Options",
value: "SAMEORIGIN", // Prevents clickjacking by only allowing the site to be framed by itself.
},
{
key: "X-Content-Type-Options",
value: "nosniff", // Prevents browsers from MIME-sniffing a response away from the declared content-type.
},
{
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin", // Sends only the origin as referrer to other origins. Protects user privacy.
},
{
key: "Content-Security-Policy",
value: cspHeaders, // Applies the above CSP rules to all responses.
},
{
key: "Strict-Transport-Security",
value: "max-age=31536000; includeSubDomains", // Forces HTTPS for 1 year on all subdomains.
},
{
key: "Permissions-Policy",
value: "camera=(self), microphone=(self), geolocation=(self), payment=(self), usb=(), bluetooth=(), serial=()", // Restricts access to sensitive APIs to self only.
},
];
/*
// ORIGINAL COMMENTED SECURITY HEADERS (kept for reference)
// Default Content Security Policy
const cspHeaders = [
// "default-src 'self';",
// "script-src * 'unsafe-inline';", // 'unsafe-eval' is not recommended
// "style-src 'self' 'unsafe-inline';",
// "img-src 'self' blob: data:;",
// "font-src 'self';",
// "object-src 'self';",
// "base-uri 'self';",
// "form-action 'self';", // 'self' or *
"frame-src *;",
"frame-ancestors 'none';",
// "upgrade-insecure-requests;",
];
// Security headers
const securityHeaders = [
{
//
key: "X-DNS-Prefetch-Control",
value: "on",
},
{
// Prevent clickjacking by ensuring that their content is not embedded into other sites. To allow embedding, use "ALLOW-FROM https://example.com". To allow embedding into any site, use "ALLOW-FROM *".
key: "X-Frame-Options",
value: "SAMEORIGIN",
},
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "X-XSS-Protection",
value: "1; mode=block",
},
{
key: "Referrer-Policy",
value: "origin-when-cross-origin",
},
{
key: "Content-Security-Policy",
value: cspHeaders.join(" "),
},
];
*/
// Config for removing console logs in production
const excludeLogTypes = ["error", "info"];
if (isDebugMode) {
excludeLogTypes.push("debug");
}
const removeConsoleOptions = isProd ? { exclude: excludeLogTypes } : false;
const bundleAnalyzer = withBundleAnalyzer({
enabled: process.env.ANALYZE === "true",
openAnalyzer: true,
});
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
/* config options here */
// env: {
// NEXT_PUBLIC_APP_VERSION: version, // Expose app version from package.json to the client
// },
reactStrictMode: isDev ? true : false,
poweredByHeader: false,
swcMinify: true,
eslint: {
dirs: [
"components",
"page-components",
"tf-components",
"layout-components",
"pages",
"features",
"libs",
"utils",
"helpers",
"hooks",
"contexts",
"constants",
],
},
compiler: {
reactRemoveProperties: true, // removes ^data-test properties in build
removeConsole: removeConsoleOptions,
},
images: {
remotePatterns: [
{
protocol: "https",
hostname: "**.eko.in",
},
{
protocol: "https",
hostname: "files.eko.co.in",
},
],
},
async rewrites() {
return [
{
// Dynamic manifest.json for each organization
source: "/manifest.json",
destination: "/api/manifest-proxy",
},
{
// Redirect internal "_files" subdirectory to https://files.eko.in for file downloads.
// This is to hide the actual file server URL from the client, and use their domain instead.
source: "/_files/:path*",
destination: "https://files.eko.co.in/:path*",
},
{
// Admin: Redirect internal "_files" subdirectory to https://files.eko.in for file downloads.
// This is to hide the actual file server URL from the client, and use their domain instead.
source: "/admin/_files/:path*",
destination: "https://files.eko.co.in/:path*",
},
// URL Rewrites to fix Connect Widget bug of invalid partial paths (Eg: 'images/brands/...')
{
source: "/transaction/images/brands/:path*", // :path* is a catch-all
destination: "/images/brands/:path*",
},
{
source: "/transaction/locale/:path*", // :path* is a catch-all
destination: "https://connect.eko.in/locale/:path*",
},
{
source: "/transaction/script/:path*", // :path* is a catch-all
destination: "https://connect.eko.in/script/:path*",
},
{
source: "/admin/transaction/images/brands/:path*", // :path* is a catch-all
destination: "/images/brands/:path*",
},
{
source: "/admin/transaction/locale/:path*", // :path* is a catch-all
destination: "https://connect.eko.in/locale/:path*",
},
{
source: "/admin/transaction/script/:path*", // :path* is a catch-all
destination: "https://connect.eko.in/script/:path*",
},
// Dynamic sitemap.xml for each organization (not needed for now)
// {
// source: "/sitemap.xml",
// destination: "/api/sitemap-proxy",
// },
];
},
/**
* Conditionally applies security headers to all application routes.
*
* This function checks the `ENABLE_SECURITY_HEADERS` environment variable.
* - If `true`, it applies the `securityHeaders` array to all routes (`/(.*)`).
* - If `false`, it returns an empty array, effectively disabling all custom security headers.
*
* Note: Next.js does not apply security headers like CSP by default. Disabling this
* in production will expose the application to common web vulnerabilities. Standard
* HTTP headers required for responses (e.g., `Content-Type`) will still be applied
* even if this function returns an empty array.
* @returns {Promise<Array<{source: string, headers: Array<{key: string, value: string}>}>>} A promise that resolves to an array of header objects.
*/
async headers() {
if (ENABLE_SECURITY_HEADERS) {
return [
{
// Apply security headers to all routes to fix security vulnerabilities
source: "/(.*)",
headers: securityHeaders,
},
];
}
return []; // Disable security headers by default
},
// async headers() {
// return [
// {
// // Allow the /gateway/ route to be embedded in an iframe from any site
// source: "/gateway/(.*)",
// headers: [
// {
// // Allow embedding the /gateway/ route in iframe from any site
// key: "X-Frame-Options",
// value: "ALLOW-FROM *",
// },
// {
// key: "Content-Security-Policy",
// value: `frame-ancestors *`,
// },
// ],
// },
// {
// source: "/(.*)",
// headers: securityHeaders,
// },
// ];
// },
};
// Add standalone build config for Docker
if (isDockerBuild) {
nextConfig.output = "standalone";
}
export default bundleAnalyzer(nextConfig);