diff --git a/package.json b/package.json index 86af9de..0cfbbaa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "docfast-api", - "version": "0.3.3", + "version": "0.3.4", "description": "Markdown/HTML to PDF API with built-in invoice templates", "main": "dist/index.js", "scripts": { diff --git a/src/routes/billing.ts b/src/routes/billing.ts index 262b298..98c8a91 100644 --- a/src/routes/billing.ts +++ b/src/routes/billing.ts @@ -1,4 +1,5 @@ import { Router, Request, Response } from "express"; +import rateLimit from "express-rate-limit"; import Stripe from "stripe"; import { createProKey, downgradeByCustomer, updateEmailByCustomer } from "../services/keys.js"; import logger from "../services/logger.js"; @@ -45,8 +46,25 @@ async function isDocFastSubscription(subscriptionId: string): Promise { } } +// Rate limit checkout: max 3 requests per IP per hour +const checkoutLimiter = rateLimit({ + windowMs: 60 * 60 * 1000, // 1 hour + max: 3, + keyGenerator: (req: Request) => req.ip || req.socket.remoteAddress || "unknown", + standardHeaders: true, + legacyHeaders: false, + message: { error: "Too many checkout requests. Please try again later." }, +}); + // Create a Stripe Checkout session for Pro subscription -router.post("/checkout", async (_req: Request, res: Response) => { +router.post("/checkout", checkoutLimiter, async (req: Request, res: Response) => { + // Reject suspiciously large request bodies (>1KB) + const contentLength = parseInt(req.headers["content-length"] || "0", 10); + if (contentLength > 1024) { + res.status(413).json({ error: "Request body too large" }); + return; + } + try { const priceId = await getOrCreateProPrice(); @@ -58,6 +76,9 @@ router.post("/checkout", async (_req: Request, res: Response) => { cancel_url: `${process.env.BASE_URL || "https://docfast.dev"}/#pricing`, }); + const clientIp = req.ip || req.socket.remoteAddress || "unknown"; + logger.info({ clientIp, sessionId: session.id }, "Checkout session created"); + res.json({ url: session.url }); } catch (err: any) { logger.error({ err }, "Checkout error");