Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
5.8 kB ยท 174 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175import { db, sql } from "@openstatus/db";import { page, selectPageSchema } from "@openstatus/db/src/schema";import { resolveClientIp } from "@openstatus/services/page-access";import { NextResponse } from "next/server";
import { auth } from "./lib/auth";import { createProtectedCookieKey } from "./lib/protected";import { applyPageLocaleOverride } from "./lib/proxy/apply-page-locale-override";import { applyPageSlugPrefix } from "./lib/proxy/apply-page-slug-prefix";import { composePageAction } from "./lib/proxy/compose-page-action";import { detectMarkdown } from "./lib/proxy/detect-markdown";import { resolveUnresolvedHostAction } from "./lib/proxy/resolve-unresolved-host-action";import { sanitizeRedirectParam } from "./lib/proxy/sanitize-redirect-param";import { resolveRoute } from "./lib/resolve-route";
const isSelfHosted = process.env.SELF_HOST === "true";
export default auth(async (req) => { const url = req.nextUrl.clone(); const passthroughResponse = NextResponse.next();
// HTML and markdown share the same URL (negotiated by Accept) โ tell shared // caches to key on it so a markdown variant is never served to a browser. passthroughResponse.headers.set("Vary", "Accept"); const host = req.headers.get("x-forwarded-host");
// HTML served via internal rewrite shares its URL with the markdown variant โ // carry the same Vary as the passthrough so caches don't cross them. const rewriteWithVary = ( target: URL, init?: Parameters<typeof NextResponse.rewrite>[1], ) => { const response = NextResponse.rewrite(target, init); response.headers.set("Vary", "Accept"); return response; };
// `/` is the theme explorer, so a host that resolves to no page must 404 // rather than fall through to it. const unresolvedHostResponse = () => { const action = resolveUnresolvedHostAction({ host, urlHost: url.host, requestUrl: req.url, }); if (action.type !== "rewrite") return passthroughResponse; return rewriteWithVary(action.url); };
// Strip a `.md` suffix before route resolution so path-based markdown // (`/foo/en/monitors/123.md`) parses slug/locale correctly. const { wantsMarkdown, source, pathname } = detectMarkdown({ pathname: url.pathname, accept: req.headers.get("accept"), });
const initialRoute = resolveRoute({ host, urlHost: url.host, pathname, });
if (!initialRoute) { return unresolvedHostResponse(); }
// Markdown requests bypass the proxy's DB lookup and gate chain: the route is // reachable directly via `/api` anyway, so it re-validates every gate itself. // Short-circuiting before the gates avoids 307-redirecting a gated `.md` to // /login (it would never reach the route). if (wantsMarkdown) { const rewriteUrl = url.clone(); rewriteUrl.pathname = `/api/markdown${initialRoute.rewritePath}`; const requestHeaders = new Headers(req.headers); requestHeaders.set("x-md-source", source ?? "header"); return NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders }, }); }
const query = await db .select() .from(page) .where( sql`lower(${page.slug}) = ${initialRoute.prefix} OR lower(${page.customDomain}) = ${initialRoute.prefix}`, ) .get();
const validation = selectPageSchema.safeParse(query);
// No page for this host/slug โ never fall through to the theme explorer. if (!validation.success) { return unresolvedHostResponse(); }
const _page = validation.data; const route = applyPageSlugPrefix( applyPageLocaleOverride(initialRoute, _page), _page, );
const clientIp = resolveClientIp(req.headers); const queryPassword = url.searchParams.get("pw");
console.log("[proxy] request", { host, pathname: url.pathname, slug: _page.slug, customDomain: _page.customDomain || null, accessType: _page.accessType, route, authEmailPresent: !!req.auth?.user?.email, clientIp: clientIp ?? null, isSelfHosted, });
const action = composePageAction({ route, page: _page, host, urlHost: url.host, pathname: url.pathname, search: url.search, isSelfHosted, requestUrl: req.url, origin: req.nextUrl.origin, cookiePassword: req.cookies.get(createProtectedCookieKey(_page.slug)) ?.value, queryPassword, redirectParam: sanitizeRedirectParam(url.searchParams.get("redirect")), authEmail: req.auth?.user?.email, clientIp, });
console.log("[proxy] action", { type: action.type, reason: action.reason, url: action.url?.toString() ?? null, });
// A `?pw=` link carries no cookie yet, and the tRPC gate downstream only // sees cookies โ forward the password as one on the internal request. const request = _page.accessType === "password" && queryPassword ? { headers: withPasswordCookie(req.headers, _page.slug, queryPassword) } : undefined;
switch (action.type) { case "redirect": return NextResponse.redirect(action.url); case "rewrite": return rewriteWithVary(action.url, { request }); case "passthrough": { if (!request) return passthroughResponse; const response = NextResponse.next({ request }); response.headers.set("Vary", "Accept"); return response; } }});
function withPasswordCookie(headers: Headers, slug: string, password: string) { const next = new Headers(headers); const cookie = `${createProtectedCookieKey(slug)}=${encodeURIComponent(password)}`; const existing = headers.get("cookie"); next.set("cookie", existing ? `${existing}; ${cookie}` : cookie); return next;}
export const config = { matcher: [ "/((?!api|assets|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)", ],};