Backend hardening: structured logging, timeouts, memory leak fixes, compression, XSS fix
Some checks failed
Deploy to Production / Deploy to Server (push) Failing after 20s
Some checks failed
Deploy to Production / Deploy to Server (push) Failing after 20s
- Add pino structured logging with request IDs (X-Request-Id header) - Add 30s timeout to acquirePage() and renderPdf/renderUrlPdf - Add verification cache cleanup (every 15min) and rate limit cleanup (every 60s) - Read version from package.json in health endpoint - Add compression middleware - Escape currency in templates (XSS fix) - Add static asset caching (1h maxAge) - Remove deprecated docker-compose version field - Replace all console.log/error with pino logger
This commit is contained in:
parent
4833edf44c
commit
9541ae1826
20 changed files with 319 additions and 74 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { Router, Request, Response } from "express";
|
||||
import Stripe from "stripe";
|
||||
import { createProKey, revokeByCustomer } from "../services/keys.js";
|
||||
import logger from "../services/logger.js";
|
||||
|
||||
function escapeHtml(s: string): string {
|
||||
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
||||
|
|
@ -33,7 +34,7 @@ router.post("/checkout", async (_req: Request, res: Response) => {
|
|||
|
||||
res.json({ url: session.url });
|
||||
} catch (err: any) {
|
||||
console.error("Checkout error:", err.message);
|
||||
logger.error({ err }, "Checkout error");
|
||||
res.status(500).json({ error: "Failed to create checkout session" });
|
||||
}
|
||||
});
|
||||
|
|
@ -79,7 +80,7 @@ a { color: #4f9; }
|
|||
<p><a href="/docs">View API docs →</a></p>
|
||||
</div></body></html>`);
|
||||
} catch (err: any) {
|
||||
console.error("Success page error:", err.message);
|
||||
logger.error({ err }, "Success page error");
|
||||
res.status(500).json({ error: "Failed to retrieve session" });
|
||||
}
|
||||
});
|
||||
|
|
@ -97,7 +98,7 @@ router.post("/webhook", async (req: Request, res: Response) => {
|
|||
try {
|
||||
event = JSON.parse(typeof req.body === "string" ? req.body : req.body.toString()) as Stripe.Event;
|
||||
} catch (err: any) {
|
||||
console.error("Failed to parse webhook body:", err.message);
|
||||
logger.error({ err }, "Failed to parse webhook body");
|
||||
res.status(400).json({ error: "Invalid payload" });
|
||||
return;
|
||||
}
|
||||
|
|
@ -108,7 +109,7 @@ router.post("/webhook", async (req: Request, res: Response) => {
|
|||
try {
|
||||
event = getStripe().webhooks.constructEvent(req.body, sig, webhookSecret);
|
||||
} catch (err: any) {
|
||||
console.error("Webhook signature verification failed:", err.message);
|
||||
logger.error({ err }, "Webhook signature verification failed");
|
||||
res.status(400).json({ error: "Invalid signature" });
|
||||
return;
|
||||
}
|
||||
|
|
@ -133,11 +134,11 @@ router.post("/webhook", async (req: Request, res: Response) => {
|
|||
return productId === DOCFAST_PRODUCT_ID;
|
||||
});
|
||||
if (!hasDocfastProduct) {
|
||||
console.log(`Ignoring event for different product (session: ${session.id})`);
|
||||
logger.info({ sessionId: session.id }, "Ignoring event for different product");
|
||||
break;
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(`Failed to retrieve session line_items: ${err.message}, skipping`);
|
||||
logger.error({ err, sessionId: session.id }, "Failed to retrieve session line_items");
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -147,14 +148,14 @@ router.post("/webhook", async (req: Request, res: Response) => {
|
|||
}
|
||||
|
||||
const keyInfo = await createProKey(email, customerId);
|
||||
console.log(`checkout.session.completed: provisioned pro key for ${email} (customer: ${customerId}, key: ${keyInfo.key.slice(0, 12)}...)`);
|
||||
logger.info({ email, customerId }, "checkout.session.completed: provisioned pro key");
|
||||
break;
|
||||
}
|
||||
case "customer.subscription.deleted": {
|
||||
const sub = event.data.object as Stripe.Subscription;
|
||||
const customerId = sub.customer as string;
|
||||
await revokeByCustomer(customerId);
|
||||
console.log(`Subscription cancelled for ${customerId}, key revoked`);
|
||||
logger.info({ customerId }, "Subscription cancelled, key revoked");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue