This commit is contained in:
165
packages/server/tests/local/browser-ext-auth.test.ts
Normal file
165
packages/server/tests/local/browser-ext-auth.test.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { describe, expect, it, beforeEach, afterEach } from 'vitest';
|
||||
import Fastify from 'fastify';
|
||||
import { securityMiddleware } from '../../src/local/security-middleware.js';
|
||||
import { browserExtRoutes } from '../../src/local/routes/browser-ext.js';
|
||||
|
||||
const TEST_TOKEN = 'browser-ext-session-token';
|
||||
const EXTENSION_ID = 'abcdefghijklmnopabcdefghijklmnop';
|
||||
const EXTENSION_ORIGIN = `chrome-extension://${EXTENSION_ID}`;
|
||||
const OTHER_EXTENSION_ORIGIN = 'chrome-extension://ponmlkjihgfedcbaponmlkjihgfedcba';
|
||||
|
||||
async function createBrowserExtServer() {
|
||||
const server = Fastify({ logger: false });
|
||||
server.decorate('agentState', {
|
||||
wsSessionToken: TEST_TOKEN,
|
||||
activeWorkspaceId: 'workspace-1',
|
||||
});
|
||||
await server.register(securityMiddleware, { sessionToken: TEST_TOKEN });
|
||||
server.get('/api/memory/frames', async () => ({ ok: true }));
|
||||
await server.register(browserExtRoutes);
|
||||
await server.ready();
|
||||
return server;
|
||||
}
|
||||
|
||||
describe('Browser Companion auth bootstrap', () => {
|
||||
const originalExtIds = process.env.WAGGLE_BROWSER_EXT_IDS;
|
||||
const originalTrustLocalhost = process.env.WAGGLE_TRUST_LOCALHOST;
|
||||
const originalDevAllow = process.env.WAGGLE_DEV_ALLOW_ANY_EXTENSION;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.WAGGLE_TRUST_LOCALHOST = '0';
|
||||
process.env.WAGGLE_BROWSER_EXT_IDS = EXTENSION_ID;
|
||||
delete process.env.WAGGLE_DEV_ALLOW_ANY_EXTENSION;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalExtIds === undefined) delete process.env.WAGGLE_BROWSER_EXT_IDS;
|
||||
else process.env.WAGGLE_BROWSER_EXT_IDS = originalExtIds;
|
||||
if (originalTrustLocalhost === undefined) delete process.env.WAGGLE_TRUST_LOCALHOST;
|
||||
else process.env.WAGGLE_TRUST_LOCALHOST = originalTrustLocalhost;
|
||||
if (originalDevAllow === undefined) delete process.env.WAGGLE_DEV_ALLOW_ANY_EXTENSION;
|
||||
else process.env.WAGGLE_DEV_ALLOW_ANY_EXTENSION = originalDevAllow;
|
||||
});
|
||||
|
||||
it('returns the session token to an explicitly allowlisted extension origin without an existing bearer', async () => {
|
||||
const server = await createBrowserExtServer();
|
||||
try {
|
||||
const res = await server.inject({
|
||||
method: 'GET',
|
||||
url: '/api/browser-ext/session-token',
|
||||
headers: { origin: EXTENSION_ORIGIN },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.json()).toEqual({ token: TEST_TOKEN });
|
||||
} finally {
|
||||
await server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('returns the session token to an allowlisted MV3 service-worker request without an Origin header', async () => {
|
||||
const server = await createBrowserExtServer();
|
||||
try {
|
||||
const res = await server.inject({
|
||||
method: 'GET',
|
||||
url: '/api/browser-ext/session-token',
|
||||
headers: {
|
||||
'x-waggle-extension-id': EXTENSION_ID,
|
||||
'sec-fetch-site': 'none',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.json()).toEqual({ token: TEST_TOKEN });
|
||||
} finally {
|
||||
await server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects the token bootstrap for unallowlisted extension origins', async () => {
|
||||
const server = await createBrowserExtServer();
|
||||
try {
|
||||
const res = await server.inject({
|
||||
method: 'GET',
|
||||
url: '/api/browser-ext/session-token',
|
||||
headers: { origin: OTHER_EXTENSION_ORIGIN },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(403);
|
||||
expect(res.json().code).toBe('EXTENSION_NOT_ALLOWLISTED');
|
||||
} finally {
|
||||
await server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects MV3 service-worker token bootstrap when the extension id header is missing', async () => {
|
||||
const server = await createBrowserExtServer();
|
||||
try {
|
||||
const res = await server.inject({
|
||||
method: 'GET',
|
||||
url: '/api/browser-ext/session-token',
|
||||
headers: { 'sec-fetch-site': 'none' },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(403);
|
||||
expect(res.json().code).toBe('EXTENSION_NOT_ALLOWLISTED');
|
||||
} finally {
|
||||
await server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects MV3 service-worker token bootstrap with an unallowlisted extension id header', async () => {
|
||||
const server = await createBrowserExtServer();
|
||||
try {
|
||||
const res = await server.inject({
|
||||
method: 'GET',
|
||||
url: '/api/browser-ext/session-token',
|
||||
headers: {
|
||||
'x-waggle-extension-id': 'ponmlkjihgfedcbaponmlkjihgfedcba',
|
||||
'sec-fetch-site': 'none',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(403);
|
||||
expect(res.json().code).toBe('EXTENSION_NOT_ALLOWLISTED');
|
||||
} finally {
|
||||
await server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not let an allowlisted extension skip bearer auth on normal API routes', async () => {
|
||||
const server = await createBrowserExtServer();
|
||||
try {
|
||||
const res = await server.inject({
|
||||
method: 'GET',
|
||||
url: '/api/memory/frames',
|
||||
headers: { origin: EXTENSION_ORIGIN },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(401);
|
||||
expect(res.json().code).toBe('MISSING_TOKEN');
|
||||
} finally {
|
||||
await server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('labels the health workspace value as an id while preserving the legacy field', async () => {
|
||||
const server = await createBrowserExtServer();
|
||||
try {
|
||||
const res = await server.inject({
|
||||
method: 'GET',
|
||||
url: '/api/browser-ext/health',
|
||||
headers: { authorization: `Bearer ${TEST_TOKEN}` },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.json()).toMatchObject({
|
||||
ok: true,
|
||||
activeWorkspaceId: 'workspace-1',
|
||||
activeWorkspace: 'workspace-1',
|
||||
});
|
||||
} finally {
|
||||
await server.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user