Files
wehub-resource-sync 070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:00:47 +08:00

118 lines
4.4 KiB
TypeScript

// Plan §3.K1 / spec §15.7 — bound-API-token guard.
//
// Two halves:
// 1. The daemon refuses to start with OD_BIND_HOST=0.0.0.0 when no
// OD_API_TOKEN is set.
// 2. When OD_API_TOKEN is set, every /api/* request from a non-loopback
// peer must carry `Authorization: Bearer <OD_API_TOKEN>`. The
// health/readiness/version probes stay open for monitoring.
//
// Tests force the bearer-required code path by stamping the env vars
// before startServer. The daemon listens on 127.0.0.1 throughout (so
// the "refuse 0.0.0.0 without token" path is exercised by a separate
// negative case that constructs the start call directly).
import type http from 'node:http';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { isApiAuthDisabled, isApiTokenMiddlewareEnabled } from '../src/api-token-auth.js';
import { startServer } from '../src/server.js';
const PREVIOUS_TOKEN = process.env.OD_API_TOKEN;
const PREVIOUS_HOST = process.env.OD_BIND_HOST;
const PREVIOUS_DISABLE_API_AUTH = process.env.OD_DISABLE_API_AUTH;
let server: http.Server | undefined;
let baseUrl = '';
let shutdown: (() => Promise<void> | void) | undefined;
afterEach(async () => {
if (shutdown) await Promise.resolve(shutdown());
if (server) await new Promise<void>((resolve) => server!.close(() => resolve()));
server = undefined;
shutdown = undefined;
if (PREVIOUS_TOKEN === undefined) delete process.env.OD_API_TOKEN;
else process.env.OD_API_TOKEN = PREVIOUS_TOKEN;
if (PREVIOUS_HOST === undefined) delete process.env.OD_BIND_HOST;
else process.env.OD_BIND_HOST = PREVIOUS_HOST;
if (PREVIOUS_DISABLE_API_AUTH === undefined) delete process.env.OD_DISABLE_API_AUTH;
else process.env.OD_DISABLE_API_AUTH = PREVIOUS_DISABLE_API_AUTH;
});
describe('bound-API-token guard', () => {
it('refuses to start with OD_BIND_HOST=0.0.0.0 when OD_API_TOKEN is unset', async () => {
delete process.env.OD_API_TOKEN;
await expect(startServer({ port: 0, host: '0.0.0.0', returnServer: true }))
.rejects.toThrow(/OD_API_TOKEN/);
});
it('starts on a public host when OD_API_TOKEN is set', async () => {
process.env.OD_API_TOKEN = 'test-token-abc';
// Bind to 127.0.0.1 (loopback) but pretend we crossed the guard
// by setting the env var; the assertion is that startup succeeds.
const started = (await startServer({ port: 0, host: '127.0.0.1', returnServer: true })) as {
url: string;
server: http.Server;
shutdown?: () => Promise<void> | void;
};
server = started.server;
shutdown = started.shutdown;
baseUrl = started.url;
expect(baseUrl).toMatch(/^http:\/\/127\.0\.0\.1:/);
});
it('starts on a public host without OD_API_TOKEN when OD_DISABLE_API_AUTH=1', async () => {
delete process.env.OD_API_TOKEN;
process.env.OD_DISABLE_API_AUTH = '1';
const started = (await startServer({ port: 0, host: '0.0.0.0', returnServer: true })) as {
server: http.Server;
shutdown?: () => Promise<void> | void;
};
server = started.server;
shutdown = started.shutdown;
});
});
describe('bearer middleware', () => {
beforeEach(async () => {
process.env.OD_API_TOKEN = 'secret-test-token';
const started = (await startServer({ port: 0, host: '127.0.0.1', returnServer: true })) as {
url: string;
server: http.Server;
shutdown?: () => Promise<void> | void;
};
baseUrl = started.url;
server = started.server;
shutdown = started.shutdown;
});
it('accepts loopback callers without a bearer (desktop UI flow)', async () => {
// The HTTP test client is on the same machine → req.socket.remoteAddress
// is 127.0.0.1 → middleware short-circuits.
const resp = await fetch(`${baseUrl}/api/plugins`);
expect(resp.status).toBe(200);
});
it('keeps health / readiness / version probes open without a bearer', async () => {
for (const path of ['/api/health', '/api/ready', '/api/version']) {
const resp = await fetch(`${baseUrl}${path}`);
expect(resp.status).toBe(200);
}
});
it('disables bearer middleware when OD_DISABLE_API_AUTH=1 even if OD_API_TOKEN is set', () => {
expect(
isApiTokenMiddlewareEnabled({
...process.env,
OD_API_TOKEN: 'secret-test-token',
OD_DISABLE_API_AUTH: '1',
}),
).toBe(false);
expect(
isApiAuthDisabled({
...process.env,
OD_DISABLE_API_AUTH: '1',
}),
).toBe(true);
});
});