Cover AI and SAR proxy auth routes

This commit is contained in:
BigBodyCobain
2026-05-29 08:15:06 -06:00
parent 41e35e4da2
commit a16f22ed34
2 changed files with 98 additions and 2 deletions
@@ -7,7 +7,7 @@
* - /api/tools/* (Sprint 1C addition)
* - /api/wormhole/* (pre-existing, regression)
* - /api/settings/* (pre-existing, regression)
* - /api/layers, /api/ais/feed, /api/ai/agent-actions
* - /api/layers, /api/ais/feed, /api/ai/*, /api/sar/mode-b/*
*
* Also verifies that:
* - non-sensitive mesh paths (e.g. mesh/events) do NOT receive injected key
@@ -344,6 +344,99 @@ describe('proxy admin-key injection coverage', () => {
expect(capturedHeaders(fetchMock).get('X-Admin-Key')).toBe(ADMIN_KEY);
});
it('GET /api/ai/pins with valid session injects X-Admin-Key', async () => {
const cookie = await mintSession(ADMIN_KEY);
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ ok: true, pins: [] }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}),
);
vi.stubGlobal('fetch', fetchMock);
const req = new NextRequest('http://localhost/api/ai/pins?limit=500', {
method: 'GET',
headers: { cookie },
});
const res = await proxyGet(req, {
params: Promise.resolve({ path: ['ai', 'pins'] }),
});
expect(res.status).toBe(200);
expect(capturedHeaders(fetchMock).get('X-Admin-Key')).toBe(ADMIN_KEY);
});
it('GET /api/ai/layers with valid session injects X-Admin-Key', async () => {
const cookie = await mintSession(ADMIN_KEY);
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ ok: true, layers: [] }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}),
);
vi.stubGlobal('fetch', fetchMock);
const req = new NextRequest('http://localhost/api/ai/layers', {
method: 'GET',
headers: { cookie },
});
const res = await proxyGet(req, {
params: Promise.resolve({ path: ['ai', 'layers'] }),
});
expect(res.status).toBe(200);
expect(capturedHeaders(fetchMock).get('X-Admin-Key')).toBe(ADMIN_KEY);
});
it('GET /api/ai/timemachine/config with valid session injects X-Admin-Key', async () => {
const cookie = await mintSession(ADMIN_KEY);
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ ok: true, config: {} }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}),
);
vi.stubGlobal('fetch', fetchMock);
const req = new NextRequest('http://localhost/api/ai/timemachine/config', {
method: 'GET',
headers: { cookie },
});
const res = await proxyGet(req, {
params: Promise.resolve({ path: ['ai', 'timemachine', 'config'] }),
});
expect(res.status).toBe(200);
expect(capturedHeaders(fetchMock).get('X-Admin-Key')).toBe(ADMIN_KEY);
});
it('POST /api/sar/mode-b/enable with valid session injects X-Admin-Key', async () => {
const cookie = await mintSession(ADMIN_KEY);
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}),
);
vi.stubGlobal('fetch', fetchMock);
const req = new NextRequest('http://localhost/api/sar/mode-b/enable', {
method: 'POST',
body: JSON.stringify({ earthdata_user: 'operator', earthdata_token: 'token' }),
headers: { cookie, 'Content-Type': 'application/json' },
});
const res = await proxyPost(req, {
params: Promise.resolve({ path: ['sar', 'mode-b', 'enable'] }),
});
expect(res.status).toBe(200);
expect(capturedHeaders(fetchMock).get('X-Admin-Key')).toBe(ADMIN_KEY);
});
// -------------------------------------------------------------------------
// Non-sensitive mesh paths must NOT receive injected admin key
// -------------------------------------------------------------------------
+4 -1
View File
@@ -66,7 +66,10 @@ function isSensitiveProxyPath(pathSegments: string[]): boolean {
if (joined === 'system/update') return true;
if (joined === 'layers') return true;
if (joined === 'ais/feed') return true;
if (joined === 'ai/agent-actions') return true;
if (pathSegments[0] === 'ai') return true;
if (pathSegments[0] === 'sar' && (pathSegments[1] === 'mode-b' || pathSegments[1] === 'aois')) {
return true;
}
if (pathSegments[0] === 'settings') return true;
if (joined === 'mesh/infonet/ingest') return true;
if (joined === 'mesh/meshtastic/send') return true;