spb/chat-spboucher Public
Private universal chat interface over the OpenRouter ecosystem — 400+ models, branching, streaming, usage tracking. Next.js 16 + SQLite, PWA, deployed on m4m64a at chat.spboucher.ai
TypeScript 78.8%
CSS 15.1%
JavaScript 4.9%
Shell 1.2%
1// Author: Simon-Pierre Boucher2// Contact: contact@spboucher.ai3// Project: chat.spboucher.ai45// Edge proxy (Next 16 middleware convention): cheap cookie-presence gate + security headers.6// Real session validation (DB-backed) happens in every route handler via requireSession().7import { NextResponse, type NextRequest } from "next/server";89const SESSION_COOKIE = "spb_session";1011const PUBLIC_PATHS = new Set(["/login", "/api/auth/login", "/manifest.webmanifest", "/sw.js"]);1213function isPublic(pathname: string): boolean {14 if (PUBLIC_PATHS.has(pathname)) return true;15 if (pathname.startsWith("/_next/")) return true;16 if (pathname.startsWith("/icons/")) return true;17 if (pathname === "/favicon.ico") return true;18 return false;19}2021export function proxy(req: NextRequest) {22 const { pathname } = req.nextUrl;23 const hasCookie = Boolean(req.cookies.get(SESSION_COOKIE)?.value);2425 let res: NextResponse;26 if (!isPublic(pathname) && !hasCookie) {27 if (pathname.startsWith("/api/")) {28 res = NextResponse.json({ error: "Unauthorized" }, { status: 401 });29 } else {30 const url = req.nextUrl.clone();31 url.pathname = "/login";32 url.search = "";33 res = NextResponse.redirect(url);34 }35 } else if (pathname === "/login" && hasCookie) {36 const url = req.nextUrl.clone();37 url.pathname = "/";38 url.search = "";39 res = NextResponse.redirect(url);40 } else {41 res = NextResponse.next();42 }4344 res.headers.set("X-Content-Type-Options", "nosniff");45 res.headers.set("Referrer-Policy", "no-referrer");46 res.headers.set("X-Frame-Options", "DENY");47 res.headers.set(48 "Content-Security-Policy",49 [50 "default-src 'self'",51 "script-src 'self' 'unsafe-inline'",52 "style-src 'self' 'unsafe-inline'",53 "img-src 'self' data: blob:",54 "font-src 'self' data:",55 "connect-src 'self'",56 "frame-ancestors 'none'",57 "base-uri 'self'",58 "form-action 'self'",59 ].join("; ")60 );61 return res;62}6364export const config = {65 matcher: ["/((?!_next/static|_next/image).*)"],66};67