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

- 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:
OpenClaw 2026-02-20 07:07:27 +00:00
parent 32a00be0b3
commit 17c1f00e2b
2 changed files with 23 additions and 2 deletions

View file

@ -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");