Fix tracker script name route matching
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { type NextRequest, NextResponse } from 'next/server';
|
import { type NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { matchesConfiguredPath } from '@/lib/match-configured-path';
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: '/:path*',
|
matcher: '/:path*',
|
||||||
@@ -7,6 +8,7 @@ export const config = {
|
|||||||
const TRACKER_PATH = '/script.js';
|
const TRACKER_PATH = '/script.js';
|
||||||
const COLLECT_PATH = '/api/send';
|
const COLLECT_PATH = '/api/send';
|
||||||
const LOGIN_PATH = '/login';
|
const LOGIN_PATH = '/login';
|
||||||
|
const BASE_PATH = process.env.BASE_PATH || '';
|
||||||
|
|
||||||
const apiHeaders = {
|
const apiHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -27,7 +29,7 @@ function customCollectEndpoint(request: NextRequest) {
|
|||||||
if (collectEndpoint) {
|
if (collectEndpoint) {
|
||||||
const url = request.nextUrl.clone();
|
const url = request.nextUrl.clone();
|
||||||
|
|
||||||
if (url.pathname.endsWith(collectEndpoint)) {
|
if (matchesConfiguredPath(url.pathname, collectEndpoint, BASE_PATH)) {
|
||||||
url.pathname = COLLECT_PATH;
|
url.pathname = COLLECT_PATH;
|
||||||
return NextResponse.rewrite(url, { headers: apiHeaders });
|
return NextResponse.rewrite(url, { headers: apiHeaders });
|
||||||
}
|
}
|
||||||
@@ -41,7 +43,7 @@ function customScriptName(request: NextRequest) {
|
|||||||
const url = request.nextUrl.clone();
|
const url = request.nextUrl.clone();
|
||||||
const names = scriptName.split(',').map(name => name.trim().replace(/^\/+/, ''));
|
const names = scriptName.split(',').map(name => name.trim().replace(/^\/+/, ''));
|
||||||
|
|
||||||
if (names.find(name => url.pathname.endsWith(name))) {
|
if (names.find(name => matchesConfiguredPath(url.pathname, name, BASE_PATH))) {
|
||||||
url.pathname = TRACKER_PATH;
|
url.pathname = TRACKER_PATH;
|
||||||
return NextResponse.rewrite(url, { headers: trackerHeaders });
|
return NextResponse.rewrite(url, { headers: trackerHeaders });
|
||||||
}
|
}
|
||||||
@@ -51,7 +53,7 @@ function customScriptName(request: NextRequest) {
|
|||||||
function customScriptUrl(request: NextRequest) {
|
function customScriptUrl(request: NextRequest) {
|
||||||
const scriptUrl = process.env.TRACKER_SCRIPT_URL;
|
const scriptUrl = process.env.TRACKER_SCRIPT_URL;
|
||||||
|
|
||||||
if (scriptUrl && request.nextUrl.pathname.endsWith(TRACKER_PATH)) {
|
if (scriptUrl && matchesConfiguredPath(request.nextUrl.pathname, TRACKER_PATH, BASE_PATH)) {
|
||||||
return NextResponse.rewrite(scriptUrl, { headers: trackerHeaders });
|
return NextResponse.rewrite(scriptUrl, { headers: trackerHeaders });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,7 +61,7 @@ function customScriptUrl(request: NextRequest) {
|
|||||||
function disableLogin(request: NextRequest) {
|
function disableLogin(request: NextRequest) {
|
||||||
const loginDisabled = process.env.DISABLE_LOGIN;
|
const loginDisabled = process.env.DISABLE_LOGIN;
|
||||||
|
|
||||||
if (loginDisabled && request.nextUrl.pathname.endsWith(LOGIN_PATH)) {
|
if (loginDisabled && matchesConfiguredPath(request.nextUrl.pathname, LOGIN_PATH, BASE_PATH)) {
|
||||||
return new NextResponse('Access denied', { status: 403 });
|
return new NextResponse('Access denied', { status: 403 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { matchesConfiguredPath } from '../match-configured-path';
|
||||||
|
|
||||||
|
test('matches the exact configured path', () => {
|
||||||
|
expect(matchesConfiguredPath('/d.js', 'd.js')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not match unrelated asset paths that only share the suffix', () => {
|
||||||
|
expect(matchesConfiguredPath('/_next/static/chunks/app/dashboard.js', 'd.js')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('matches paths under the configured base path', () => {
|
||||||
|
expect(matchesConfiguredPath('/umami/d.js', 'd.js', '/umami')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('normalizes leading slashes in configured paths', () => {
|
||||||
|
expect(matchesConfiguredPath('/script.js', '/script.js')).toBe(true);
|
||||||
|
});
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
function normalizePathname(pathname?: string) {
|
||||||
|
if (!pathname) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return `/${pathname.replace(/^\/+/, '')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeBasePath(basePath?: string) {
|
||||||
|
if (!basePath) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return `/${basePath.replace(/^\/+|\/+$/g, '')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function matchesConfiguredPath(
|
||||||
|
pathname: string,
|
||||||
|
configuredPath?: string,
|
||||||
|
basePath?: string,
|
||||||
|
) {
|
||||||
|
const normalizedPathname = normalizePathname(pathname);
|
||||||
|
const normalizedConfiguredPath = normalizePathname(configuredPath);
|
||||||
|
|
||||||
|
if (!normalizedConfiguredPath) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedPathname === normalizedConfiguredPath) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedBasePath = normalizeBasePath(basePath);
|
||||||
|
|
||||||
|
return normalizedBasePath
|
||||||
|
? normalizedPathname === `${normalizedBasePath}${normalizedConfiguredPath}`
|
||||||
|
: false;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user