mirror of
https://github.com/DumbWareio/DumbDrop.git
synced 2025-10-23 07:41:58 +00:00
CORS/CSP fix
This commit is contained in:
@@ -10,7 +10,7 @@ PORT=3000
|
||||
BASE_URL=http://localhost:3000/
|
||||
|
||||
# Node environment (default: development)
|
||||
NODE_ENV=development
|
||||
NODE_ENV=production
|
||||
|
||||
#########################################
|
||||
# FILE UPLOAD SETTINGS
|
||||
|
28
package-lock.json
generated
28
package-lock.json
generated
@@ -15,7 +15,8 @@
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
"multer": "^1.4.5-lts.1"
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"toastify-js": "^1.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.56.0",
|
||||
@@ -188,9 +189,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
|
||||
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -356,9 +357,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -625,9 +626,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1801,6 +1802,7 @@
|
||||
"version": "1.4.5-lts.2",
|
||||
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",
|
||||
"integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==",
|
||||
"deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"append-field": "^1.0.0",
|
||||
@@ -2658,6 +2660,12 @@
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/toastify-js": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.12.0.tgz",
|
||||
"integrity": "sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
|
@@ -21,7 +21,8 @@
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
"multer": "^1.4.5-lts.1"
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"toastify-js": "^1.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.56.0",
|
||||
|
@@ -4,12 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{SITE_TITLE}} - Simple File Upload</title>
|
||||
<link rel="stylesheet" href="{{BASE_URL}}styles.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
|
||||
<link rel="manifest" href="{{BASE_URL}}manifest.json">
|
||||
<link rel="icon" type="image/svg+xml" href="{{BASE_URL}}assets/icon.svg">
|
||||
<script>window.BASE_URL = '{{BASE_URL}}';</script>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="toastify/toastify.css">
|
||||
<script src="toastify/toastify.js"></script>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="icon" type="image/svg+xml" href="assets/icon.svg">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
@@ -156,7 +155,7 @@
|
||||
|
||||
// Remove leading slash from API path before concatenating
|
||||
const apiUrl = '/api/upload/init'.startsWith('/') ? '/api/upload/init'.substring(1) : '/api/upload/init';
|
||||
const response = await fetch(window.BASE_URL + apiUrl, {
|
||||
const response = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
@@ -219,7 +218,7 @@
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30-second timeout per attempt
|
||||
|
||||
const response = await fetch(window.BASE_URL + chunkApiUrl, {
|
||||
const response = await fetch(chunkApiUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
@@ -395,7 +394,7 @@
|
||||
const cancelApiUrlPath = `/api/upload/cancel/${this.uploadId}`;
|
||||
const cancelApiUrl = cancelApiUrlPath.startsWith('/') ? cancelApiUrlPath.substring(1) : cancelApiUrlPath;
|
||||
// No need to wait for response here, just fire and forget
|
||||
fetch(window.BASE_URL + cancelApiUrl, { method: 'POST' }).catch(err => {
|
||||
fetch(cancelApiUrl, { method: 'POST' }).catch(err => {
|
||||
console.warn(`Sending cancel request failed for upload ${this.uploadId}:`, err);
|
||||
});
|
||||
} catch (cancelError) {
|
||||
|
@@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{SITE_TITLE}} - Login</title>
|
||||
<link rel="stylesheet" href="{{BASE_URL}}styles.css">
|
||||
<link rel="icon" type="image/svg+xml" href="{{BASE_URL}}assets/icon.svg">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="icon" type="image/svg+xml" href="assets/icon.svg">
|
||||
<style>
|
||||
.login-container {
|
||||
display: flex;
|
||||
@@ -54,7 +54,6 @@
|
||||
background-color: var(--textarea-bg);
|
||||
}
|
||||
</style>
|
||||
<script>window.BASE_URL = '{{BASE_URL}}';</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
@@ -126,10 +125,12 @@
|
||||
// Handle form submission
|
||||
const verifyPin = async (pin) => {
|
||||
try {
|
||||
const response = await fetch(window.BASE_URL + 'api/auth/verify-pin', {
|
||||
const response = await fetch('/api/auth/verify-pin', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ pin })
|
||||
body: JSON.stringify({ pin }),
|
||||
credentials: 'include', // Ensure cookies are sent
|
||||
// redirect: 'follow' // Follow server redirects
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
@@ -212,7 +213,7 @@
|
||||
};
|
||||
|
||||
// Check PIN length and initialize
|
||||
fetch(window.BASE_URL + 'api/auth/pin-required')
|
||||
fetch('/api/auth/pin-required')
|
||||
.then(response => {
|
||||
if (response.status === 429) {
|
||||
throw new Error('Too many attempts. Please wait before trying again.');
|
||||
@@ -241,17 +242,6 @@
|
||||
pinContainer.style.pointerEvents = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Rewrite asset URLs to use BASE_URL as prefix if not absolute
|
||||
const baseUrl = window.BASE_URL;
|
||||
document.querySelectorAll('link[rel="stylesheet"], link[rel="icon"]').forEach(link => {
|
||||
const href = link.getAttribute('href');
|
||||
if (href && !href.startsWith('http') && !href.startsWith('data:') && !href.startsWith(baseUrl)) {
|
||||
link.setAttribute('href', baseUrl + href.replace(/^\//, ''));
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
48
src/app.js
48
src/app.js
@@ -14,31 +14,59 @@ const fsPromises = require('fs').promises;
|
||||
const { config, validateConfig } = require('./config');
|
||||
const logger = require('./utils/logger');
|
||||
const { ensureDirectoryExists } = require('./utils/fileUtils');
|
||||
const { securityHeaders, requirePin } = require('./middleware/security');
|
||||
const { requirePin } = require('./middleware/security');
|
||||
const { safeCompare } = require('./utils/security');
|
||||
const { initUploadLimiter, pinVerifyLimiter, downloadLimiter } = require('./middleware/rateLimiter');
|
||||
const { injectDemoBanner, demoMiddleware } = require('./utils/demoMode');
|
||||
const { originValidationMiddleware, getCorsOptions } = require('./middleware/cors');
|
||||
|
||||
// Create Express app
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const BASE_URL = process.env.BASE_URL || `http://localhost:${PORT}`;
|
||||
|
||||
// Add this line to trust the first proxy
|
||||
app.set('trust proxy', 1);
|
||||
|
||||
// Middleware setup
|
||||
app.use(cors());
|
||||
app.use(cors(getCorsOptions(BASE_URL)));
|
||||
app.use(cookieParser());
|
||||
app.use(express.json());
|
||||
app.use(securityHeaders);
|
||||
// --- AUTHENTICATION MIDDLEWARE FOR ALL PROTECTED ROUTES ---
|
||||
app.use((req, res, next) => {
|
||||
// List of paths that should be publicly accessible
|
||||
const publicPaths = [
|
||||
'/login',
|
||||
'/login.html',
|
||||
'/api/auth/verify-pin',
|
||||
'/api/auth/pin-required',
|
||||
'/api/auth/pin-length',
|
||||
'/pin-length',
|
||||
'/verify-pin',
|
||||
'/config.js',
|
||||
'/assets/',
|
||||
'/styles.css',
|
||||
'/manifest.json',
|
||||
'/asset-manifest.json',
|
||||
'/toastify',
|
||||
];
|
||||
|
||||
// Check if the current path matches any of the public paths
|
||||
if (publicPaths.some(path => req.path.startsWith(path))) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// For all other paths, apply both origin validation and auth middleware
|
||||
originValidationMiddleware(req, res, () => {
|
||||
demoMiddleware(req, res, next);
|
||||
});
|
||||
});
|
||||
|
||||
// Import routes
|
||||
const { router: uploadRouter } = require('./routes/upload');
|
||||
const fileRoutes = require('./routes/files');
|
||||
const authRoutes = require('./routes/auth');
|
||||
|
||||
// Add demo middleware before your routes
|
||||
app.use(demoMiddleware);
|
||||
|
||||
// Use routes with appropriate middleware
|
||||
app.use('/api/auth', pinVerifyLimiter, authRoutes);
|
||||
app.use('/api/upload', requirePin(config.pin), initUploadLimiter, uploadRouter);
|
||||
@@ -55,9 +83,6 @@ app.get('/', (req, res) => {
|
||||
html = html.replace(/{{SITE_TITLE}}/g, config.siteTitle);
|
||||
html = html.replace('{{AUTO_UPLOAD}}', config.autoUpload.toString());
|
||||
html = html.replace('{{MAX_RETRIES}}', config.clientMaxRetries.toString());
|
||||
// Ensure baseUrl has a trailing slash for correct asset linking
|
||||
const baseUrlWithSlash = config.baseUrl.endsWith('/') ? config.baseUrl : config.baseUrl + '/';
|
||||
html = html.replace(/{{BASE_URL}}/g, baseUrlWithSlash);
|
||||
html = injectDemoBanner(html);
|
||||
res.send(html);
|
||||
});
|
||||
@@ -71,9 +96,6 @@ app.get('/login.html', (req, res) => {
|
||||
|
||||
let html = fs.readFileSync(path.join(__dirname, '../public', 'login.html'), 'utf8');
|
||||
html = html.replace(/{{SITE_TITLE}}/g, config.siteTitle);
|
||||
// Ensure baseUrl has a trailing slash
|
||||
const baseUrlWithSlash = config.baseUrl.endsWith('/') ? config.baseUrl : config.baseUrl + '/';
|
||||
html = html.replace(/{{BASE_URL}}/g, baseUrlWithSlash);
|
||||
html = injectDemoBanner(html);
|
||||
res.send(html);
|
||||
});
|
||||
@@ -104,6 +126,8 @@ app.use((req, res, next) => {
|
||||
|
||||
// Serve remaining static files
|
||||
app.use(express.static('public'));
|
||||
// Serve Toastify assets under /toastify
|
||||
app.use('/toastify', express.static(path.join(__dirname, '../node_modules/toastify-js/src')));
|
||||
|
||||
// Error handling middleware
|
||||
app.use((err, req, res, next) => { // eslint-disable-line no-unused-vars
|
||||
|
@@ -46,7 +46,8 @@ const logConfig = (message, level = 'info') => {
|
||||
const DEFAULT_PORT = 3000;
|
||||
const DEFAULT_CHUNK_SIZE = 1024 * 1024 * 100; // 100MB
|
||||
const DEFAULT_SITE_TITLE = 'DumbDrop';
|
||||
const DEFAULT_BASE_URL = 'http://localhost:3000';
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const BASE_URL = process.env.BASE_URL || `http://localhost:${PORT}`;
|
||||
const DEFAULT_CLIENT_MAX_RETRIES = 5; // Default retry count
|
||||
|
||||
const logAndReturn = (key, value, isDefault = false) => {
|
||||
@@ -131,7 +132,7 @@ const config = {
|
||||
* Base URL for the app (default: http://localhost:${PORT})
|
||||
* Set via BASE_URL in .env
|
||||
*/
|
||||
baseUrl: process.env.BASE_URL || DEFAULT_BASE_URL,
|
||||
baseUrl: BASE_URL,
|
||||
|
||||
// =====================
|
||||
// =====================
|
||||
|
83
src/middleware/cors.js
Normal file
83
src/middleware/cors.js
Normal file
@@ -0,0 +1,83 @@
|
||||
const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS || '*';
|
||||
const NODE_ENV = process.env.NODE_ENV || 'production';
|
||||
let allowedOrigins = [];
|
||||
|
||||
function setupOrigins(baseUrl) {
|
||||
allowedOrigins = [ baseUrl ];
|
||||
|
||||
if (NODE_ENV === 'development' || ALLOWED_ORIGINS === '*') allowedOrigins = '*';
|
||||
else if (ALLOWED_ORIGINS && typeof ALLOWED_ORIGINS === 'string') {
|
||||
try {
|
||||
const allowed = ALLOWED_ORIGINS.split(',').map(origin => origin.trim());
|
||||
allowed.forEach(origin => {
|
||||
const normalizedOrigin = normalizeOrigin(origin);
|
||||
if (normalizedOrigin !== baseUrl) allowedOrigins.push(normalizedOrigin);
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error setting up ALLOWED_ORIGINS: ${ALLOWED_ORIGINS}:`, error);
|
||||
}
|
||||
}
|
||||
console.log("ALLOWED ORIGINS:", allowedOrigins);
|
||||
return allowedOrigins;
|
||||
}
|
||||
|
||||
function normalizeOrigin(origin) {
|
||||
if (origin) {
|
||||
try {
|
||||
const normalizedOrigin = new URL(origin).origin;
|
||||
return normalizedOrigin;
|
||||
} catch (error) {
|
||||
console.error("Error parsing referer URL:", error);
|
||||
throw new Error("Error parsing referer URL:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateOrigin(origin) {
|
||||
if (NODE_ENV === 'development' || allowedOrigins === '*') return true;
|
||||
|
||||
try {
|
||||
if (origin) origin = normalizeOrigin(origin);
|
||||
else {
|
||||
console.warn("No origin to validate.");
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log("Validating Origin:", origin);
|
||||
if (allowedOrigins.includes(origin)) {
|
||||
console.log("Allowed request from origin:", origin);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
console.warn("Blocked request from origin:", origin);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
function originValidationMiddleware(req, res, next) {
|
||||
const origin = req.headers.referer || `${req.protocol}://${req.headers.host}`;
|
||||
const isOriginValid = validateOrigin(origin);
|
||||
if (isOriginValid) {
|
||||
next();
|
||||
} else {
|
||||
res.status(403).json({ error: 'Forbidden' });
|
||||
}
|
||||
}
|
||||
|
||||
function getCorsOptions(baseUrl) {
|
||||
const allowedOrigins = setupOrigins(baseUrl);
|
||||
const corsOptions = {
|
||||
origin: allowedOrigins,
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'X-Pin', 'X-Batch-Id'],
|
||||
};
|
||||
return corsOptions;
|
||||
}
|
||||
|
||||
module.exports = { getCorsOptions, originValidationMiddleware, validateOrigin, allowedOrigins };
|
@@ -6,42 +6,46 @@
|
||||
|
||||
const { safeCompare } = require('../utils/security');
|
||||
const logger = require('../utils/logger');
|
||||
const { config } = require('../config');
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const NODE_ENV = process.env.NODE_ENV || 'production';
|
||||
const BASE_URL = process.env.BASE_URL || `http://localhost:${PORT}`;
|
||||
// const { config } = require('../config');
|
||||
|
||||
/**
|
||||
* Security headers middleware
|
||||
* DEPRECATED
|
||||
*/
|
||||
function securityHeaders(req, res, next) {
|
||||
// Content Security Policy
|
||||
let csp =
|
||||
"default-src 'self'; " +
|
||||
"connect-src 'self'; " +
|
||||
"style-src 'self' 'unsafe-inline' cdn.jsdelivr.net; " +
|
||||
"script-src 'self' 'unsafe-inline' cdn.jsdelivr.net; " +
|
||||
"img-src 'self' data: blob:;";
|
||||
// function securityHeaders(req, res, next) {
|
||||
// // Content Security Policy
|
||||
// let csp =
|
||||
// "default-src 'self'; " +
|
||||
// "connect-src 'self'; " +
|
||||
// "style-src 'self' 'unsafe-inline' cdn.jsdelivr.net; " +
|
||||
// "script-src 'self' 'unsafe-inline' cdn.jsdelivr.net; " +
|
||||
// "img-src 'self' data: blob:;";
|
||||
|
||||
// If allowedIframeOrigins is set, allow those origins to embed via iframe
|
||||
if (config.allowedIframeOrigins && config.allowedIframeOrigins.length > 0) {
|
||||
// Remove X-Frame-Options header (do not set it)
|
||||
// Add frame-ancestors directive to CSP
|
||||
const frameAncestors = ["'self'", ...config.allowedIframeOrigins].join(' ');
|
||||
csp += ` frame-ancestors ${frameAncestors};`;
|
||||
} else {
|
||||
// Default: only allow same origin if not configured
|
||||
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
|
||||
}
|
||||
// // If allowedIframeOrigins is set, allow those origins to embed via iframe
|
||||
// if (config.allowedIframeOrigins && config.allowedIframeOrigins.length > 0) {
|
||||
// // Remove X-Frame-Options header (do not set it)
|
||||
// // Add frame-ancestors directive to CSP
|
||||
// const frameAncestors = ["'self'", ...config.allowedIframeOrigins].join(' ');
|
||||
// csp += ` frame-ancestors ${frameAncestors};`;
|
||||
// } else {
|
||||
// // Default: only allow same origin if not configured
|
||||
// res.setHeader('X-Frame-Options', 'SAMEORIGIN');
|
||||
// }
|
||||
|
||||
res.setHeader('Content-Security-Policy', csp);
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
res.setHeader('X-XSS-Protection', '1; mode=block');
|
||||
// res.setHeader('Content-Security-Policy', csp);
|
||||
// res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
// res.setHeader('X-XSS-Protection', '1; mode=block');
|
||||
|
||||
// Strict Transport Security (when in production)
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
||||
}
|
||||
// // Strict Transport Security (when in production)
|
||||
// if (process.env.NODE_ENV === 'production') {
|
||||
// res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
||||
// }
|
||||
|
||||
next();
|
||||
}
|
||||
// next();
|
||||
// }
|
||||
|
||||
/**
|
||||
* PIN protection middleware
|
||||
@@ -66,7 +70,7 @@ function requirePin(PIN) {
|
||||
// Set cookie for subsequent requests with enhanced security
|
||||
const cookieOptions = {
|
||||
httpOnly: true, // Always enable HttpOnly
|
||||
secure: req.secure || req.headers['x-forwarded-proto'] === 'https', // Enable secure flag only if the request is over HTTPS
|
||||
secure: req.secure || (BASE_URL.startsWith('https') && NODE_ENV === 'production'),
|
||||
sameSite: 'strict',
|
||||
path: '/',
|
||||
maxAge: 24 * 60 * 60 * 1000 // 24 hour expiry
|
||||
@@ -82,6 +86,6 @@ function requirePin(PIN) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
securityHeaders,
|
||||
// securityHeaders,
|
||||
requirePin
|
||||
};
|
@@ -11,7 +11,9 @@ const {
|
||||
MAX_ATTEMPTS,
|
||||
LOCKOUT_DURATION
|
||||
} = require('../utils/security');
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const NODE_ENV = process.env.NODE_ENV || 'production';
|
||||
const BASE_URL = process.env.BASE_URL || `http://localhost:${PORT}`;
|
||||
/**
|
||||
* Verify PIN
|
||||
*/
|
||||
@@ -22,13 +24,14 @@ router.post('/verify-pin', (req, res) => {
|
||||
try {
|
||||
// If no PIN is set in config, always return success
|
||||
if (!config.pin) {
|
||||
res.cookie('DUMBDROP_PIN', '', {
|
||||
httpOnly: true,
|
||||
secure: req.secure || (process.env.NODE_ENV === 'production' && config.baseUrl.startsWith('https')),
|
||||
sameSite: 'strict',
|
||||
path: '/'
|
||||
});
|
||||
return res.json({ success: true, error: null });
|
||||
// res.cookie('DUMBDROP_PIN', '', {
|
||||
// httpOnly: true,
|
||||
// secure: req.secure || (BASE_URL.startsWith('https') && NODE_ENV === 'production'),
|
||||
// sameSite: 'strict',
|
||||
// path: '/'
|
||||
// });
|
||||
res.clearCookie('DUMBDROP_PIN', { path: '/' });
|
||||
return res.json({ success: true, error: null, path: '/' });
|
||||
}
|
||||
|
||||
// Validate PIN format
|
||||
@@ -63,7 +66,7 @@ router.post('/verify-pin', (req, res) => {
|
||||
// Set secure cookie with cleaned PIN
|
||||
res.cookie('DUMBDROP_PIN', cleanedPin, {
|
||||
httpOnly: true,
|
||||
secure: req.secure || (process.env.NODE_ENV === 'production' && config.baseUrl.startsWith('https')),
|
||||
secure: req.secure || (BASE_URL.startsWith('https') && NODE_ENV === 'production'),
|
||||
sameSite: 'strict',
|
||||
path: '/'
|
||||
});
|
||||
|
Reference in New Issue
Block a user