rate-limit,cors
SAP-ERP Portal CI/CD / build (push) Successful in 2m58s

This commit is contained in:
John
2026-10-05 19:12:24 +05:30
parent eead8f5ffd
commit 1a596217b7
5 changed files with 175 additions and 3 deletions
+38 -3
View File
@@ -4,6 +4,8 @@
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const path = require('path');
const jwt = require('jsonwebtoken');
const { body, validationResult } = require('express-validator');
@@ -18,7 +20,40 @@ app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ limit: '50mb', extended: true }));
// Audit trail — records every mutating /api request (must be after body parsers)
app.use(require('./middleware/auditLogger'));
app.use(cors({ origin: true, credentials: true }));
// Baseline security headers (X-Content-Type-Options, X-Frame-Options, HSTS,
// etc.). CSP is left off — this app serves lots of inline <script> across
// ~60 hand-written pages with no nonce/hash setup, so a default CSP would
// break every page; revisit only if/when those pages are restructured.
app.use(helmet({ contentSecurityPolicy: false }));
// ── CORS allowlist ────────────────────────────────────────────────
// CORS_ALLOWED_ORIGINS in .env: comma-separated list of exact origins
// (e.g. "https://portal.mitraindustries.com,https://192.9.201.210:8001").
// Unset/empty = reflect any origin (today's behavior) — fine while this
// only runs on a private LAN. Once this has a real public domain, set
// CORS_ALLOWED_ORIGINS so credentialed requests (cookies) are only ever
// accepted from pages this app itself serves, not from any arbitrary site
// a logged-in user's browser happens to have open.
const corsAllowedOrigins = (process.env.CORS_ALLOWED_ORIGINS || '')
.split(',').map(s => s.trim()).filter(Boolean);
app.use(cors({
origin: corsAllowedOrigins.length
? (origin, cb) => cb(null, !origin || corsAllowedOrigins.includes(origin))
: true,
credentials: true,
}));
// ── Login rate limiting ───────────────────────────────────────────
// Applied to every authentication entry point (password login, SSO token
// exchange) — not the rest of the API, which already requires a valid JWT.
// 10 attempts per 15 minutes per IP is generous for a real user mistyping a
// password a few times, but shuts down automated credential-stuffing/
// brute-force against a now-internet-reachable login endpoint.
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
standardHeaders: true,
legacyHeaders: false,
message: { success: false, message: 'Too many login attempts — please wait a few minutes and try again.' },
});
// auth-guard.js is the first <script> on every protected page. Serve it with
// the default SAP company (from .env, via companyConfig) injected as a global
@@ -436,7 +471,7 @@ function safeSapLogins(sapLogins) {
return out;
}
authRouter.post('/login', async (req, res) => {
authRouter.post('/login', loginLimiter, async (req, res) => {
const { username, password } = req.body;
if (!username || !password)
return res.status(400).json({ success: false, message: 'Username and password required' });
@@ -480,7 +515,7 @@ authRouter.post('/login', async (req, res) => {
const CENTRAL_AUTH_API_URL = process.env.CENTRAL_AUTH_API_URL || 'http://14.99.235.114:8016';
const CENTRAL_AUTH_APP_NAME = process.env.CENTRAL_AUTH_APP_NAME || 'SAP ERP';
authRouter.post('/sso-login', async (req, res) => {
authRouter.post('/sso-login', loginLimiter, async (req, res) => {
const { token } = req.body || {};
if (!token) return res.status(400).json({ success: false, message: 'Missing SSO token' });
try {