diff --git a/Overlord-Server/src/main-server.ts b/Overlord-Server/src/main-server.ts index 6fc51ee..ab15013 100644 --- a/Overlord-Server/src/main-server.ts +++ b/Overlord-Server/src/main-server.ts @@ -429,6 +429,7 @@ async function startServer() { }, plugin: { PLUGIN_ROOT, + PUBLIC_ROOT, pluginState, pluginLoadedByClient, pluginLoadingByClient, diff --git a/Overlord-Server/src/server/routes/plugin-routes.ts b/Overlord-Server/src/server/routes/plugin-routes.ts index 357ed9e..71db1c2 100644 --- a/Overlord-Server/src/server/routes/plugin-routes.ts +++ b/Overlord-Server/src/server/routes/plugin-routes.ts @@ -30,6 +30,7 @@ type PluginState = { type PluginRouteDeps = { PLUGIN_ROOT: string; + PUBLIC_ROOT: string; pluginState: PluginState; pluginLoadedByClient: Map>; pluginLoadingByClient: Map>; @@ -62,6 +63,14 @@ export async function handlePluginRoutes( return null; } + async function serveLoginOrUnauthorized(): Promise { + const loginFile = Bun.file(`${deps.PUBLIC_ROOT}/login.html`); + if (await loginFile.exists()) { + return new Response(loginFile, { headers: deps.secureHeaders(deps.mimeType("/login.html")) }); + } + return new Response("Unauthorized", { status: 401 }); + } + if (req.method === "GET" && url.pathname === "/api/plugins") { if (!(await authenticateRequest(req))) { return new Response("Unauthorized", { status: 401 }); @@ -699,6 +708,12 @@ export async function handlePluginRoutes( const pluginFrameMatch = url.pathname.match(/^\/plugins\/([^/]+)\/frame$/); if (req.method === "GET" && pluginFrameMatch) { + const user = await authenticateRequest(req); + if (!user) return serveLoginOrUnauthorized(); + if (user.role === "viewer") { + return new Response("Forbidden: Viewers cannot access interactive features", { status: 403 }); + } + let pluginId = ""; try { pluginId = deps.sanitizePluginId(pluginFrameMatch[1]); @@ -728,6 +743,12 @@ export async function handlePluginRoutes( const pluginPageMatch = url.pathname.match(/^\/plugins\/([^/]+)$/); if (req.method === "GET" && pluginPageMatch) { + const user = await authenticateRequest(req); + if (!user) return serveLoginOrUnauthorized(); + if (user.role === "viewer") { + return new Response("Forbidden: Viewers cannot access interactive features", { status: 403 }); + } + let pluginId = ""; try { pluginId = deps.sanitizePluginId(pluginPageMatch[1]); @@ -736,6 +757,9 @@ export async function handlePluginRoutes( } const clientId = url.searchParams.get("clientId") || ""; + if (clientId && !canUserAccessClient(user.userId, user.role, clientId)) { + return new Response("Forbidden: Client access denied", { status: 403 }); + } const htmlFile = path.join(deps.PLUGIN_ROOT, pluginId, "assets", `${pluginId}.html`); const file = Bun.file(htmlFile); @@ -798,6 +822,12 @@ export async function handlePluginRoutes( const pluginAssetMatch = url.pathname.match(/^\/plugins\/([^/]+)\/assets\/(.+)$/); if (req.method === "GET" && pluginAssetMatch) { + const user = await authenticateRequest(req); + if (!user) return new Response("Unauthorized", { status: 401 }); + if (user.role === "viewer") { + return new Response("Forbidden: Viewers cannot access interactive features", { status: 403 }); + } + const [, pluginId, assetPath] = pluginAssetMatch; let decodedPath = assetPath; try {