Remove v2API key
This commit is contained in:
+78
-67
@@ -129,72 +129,7 @@ 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 () => {
|
|
||||||
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);
|
getSession.mockResolvedValue(null);
|
||||||
|
|
||||||
const event = createMockEvent('/dashboard');
|
const event = createMockEvent('/dashboard');
|
||||||
@@ -203,5 +138,81 @@ describe('hooks.server handle', () => {
|
|||||||
expect(validateApiKeyMock).not.toHaveBeenCalled();
|
expect(validateApiKeyMock).not.toHaveBeenCalled();
|
||||||
expect(svelteKitHandler).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 { 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/') &&
|
||||||
|
|||||||
Reference in New Issue
Block a user