// Synthetic Core only. The application and its production proxy run unchanged // in a disposable Next dev copy; no real accounts, .env files or OAuth services. import { createServer } from "node:http"; import { createHash, randomBytes } from "node:crypto"; import { cp, mkdtemp, readFile, rm, symlink } from "node:fs/promises"; import { createRequire } from "node:module"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { spawn } from "node:child_process"; const root = fileURLToPath(new URL("../../", import.meta.url)); const origin = "http://localhost:17323"; const snapshot = JSON.parse(await readFile(join(root, "src/data/papers.json"), "utf8")); const sessions = new Set(), grants = new Map(); let meFailure = false, logoutFailure = false, verified = false; let counts = {}; let paperStatus = 0, paperMalformed = false; function reset() { sessions.clear(); grants.clear(); meFailure = false; logoutFailure = false; verified = false; paperStatus = 0; paperMalformed = false; counts = { exchange: 0, verified: 0, logout: 0, google: 0, slack: 0 }; } reset(); const token = () => randomBytes(32).toString("hex"); function reply(res, status, data) { res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" }); res.end(status === 204 ? undefined : JSON.stringify(data)); } function session(res) { const value = token(); sessions.add(value); reply(res, 200, { access_token: value, expires_in: 2592000 }); } async function body(req) { const parts = []; for await (const part of req) parts.push(part); return JSON.parse(Buffer.concat(parts).toString() || "{}"); } const server = createServer(async (req, res) => { try { const url = new URL(req.url, "http://localhost:17324"); if (url.pathname === "/__test/reset" && req.method === "POST") { reset(); return reply(res, 200, {}); } if (url.pathname === "/__test/state") return reply(res, 200, { ...counts, sessions: sessions.size }); if (url.pathname === "/__test/control" && req.method === "POST") { const data = await body(req); if (data.expire) sessions.clear(); if (typeof data.meFailure === "boolean") meFailure = data.meFailure; if (typeof data.logoutFailure === "boolean") logoutFailure = data.logoutFailure; if ([0, 400, 429, 503, 504].includes(data.paperStatus)) paperStatus = data.paperStatus; if (typeof data.paperMalformed === "boolean") paperMalformed = data.paperMalformed; return reply(res, 200, {}); } if (url.pathname.startsWith("/projects/thesisly/research/papers/")) { const paper = snapshot.papers.find(p => p.id === url.pathname.split("/").at(-1)); return reply(res, paper ? 200 : 404, paper || { error: "not_found" }); } if (url.pathname === "/projects/thesisly/research/papers") { if (paperStatus) return reply(res, paperStatus, { error: "synthetic research failure" }); if (paperMalformed) return reply(res, 200, { unexpected: true }); const page = Number(url.searchParams.get("page") || 1), pageSize = Number(url.searchParams.get("pageSize") || 8); // Fixture transport data, not an implementation of Core search semantics. return reply(res, 200, { items: snapshot.papers.slice((page - 1) * pageSize, page * pageSize), total: snapshot.papers.length, page, pageSize, topics: [...new Set(snapshot.papers.flatMap(p => p.topics))] }); } const match = /^\/projects\/thesisly\/auth\/([a-z-]+)$/.exec(url.pathname); if (!match) return reply(res, 404, { error: "not_found" }); const action = match[1]; if (["google", "slack"].includes(action) && req.method === "GET") { counts[action]++; const challenge = url.searchParams.get("challenge"); if (!/^[-_a-zA-Z0-9]{43}$/.test(challenge || "")) return reply(res, 400, { error: "invalid_request" }); const code = token(); grants.set(code, challenge); res.writeHead(302, { Location: `${origin}/auth/callback#code=${code}` }); return res.end(); } if (action === "exchange" && req.method === "POST") { counts.exchange++; const code = req.headers["x-foundry-code"], verifier = req.headers["x-foundry-verifier"]; const expected = grants.get(code); if (!expected || typeof verifier !== "string" || createHash("sha256").update(verifier).digest("base64url") !== expected) return reply(res, 400, { error: "invalid_verification" }); grants.delete(code); return session(res); } if (action === "register" && req.method === "POST") return reply(res, 202, { status: "verification_required" }); if (action === "verify-email" && req.method === "POST") { counts.verified++; const data = await body(req); if (verified || data.token !== "fixture-email-verification") return reply(res, 400, { error: "invalid_verification" }); verified = true; return session(res); } if (action === "login" && req.method === "POST") { const data = await body(req); if (data.email !== "reader@example.test" || data.password !== "fixture-password-123") return reply(res, 401, { error: "invalid_credentials" }); return session(res); } const bearer = (req.headers.authorization || "").replace(/^Bearer /, ""); if (!sessions.has(bearer)) return reply(res, 401, { error: "invalid_session" }); if (action === "me" && req.method === "GET") { if (meFailure) return reply(res, 503, { error: "temporarily_unavailable" }); res.setHeader("X-Foundry-Session-Expires-In", "2592000"); return reply(res, 200, { id: "fixture-account", email: "reader@example.test", project: "thesisly" }); } if (action === "logout" && req.method === "POST") { if (logoutFailure) return reply(res, 503, { error: "temporarily_unavailable" }); sessions.delete(bearer); counts.logout++; return reply(res, 204); } return reply(res, 405, { error: "method_not_allowed" }); } catch { reply(res, 500, { error: "fixture_error" }); } }); const directory = await mkdtemp(join(root, ".session-test-")); let child; let closing = false; async function close(code = 0) { if (closing) return; closing = true; if (child && child.exitCode === null) { child.kill("SIGTERM"); await new Promise(resolve => { child.once("exit", resolve); setTimeout(() => { child.kill("SIGKILL"); resolve(); }, 5000).unref(); }); } server.closeAllConnections(); await new Promise(resolve => server.close(resolve)); await rm(directory, { recursive: true, force: true }); process.exit(code); } process.on("SIGTERM", () => void close()); process.on("SIGINT", () => void close()); try { for (const name of ["src", "package.json", "tsconfig.json", "postcss.config.mjs", "next.config.ts", "next-env.d.ts"]) await cp(join(root, name), join(directory, name), { recursive: true }); await symlink(join(root, "node_modules"), join(directory, "node_modules"), "dir"); await new Promise((resolve, reject) => { server.once("error", reject); server.listen(17324, "localhost", resolve); }); const require = createRequire(import.meta.url); child = spawn(process.execPath, [require.resolve("next/dist/bin/next"), "dev", "--hostname", "localhost", "--port", "17323"], { cwd: directory, stdio: "inherit", env: { ...process.env, FOUNDRY_PROJECT: "thesisly", FOUNDRY_API_ORIGIN: "http://localhost:17324", APP_ORIGIN: origin, PAPER_API_ORIGIN: process.env.THESISLY_PAPER_MODE === "sample" ? "" : "http://localhost:17324/projects/thesisly/research/", NEXT_TELEMETRY_DISABLED: "1" }, }); child.once("exit", code => { if (!closing) void close(code || 1); }); } catch (error) { console.error(error.message); await close(1); }