fix(billing): add rate limiting, body size check, and logging to checkout endpoint (BUG-079)
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 11m9s
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 11m9s
- Rate limit /checkout to 3 requests per IP per hour via express-rate-limit - Reject request bodies >1KB (413) - Log checkout session creation with client IP - Bump version to 0.3.4
This commit is contained in:
parent
32a00be0b3
commit
17c1f00e2b
2 changed files with 23 additions and 2 deletions
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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<boolean> {
|
|||
}
|
||||
}
|
||||
|
||||
// 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");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue