Remove v2API key
Build and Push to Digital Ocean Container Registry / build-and-push (push) Failing after 2s
Build and Push to Digital Ocean Container Registry / migrate (push) Skipped

This commit is contained in:
NeilClack
2026-08-17 21:08:50 -05:00
parent 7211beef69
commit 0753cfaa56
2 changed files with 110 additions and 98 deletions
+83 -72
View File
@@ -129,79 +129,90 @@ describe('hooks.server handle', () => {
);
});
describe('v2 auth gate', () => {
it('returns 401 for v2 routes without an Authorization header', async () => {
const event = createMockEvent('/api/v2/proclamations');
const res = await runHandle(event);
it('does not apply API key gate to non-API routes', async () => {
getSession.mockResolvedValue(null);
expect(res.status).toBe(401);
expect(await res.json()).toEqual({ error: 'API key required' });
expect(validateApiKeyMock).not.toHaveBeenCalled();
expect(event.resolve).not.toHaveBeenCalled();
});
const event = createMockEvent('/dashboard');
await runHandle(event);
it('returns 401 for v2 routes with an invalid bearer key', async () => {
validateApiKeyMock.mockResolvedValue(null);
const event = createMockEvent('/api/v2/proclamations');
event.request = new Request('http://localhost/api/v2/proclamations', {
headers: { authorization: 'Bearer sk_invalid' }
});
const res = await runHandle(event);
expect(res.status).toBe(401);
expect(await res.json()).toEqual({ error: 'Invalid or expired API key' });
expect(validateApiKeyMock).toHaveBeenCalledWith('sk_invalid');
expect(event.resolve).not.toHaveBeenCalled();
});
it('resolves v2 routes with a valid bearer key and sets locals.apiKey', async () => {
validateApiKeyMock.mockResolvedValue({ id: 'key-1', keyPrefix: 'sk_abc123' });
const event = createMockEvent('/api/v2/proclamations');
event.request = new Request('http://localhost/api/v2/proclamations', {
headers: { authorization: 'Bearer sk_abc123def' }
});
await runHandle(event);
expect(event.resolve).toHaveBeenCalled();
expect(event.locals.apiKey).toEqual({ id: 'key-1', keyPrefix: 'sk_abc123' });
expect(validateApiKeyMock).toHaveBeenCalledWith('sk_abc123def');
});
it('always validates the v2 API key even when API_KEYS_ACTIVE would bypass v1', async () => {
envMock.API_KEYS_ACTIVE = 'false';
validateApiKeyMock.mockResolvedValue(null);
const event = createMockEvent('/api/v2/frequencies');
event.request = new Request('http://localhost/api/v2/frequencies', {
headers: { authorization: 'Bearer sk_abc' }
});
const res = await runHandle(event);
expect(res.status).toBe(401);
expect(await res.json()).toEqual({ error: 'Invalid or expired API key' });
expect(validateApiKeyMock).toHaveBeenCalledWith('sk_abc');
});
it('does not require an API key for signed v2 onboarding routes', async () => {
getSession.mockResolvedValue(null);
const event = createMockEvent('/api/v2/automations/create/account');
await runHandle(event);
expect(validateApiKeyMock).not.toHaveBeenCalled();
expect(svelteKitHandler).toHaveBeenCalled();
});
it('does not apply the v2 gate to non-API routes', async () => {
getSession.mockResolvedValue(null);
const event = createMockEvent('/dashboard');
await runHandle(event);
expect(validateApiKeyMock).not.toHaveBeenCalled();
expect(svelteKitHandler).toHaveBeenCalled();
});
expect(validateApiKeyMock).not.toHaveBeenCalled();
expect(svelteKitHandler).toHaveBeenCalled();
});
// TODO: re-enable when v2 API key auth is ready.
// describe('v2 auth gate', () => {
// it('returns 401 for v2 routes without an Authorization header', async () => {
// const event = createMockEvent('/api/v2/proclamations');
// const res = await runHandle(event);
//
// expect(res.status).toBe(401);
// expect(await res.json()).toEqual({ error: 'API key required' });
// expect(validateApiKeyMock).not.toHaveBeenCalled();
// expect(event.resolve).not.toHaveBeenCalled();
// });
//
// it('returns 401 for v2 routes with an invalid bearer key', async () => {
// validateApiKeyMock.mockResolvedValue(null);
//
// const event = createMockEvent('/api/v2/proclamations');
// event.request = new Request('http://localhost/api/v2/proclamations', {
// headers: { authorization: 'Bearer sk_invalid' }
// });
// const res = await runHandle(event);
//
// expect(res.status).toBe(401);
// expect(await res.json()).toEqual({ error: 'Invalid or expired API key' });
// expect(validateApiKeyMock).toHaveBeenCalledWith('sk_invalid');
// expect(event.resolve).not.toHaveBeenCalled();
// });
//
// it('resolves v2 routes with a valid bearer key and sets locals.apiKey', async () => {
// validateApiKeyMock.mockResolvedValue({ id: 'key-1', keyPrefix: 'sk_abc123' });
//
// const event = createMockEvent('/api/v2/proclamations');
// event.request = new Request('http://localhost/api/v2/proclamations', {
// headers: { authorization: 'Bearer sk_abc123def' }
// });
// await runHandle(event);
//
// expect(event.resolve).toHaveBeenCalled();
// expect(event.locals.apiKey).toEqual({ id: 'key-1', keyPrefix: 'sk_abc123' });
// expect(validateApiKeyMock).toHaveBeenCalledWith('sk_abc123def');
// });
//
// it('always validates the v2 API key even when API_KEYS_ACTIVE would bypass v1', async () => {
// envMock.API_KEYS_ACTIVE = 'false';
// validateApiKeyMock.mockResolvedValue(null);
//
// const event = createMockEvent('/api/v2/frequencies');
// event.request = new Request('http://localhost/api/v2/frequencies', {
// headers: { authorization: 'Bearer sk_abc' }
// });
// const res = await runHandle(event);
//
// expect(res.status).toBe(401);
// expect(await res.json()).toEqual({ error: 'Invalid or expired API key' });
// expect(validateApiKeyMock).toHaveBeenCalledWith('sk_abc');
// });
//
// it('does not require an API key for signed v2 onboarding routes', async () => {
// getSession.mockResolvedValue(null);
//
// const event = createMockEvent('/api/v2/automations/create/account');
// await runHandle(event);
//
// expect(validateApiKeyMock).not.toHaveBeenCalled();
// expect(svelteKitHandler).toHaveBeenCalled();
// });
//
// it('does not apply the v2 gate to non-API routes', async () => {
// getSession.mockResolvedValue(null);
//
// const event = createMockEvent('/dashboard');
// await runHandle(event);
//
// expect(validateApiKeyMock).not.toHaveBeenCalled();
// expect(svelteKitHandler).toHaveBeenCalled();
// });
// });
});
+27 -26
View File
@@ -1,37 +1,38 @@
import { auth } from '$lib/auth';
import { svelteKitHandler } from 'better-auth/svelte-kit';
import { building } from '$app/environment';
import { validateApiKey } from '$lib/server/api-key';
import { env } from '$env/dynamic/private';
import { validateApiKey } from '$lib/server/api-key';
export async function handle({ event, resolve }) {
// v2 auth is ALWAYS enforced — no API_KEYS_ACTIVE bypass. The only
// exceptions are the signed onboarding routes (HMAC-protected).
const isV2 = event.url.pathname.startsWith('/api/v2/');
const isSignedOnboarding = event.url.pathname.startsWith('/api/v2/automations/');
if (isV2 && !isSignedOnboarding) {
const authHeader = event.request.headers.get('authorization');
if (!authHeader?.startsWith('Bearer ')) {
return new Response(JSON.stringify({ error: 'API key required' }), {
status: 401,
headers: { 'content-type': 'application/json' }
});
}
const key = authHeader.slice(7);
const record = await validateApiKey(key);
if (!record) {
return new Response(JSON.stringify({ error: 'Invalid or expired API key' }), {
status: 401,
headers: { 'content-type': 'application/json' }
});
}
event.locals.apiKey = record;
return resolve(event);
}
// TODO: re-enable when v2 API key auth is ready.
// const isV2 = event.url.pathname.startsWith('/api/v2/');
// const isSignedOnboarding = event.url.pathname.startsWith('/api/v2/automations/');
//
// if (isV2 && !isSignedOnboarding) {
// const authHeader = event.request.headers.get('authorization');
// if (!authHeader?.startsWith('Bearer ')) {
// return new Response(JSON.stringify({ error: 'API key required' }), {
// status: 401,
// headers: { 'content-type': 'application/json' }
// });
// }
//
// const key = authHeader.slice(7);
// const record = await validateApiKey(key);
// if (!record) {
// return new Response(JSON.stringify({ error: 'Invalid or expired API key' }), {
// status: 401,
// headers: { 'content-type': 'application/json' }
// });
// }
//
// event.locals.apiKey = record;
//
// return resolve(event);
// }
if (
event.url.pathname.startsWith('/api/v1/') &&