2026-04-21 17:55:14 +02:00
|
|
|
// @ts-nocheck
|
|
|
|
|
import { resolveConfigFetchPolicy, fetchConfigJson } from './secureConfigFetch';
|
|
|
|
|
|
|
|
|
|
describe('secureConfigFetch', () => {
|
|
|
|
|
describe('resolveConfigFetchPolicy', () => {
|
|
|
|
|
it('allows arbitrary origin in unauthenticated environments', () => {
|
|
|
|
|
const result = resolveConfigFetchPolicy('https://untrusted.example.com/config.json', {
|
|
|
|
|
userAuthenticationService: {
|
|
|
|
|
getAuthorizationHeader: () => ({}),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(result.normalizedUrl).toBe('https://untrusted.example.com/config.json');
|
|
|
|
|
expect(result.isAuthenticated).toBe(false);
|
2026-04-27 13:06:06 +02:00
|
|
|
expect(result.isSameOrigin).toBe(false);
|
2026-04-21 17:55:14 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('blocks non-allowlisted origins in authenticated environments', () => {
|
|
|
|
|
expect(() =>
|
|
|
|
|
resolveConfigFetchPolicy('https://untrusted.example.com/config.json', {
|
|
|
|
|
allowedOrigins: ['https://trusted.example.com'],
|
|
|
|
|
userAuthenticationService: {
|
|
|
|
|
getAuthorizationHeader: () => ({ Authorization: 'Bearer token123' }),
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
).toThrow('Blocked remote configuration origin');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('allows allowlisted origin in authenticated environments', () => {
|
|
|
|
|
const result = resolveConfigFetchPolicy('http://localhost:5000/config.json', {
|
|
|
|
|
allowedOrigins: ['http://localhost:5000', 'https://trusted.example.com'],
|
|
|
|
|
userAuthenticationService: {
|
|
|
|
|
getAuthorizationHeader: () => ({ Authorization: 'Bearer token123' }),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(result.normalizedUrl).toBe('http://localhost:5000/config.json');
|
|
|
|
|
expect(result.isAuthenticated).toBe(true);
|
2026-04-27 13:06:06 +02:00
|
|
|
expect(result.isSameOrigin).toBe(false);
|
2026-04-21 17:55:14 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('blocks authenticated fetch when allowlist is missing', () => {
|
|
|
|
|
expect(() =>
|
|
|
|
|
resolveConfigFetchPolicy('https://noTrustList.example.com/config.json', {
|
|
|
|
|
userAuthenticationService: {
|
|
|
|
|
getAuthorizationHeader: () => ({ Authorization: 'Bearer token123' }),
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
).toThrow('Blocked remote configuration origin');
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-27 13:06:06 +02:00
|
|
|
it('allows same-origin in authenticated environments without allowlist', () => {
|
|
|
|
|
const result = resolveConfigFetchPolicy('/protected/config.json', {
|
|
|
|
|
userAuthenticationService: {
|
|
|
|
|
getAuthorizationHeader: () => ({ Authorization: 'Bearer token123' }),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(result.normalizedUrl).toBe(`${window.location.origin}/protected/config.json`);
|
|
|
|
|
expect(result.isAuthenticated).toBe(true);
|
|
|
|
|
expect(result.isSameOrigin).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-21 17:55:14 +02:00
|
|
|
it('rejects embedded userinfo in config URLs', () => {
|
|
|
|
|
expect(() =>
|
|
|
|
|
resolveConfigFetchPolicy('https://user:pass@trusted.example.com/config.json', {
|
|
|
|
|
allowedOrigins: ['https://trusted.example.com'],
|
|
|
|
|
userAuthenticationService: {
|
|
|
|
|
getAuthorizationHeader: () => ({ Authorization: 'Bearer token123' }),
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
).toThrow('URL userinfo is not allowed for dynamic datasource configuration');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('fetchConfigJson', () => {
|
|
|
|
|
const originalFetch = global.fetch;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
global.fetch = jest.fn();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
jest.restoreAllMocks();
|
|
|
|
|
global.fetch = originalFetch;
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-27 13:06:06 +02:00
|
|
|
it('uses hardened fetch options for unauthenticated cross-origin requests', async () => {
|
2026-04-21 17:55:14 +02:00
|
|
|
global.fetch.mockResolvedValue({
|
|
|
|
|
status: 200,
|
|
|
|
|
ok: true,
|
|
|
|
|
json: async () => ({ ok: true }),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await fetchConfigJson({
|
|
|
|
|
normalizedUrl: 'https://example.com/config.json',
|
|
|
|
|
isAuthenticated: false,
|
2026-04-27 13:06:06 +02:00
|
|
|
isSameOrigin: false,
|
2026-04-21 17:55:14 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
|
|
|
'https://example.com/config.json',
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
method: 'GET',
|
|
|
|
|
mode: 'cors',
|
2026-04-29 17:57:33 +02:00
|
|
|
credentials: 'same-origin',
|
2026-04-21 17:55:14 +02:00
|
|
|
redirect: 'error',
|
|
|
|
|
referrerPolicy: 'no-referrer',
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-29 17:57:33 +02:00
|
|
|
it('uses hardened fetch options for unauthenticated same-origin requests', async () => {
|
2026-04-27 13:06:06 +02:00
|
|
|
global.fetch.mockResolvedValue({
|
|
|
|
|
status: 200,
|
|
|
|
|
ok: true,
|
|
|
|
|
json: async () => ({ ok: true }),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await fetchConfigJson({
|
|
|
|
|
normalizedUrl: `${window.location.origin}/protected/config.json`,
|
|
|
|
|
isAuthenticated: false,
|
|
|
|
|
isSameOrigin: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
2026-04-29 17:57:33 +02:00
|
|
|
`${window.location.origin}/protected/config.json`,
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
method: 'GET',
|
|
|
|
|
mode: 'cors',
|
|
|
|
|
credentials: 'same-origin',
|
|
|
|
|
redirect: 'error',
|
|
|
|
|
referrerPolicy: 'no-referrer',
|
|
|
|
|
})
|
2026-04-27 13:06:06 +02:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-29 17:57:33 +02:00
|
|
|
it('uses hardened fetch options in authenticated environments', async () => {
|
2026-04-21 17:55:14 +02:00
|
|
|
global.fetch.mockResolvedValue({
|
|
|
|
|
status: 200,
|
|
|
|
|
ok: true,
|
|
|
|
|
json: async () => ({ ok: true }),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await fetchConfigJson({
|
|
|
|
|
normalizedUrl: 'https://trusted.example.com/config.json',
|
|
|
|
|
isAuthenticated: true,
|
2026-04-27 13:06:06 +02:00
|
|
|
isSameOrigin: false,
|
2026-04-21 17:55:14 +02:00
|
|
|
});
|
|
|
|
|
|
2026-04-29 17:57:33 +02:00
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
|
|
|
'https://trusted.example.com/config.json',
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
method: 'GET',
|
|
|
|
|
mode: 'cors',
|
|
|
|
|
credentials: 'same-origin',
|
|
|
|
|
redirect: 'error',
|
|
|
|
|
referrerPolicy: 'no-referrer',
|
|
|
|
|
})
|
|
|
|
|
);
|
2026-04-21 17:55:14 +02:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|