fix: require auth on cookie-picker data routes (CRITICAL-01)

- Add Bearer token auth gate on all /cookie-picker/* data/action routes
- GET /cookie-picker HTML page stays unauthenticated (UI shell)
- Token embedded in served HTML for picker's fetch calls
- CORS preflight now allows Authorization header
This commit is contained in:
Garry Tan 2026-03-27 22:13:48 -07:00
parent e16bf23ca0
commit 480f4bb23d
No known key found for this signature in database
GPG Key ID: C1F69E85C74EFE1D
2 changed files with 19 additions and 4 deletions

View File

@ -53,6 +53,7 @@ export async function handleCookiePickerRoute(
url: URL, url: URL,
req: Request, req: Request,
bm: BrowserManager, bm: BrowserManager,
authToken?: string,
): Promise<Response> { ): Promise<Response> {
const pathname = url.pathname; const pathname = url.pathname;
const port = parseInt(url.port, 10) || 9400; const port = parseInt(url.port, 10) || 9400;
@ -64,7 +65,7 @@ export async function handleCookiePickerRoute(
headers: { headers: {
'Access-Control-Allow-Origin': corsOrigin(port), 'Access-Control-Allow-Origin': corsOrigin(port),
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type', 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}, },
}); });
} }
@ -72,13 +73,24 @@ export async function handleCookiePickerRoute(
try { try {
// GET /cookie-picker — serve the picker UI // GET /cookie-picker — serve the picker UI
if (pathname === '/cookie-picker' && req.method === 'GET') { if (pathname === '/cookie-picker' && req.method === 'GET') {
const html = getCookiePickerHTML(port); const html = getCookiePickerHTML(port, authToken);
return new Response(html, { return new Response(html, {
status: 200, status: 200,
headers: { 'Content-Type': 'text/html; charset=utf-8' }, headers: { 'Content-Type': 'text/html; charset=utf-8' },
}); });
} }
// ─── Auth gate: all data/action routes below require Bearer token ───
if (authToken) {
const authHeader = req.headers.get('authorization');
if (!authHeader || authHeader !== `Bearer ${authToken}`) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
});
}
}
// GET /cookie-picker/browsers — list installed browsers // GET /cookie-picker/browsers — list installed browsers
if (pathname === '/cookie-picker/browsers' && req.method === 'GET') { if (pathname === '/cookie-picker/browsers' && req.method === 'GET') {
const browsers = findInstalledBrowsers(); const browsers = findInstalledBrowsers();

View File

@ -7,7 +7,7 @@
* No cookie values exposed anywhere. * No cookie values exposed anywhere.
*/ */
export function getCookiePickerHTML(serverPort: number): string { export function getCookiePickerHTML(serverPort: number, authToken?: string): string {
const baseUrl = `http://127.0.0.1:${serverPort}`; const baseUrl = `http://127.0.0.1:${serverPort}`;
return `<!DOCTYPE html> return `<!DOCTYPE html>
@ -330,6 +330,7 @@ export function getCookiePickerHTML(serverPort: number): string {
<script> <script>
(function() { (function() {
const BASE = '${baseUrl}'; const BASE = '${baseUrl}';
const AUTH_TOKEN = '${authToken || ''}';
let activeBrowser = null; let activeBrowser = null;
let activeProfile = 'Default'; let activeProfile = 'Default';
let allProfiles = []; let allProfiles = [];
@ -372,7 +373,9 @@ export function getCookiePickerHTML(serverPort: number): string {
// ─── API ──────────────────────────────── // ─── API ────────────────────────────────
async function api(path, opts) { async function api(path, opts) {
const res = await fetch(BASE + '/cookie-picker' + path, opts); const headers = { ...(opts?.headers || {}) };
if (AUTH_TOKEN) headers['Authorization'] = 'Bearer ' + AUTH_TOKEN;
const res = await fetch(BASE + '/cookie-picker' + path, { ...opts, headers });
const data = await res.json(); const data = await res.json();
if (!res.ok) { if (!res.ok) {
const err = new Error(data.error || 'Request failed'); const err = new Error(data.error || 'Request failed');