Files
profilarr/tests/integration/auth/specs/apiKey.test.ts
2026-04-09 05:47:20 +09:30

89 lines
2.8 KiB
TypeScript

/**
* Integration tests: API key authentication
*
* Tests:
* 1. Valid API key → 200
* 2. Invalid API key → 401
* 3. No auth → 401
* 4. API key in query param → 401 (only header accepted)
* 5. API key rejected for browser pages (scoped to /api/ only)
* 6. API key rejected for form actions (scoped to /api/ only)
*/
import { assertEquals } from '@std/assert';
import { TestClient } from '$test-harness/client.ts';
import { startServer, stopServer, getDbPath } from '$test-harness/server.ts';
import { createUser, setApiKey } from '$test-harness/setup.ts';
import { setup, teardown, test, run } from '$test-harness/runner.ts';
const PORT = 7004;
const ORIGIN = `http://localhost:${PORT}`;
const API_KEY = 'test-api-key-valid-12345';
let client: TestClient;
setup(async () => {
await startServer(PORT, { AUTH: 'on', ORIGIN }, 'preview');
client = new TestClient(ORIGIN);
await createUser(client, 'admin', 'password123', ORIGIN);
await setApiKey(getDbPath(PORT), API_KEY);
});
teardown(async () => {
await stopServer(PORT);
});
test('valid API key returns 200', async () => {
const c = new TestClient(ORIGIN);
const res = await c.get('/api/v1/status', {
headers: { 'X-Api-Key': API_KEY }
});
assertEquals(res.status, 200);
});
test('invalid API key returns 401', async () => {
const c = new TestClient(ORIGIN);
const res = await c.get('/api/v1/status', {
headers: { 'X-Api-Key': 'wrong-key' }
});
assertEquals(res.status, 401);
});
test('no auth returns 401', async () => {
const unauthClient = new TestClient(ORIGIN);
const res = await unauthClient.get('/api/v1/status');
assertEquals(res.status, 401);
});
test('API key in query param returns 401', async () => {
const unauthClient = new TestClient(ORIGIN);
const res = await unauthClient.get(`/api/v1/status?apikey=${API_KEY}`);
assertEquals(res.status, 401);
});
// --- API key scoping: only /api/ paths, not browser pages or form actions ---
test('API key rejected for browser pages', async () => {
// API key auth is scoped to /api/ paths only.
// Non-API paths should get 403 when using API key without a session.
const c = new TestClient(ORIGIN);
const res = await c.get('/databases', {
headers: { 'X-Api-Key': API_KEY }
});
assertEquals(res.status, 403, `Expected 403 for browser page with API key, got ${res.status}`);
});
test('API key rejected for form actions', async () => {
// API key should not be able to invoke settings form actions.
// regenerateApiKey is particularly dangerous — lets an API key holder lock out the admin.
const c = new TestClient(ORIGIN);
const res = await c.postForm(
'/settings/security?/regenerateApiKey',
{},
{ headers: { Origin: ORIGIN, 'X-Api-Key': API_KEY } }
);
assertEquals(res.status, 403, `Expected 403 for form action with API key, got ${res.status}`);
});
await run();