mirror of
				https://github.com/DumbWareio/DumbDrop.git
				synced 2025-11-03 21:43:26 +00:00 
			
		
		
		
	CORS/CSP fix
This commit is contained in:
		@@ -10,7 +10,7 @@ PORT=3000
 | 
				
			|||||||
BASE_URL=http://localhost:3000/
 | 
					BASE_URL=http://localhost:3000/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Node environment (default: development)
 | 
					# Node environment (default: development)
 | 
				
			||||||
NODE_ENV=development
 | 
					NODE_ENV=production
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#########################################
 | 
					#########################################
 | 
				
			||||||
# FILE UPLOAD SETTINGS
 | 
					# FILE UPLOAD SETTINGS
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										28
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										28
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -15,7 +15,8 @@
 | 
				
			|||||||
        "dotenv": "^16.0.3",
 | 
					        "dotenv": "^16.0.3",
 | 
				
			||||||
        "express": "^4.18.2",
 | 
					        "express": "^4.18.2",
 | 
				
			||||||
        "express-rate-limit": "^7.1.5",
 | 
					        "express-rate-limit": "^7.1.5",
 | 
				
			||||||
        "multer": "^1.4.5-lts.1"
 | 
					        "multer": "^1.4.5-lts.1",
 | 
				
			||||||
 | 
					        "toastify-js": "^1.12.0"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "devDependencies": {
 | 
					      "devDependencies": {
 | 
				
			||||||
        "eslint": "^8.56.0",
 | 
					        "eslint": "^8.56.0",
 | 
				
			||||||
@@ -188,9 +189,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/acorn": {
 | 
					    "node_modules/acorn": {
 | 
				
			||||||
      "version": "8.14.1",
 | 
					      "version": "8.15.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
 | 
					      "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "license": "MIT",
 | 
					      "license": "MIT",
 | 
				
			||||||
      "bin": {
 | 
					      "bin": {
 | 
				
			||||||
@@ -356,9 +357,9 @@
 | 
				
			|||||||
      "license": "MIT"
 | 
					      "license": "MIT"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/brace-expansion": {
 | 
					    "node_modules/brace-expansion": {
 | 
				
			||||||
      "version": "1.1.11",
 | 
					      "version": "1.1.12",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
 | 
				
			||||||
      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
 | 
					      "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "license": "MIT",
 | 
					      "license": "MIT",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
@@ -625,9 +626,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/debug": {
 | 
					    "node_modules/debug": {
 | 
				
			||||||
      "version": "4.4.0",
 | 
					      "version": "4.4.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
 | 
					      "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "license": "MIT",
 | 
					      "license": "MIT",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
@@ -1801,6 +1802,7 @@
 | 
				
			|||||||
      "version": "1.4.5-lts.2",
 | 
					      "version": "1.4.5-lts.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==",
 | 
					      "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",
 | 
					      "license": "MIT",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "append-field": "^1.0.0",
 | 
					        "append-field": "^1.0.0",
 | 
				
			||||||
@@ -2658,6 +2660,12 @@
 | 
				
			|||||||
        "node": ">=8.0"
 | 
					        "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": {
 | 
					    "node_modules/toidentifier": {
 | 
				
			||||||
      "version": "1.0.1",
 | 
					      "version": "1.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,8 @@
 | 
				
			|||||||
    "dotenv": "^16.0.3",
 | 
					    "dotenv": "^16.0.3",
 | 
				
			||||||
    "express": "^4.18.2",
 | 
					    "express": "^4.18.2",
 | 
				
			||||||
    "express-rate-limit": "^7.1.5",
 | 
					    "express-rate-limit": "^7.1.5",
 | 
				
			||||||
    "multer": "^1.4.5-lts.1"
 | 
					    "multer": "^1.4.5-lts.1",
 | 
				
			||||||
 | 
					    "toastify-js": "^1.12.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "eslint": "^8.56.0",
 | 
					    "eslint": "^8.56.0",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,12 +4,11 @@
 | 
				
			|||||||
    <meta charset="UTF-8">
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
    <title>{{SITE_TITLE}} - Simple File Upload</title>
 | 
					    <title>{{SITE_TITLE}} - Simple File Upload</title>
 | 
				
			||||||
    <link rel="stylesheet" href="{{BASE_URL}}styles.css">
 | 
					    <link rel="stylesheet" href="styles.css">
 | 
				
			||||||
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css">
 | 
					    <link rel="stylesheet" href="toastify/toastify.css">
 | 
				
			||||||
    <script src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
 | 
					    <script src="toastify/toastify.js"></script>
 | 
				
			||||||
    <link rel="manifest" href="{{BASE_URL}}manifest.json">
 | 
					    <link rel="manifest" href="manifest.json">
 | 
				
			||||||
    <link rel="icon" type="image/svg+xml" href="{{BASE_URL}}assets/icon.svg">
 | 
					    <link rel="icon" type="image/svg+xml" href="assets/icon.svg">
 | 
				
			||||||
    <script>window.BASE_URL = '{{BASE_URL}}';</script>
 | 
					 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
    <div class="container">
 | 
					    <div class="container">
 | 
				
			||||||
@@ -156,7 +155,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                // Remove leading slash from API path before concatenating
 | 
					                // Remove leading slash from API path before concatenating
 | 
				
			||||||
                const apiUrl = '/api/upload/init'.startsWith('/') ? '/api/upload/init'.substring(1) : '/api/upload/init';
 | 
					                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',
 | 
					                    method: 'POST',
 | 
				
			||||||
                    headers,
 | 
					                    headers,
 | 
				
			||||||
                    body: JSON.stringify({
 | 
					                    body: JSON.stringify({
 | 
				
			||||||
@@ -219,7 +218,7 @@
 | 
				
			|||||||
                        const controller = new AbortController();
 | 
					                        const controller = new AbortController();
 | 
				
			||||||
                        const timeoutId = setTimeout(() => controller.abort(), 30000); // 30-second timeout per attempt
 | 
					                        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',
 | 
					                            method: 'POST',
 | 
				
			||||||
                            headers: {
 | 
					                            headers: {
 | 
				
			||||||
                                'Content-Type': 'application/octet-stream',
 | 
					                                'Content-Type': 'application/octet-stream',
 | 
				
			||||||
@@ -395,7 +394,7 @@
 | 
				
			|||||||
                     const cancelApiUrlPath = `/api/upload/cancel/${this.uploadId}`;
 | 
					                     const cancelApiUrlPath = `/api/upload/cancel/${this.uploadId}`;
 | 
				
			||||||
                     const cancelApiUrl = cancelApiUrlPath.startsWith('/') ? cancelApiUrlPath.substring(1) : cancelApiUrlPath;
 | 
					                     const cancelApiUrl = cancelApiUrlPath.startsWith('/') ? cancelApiUrlPath.substring(1) : cancelApiUrlPath;
 | 
				
			||||||
                    // No need to wait for response here, just fire and forget
 | 
					                    // 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);
 | 
					                         console.warn(`Sending cancel request failed for upload ${this.uploadId}:`, err);
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
                } catch (cancelError) {
 | 
					                } catch (cancelError) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,8 +4,8 @@
 | 
				
			|||||||
    <meta charset="UTF-8">
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
    <title>{{SITE_TITLE}} - Login</title>
 | 
					    <title>{{SITE_TITLE}} - Login</title>
 | 
				
			||||||
    <link rel="stylesheet" href="{{BASE_URL}}styles.css">
 | 
					    <link rel="stylesheet" href="styles.css">
 | 
				
			||||||
    <link rel="icon" type="image/svg+xml" href="{{BASE_URL}}assets/icon.svg">
 | 
					    <link rel="icon" type="image/svg+xml" href="assets/icon.svg">
 | 
				
			||||||
    <style>
 | 
					    <style>
 | 
				
			||||||
        .login-container {
 | 
					        .login-container {
 | 
				
			||||||
            display: flex;
 | 
					            display: flex;
 | 
				
			||||||
@@ -54,7 +54,6 @@
 | 
				
			|||||||
            background-color: var(--textarea-bg);
 | 
					            background-color: var(--textarea-bg);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    </style>
 | 
					    </style>
 | 
				
			||||||
    <script>window.BASE_URL = '{{BASE_URL}}';</script>
 | 
					 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
    <div class="login-container">
 | 
					    <div class="login-container">
 | 
				
			||||||
@@ -126,10 +125,12 @@
 | 
				
			|||||||
        // Handle form submission
 | 
					        // Handle form submission
 | 
				
			||||||
        const verifyPin = async (pin) => {
 | 
					        const verifyPin = async (pin) => {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                const response = await fetch(window.BASE_URL + 'api/auth/verify-pin', {
 | 
					                const response = await fetch('/api/auth/verify-pin', {
 | 
				
			||||||
                    method: 'POST',
 | 
					                    method: 'POST',
 | 
				
			||||||
                    headers: { 'Content-Type': 'application/json' },
 | 
					                    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();
 | 
					                const data = await response.json();
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
@@ -212,7 +213,7 @@
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Check PIN length and initialize
 | 
					        // Check PIN length and initialize
 | 
				
			||||||
        fetch(window.BASE_URL + 'api/auth/pin-required')
 | 
					        fetch('/api/auth/pin-required')
 | 
				
			||||||
            .then(response => {
 | 
					            .then(response => {
 | 
				
			||||||
                if (response.status === 429) {
 | 
					                if (response.status === 429) {
 | 
				
			||||||
                    throw new Error('Too many attempts. Please wait before trying again.');
 | 
					                    throw new Error('Too many attempts. Please wait before trying again.');
 | 
				
			||||||
@@ -241,17 +242,6 @@
 | 
				
			|||||||
                    pinContainer.style.pointerEvents = 'none';
 | 
					                    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>
 | 
					    </script>
 | 
				
			||||||
</body>
 | 
					</body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										48
									
								
								src/app.js
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								src/app.js
									
									
									
									
									
								
							@@ -14,31 +14,59 @@ const fsPromises = require('fs').promises;
 | 
				
			|||||||
const { config, validateConfig } = require('./config');
 | 
					const { config, validateConfig } = require('./config');
 | 
				
			||||||
const logger = require('./utils/logger');
 | 
					const logger = require('./utils/logger');
 | 
				
			||||||
const { ensureDirectoryExists } = require('./utils/fileUtils');
 | 
					const { ensureDirectoryExists } = require('./utils/fileUtils');
 | 
				
			||||||
const { securityHeaders, requirePin } = require('./middleware/security');
 | 
					const { requirePin } = require('./middleware/security');
 | 
				
			||||||
const { safeCompare } = require('./utils/security');
 | 
					const { safeCompare } = require('./utils/security');
 | 
				
			||||||
const { initUploadLimiter, pinVerifyLimiter, downloadLimiter } = require('./middleware/rateLimiter');
 | 
					const { initUploadLimiter, pinVerifyLimiter, downloadLimiter } = require('./middleware/rateLimiter');
 | 
				
			||||||
const { injectDemoBanner, demoMiddleware } = require('./utils/demoMode');
 | 
					const { injectDemoBanner, demoMiddleware } = require('./utils/demoMode');
 | 
				
			||||||
 | 
					const { originValidationMiddleware, getCorsOptions } = require('./middleware/cors');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Create Express app
 | 
					// Create Express app
 | 
				
			||||||
const app = express();
 | 
					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
 | 
					// Add this line to trust the first proxy
 | 
				
			||||||
app.set('trust proxy', 1);
 | 
					app.set('trust proxy', 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Middleware setup
 | 
					// Middleware setup
 | 
				
			||||||
app.use(cors());
 | 
					app.use(cors(getCorsOptions(BASE_URL)));
 | 
				
			||||||
app.use(cookieParser());
 | 
					app.use(cookieParser());
 | 
				
			||||||
app.use(express.json());
 | 
					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
 | 
					// Import routes
 | 
				
			||||||
const { router: uploadRouter } = require('./routes/upload');
 | 
					const { router: uploadRouter } = require('./routes/upload');
 | 
				
			||||||
const fileRoutes = require('./routes/files');
 | 
					const fileRoutes = require('./routes/files');
 | 
				
			||||||
const authRoutes = require('./routes/auth');
 | 
					const authRoutes = require('./routes/auth');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Add demo middleware before your routes
 | 
					 | 
				
			||||||
app.use(demoMiddleware);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Use routes with appropriate middleware
 | 
					// Use routes with appropriate middleware
 | 
				
			||||||
app.use('/api/auth', pinVerifyLimiter, authRoutes);
 | 
					app.use('/api/auth', pinVerifyLimiter, authRoutes);
 | 
				
			||||||
app.use('/api/upload', requirePin(config.pin), initUploadLimiter, uploadRouter);
 | 
					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(/{{SITE_TITLE}}/g, config.siteTitle);
 | 
				
			||||||
  html = html.replace('{{AUTO_UPLOAD}}', config.autoUpload.toString());
 | 
					  html = html.replace('{{AUTO_UPLOAD}}', config.autoUpload.toString());
 | 
				
			||||||
  html = html.replace('{{MAX_RETRIES}}', config.clientMaxRetries.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);
 | 
					  html = injectDemoBanner(html);
 | 
				
			||||||
  res.send(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');
 | 
					  let html = fs.readFileSync(path.join(__dirname, '../public', 'login.html'), 'utf8');
 | 
				
			||||||
  html = html.replace(/{{SITE_TITLE}}/g, config.siteTitle);
 | 
					  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);
 | 
					  html = injectDemoBanner(html);
 | 
				
			||||||
  res.send(html);
 | 
					  res.send(html);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -104,6 +126,8 @@ app.use((req, res, next) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Serve remaining static files
 | 
					// Serve remaining static files
 | 
				
			||||||
app.use(express.static('public'));
 | 
					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
 | 
					// Error handling middleware
 | 
				
			||||||
app.use((err, req, res, next) => { // eslint-disable-line no-unused-vars
 | 
					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_PORT = 3000;
 | 
				
			||||||
const DEFAULT_CHUNK_SIZE = 1024 * 1024 * 100; // 100MB
 | 
					const DEFAULT_CHUNK_SIZE = 1024 * 1024 * 100; // 100MB
 | 
				
			||||||
const DEFAULT_SITE_TITLE = 'DumbDrop';
 | 
					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 DEFAULT_CLIENT_MAX_RETRIES = 5; // Default retry count
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const logAndReturn = (key, value, isDefault = false) => {
 | 
					const logAndReturn = (key, value, isDefault = false) => {
 | 
				
			||||||
@@ -131,7 +132,7 @@ const config = {
 | 
				
			|||||||
   * Base URL for the app (default: http://localhost:${PORT})
 | 
					   * Base URL for the app (default: http://localhost:${PORT})
 | 
				
			||||||
   * Set via BASE_URL in .env
 | 
					   * 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 { safeCompare } = require('../utils/security');
 | 
				
			||||||
const logger = require('../utils/logger');
 | 
					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
 | 
					 * Security headers middleware
 | 
				
			||||||
 | 
					 * DEPRECATED
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function securityHeaders(req, res, next) {
 | 
					// function securityHeaders(req, res, next) {
 | 
				
			||||||
  // Content Security Policy
 | 
					//   // Content Security Policy
 | 
				
			||||||
  let csp =
 | 
					//   let csp =
 | 
				
			||||||
    "default-src 'self'; " +
 | 
					//     "default-src 'self'; " +
 | 
				
			||||||
    "connect-src 'self'; " +
 | 
					//     "connect-src 'self'; " +
 | 
				
			||||||
    "style-src 'self' 'unsafe-inline' cdn.jsdelivr.net; " +
 | 
					//     "style-src 'self' 'unsafe-inline' cdn.jsdelivr.net; " +
 | 
				
			||||||
    "script-src 'self' 'unsafe-inline' cdn.jsdelivr.net; " +
 | 
					//     "script-src 'self' 'unsafe-inline' cdn.jsdelivr.net; " +
 | 
				
			||||||
    "img-src 'self' data: blob:;";
 | 
					//     "img-src 'self' data: blob:;";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // If allowedIframeOrigins is set, allow those origins to embed via iframe
 | 
					//   // If allowedIframeOrigins is set, allow those origins to embed via iframe
 | 
				
			||||||
  if (config.allowedIframeOrigins && config.allowedIframeOrigins.length > 0) {
 | 
					//   if (config.allowedIframeOrigins && config.allowedIframeOrigins.length > 0) {
 | 
				
			||||||
    // Remove X-Frame-Options header (do not set it)
 | 
					//     // Remove X-Frame-Options header (do not set it)
 | 
				
			||||||
    // Add frame-ancestors directive to CSP
 | 
					//     // Add frame-ancestors directive to CSP
 | 
				
			||||||
    const frameAncestors = ["'self'", ...config.allowedIframeOrigins].join(' ');
 | 
					//     const frameAncestors = ["'self'", ...config.allowedIframeOrigins].join(' ');
 | 
				
			||||||
    csp += ` frame-ancestors ${frameAncestors};`;
 | 
					//     csp += ` frame-ancestors ${frameAncestors};`;
 | 
				
			||||||
  } else {
 | 
					//   } else {
 | 
				
			||||||
    // Default: only allow same origin if not configured
 | 
					//     // Default: only allow same origin if not configured
 | 
				
			||||||
    res.setHeader('X-Frame-Options', 'SAMEORIGIN');
 | 
					//     res.setHeader('X-Frame-Options', 'SAMEORIGIN');
 | 
				
			||||||
  }
 | 
					//   }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  res.setHeader('Content-Security-Policy', csp);
 | 
					//   res.setHeader('Content-Security-Policy', csp);
 | 
				
			||||||
  res.setHeader('X-Content-Type-Options', 'nosniff');
 | 
					//   res.setHeader('X-Content-Type-Options', 'nosniff');
 | 
				
			||||||
  res.setHeader('X-XSS-Protection', '1; mode=block');
 | 
					//   res.setHeader('X-XSS-Protection', '1; mode=block');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Strict Transport Security (when in production)
 | 
					//   // Strict Transport Security (when in production)
 | 
				
			||||||
  if (process.env.NODE_ENV === 'production') {
 | 
					//   if (process.env.NODE_ENV === 'production') {
 | 
				
			||||||
    res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
 | 
					//     res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
 | 
				
			||||||
  }
 | 
					//   }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  next();
 | 
					//   next();
 | 
				
			||||||
}
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * PIN protection middleware
 | 
					 * PIN protection middleware
 | 
				
			||||||
@@ -66,7 +70,7 @@ function requirePin(PIN) {
 | 
				
			|||||||
      // Set cookie for subsequent requests with enhanced security
 | 
					      // Set cookie for subsequent requests with enhanced security
 | 
				
			||||||
      const cookieOptions = {
 | 
					      const cookieOptions = {
 | 
				
			||||||
        httpOnly: true, // Always enable HttpOnly
 | 
					        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',
 | 
					        sameSite: 'strict',
 | 
				
			||||||
        path: '/',
 | 
					        path: '/',
 | 
				
			||||||
        maxAge: 24 * 60 * 60 * 1000 // 24 hour expiry
 | 
					        maxAge: 24 * 60 * 60 * 1000 // 24 hour expiry
 | 
				
			||||||
@@ -82,6 +86,6 @@ function requirePin(PIN) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
  securityHeaders,
 | 
					  // securityHeaders,
 | 
				
			||||||
  requirePin
 | 
					  requirePin
 | 
				
			||||||
}; 
 | 
					}; 
 | 
				
			||||||
@@ -11,7 +11,9 @@ const {
 | 
				
			|||||||
  MAX_ATTEMPTS,
 | 
					  MAX_ATTEMPTS,
 | 
				
			||||||
  LOCKOUT_DURATION 
 | 
					  LOCKOUT_DURATION 
 | 
				
			||||||
} = require('../utils/security');
 | 
					} = 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
 | 
					 * Verify PIN
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@@ -22,13 +24,14 @@ router.post('/verify-pin', (req, res) => {
 | 
				
			|||||||
  try {
 | 
					  try {
 | 
				
			||||||
    // If no PIN is set in config, always return success
 | 
					    // If no PIN is set in config, always return success
 | 
				
			||||||
    if (!config.pin) {
 | 
					    if (!config.pin) {
 | 
				
			||||||
      res.cookie('DUMBDROP_PIN', '', {
 | 
					      // res.cookie('DUMBDROP_PIN', '', {
 | 
				
			||||||
        httpOnly: true,
 | 
					      //   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',
 | 
					      //   sameSite: 'strict',
 | 
				
			||||||
        path: '/'
 | 
					      //   path: '/'
 | 
				
			||||||
      });
 | 
					      // });
 | 
				
			||||||
      return res.json({ success: true, error: null });
 | 
					      res.clearCookie('DUMBDROP_PIN', { path: '/' });
 | 
				
			||||||
 | 
					      return res.json({ success: true, error: null, path: '/' });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Validate PIN format
 | 
					    // Validate PIN format
 | 
				
			||||||
@@ -63,7 +66,7 @@ router.post('/verify-pin', (req, res) => {
 | 
				
			|||||||
      // Set secure cookie with cleaned PIN
 | 
					      // Set secure cookie with cleaned PIN
 | 
				
			||||||
      res.cookie('DUMBDROP_PIN', cleanedPin, {
 | 
					      res.cookie('DUMBDROP_PIN', cleanedPin, {
 | 
				
			||||||
        httpOnly: true,
 | 
					        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',
 | 
					        sameSite: 'strict',
 | 
				
			||||||
        path: '/'
 | 
					        path: '/'
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user