Spaces:
Running
Running
File size: 3,265 Bytes
6e13570 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | const express = require("express");
const helmet = require("helmet");
const cors = require("cors");
const cookieParser = require("cookie-parser");
const compression = require("compression");
const morgan = require("morgan");
const mongoSanitize = require("express-mongo-sanitize");
const hpp = require("hpp");
const { env } = require("./config/env");
const { apiLimiter } = require("./middleware/rateLimiters");
const { errorHandler, notFound } = require("./middleware/errorHandler");
const logger = require("./utils/logger");
const authRoutes = require("./routes/auth.routes");
const courseRoutes = require("./routes/course.routes");
const orderRoutes = require("./routes/order.routes");
const adminRoutes = require("./routes/admin.routes");
const app = express();
// Trust the first proxy hop (Render/Heroku/Nginx/etc.) so req.ip and the
// "secure" cookie/HSTS logic reflect the real client over HTTPS.
app.set("trust proxy", 1);
// --- Security headers -------------------------------------------------
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
objectSrc: ["'none'"],
frameAncestors: ["'none'"],
},
},
crossOriginResourcePolicy: { policy: "same-site" },
hsts: { maxAge: 63072000, includeSubDomains: true, preload: true },
})
);
// --- CORS: only the configured frontend origins may call the API, and
// credentials (refresh-token cookie) are only sent to those origins. ---
app.use(
cors({
origin(origin, callback) {
if (!origin || env.clientOrigins.includes(origin)) return callback(null, true);
// Logged (not just thrown) so a mismatched frontend domain shows up
// immediately in the server logs with the exact rejected value —
// compare this directly against the CORS allow-list logged at
// startup to spot typos, protocol mismatches, or a missing entry.
logger.warn(`CORS rejected origin: "${origin}" (allow-list: ${JSON.stringify(env.clientOrigins)})`);
return callback(new Error("Not allowed by CORS"));
},
credentials: true,
})
);
app.use(compression());
app.use(express.json({ limit: "100kb" })); // small limit: this API never needs large bodies
app.use(express.urlencoded({ extended: true, limit: "100kb" }));
app.use(cookieParser(env.cookieSecret));
// Strip any keys starting with "$" or containing "." from user input to
// block MongoDB operator-injection attacks.
app.use(mongoSanitize());
// Prevent HTTP parameter-pollution (e.g. ?role=user&role=admin).
app.use(hpp());
app.use(
morgan("combined", {
stream: { write: (message) => logger.info(message.trim()) },
skip: () => env.nodeEnv === "test",
})
);
app.use("/api/", apiLimiter);
app.get("/api/health", (req, res) => res.json({ status: "ok", time: new Date().toISOString() }));
app.use("/api/auth", authRoutes);
app.use("/api/courses", courseRoutes);
app.use("/api/orders", orderRoutes);
app.use("/api/admin", adminRoutes);
app.use(notFound);
app.use(errorHandler);
module.exports = app;
|