test(runtime): verify live same-origin canary

This commit is contained in:
chick
2026-07-18 15:45:57 +08:00
parent f89f393485
commit 3718c91706
7 changed files with 185 additions and 6 deletions
+8
View File
@@ -179,6 +179,14 @@ describe('same-origin canary gateway', () => {
expect(route.headers.get('content-type')).toMatch(/^text\/html/);
expect(await route.text()).toContain('canary');
const favicon = await fetch(`${origin}/favicon.ico`);
expect(favicon.status).toBe(204);
expect(await favicon.text()).toBe('');
const unknownFile = await fetch(`${origin}/missing.ico`);
expect(unknownFile.status).toBe(404);
expect(await unknownFile.text()).not.toContain('canary');
const unknownApi = await fetch(`${origin}/api/not-real`);
expect(unknownApi.status).toBe(404);
expect(await unknownApi.text()).not.toContain('canary');
+8
View File
@@ -256,6 +256,14 @@ export function createCanaryGateway(options: CanaryGatewayOptions): CanaryGatewa
return sendText(response, 405, 'Method Not Allowed');
}
// Browsers request this implicitly. Avoid a noisy console 404 without routing
// this file-like path through the SPA fallback.
if (pathname === '/favicon.ico') {
response.writeHead(204, { 'cache-control': 'no-cache' });
response.end();
return;
}
const distRoot = await realpath(resolve(options.distDir)).catch(() => resolve(options.distDir));
if (pathname !== '/' && (await serveFile(request, response, distRoot, pathname))) return;
if (
@@ -0,0 +1,73 @@
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, describe, expect, it } from 'vitest';
import {
buildProductionControlPlane,
type ProductionControlPlaneOptions,
} from './production-control-plane.js';
import { GATEWAY_AUTH_HEADER } from './runtime-config.js';
const cleanup: Array<() => Promise<void>> = [];
const gatewayToken = 'production-gateway-token-with-at-least-32-bytes';
afterEach(async () => {
await Promise.all(cleanup.splice(0).map((fn) => fn()));
});
async function fixtureOptions(): Promise<ProductionControlPlaneOptions> {
const directory = await mkdtemp(join(tmpdir(), 'production-control-plane-'));
cleanup.push(() => rm(directory, { recursive: true, force: true }));
return {
databasePath: join(directory, 'db.sqlite'),
gatewayToken,
store: {
set: async () => '',
get: async () => undefined,
delete: async () => false,
},
upstream: {
get: async () => ({ status: 200, headers: {}, body: '' }),
request: async () => ({ status: 200, headers: {}, body: '' }),
postNetworkRegisterAuto: async () => ({ status: 200 }),
},
keychainMetadataCheck: async () => true,
};
}
describe('production control-plane authentication composition', () => {
it('keeps the event route fail-closed when trusted-gateway auth is not configured', async () => {
const options = await fixtureOptions();
const app = buildProductionControlPlane({
databasePath: options.databasePath,
store: options.store!,
upstream: options.upstream!,
keychainMetadataCheck: options.keychainMetadataCheck!,
});
cleanup.push(() => app.close());
const response = await app.inject({ method: 'GET', url: '/api/v1/events' });
expect(response.statusCode).toBe(401);
expect(response.json()).toMatchObject({ code: 'UNAUTHORIZED' });
});
it('opens SSE only after mandatory trusted-gateway authentication succeeds', async () => {
const app = buildProductionControlPlane(await fixtureOptions());
cleanup.push(() => app.close());
const direct = await app.inject({ method: 'GET', url: '/api/v1/events' });
expect(direct.statusCode).toBe(401);
expect(direct.json()).toMatchObject({ code: 'GATEWAY_AUTH_REQUIRED' });
await app.listen({ host: '127.0.0.1', port: 0 });
const address = app.server.address();
if (!address || typeof address === 'string') throw new Error('Expected TCP server address');
const response = await fetch(`http://127.0.0.1:${address.port}/api/v1/events`, {
headers: { [GATEWAY_AUTH_HEADER]: gatewayToken },
});
expect(response.status).toBe(200);
expect(response.headers.get('content-type')).toMatch(/^text\/event-stream/);
await response.body?.cancel();
});
});
+4
View File
@@ -37,6 +37,10 @@ export function buildProductionControlPlane(
db,
store,
upstream: options.upstream ?? createProductionUpstream(),
// This route-local decision is safe only in this composition: when a token is
// configured, buildApp's global onRequest hook rejects the request first.
// Keep all other control-plane compositions fail-closed by default.
...(options.gatewayToken ? { authenticateEventStream: () => true } : {}),
app: {
logger: {},
...(options.gatewayToken ? { gatewayToken: options.gatewayToken } : {}),