mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 16:13:15 +00:00
75 lines
2.6 KiB
TypeScript
75 lines
2.6 KiB
TypeScript
import { afterEach, beforeEach, describe, it, expect, vi } from 'vitest';
|
|
import type { InstalledPlugin } from '../plugin-types';
|
|
import { useAuthStore } from '@/stores/auth-store';
|
|
|
|
vi.mock('@/stores/toast-store', () => ({
|
|
toast: { success: vi.fn(), error: vi.fn(), info: vi.fn(), warning: vi.fn() },
|
|
}));
|
|
|
|
import { dispatchApiCall } from '../plugin-sandbox/host-api';
|
|
|
|
// http.post attaches the user's Authorization header to a same-origin path
|
|
// the plugin's own manifest allowlists. A manifest listing the JMAP
|
|
// passthrough or /api/admin/ got full JMAP access (or, with an admin cookie
|
|
// present, the admin API) out of a plain http:post grant.
|
|
|
|
function plugin(apiPostPaths: string[]): InstalledPlugin {
|
|
return {
|
|
id: 'p1',
|
|
name: 'Test plugin',
|
|
version: '1.0.0',
|
|
author: 'test',
|
|
description: '',
|
|
type: 'hook',
|
|
permissions: ['http:post'],
|
|
grantedPermissions: ['http:post'],
|
|
entrypoint: 'index.js',
|
|
enabled: true,
|
|
status: 'running',
|
|
settings: {},
|
|
apiPostPaths,
|
|
};
|
|
}
|
|
|
|
const fetchSpy = vi.fn();
|
|
|
|
beforeEach(() => {
|
|
fetchSpy.mockReset().mockResolvedValue(new Response('{}', { status: 200 }));
|
|
vi.stubGlobal('fetch', fetchSpy);
|
|
useAuthStore.setState({
|
|
client: { getAuthHeader: () => 'Basic dGVzdA==', getUsername: () => 'tester' } as never,
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals();
|
|
useAuthStore.setState({ client: null });
|
|
});
|
|
|
|
describe('plugin http.post denied paths', () => {
|
|
it.each([
|
|
['/api/account/stalwart/jmap', ['/api/account/stalwart/jmap']],
|
|
['/api/admin/config', ['/api/admin/']],
|
|
['/api/settings', ['/api/settings']],
|
|
['/api/wopi', ['/api/wopi']],
|
|
['/api/auth/session', ['/api/']],
|
|
['/api/%61dmin/config', ['/api/%61dmin/']],
|
|
['/api//admin/config', ['/api/']],
|
|
])('refuses %s even when the manifest lists it', async (path, allow) => {
|
|
await expect(dispatchApiCall(plugin(allow), 'http.post', [path, {}])).rejects.toThrow(/not available to plugins/);
|
|
expect(fetchSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('still reaches a plugin sidecar route', async () => {
|
|
// /api/translate used to be the example here; it is a third-party relay
|
|
// with the user's cookies and is denied now like the other app routes.
|
|
await dispatchApiCall(plugin(['/api/my-plugin/translate']), 'http.post', ['/api/my-plugin/translate', { text: 'hi' }]);
|
|
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('reaches a route that only shares a prefix with a denied one', async () => {
|
|
await dispatchApiCall(plugin(['/api/admin-helper']), 'http.post', ['/api/admin-helper', {}]);
|
|
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|