feat: multi-server JMAP support

This commit is contained in:
Linus Rath
2026-05-06 17:33:55 +02:00
parent 43475945bf
commit d3d79be64c
20 changed files with 818 additions and 108 deletions
+10 -6
View File
@@ -12,6 +12,7 @@ import {
import { configManager } from '@/lib/admin/config-manager';
import { isPublicHttpUrl } from '@/lib/security/url-guard';
import { recordLogin } from '@/lib/telemetry/login-tracker';
import { parseJmapServers, resolveTrustedJmapUrl } from '@/lib/admin/jmap-servers';
const COOKIE_OPTIONS = {
...getCookieOptions(),
@@ -39,10 +40,11 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
}
// Pin the upstream URL to the configured JMAP server so an unauthenticated
// caller cannot point this route at internal hosts. Only when no server URL
// is configured AND the deployment explicitly allows custom JMAP endpoints
// do we honor the body URL — and even then it must be a public URL.
// Pin the upstream URL to a configured JMAP server so an unauthenticated
// caller cannot point this route at internal hosts. We accept the global
// `jmapServerUrl` and any entry from `jmapServers`. When neither matches,
// we fall back to the request URL only if `allowCustomJmapEndpoint` is on
// — and even then the URL must resolve to a public address.
await configManager.ensureLoaded();
const configuredServerUrl =
configManager.get<string>('jmapServerUrl', '') ||
@@ -50,11 +52,13 @@ export async function POST(request: NextRequest) {
process.env.NEXT_PUBLIC_JMAP_SERVER_URL ||
'';
const allowCustomEndpoint = configManager.get<boolean>('allowCustomJmapEndpoint', false);
const serverList = parseJmapServers(configManager.get<unknown>('jmapServers', []));
const trustedUrl = resolveTrustedJmapUrl(serverUrl, configuredServerUrl, serverList);
let upstreamUrl: string;
let upstreamTrusted: boolean;
if (configuredServerUrl) {
upstreamUrl = configuredServerUrl;
if (trustedUrl) {
upstreamUrl = trustedUrl;
upstreamTrusted = true;
} else if (allowCustomEndpoint) {
if (!(await isPublicHttpUrl(serverUrl))) {