Merge branch 'dev' of https://github.com/umami-software/umami into dev
This commit is contained in:
commit
b5d27620e7
3 files changed
+45
-26
No files matched your search
+15
-3
@@ -1,4 +1,5 @@
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
import { getContentSecurityPolicy } from '@/lib/csp';
|
||||
import { matchesConfiguredPath } from '@/lib/match-configured-path';
|
||||
|
||||
export const config = {
|
||||
@@ -23,6 +24,9 @@ const trackerHeaders = {
|
||||
'Cache-Control': 'public, max-age=86400, must-revalidate',
|
||||
};
|
||||
|
||||
// Resolved once at startup — env vars don't change after the process starts.
|
||||
const contentSecurityPolicy = getContentSecurityPolicy();
|
||||
|
||||
function customCollectEndpoint(request: NextRequest) {
|
||||
const collectEndpoint = process.env.COLLECT_API_ENDPOINT;
|
||||
|
||||
@@ -69,12 +73,20 @@ function disableLogin(request: NextRequest) {
|
||||
export default function middleware(req: NextRequest) {
|
||||
const fns = [customCollectEndpoint, customScriptName, customScriptUrl, disableLogin];
|
||||
|
||||
let res: NextResponse | undefined;
|
||||
|
||||
for (const fn of fns) {
|
||||
const res = fn(req);
|
||||
res = fn(req);
|
||||
if (res) {
|
||||
return res;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
res ??= NextResponse.next();
|
||||
|
||||
// Set the CSP here, not only at build time in next.config.ts, so
|
||||
// ALLOWED_FRAME_URLS is resolved from the runtime environment.
|
||||
res.headers.set('Content-Security-Policy', contentSecurityPolicy);
|
||||
|
||||
return res;
|
||||
}
|
||||
+2
-23
@@ -1,6 +1,7 @@
|
||||
import 'dotenv/config';
|
||||
import createNextIntlPlugin from 'next-intl/plugin';
|
||||
import pkg from './package.json' with { type: 'json' };
|
||||
import { getContentSecurityPolicy } from './src/lib/csp';
|
||||
|
||||
const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');
|
||||
|
||||
@@ -17,20 +18,11 @@ const corsMaxAge = process.env.CORS_MAX_AGE || '';
|
||||
const defaultCurrency = process.env.DEFAULT_CURRENCY || '';
|
||||
const defaultLocale = process.env.DEFAULT_LOCALE || '';
|
||||
const forceSSL = process.env.FORCE_SSL || '';
|
||||
const frameAncestors = process.env.ALLOWED_FRAME_URLS || '';
|
||||
const trackerScriptName = process.env.TRACKER_SCRIPT_NAME || '';
|
||||
const trackerScriptURL = process.env.TRACKER_SCRIPT_URL || '';
|
||||
const selfTrack = process.env.UMAMI_SELF_TRACK || '';
|
||||
const selfRecord = process.env.UMAMI_SELF_RECORD || '';
|
||||
|
||||
function getUrlOrigin(url: string) {
|
||||
try {
|
||||
return new URL(url).origin;
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function isRelativeUrl(url: string) {
|
||||
return Boolean(url && !/^https?:\/\//i.test(url));
|
||||
}
|
||||
@@ -39,19 +31,6 @@ function normalizePath(url: string) {
|
||||
return `/${url.replace(/^\/+|\/+$/g, '')}`;
|
||||
}
|
||||
|
||||
const apiUrlOrigin = getUrlOrigin(apiUrl);
|
||||
const connectSrc = ["'self'", 'https:', apiUrlOrigin].filter(Boolean).join(' ');
|
||||
|
||||
const contentSecurityPolicy = `
|
||||
default-src 'self';
|
||||
img-src 'self' https: data: blob:;
|
||||
script-src 'self' 'unsafe-eval' 'unsafe-inline';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
connect-src ${connectSrc};
|
||||
frame-src 'self' http: https:;
|
||||
frame-ancestors 'self' ${frameAncestors};
|
||||
`;
|
||||
|
||||
const defaultHeaders = [
|
||||
{
|
||||
key: 'X-DNS-Prefetch-Control',
|
||||
@@ -59,7 +38,7 @@ const defaultHeaders = [
|
||||
},
|
||||
{
|
||||
key: 'Content-Security-Policy',
|
||||
value: contentSecurityPolicy.replace(/\s{2,}/g, ' ').trim(),
|
||||
value: getContentSecurityPolicy(),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
function getUrlOrigin(url: string) {
|
||||
try {
|
||||
return new URL(url).origin;
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// Builds the Content-Security-Policy. Reads the environment when called, so it
|
||||
// can run at build time (next.config.ts) or per request in the Docker proxy,
|
||||
// where ALLOWED_FRAME_URLS and API_URL then apply without rebuilding the image.
|
||||
export function getContentSecurityPolicy() {
|
||||
const apiUrlOrigin = getUrlOrigin(process.env.API_URL || '');
|
||||
const connectSrc = ["'self'", 'https:', apiUrlOrigin].filter(Boolean).join(' ');
|
||||
const frameAncestors = ["'self'", process.env.ALLOWED_FRAME_URLS || ''].filter(Boolean).join(' ');
|
||||
|
||||
const directives = [
|
||||
`default-src 'self'`,
|
||||
`img-src 'self' https: data: blob:`,
|
||||
`script-src 'self' 'unsafe-eval' 'unsafe-inline'`,
|
||||
`style-src 'self' 'unsafe-inline'`,
|
||||
`connect-src ${connectSrc}`,
|
||||
`frame-src 'self' http: https:`,
|
||||
`frame-ancestors ${frameAncestors}`,
|
||||
];
|
||||
|
||||
return `${directives.join('; ')};`;
|
||||
}
|
||||
Reference in new issue
Block a user