This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user