diff --git a/src/browser/bridge-server.ts b/src/browser/bridge-server.ts index e91555c419..402df2322f 100644 --- a/src/browser/bridge-server.ts +++ b/src/browser/bridge-server.ts @@ -5,14 +5,16 @@ import type { ResolvedBrowserConfig } from "./config.js"; import type { BrowserRouteRegistrar } from "./routes/types.js"; import { isLoopbackHost } from "../gateway/net.js"; import { deleteBridgeAuthForPort, setBridgeAuthForPort } from "./bridge-auth-registry.js"; -import { browserMutationGuardMiddleware } from "./csrf.js"; -import { isAuthorizedBrowserRequest } from "./http-auth.js"; import { registerBrowserRoutes } from "./routes/index.js"; import { type BrowserServerState, createBrowserRouteContext, type ProfileContext, } from "./server-context.js"; +import { + installBrowserAuthMiddleware, + installBrowserCommonMiddleware, +} from "./server-middleware.js"; export type BrowserBridge = { server: Server; @@ -36,35 +38,14 @@ export async function startBrowserBridgeServer(params: { const port = params.port ?? 0; const app = express(); - app.use((req, res, next) => { - const ctrl = new AbortController(); - const abort = () => ctrl.abort(new Error("request aborted")); - req.once("aborted", abort); - res.once("close", () => { - if (!res.writableEnded) { - abort(); - } - }); - // Make the signal available to browser route handlers (best-effort). - (req as unknown as { signal?: AbortSignal }).signal = ctrl.signal; - next(); - }); - app.use(express.json({ limit: "1mb" })); - app.use(browserMutationGuardMiddleware()); + installBrowserCommonMiddleware(app); const authToken = params.authToken?.trim() || undefined; const authPassword = params.authPassword?.trim() || undefined; if (!authToken && !authPassword) { throw new Error("bridge server requires auth (authToken/authPassword missing)"); } - if (authToken || authPassword) { - app.use((req, res, next) => { - if (isAuthorizedBrowserRequest(req, { token: authToken, password: authPassword })) { - return next(); - } - res.status(401).send("Unauthorized"); - }); - } + installBrowserAuthMiddleware(app, { token: authToken, password: authPassword }); const state: BrowserServerState = { server: null as unknown as Server, diff --git a/src/browser/server-middleware.ts b/src/browser/server-middleware.ts new file mode 100644 index 0000000000..99eeb9f226 --- /dev/null +++ b/src/browser/server-middleware.ts @@ -0,0 +1,37 @@ +import type { Express } from "express"; +import express from "express"; +import { browserMutationGuardMiddleware } from "./csrf.js"; +import { isAuthorizedBrowserRequest } from "./http-auth.js"; + +export function installBrowserCommonMiddleware(app: Express) { + app.use((req, res, next) => { + const ctrl = new AbortController(); + const abort = () => ctrl.abort(new Error("request aborted")); + req.once("aborted", abort); + res.once("close", () => { + if (!res.writableEnded) { + abort(); + } + }); + // Make the signal available to browser route handlers (best-effort). + (req as unknown as { signal?: AbortSignal }).signal = ctrl.signal; + next(); + }); + app.use(express.json({ limit: "1mb" })); + app.use(browserMutationGuardMiddleware()); +} + +export function installBrowserAuthMiddleware( + app: Express, + auth: { token?: string; password?: string }, +) { + if (!auth.token && !auth.password) { + return; + } + app.use((req, res, next) => { + if (isAuthorizedBrowserRequest(req, auth)) { + return next(); + } + res.status(401).send("Unauthorized"); + }); +} diff --git a/src/browser/server.ts b/src/browser/server.ts index 37213692e3..57f5716ccc 100644 --- a/src/browser/server.ts +++ b/src/browser/server.ts @@ -5,9 +5,7 @@ import { loadConfig } from "../config/config.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import { resolveBrowserConfig, resolveProfile } from "./config.js"; import { ensureBrowserControlAuth, resolveBrowserControlAuth } from "./control-auth.js"; -import { browserMutationGuardMiddleware } from "./csrf.js"; import { ensureChromeExtensionRelayServer } from "./extension-relay.js"; -import { isAuthorizedBrowserRequest } from "./http-auth.js"; import { isPwAiLoaded } from "./pw-ai-state.js"; import { registerBrowserRoutes } from "./routes/index.js"; import { @@ -15,6 +13,10 @@ import { createBrowserRouteContext, listKnownProfileNames, } from "./server-context.js"; +import { + installBrowserAuthMiddleware, + installBrowserCommonMiddleware, +} from "./server-middleware.js"; let state: BrowserServerState | null = null; const log = createSubsystemLogger("browser"); @@ -43,30 +45,8 @@ export async function startBrowserControlServerFromConfig(): Promise { - const ctrl = new AbortController(); - const abort = () => ctrl.abort(new Error("request aborted")); - req.once("aborted", abort); - res.once("close", () => { - if (!res.writableEnded) { - abort(); - } - }); - // Make the signal available to browser route handlers (best-effort). - (req as unknown as { signal?: AbortSignal }).signal = ctrl.signal; - next(); - }); - app.use(express.json({ limit: "1mb" })); - app.use(browserMutationGuardMiddleware()); - - if (browserAuth.token || browserAuth.password) { - app.use((req, res, next) => { - if (isAuthorizedBrowserRequest(req, browserAuth)) { - return next(); - } - res.status(401).send("Unauthorized"); - }); - } + installBrowserCommonMiddleware(app); + installBrowserAuthMiddleware(app, browserAuth); const ctx = createBrowserRouteContext({ getState: () => state,