diff --git a/dist/index.js b/dist/index.js index 26ac9a1..69ceb0e 100644 --- a/dist/index.js +++ b/dist/index.js @@ -86,8 +86,9 @@ app.use("/v1/signup", signupRouter); app.use("/v1/recover", recoverRouter); app.use("/v1/billing", billingRouter); app.use("/v1/email-change", emailChangeRouter); -// Authenticated routes -app.use("/v1/convert", authMiddleware, usageMiddleware, pdfRateLimitMiddleware, convertRouter); +// Authenticated routes — conversion routes get tighter body limits (500KB) +const convertBodyLimit = express.json({ limit: "500kb" }); +app.use("/v1/convert", convertBodyLimit, authMiddleware, usageMiddleware, pdfRateLimitMiddleware, convertRouter); app.use("/v1/templates", authMiddleware, usageMiddleware, templatesRouter); // Admin: usage stats (admin key required) const adminAuth = (req, res, next) => { diff --git a/dist/routes/billing.js b/dist/routes/billing.js index 9fe5a24..ead3a18 100644 --- a/dist/routes/billing.js +++ b/dist/routes/billing.js @@ -16,6 +16,8 @@ function getStripe() { return _stripe; } const router = Router(); +// Track provisioned session IDs to prevent duplicate key creation +const provisionedSessions = new Set(); // Create a Stripe Checkout session for Pro subscription router.post("/checkout", async (_req, res) => { try { @@ -41,6 +43,11 @@ router.get("/success", async (req, res) => { res.status(400).json({ error: "Missing session_id" }); return; } + // Prevent duplicate provisioning from same session + if (provisionedSessions.has(sessionId)) { + res.status(409).send("This checkout session has already been used to provision a key. If you lost your key, use the key recovery feature."); + return; + } try { const session = await getStripe().checkout.sessions.retrieve(sessionId); const customerId = session.customer; @@ -50,6 +57,7 @@ router.get("/success", async (req, res) => { return; } const keyInfo = await createProKey(email, customerId); + provisionedSessions.add(session.id); // Return a nice HTML page instead of raw JSON res.send(`