Remove v2API key
This commit is contained in:
+83
-72
@@ -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
@@ -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/') &&
|
||||
|
||||
Reference in New Issue
Block a user