fcsc_ipi_backend/server.js

226 lines
5.4 KiB
JavaScript

const express = require("express");
const cors = require("cors");
const helmet = require("helmet");
const morgan = require("morgan");
const swaggerUi = require("swagger-ui-express");
const swaggerJsdoc = require("swagger-jsdoc");
const routes = require("./app/routes/routes");
const db = require("./app/models");
const path = require("path");
const cookieParser = require("cookie-parser");
const sanitizeInput = require("./app/utils/sanitizeInput");
const verifySignature = require("./app/middleware/app.middleware");
const authController = require("./app/controllers/auth.controller");
const establishmentController = require("./app/controllers/establishment.controller");
require("dotenv").config();
const csrf = require("csurf");
const isProd = process.env.NODE_ENV === "production";
const csrfProtection = csrf({
cookie: {
key: "_csrf",
httpOnly: true, // always true
secure: isProd, // true only in production (HTTPS)
sameSite: isProd ? "none" : "lax",
// prod: cross-domain frontend → backend
// dev : localhost friendly
},
});
const app = express();
// REQUIRED for HSTS when behind proxy (AWS ALB / Nginx / Cloudflare)
app.set("trust proxy", 1);
// GLOBAL MIDDLEWARE (body parsing)
app.use(express.json());
app.use(cookieParser());
const isLocal = process.env.NODE_ENV === "development";
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'none'"],
connectSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:"],
fontSrc: ["'self'", "data:"],
frameAncestors: ["'none'"],
baseUri: ["'none'"],
formAction: ["'self'"],
},
},
})
);
// Clickjacking protection
app.use(helmet.frameguard({ action: "deny" }));
// HSTS for UAT + PROD
if (!isLocal) {
app.use(
helmet.hsts({
maxAge: 31536000,
includeSubDomains: true,
preload: true,
})
);
}
app.use(morgan("dev"));
app.use(sanitizeInput);
const allowedOrigins = process.env.ALLOWED_ORIGINS
? process.env.ALLOWED_ORIGINS.split(",").map(origin => origin.trim())
: [];
app.use(cors({
origin: allowedOrigins,
credentials: true
}));
console.log("is production = " + process.env.NODE_ENV === "production");
console.log("allowedOrigins = " + allowedOrigins);
app.use((req, res, next) => {
console.log("Origin from header = " + req.headers.origin);
if (allowedOrigins.includes(req.headers.origin)) {
console.log("Origin set = " + req.headers.origin);
res.header("Access-Control-Allow-Origin", req.headers.origin);
}
res.header("Access-Control-Allow-Credentials", "true");
res.header(
"Access-Control-Allow-Headers",
"Content-Type, Authorization, APP_SIGNATURE, x-app-signature"
);
res.header(
"Access-Control-Allow-Methods",
"GET, POST, PUT, PATCH, DELETE, OPTIONS"
);
if (req.method === "OPTIONS") {
return res.sendStatus(200);
}
next();
});
// Swagger setup
const swaggerOptions = {
definition: {
openapi: "3.0.0",
info: {
title: "FCSC IPI Survey",
version: "1.0.0",
description: "Federal Competitiveness and Statistics Centre (FCSC) - Industrial Production Index (IPI)",
},
components: {
securitySchemes: {
// Optional: Still support Bearer for Swagger-only testing
bearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
},
appSignature: {
type: "apiKey",
in: "header",
name: "x-app-signature"
}
},
},
},
apis: ["./app/routes/*.js"],
};
const swaggerDocs = swaggerJsdoc(swaggerOptions);
app.use(
"/api-docs",
swaggerUi.serve,
swaggerUi.setup(swaggerDocs, {
swaggerOptions: {
withCredentials: true, // 🔥 VERY IMPORTANT
},
})
);
// Routes
/**
* --------------------
* AUTH (NO CSRF)
* --------------------
*/
app.post("/api/auth/login",[verifySignature], authController.login);
app.post("/api/forgot-password/request-otp",[verifySignature], establishmentController.forgotPasswordRequestOTP);
app.post("/api/forgot-password/verify-otp",[verifySignature], establishmentController.forgotPasswordVerifyOTP);
/**
* --------------------
* CSRF TOKEN ENDPOINT
* --------------------
*/
app.get("/api/csrf-token", csrfProtection, (req, res) => {
res.status(200).json({
csrfToken: req.csrfToken(),
});
});
/**
* --------------------
* PROTECTED ROUTES (CSRF REQUIRED)
* --------------------
*/
app.use("/api", csrfProtection, routes);
// app.use("/api", routes);
/**
* --------------------
* CSRF ERROR HANDLER
* --------------------
*/
app.use((err, req, res, next) => {
if (err.code === "EBADCSRFTOKEN") {
return res.status(403).json({
status: "failed",
message: "Invalid or missing CSRF token",
});
}
next(err);
});
apis: [path.join(__dirname, "app/routes/*.js")],
app.get("/api/test", (req, res) => {
res.json({ status: "success", message: "Test API working fine 🚀" });
});
//deployment route
const deploymentController = require("./app/controllers/deployment.controller");
app.post("/deploy", deploymentController.deployment);
// Sync DB
// db.sequelize.sync({ alter: true }).then(() => {
// console.log("✅ Database connected & synced.");
// });
// Start server
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`🚀 Server running on port ${PORT}`);
});