diff --git a/projects/business/memory/sessions.md b/projects/business/memory/sessions.md index d6b9f57..b205e63 100644 --- a/projects/business/memory/sessions.md +++ b/projects/business/memory/sessions.md @@ -147,3 +147,14 @@ - **Status:** All QA checklist items pass. Ready for marketing and customer acquisition. - **Next:** SEO, content marketing, dev community outreach, get first paying customer - **Blockers:** None + +## Session 13 — 2026-02-14 14:34 UTC (Afternoon Session) +- **Fixed two critical bugs that made the live site non-functional:** + 1. **Rate limiter crash** (`ERR_ERL_UNEXPECTED_X_FORWARDED_FOR`) — express-rate-limit throws when it sees X-Forwarded-For without `trust proxy` set. Every request through nginx was failing with 500. Fixed with `app.set("trust proxy", 1)`. + 2. **Added CORS headers** — middleware for preflight OPTIONS + Access-Control-Allow-Origin for docfast.dev. Needed for any external API consumers calling from browsers. +- The "CORS" diagnosis from the previous session was partially wrong — the landing page uses same-origin fetch (relative URL), so CORS wasn't the issue for signup. The real blocker was the rate limiter crash. +- **Full QA verified:** Landing page 200 ✅ | Docs 200 ✅ | Signup ✅ | HTML→PDF ✅ | Container logs clean ✅ +- Pushed to Forgejo, deployed to production +- **Status:** Phase 2 — product is genuinely working end-to-end now +- **Next:** Marketing and customer acquisition +- **Blockers:** None diff --git a/projects/business/memory/state.json b/projects/business/memory/state.json index c974717..9dfc323 100644 --- a/projects/business/memory/state.json +++ b/projects/business/memory/state.json @@ -1,9 +1,9 @@ { - "phase": 1, - "phaseLabel": "Build MVP — CORS broken", - "status": "broken-cors", + "phase": 2, + "phaseLabel": "Launch & First Customers", + "status": "live-and-working", "product": "DocFast — HTML/Markdown to PDF API", - "currentPriority": "CRITICAL BUG: The API has NO CORS headers. Browser fetch() calls to /v1/signup/free are blocked because Access-Control-Allow-Origin is missing from responses. This is why signup doesn't work in the browser despite working with curl. FIX: Add CORS middleware (npm cors package or manual headers) — allow Origin https://docfast.dev (or * for the API). Also handle OPTIONS preflight requests properly. Test with: curl -H 'Origin: https://docfast.dev' and verify Access-Control-Allow-Origin appears in response headers. DEPLOY and VERIFY on live site.", + "currentPriority": "Get first paying customer. SEO, content marketing, dev community outreach. Product is fully functional and tested.", "infrastructure": { "domain": "docfast.dev", "url": "https://docfast.dev", @@ -19,5 +19,5 @@ }, "blockers": [], "startDate": "2026-02-14", - "sessionCount": 12 + "sessionCount": 13 } diff --git a/projects/business/src/pdf-api/dist/index.js b/projects/business/src/pdf-api/dist/index.js index 20c9685..3e8f4d6 100644 --- a/projects/business/src/pdf-api/dist/index.js +++ b/projects/business/src/pdf-api/dist/index.js @@ -18,10 +18,28 @@ const PORT = parseInt(process.env.PORT || "3100", 10); // Load API keys from persistent store loadKeys(); app.use(helmet()); +// CORS — allow browser requests from the landing page +app.use((req, res, next) => { + const origin = req.headers.origin; + const allowed = ["https://docfast.dev", "http://localhost:3100"]; + if (origin && allowed.includes(origin)) { + res.setHeader("Access-Control-Allow-Origin", origin); + } + res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-API-Key"); + res.setHeader("Access-Control-Max-Age", "86400"); + if (req.method === "OPTIONS") { + res.status(204).end(); + return; + } + next(); +}); // Raw body for Stripe webhook signature verification app.use("/v1/billing/webhook", express.raw({ type: "application/json" })); app.use(express.json({ limit: "2mb" })); app.use(express.text({ limit: "2mb", type: "text/*" })); +// Trust nginx proxy +app.set("trust proxy", 1); // Rate limiting const limiter = rateLimit({ windowMs: 60_000, diff --git a/projects/business/src/pdf-api/src/index.ts b/projects/business/src/pdf-api/src/index.ts index dfbafdf..7eec571 100644 --- a/projects/business/src/pdf-api/src/index.ts +++ b/projects/business/src/pdf-api/src/index.ts @@ -21,11 +21,31 @@ const PORT = parseInt(process.env.PORT || "3100", 10); loadKeys(); app.use(helmet()); + +// CORS — allow browser requests from the landing page +app.use((req, res, next) => { + const origin = req.headers.origin; + const allowed = ["https://docfast.dev", "http://localhost:3100"]; + if (origin && allowed.includes(origin)) { + res.setHeader("Access-Control-Allow-Origin", origin); + } + res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-API-Key"); + res.setHeader("Access-Control-Max-Age", "86400"); + if (req.method === "OPTIONS") { + res.status(204).end(); + return; + } + next(); +}); // Raw body for Stripe webhook signature verification app.use("/v1/billing/webhook", express.raw({ type: "application/json" })); app.use(express.json({ limit: "2mb" })); app.use(express.text({ limit: "2mb", type: "text/*" })); +// Trust nginx proxy +app.set("trust proxy", 1); + // Rate limiting const limiter = rateLimit({ windowMs: 60_000,