|
| 1 | +// Top-level middleware. Two jobs on every request: |
| 2 | +// |
| 3 | +// 1. Lock down installer/updater routes on user installs. |
| 4 | +// /install, /install/*, /update, /update/*, and the |
| 5 | +// installer-API namespace (/api/install/*, /api/update/*) only |
| 6 | +// make sense on the upstream maintainer's deployment. On a |
| 7 | +// user's install of the same code they're noise that exposes |
| 8 | +// the maintainer-only surface area to their visitors. We 404 |
| 9 | +// those routes when isMaintainer(env) is false. |
| 10 | +// |
| 11 | +// 2. Rewrite the root `/` on user installs to a minimal sign-in |
| 12 | +// landing instead of the maintainer's marketing page. The |
| 13 | +// marketing page in public/index.html only makes sense on the |
| 14 | +// upstream — on a user's domain it leaks the maintainer's |
| 15 | +// branding + screenshots and confuses their visitors. |
| 16 | +// |
| 17 | +// Maintainer detection is via env.IS_MAINTAINER==='1' or |
| 18 | +// settings.is_maintainer==='1'. See _lib/maintainer.js. Cached on |
| 19 | +// env after the first read. |
| 20 | + |
| 21 | +import { isMaintainer } from './_lib/maintainer.js'; |
| 22 | + |
| 23 | +const INSTALLER_RX = /^\/(install|update)(\/.*)?$/; |
| 24 | +const INSTALLER_API_RX = /^\/api\/(install|update)(\/.*)?$/; |
| 25 | + |
| 26 | +export const onRequest = async ({ request, env, next }) => { |
| 27 | + const url = new URL(request.url); |
| 28 | + const path = url.pathname; |
| 29 | + |
| 30 | + const isInstallerSurface = INSTALLER_RX.test(path) || INSTALLER_API_RX.test(path); |
| 31 | + const isRoot = path === '/' || path === '/index.html'; |
| 32 | + |
| 33 | + if (!isInstallerSurface && !isRoot) { |
| 34 | + return next(); |
| 35 | + } |
| 36 | + |
| 37 | + const maintainer = await isMaintainer(env); |
| 38 | + if (maintainer) { |
| 39 | + return next(); |
| 40 | + } |
| 41 | + |
| 42 | + // User install: gate. |
| 43 | + if (isInstallerSurface) { |
| 44 | + return new Response('Not Found', { |
| 45 | + status: 404, |
| 46 | + headers: { 'content-type': 'text/plain; charset=utf-8' }, |
| 47 | + }); |
| 48 | + } |
| 49 | + |
| 50 | + // Root → minimal sign-in landing. We serve the dedicated file |
| 51 | + // public/sign-in.html via the ASSETS binding rather than |
| 52 | + // redirecting, so the URL stays clean. |
| 53 | + const signin = await env.ASSETS.fetch(new URL('/sign-in.html', url)); |
| 54 | + if (signin.ok) { |
| 55 | + const headers = new Headers(signin.headers); |
| 56 | + headers.set('Cache-Control', 'public, max-age=300'); |
| 57 | + return new Response(signin.body, { status: signin.status, headers }); |
| 58 | + } |
| 59 | + // Fallback if the file is missing for any reason — redirect to /admin. |
| 60 | + return Response.redirect(new URL('/admin', url).toString(), 302); |
| 61 | +}; |
0 commit comments