Allow private HTTP core URLs (#1765)

This commit is contained in:
Srinivas Vaddi
2026-05-14 21:24:30 -07:00
committed by GitHub
parent 1e169bd588
commit cd911ab18d
13 changed files with 228 additions and 22 deletions
@@ -24,6 +24,7 @@ import { useAppDispatch, useAppSelector } from '../../store/hooks';
import {
clearStoredCoreMode,
clearStoredCoreToken,
isLocalOrPrivateNetworkHost,
storeCoreMode,
storeCoreToken,
storeRpcUrl,
@@ -116,6 +117,12 @@ function ModePicker({ onConfirm }: PickerProps) {
setUrlError('URL must start with http:// or https://');
return null;
}
if (parsed.protocol === 'http:' && !isLocalOrPrivateNetworkHost(parsed.hostname)) {
setUrlError(
'HTTP core URLs are only allowed for localhost or private network hosts. Use HTTPS for public hosts.'
);
return null;
}
} catch {
setUrlError('Please enter a valid URL (e.g. https://core.example.com/rpc)');
return null;
@@ -39,14 +39,17 @@ vi.mock('../../../services/coreRpcClient', () => ({
testCoreRpcConnection: (...args: unknown[]) => mockTestCoreRpcConnection(...args),
}));
vi.mock('../../../utils/configPersistence', () => ({
storeRpcUrl: vi.fn(),
storeCoreToken: vi.fn(),
clearStoredCoreToken: vi.fn(),
storeCoreMode: vi.fn(),
clearStoredCoreMode: vi.fn(),
isValidRpcUrl: vi.fn().mockReturnValue(true),
}));
vi.mock('../../../utils/configPersistence', async importOriginal => {
const actual = await importOriginal<typeof import('../../../utils/configPersistence')>();
return {
...actual,
storeRpcUrl: vi.fn(),
storeCoreToken: vi.fn(),
clearStoredCoreToken: vi.fn(),
storeCoreMode: vi.fn(),
clearStoredCoreMode: vi.fn(),
};
});
// ---------------------------------------------------------------------------
// Store factory
@@ -157,6 +160,43 @@ describe('BootCheckGate — picker (unset mode)', () => {
expect(screen.getByText(/Please enter the core auth token/i)).toBeInTheDocument();
});
it('accepts a Tailscale HTTP core URL in cloud mode', async () => {
mockRunBootCheck.mockResolvedValue({ kind: 'match' });
renderGate();
fireEvent.click(screen.getByText('Cloud'));
fireEvent.change(screen.getByPlaceholderText(/https:\/\/core\.example\.com/), {
target: { value: 'http://100.116.244.64:7788/rpc' },
});
fireEvent.change(screen.getByPlaceholderText(/Bearer token/i), {
target: { value: 'tok-1234' },
});
fireEvent.click(screen.getByRole('button', { name: 'Continue' }));
await waitFor(() => {
expect(screen.getByTestId('app-content')).toBeInTheDocument();
});
expect(mockRunBootCheck).toHaveBeenCalledWith(
expect.objectContaining({
kind: 'cloud',
url: 'http://100.116.244.64:7788/rpc',
token: 'tok-1234',
}),
expect.any(Object)
);
});
it('rejects public HTTP cloud URLs', () => {
renderGate();
fireEvent.click(screen.getByText('Cloud'));
const urlInput = screen.getByPlaceholderText(/https:\/\/core\.example\.com/);
fireEvent.change(urlInput, { target: { value: 'http://core.example.com/rpc' } });
fireEvent.click(screen.getByRole('button', { name: 'Continue' }));
expect(screen.getByText(/HTTP core URLs are only allowed/)).toBeInTheDocument();
});
it('clears the token error as soon as the user types into the token field', () => {
renderGate();