AndreaNotes1 commited on
Commit
c1aeffc
·
verified ·
1 Parent(s): af7c9a7

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.js +91 -0
  2. server.js +53 -0
app.js ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require("express");
2
+ const helmet = require("helmet");
3
+ const cors = require("cors");
4
+ const cookieParser = require("cookie-parser");
5
+ const compression = require("compression");
6
+ const morgan = require("morgan");
7
+ const mongoSanitize = require("express-mongo-sanitize");
8
+ const hpp = require("hpp");
9
+
10
+ const { env } = require("./config/env");
11
+ const { apiLimiter } = require("./middleware/rateLimiters");
12
+ const { errorHandler, notFound } = require("./middleware/errorHandler");
13
+ const logger = require("./utils/logger");
14
+
15
+ const authRoutes = require("./routes/auth.routes");
16
+ const courseRoutes = require("./routes/course.routes");
17
+ const orderRoutes = require("./routes/order.routes");
18
+ const adminRoutes = require("./routes/admin.routes");
19
+
20
+ const app = express();
21
+
22
+ // Trust the first proxy hop (Render/Heroku/Nginx/etc.) so req.ip and the
23
+ // "secure" cookie/HSTS logic reflect the real client over HTTPS.
24
+ app.set("trust proxy", 1);
25
+
26
+ // --- Security headers -------------------------------------------------
27
+ app.use(
28
+ helmet({
29
+ contentSecurityPolicy: {
30
+ directives: {
31
+ defaultSrc: ["'self'"],
32
+ scriptSrc: ["'self'"],
33
+ styleSrc: ["'self'", "'unsafe-inline'"],
34
+ imgSrc: ["'self'", "data:", "https:"],
35
+ objectSrc: ["'none'"],
36
+ frameAncestors: ["'none'"],
37
+ },
38
+ },
39
+ crossOriginResourcePolicy: { policy: "same-site" },
40
+ hsts: { maxAge: 63072000, includeSubDomains: true, preload: true },
41
+ })
42
+ );
43
+
44
+ // --- CORS: only the configured frontend origins may call the API, and
45
+ // credentials (refresh-token cookie) are only sent to those origins. ---
46
+ app.use(
47
+ cors({
48
+ origin(origin, callback) {
49
+ if (!origin || env.clientOrigins.includes(origin)) return callback(null, true);
50
+ // Logged (not just thrown) so a mismatched frontend domain shows up
51
+ // immediately in the server logs with the exact rejected value —
52
+ // compare this directly against the CORS allow-list logged at
53
+ // startup to spot typos, protocol mismatches, or a missing entry.
54
+ logger.warn(`CORS rejected origin: "${origin}" (allow-list: ${JSON.stringify(env.clientOrigins)})`);
55
+ return callback(new Error("Not allowed by CORS"));
56
+ },
57
+ credentials: true,
58
+ })
59
+ );
60
+
61
+ app.use(compression());
62
+ app.use(express.json({ limit: "100kb" })); // small limit: this API never needs large bodies
63
+ app.use(express.urlencoded({ extended: true, limit: "100kb" }));
64
+ app.use(cookieParser(env.cookieSecret));
65
+
66
+ // Strip any keys starting with "$" or containing "." from user input to
67
+ // block MongoDB operator-injection attacks.
68
+ app.use(mongoSanitize());
69
+ // Prevent HTTP parameter-pollution (e.g. ?role=user&role=admin).
70
+ app.use(hpp());
71
+
72
+ app.use(
73
+ morgan("combined", {
74
+ stream: { write: (message) => logger.info(message.trim()) },
75
+ skip: () => env.nodeEnv === "test",
76
+ })
77
+ );
78
+
79
+ app.use("/api/", apiLimiter);
80
+
81
+ app.get("/api/health", (req, res) => res.json({ status: "ok", time: new Date().toISOString() }));
82
+
83
+ app.use("/api/auth", authRoutes);
84
+ app.use("/api/courses", courseRoutes);
85
+ app.use("/api/orders", orderRoutes);
86
+ app.use("/api/admin", adminRoutes);
87
+
88
+ app.use(notFound);
89
+ app.use(errorHandler);
90
+
91
+ module.exports = app;
server.js ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { env, assertProductionSafety } = require("./config/env");
2
+ const { connectDB, disconnectDB } = require("./config/db");
3
+ const logger = require("./utils/logger");
4
+ const app = require("./app");
5
+
6
+ assertProductionSafety();
7
+
8
+ let server;
9
+
10
+ async function start() {
11
+ try {
12
+ await connectDB();
13
+ server = app.listen(env.port, () => {
14
+ logger.info(`Aynwerd API listening on port ${env.port} [${env.nodeEnv}]`);
15
+ // Logged deliberately at startup: this is the #1 source of CORS
16
+ // support confusion (a typo'd env var name, stray quotes, a
17
+ // trailing slash, or the value falling back to its localhost
18
+ // default because it was never actually set). Seeing the parsed
19
+ // array here removes all guesswork versus staring at the raw
20
+ // dashboard value.
21
+ logger.info(`CORS allow-list (CLIENT_ORIGINS parsed as): ${JSON.stringify(env.clientOrigins)}`);
22
+ });
23
+ } catch (err) {
24
+ logger.error("Failed to start server", { error: err.message });
25
+ process.exit(1);
26
+ }
27
+ }
28
+
29
+ async function shutdown(signal) {
30
+ logger.info(`Received ${signal}, shutting down gracefully...`);
31
+ if (server) {
32
+ server.close(async () => {
33
+ await disconnectDB();
34
+ process.exit(0);
35
+ });
36
+ // Force-exit if something hangs
37
+ setTimeout(() => process.exit(1), 10000).unref();
38
+ } else {
39
+ process.exit(0);
40
+ }
41
+ }
42
+
43
+ process.on("SIGINT", () => shutdown("SIGINT"));
44
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
45
+ process.on("unhandledRejection", (reason) => {
46
+ logger.error("Unhandled promise rejection", { reason: reason?.message || reason });
47
+ });
48
+ process.on("uncaughtException", (err) => {
49
+ logger.error("Uncaught exception", { error: err.message, stack: err.stack });
50
+ process.exit(1);
51
+ });
52
+
53
+ start();