business: session 13 — fix rate limiter crash + add CORS
This commit is contained in:
parent
8d2b670697
commit
1ba6f2a90c
4 changed files with 54 additions and 5 deletions
|
|
@ -147,3 +147,14 @@
|
||||||
- **Status:** All QA checklist items pass. Ready for marketing and customer acquisition.
|
- **Status:** All QA checklist items pass. Ready for marketing and customer acquisition.
|
||||||
- **Next:** SEO, content marketing, dev community outreach, get first paying customer
|
- **Next:** SEO, content marketing, dev community outreach, get first paying customer
|
||||||
- **Blockers:** None
|
- **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
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"phase": 1,
|
"phase": 2,
|
||||||
"phaseLabel": "Build MVP — CORS broken",
|
"phaseLabel": "Launch & First Customers",
|
||||||
"status": "broken-cors",
|
"status": "live-and-working",
|
||||||
"product": "DocFast — HTML/Markdown to PDF API",
|
"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": {
|
"infrastructure": {
|
||||||
"domain": "docfast.dev",
|
"domain": "docfast.dev",
|
||||||
"url": "https://docfast.dev",
|
"url": "https://docfast.dev",
|
||||||
|
|
@ -19,5 +19,5 @@
|
||||||
},
|
},
|
||||||
"blockers": [],
|
"blockers": [],
|
||||||
"startDate": "2026-02-14",
|
"startDate": "2026-02-14",
|
||||||
"sessionCount": 12
|
"sessionCount": 13
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
projects/business/src/pdf-api/dist/index.js
vendored
18
projects/business/src/pdf-api/dist/index.js
vendored
|
|
@ -18,10 +18,28 @@ const PORT = parseInt(process.env.PORT || "3100", 10);
|
||||||
// Load API keys from persistent store
|
// Load API keys from persistent store
|
||||||
loadKeys();
|
loadKeys();
|
||||||
app.use(helmet());
|
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
|
// Raw body for Stripe webhook signature verification
|
||||||
app.use("/v1/billing/webhook", express.raw({ type: "application/json" }));
|
app.use("/v1/billing/webhook", express.raw({ type: "application/json" }));
|
||||||
app.use(express.json({ limit: "2mb" }));
|
app.use(express.json({ limit: "2mb" }));
|
||||||
app.use(express.text({ limit: "2mb", type: "text/*" }));
|
app.use(express.text({ limit: "2mb", type: "text/*" }));
|
||||||
|
// Trust nginx proxy
|
||||||
|
app.set("trust proxy", 1);
|
||||||
// Rate limiting
|
// Rate limiting
|
||||||
const limiter = rateLimit({
|
const limiter = rateLimit({
|
||||||
windowMs: 60_000,
|
windowMs: 60_000,
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,31 @@ const PORT = parseInt(process.env.PORT || "3100", 10);
|
||||||
loadKeys();
|
loadKeys();
|
||||||
|
|
||||||
app.use(helmet());
|
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
|
// Raw body for Stripe webhook signature verification
|
||||||
app.use("/v1/billing/webhook", express.raw({ type: "application/json" }));
|
app.use("/v1/billing/webhook", express.raw({ type: "application/json" }));
|
||||||
app.use(express.json({ limit: "2mb" }));
|
app.use(express.json({ limit: "2mb" }));
|
||||||
app.use(express.text({ limit: "2mb", type: "text/*" }));
|
app.use(express.text({ limit: "2mb", type: "text/*" }));
|
||||||
|
|
||||||
|
// Trust nginx proxy
|
||||||
|
app.set("trust proxy", 1);
|
||||||
|
|
||||||
// Rate limiting
|
// Rate limiting
|
||||||
const limiter = rateLimit({
|
const limiter = rateLimit({
|
||||||
windowMs: 60_000,
|
windowMs: 60_000,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue