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('does not apply API key gate to non-API routes', async () => {
it('returns 401 for v2 routes without an Authorization header', async () => { getSession.mockResolvedValue(null);
const event = createMockEvent('/api/v2/proclamations');
const res = await runHandle(event);
expect(res.status).toBe(401); const event = createMockEvent('/dashboard');
expect(await res.json()).toEqual({ error: 'API key required' }); await runHandle(event);
expect(validateApiKeyMock).not.toHaveBeenCalled();
expect(event.resolve).not.toHaveBeenCalled();
});
it('returns 401 for v2 routes with an invalid bearer key', async () => { expect(validateApiKeyMock).not.toHaveBeenCalled();
validateApiKeyMock.mockResolvedValue(null); expect(svelteKitHandler).toHaveBeenCalled();
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();
});
}); });
// 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 { auth } from '$lib/auth';
import { svelteKitHandler } from 'better-auth/svelte-kit'; import { svelteKitHandler } from 'better-auth/svelte-kit';
import { building } from '$app/environment'; import { building } from '$app/environment';
import { validateApiKey } from '$lib/server/api-key';
import { env } from '$env/dynamic/private'; import { env } from '$env/dynamic/private';
import { validateApiKey } from '$lib/server/api-key';
export async function handle({ event, resolve }) { export async function handle({ event, resolve }) {
// v2 auth is ALWAYS enforced — no API_KEYS_ACTIVE bypass. The only // v2 auth is ALWAYS enforced — no API_KEYS_ACTIVE bypass. The only
// exceptions are the signed onboarding routes (HMAC-protected). // exceptions are the signed onboarding routes (HMAC-protected).
const isV2 = event.url.pathname.startsWith('/api/v2/'); // TODO: re-enable when v2 API key auth is ready.
const isSignedOnboarding = event.url.pathname.startsWith('/api/v2/automations/'); // 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 (isV2 && !isSignedOnboarding) {
if (!authHeader?.startsWith('Bearer ')) { // const authHeader = event.request.headers.get('authorization');
return new Response(JSON.stringify({ error: 'API key required' }), { // if (!authHeader?.startsWith('Bearer ')) {
status: 401, // return new Response(JSON.stringify({ error: 'API key required' }), {
headers: { 'content-type': 'application/json' } // status: 401,
}); // headers: { 'content-type': 'application/json' }
} // });
// }
const key = authHeader.slice(7); //
const record = await validateApiKey(key); // const key = authHeader.slice(7);
if (!record) { // const record = await validateApiKey(key);
return new Response(JSON.stringify({ error: 'Invalid or expired API key' }), { // if (!record) {
status: 401, // return new Response(JSON.stringify({ error: 'Invalid or expired API key' }), {
headers: { 'content-type': 'application/json' } // status: 401,
}); // headers: { 'content-type': 'application/json' }
} // });
// }
event.locals.apiKey = record; //
// event.locals.apiKey = record;
return resolve(event); //
} // return resolve(event);
// }
if ( if (
event.url.pathname.startsWith('/api/v1/') && event.url.pathname.startsWith('/api/v1/') &&