Files
webmail/lib/__tests__/proxy-loopback-origin.test.ts
T

77 lines
2.8 KiB
TypeScript

/**
* @vitest-environment node
*
* On HOSTNAME=127.0.0.1, Next renames the host in request.nextUrl to
* "localhost" while its router keeps 127.0.0.1. A proxy rewrite or redirect
* built from nextUrl then points at another origin: rewrites were proxied
* back to the server itself (over TLS when X-Forwarded-Proto said https, an
* EPROTO 500), and with skipProxyUrlNormalize redirects would send the
* browser to localhost (#1046, #1487).
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { NextRequest } from 'next/server';
let setupState: 'configured' | 'bootstrap' = 'configured';
vi.mock('@/lib/admin/config-manager', () => ({
configManager: {
ensureLoaded: vi.fn(async () => {}),
get: vi.fn((_key: string, fallback: unknown) => fallback),
getPolicy: vi.fn(() => ({ features: {} })),
},
}));
vi.mock('@/lib/setup/state', () => ({ detectSetupState: vi.fn(() => setupState) }));
vi.mock('@/lib/admin/csp-frame-origins', () => ({ getEnabledPluginFrameOrigins: vi.fn(async () => []) }));
vi.mock('next-intl/middleware', async () => {
const { NextResponse } = await import('next/server');
return { default: () => () => NextResponse.next() };
});
import { proxy } from '@/proxy';
beforeEach(() => {
// What the build inlines for skipProxyUrlNormalize: true. NextRequest then
// keeps request.url as given instead of the renamed nextUrl.
vi.stubEnv('__NEXT_NO_MIDDLEWARE_URL_NORMALIZE', 'true');
});
afterEach(() => {
setupState = 'configured';
vi.unstubAllEnvs();
vi.resetModules();
});
describe('proxy redirects on a loopback listen address', () => {
it('sends a configured install from /setup to /admin/login on the same origin', async () => {
const response = await proxy(new NextRequest('https://127.0.0.1:3000/setup'));
expect(response.status).toBe(307);
expect(response.headers.get('location')).toBe('https://127.0.0.1:3000/admin/login');
});
it('sends every page to /setup on the same origin while the wizard runs, keeping the query', async () => {
setupState = 'bootstrap';
const response = await proxy(new NextRequest('http://[::1]:3000/mail?x=1'));
expect(response.status).toBe(307);
expect(response.headers.get('location')).toBe('http://[::1]:3000/setup?x=1');
});
it('keeps the base path of a sub-path install', async () => {
const request = new NextRequest('https://127.0.0.1:3000/webmail/setup', {
nextConfig: { basePath: '/webmail' },
});
const response = await proxy(request);
expect(response.headers.get('location')).toBe('https://127.0.0.1:3000/webmail/admin/login');
});
});
describe('next.config', () => {
it('leaves proxy rewrites on the URL the router built', async () => {
vi.resetModules();
const { default: config } = await import('../../next.config');
expect(config.skipProxyUrlNormalize).toBe(true);
});
});