Add Prettier and ESLint tooling (Refs #77)
This commit is contained in:
@@ -5,14 +5,27 @@ import { gameEvent } from '../helpers/telemetry.js';
|
||||
|
||||
const token = 'ec_telemetry_0123456789abcdef0123456789abcdef';
|
||||
const otherToken = 'ec_telemetry_fedcba9876543210fedcba9876543210';
|
||||
const request = (values: Record<string, string | undefined> = {}, url = 'http://localhost/api/telemetry') => {
|
||||
const request = (
|
||||
values: Record<string, string | undefined> = {},
|
||||
url = 'http://localhost/api/telemetry'
|
||||
) => {
|
||||
const headers = new Headers({ 'Content-Type': 'application/json' });
|
||||
for (const [key, value] of Object.entries(values)) if (value !== undefined) headers.set(key, value);
|
||||
return new Request(url, { method: 'POST', headers, body: JSON.stringify(gameEvent('heartbeat')) });
|
||||
for (const [key, value] of Object.entries(values))
|
||||
if (value !== undefined) headers.set(key, value);
|
||||
return new Request(url, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(gameEvent('heartbeat'))
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => { vi.stubEnv('EC_TELEMETRY_TOKEN', token); });
|
||||
afterEach(() => { vi.unstubAllEnvs(); vi.restoreAllMocks(); });
|
||||
beforeEach(() => {
|
||||
vi.stubEnv('EC_TELEMETRY_TOKEN', token);
|
||||
});
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('machine telemetry credentials', () => {
|
||||
it.each([
|
||||
@@ -20,14 +33,19 @@ describe('machine telemetry credentials', () => {
|
||||
{ authorization: `bearer ${token}` },
|
||||
{ 'X-EC-Telemetry-Token': token },
|
||||
{ Authorization: `Bearer ${token}`, 'X-EC-Telemetry-Token': token }
|
||||
])('accepts an explicit telemetry secret without a browser session: %j', headers => {
|
||||
])('accepts an explicit telemetry secret without a browser session: %j', (headers) => {
|
||||
expect(authenticateTelemetry(request(headers))).toEqual({ ok: true });
|
||||
});
|
||||
|
||||
it.each([undefined, '', 'too-short', 'x'.repeat(513), 'x'.repeat(40) + ' not-header-safe'])('fails closed when configuration is unusable: %s', configured => {
|
||||
vi.stubEnv('EC_TELEMETRY_TOKEN', configured);
|
||||
expect(authenticateTelemetry(request({ Authorization: `Bearer ${token}` }))).toMatchObject({ ok: false, status: 503 });
|
||||
});
|
||||
it.each([undefined, '', 'too-short', 'x'.repeat(513), 'x'.repeat(40) + ' not-header-safe'])(
|
||||
'fails closed when configuration is unusable: %s',
|
||||
(configured) => {
|
||||
vi.stubEnv('EC_TELEMETRY_TOKEN', configured);
|
||||
expect(
|
||||
authenticateTelemetry(request({ Authorization: `Bearer ${token}` }))
|
||||
).toMatchObject({ ok: false, status: 503 });
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
{},
|
||||
@@ -42,7 +60,7 @@ describe('machine telemetry credentials', () => {
|
||||
{ Authorization: 'Bearer short', 'X-EC-Telemetry-Token': token },
|
||||
{ Authorization: `Bearer ec_host_${'1'.repeat(43)}` },
|
||||
{ Authorization: `Bearer ec_enroll_${'1'.repeat(43)}` }
|
||||
])('rejects missing, wrong, ambiguous or unrelated credentials: %j', headers => {
|
||||
])('rejects missing, wrong, ambiguous or unrelated credentials: %j', (headers) => {
|
||||
const authentication = authenticateTelemetry(request(headers));
|
||||
expect(authentication).toMatchObject({ ok: false, status: 401 });
|
||||
expect(JSON.stringify(authentication)).not.toContain(token);
|
||||
@@ -50,19 +68,37 @@ describe('machine telemetry credentials', () => {
|
||||
});
|
||||
|
||||
it('does not accept tokens placed in query parameters', () => {
|
||||
expect(authenticateTelemetry(request({}, `http://localhost/api/telemetry?token=${token}`))).toMatchObject({ ok: false, status: 401 });
|
||||
expect(
|
||||
authenticateTelemetry(request({}, `http://localhost/api/telemetry?token=${token}`))
|
||||
).toMatchObject({ ok: false, status: 401 });
|
||||
});
|
||||
|
||||
it('retains existing transport behavior for authenticated local machine relays', () => {
|
||||
expect(authenticateTelemetry(request({ 'X-EC-Telemetry-Token': token }, 'http://controller.internal/api/telemetry'))).toEqual({ ok: true });
|
||||
expect(
|
||||
authenticateTelemetry(
|
||||
request(
|
||||
{ 'X-EC-Telemetry-Token': token },
|
||||
'http://controller.internal/api/telemetry'
|
||||
)
|
||||
)
|
||||
).toEqual({ ok: true });
|
||||
});
|
||||
|
||||
it('uses the current configured token after rotation and safely rejects differing token lengths', () => {
|
||||
expect(authenticateTelemetry(request({ Authorization: `Bearer ${token}` }))).toEqual({ ok: true });
|
||||
expect(authenticateTelemetry(request({ Authorization: `Bearer ${token}` }))).toEqual({
|
||||
ok: true
|
||||
});
|
||||
vi.stubEnv('EC_TELEMETRY_TOKEN', otherToken);
|
||||
expect(authenticateTelemetry(request({ Authorization: `Bearer ${token}` }))).toMatchObject({ ok: false, status: 401 });
|
||||
expect(authenticateTelemetry(request({ Authorization: `Bearer ${otherToken}` }))).toEqual({ ok: true });
|
||||
expect(authenticateTelemetry(request({ Authorization: `Bearer ${'x'.repeat(32)}` }))).toMatchObject({ ok: false, status: 401 });
|
||||
expect(authenticateTelemetry(request({ Authorization: `Bearer ${token}` }))).toMatchObject({
|
||||
ok: false,
|
||||
status: 401
|
||||
});
|
||||
expect(authenticateTelemetry(request({ Authorization: `Bearer ${otherToken}` }))).toEqual({
|
||||
ok: true
|
||||
});
|
||||
expect(
|
||||
authenticateTelemetry(request({ Authorization: `Bearer ${'x'.repeat(32)}` }))
|
||||
).toMatchObject({ ok: false, status: 401 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -74,32 +110,40 @@ describe('telemetry endpoint authentication boundary', () => {
|
||||
const { seedServerRegistry } = await import('../../src/lib/server/fleet/servers.js');
|
||||
await seedServerRegistry();
|
||||
});
|
||||
afterEach(async () => { await fixture.cleanup(); });
|
||||
|
||||
it.each([401, 503])('returns %s before parsing or storing untrusted evidence', async status => {
|
||||
if (status === 503) vi.stubEnv('EC_TELEMETRY_TOKEN', undefined);
|
||||
const { POST } = await import('../../src/routes/api/telemetry/+server.js');
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
const { listTelemetry } = await import('../../src/lib/server/fleet/telemetry.js');
|
||||
const { subscribe } = await import('../../src/lib/server/pipeline/bus.js');
|
||||
const incoming = request({ Cookie: 'ec_session=browser-user' });
|
||||
const parse = vi.spyOn(incoming, 'json');
|
||||
const listener = vi.fn();
|
||||
const unsubscribe = subscribe(listener);
|
||||
const response = await POST({ request: incoming });
|
||||
unsubscribe();
|
||||
expect(response.status).toBe(status);
|
||||
expect(response.headers.get('cache-control')).toBe('no-store');
|
||||
expect(parse).not.toHaveBeenCalled();
|
||||
expect(incoming.bodyUsed).toBe(false);
|
||||
expect(listTelemetry()).toEqual([]);
|
||||
expect(db().prepare('SELECT COUNT(*) AS count FROM telemetry_requests').get()).toEqual({ count: 0 });
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
afterEach(async () => {
|
||||
await fixture.cleanup();
|
||||
});
|
||||
|
||||
it.each([401, 503])(
|
||||
'returns %s before parsing or storing untrusted evidence',
|
||||
async (status) => {
|
||||
if (status === 503) vi.stubEnv('EC_TELEMETRY_TOKEN', undefined);
|
||||
const { POST } = await import('../../src/routes/api/telemetry/+server.js');
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
const { listTelemetry } = await import('../../src/lib/server/fleet/telemetry.js');
|
||||
const { subscribe } = await import('../../src/lib/server/pipeline/bus.js');
|
||||
const incoming = request({ Cookie: 'ec_session=browser-user' });
|
||||
const parse = vi.spyOn(incoming, 'json');
|
||||
const listener = vi.fn();
|
||||
const unsubscribe = subscribe(listener);
|
||||
const response = await POST({ request: incoming });
|
||||
unsubscribe();
|
||||
expect(response.status).toBe(status);
|
||||
expect(response.headers.get('cache-control')).toBe('no-store');
|
||||
expect(parse).not.toHaveBeenCalled();
|
||||
expect(incoming.bodyUsed).toBe(false);
|
||||
expect(listTelemetry()).toEqual([]);
|
||||
expect(db().prepare('SELECT COUNT(*) AS count FROM telemetry_requests').get()).toEqual({
|
||||
count: 0
|
||||
});
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
}
|
||||
);
|
||||
|
||||
it('accepts authenticated automation with the unchanged event payload and excludes headers from diagnostics', async () => {
|
||||
const { POST } = await import('../../src/routes/api/telemetry/+server.js');
|
||||
const { listTelemetryRequests } = await import('../../src/lib/server/fleet/telemetry-requests.js');
|
||||
const { listTelemetryRequests } =
|
||||
await import('../../src/lib/server/fleet/telemetry-requests.js');
|
||||
const payload = gameEvent('heartbeat');
|
||||
const response = await POST({ request: request({ 'X-EC-Telemetry-Token': token }) });
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
Reference in New Issue
Block a user