fix: session cookie not stored for relative JMAP server URLs (dev mock logouts)

This commit is contained in:
Linus Rath
2026-08-20 14:42:29 +02:00
parent 2d5748e6c9
commit b9fe961ae8
2 changed files with 37 additions and 0 deletions
+28
View File
@@ -173,3 +173,31 @@ describe('verifyJmapAuth SSRF protection', () => {
expect(fetchSpy).not.toHaveBeenCalled();
});
});
describe('normalizeJmapServerUrl', () => {
async function load() {
return import('@/lib/auth/verify-jmap-auth');
}
it('accepts an app-relative URL (dev mock JMAP server)', async () => {
const { normalizeJmapServerUrl } = await load();
expect(normalizeJmapServerUrl('/api/dev-jmap')).toBe('/api/dev-jmap');
});
it('strips query, hash, and trailing slashes from an app-relative URL', async () => {
const { normalizeJmapServerUrl } = await load();
expect(normalizeJmapServerUrl('/api/dev-jmap/?x=1#frag')).toBe('/api/dev-jmap');
});
it('rejects protocol-relative URLs', async () => {
const { normalizeJmapServerUrl } = await load();
expect(() => normalizeJmapServerUrl('//evil.example.com/api')).toThrow('Invalid server URL');
});
it('normalizes absolute URLs unchanged', async () => {
const { normalizeJmapServerUrl } = await load();
expect(normalizeJmapServerUrl('https://mail.example.com/jmap/?a=b')).toBe(
'https://mail.example.com/jmap',
);
});
});
+9
View File
@@ -18,6 +18,15 @@ function isSupportedProtocol(protocol: string): boolean {
}
export function normalizeJmapServerUrl(serverUrl: string): string {
// App-relative URLs (the dev mock JMAP server, e.g. /api/dev-jmap) have no
// host or protocol to validate; they can only ever target this app itself.
// Protocol-relative URLs (//host/...) are NOT app-relative and fall through
// to the absolute-URL parse below, which rejects them.
if (serverUrl.startsWith('/') && !serverUrl.startsWith('//')) {
const url = new URL(serverUrl, 'http://relative.invalid');
return url.pathname.replace(/\/+$/, '');
}
let url: URL;
try {
url = new URL(serverUrl);