Add security middleware to backend

Security features:
- Helmet: Security headers (XSS, clickjacking protection)
- CORS: Whitelist only allowed domains
- Rate Limiting: 100 req/15min general, 5 req/15min for auth
- Stripe webhook signature verification (already had)
- Admin API key protection (already had)

Allowed origins:
- wellnuo.smartlaunchhub.com
- wellnuo.com
- localhost (dev)
- Expo dev URLs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Sergei 2025-12-19 09:50:27 -08:00
parent e1b32560ff
commit 3a20d5cc08
3 changed files with 98 additions and 2 deletions

View File

@ -13,6 +13,8 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"express": "^4.18.2", "express": "^4.18.2",
"express-rate-limit": "^8.2.1",
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"pg": "^8.16.3", "pg": "^8.16.3",
@ -571,6 +573,24 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/express-rate-limit": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz",
"integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==",
"license": "MIT",
"dependencies": {
"ip-address": "10.0.1"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/express-rate-limit"
},
"peerDependencies": {
"express": ">= 4.11"
}
},
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@ -740,6 +760,15 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/helmet": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
"integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/http-errors": { "node_modules/http-errors": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
@ -794,6 +823,15 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/ip-address": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
"integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/ipaddr.js": { "node_modules/ipaddr.js": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",

View File

@ -13,6 +13,8 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"express": "^4.18.2", "express": "^4.18.2",
"express-rate-limit": "^8.2.1",
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"pg": "^8.16.3", "pg": "^8.16.3",

View File

@ -1,6 +1,8 @@
require('dotenv').config(); require('dotenv').config();
const express = require('express'); const express = require('express');
const cors = require('cors'); const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const path = require('path'); const path = require('path');
const apiRouter = require('./routes/api'); const apiRouter = require('./routes/api');
const stripeRouter = require('./routes/stripe'); const stripeRouter = require('./routes/stripe');
@ -10,8 +12,62 @@ const adminRouter = require('./routes/admin');
const app = express(); const app = express();
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 3000;
// CORS // ============ SECURITY ============
app.use(cors());
// Helmet - добавляет security headers
app.use(helmet({
contentSecurityPolicy: false // отключаем для admin panel
}));
// CORS - разрешаем только наши домены
const allowedOrigins = [
'https://wellnuo.smartlaunchhub.com',
'https://wellnuo.com',
'http://localhost:3000',
'http://localhost:8081', // Expo dev
'exp://192.168.1.*' // Expo local
];
app.use(cors({
origin: function(origin, callback) {
// Разрешаем запросы без origin (mobile apps, Postman)
if (!origin) return callback(null, true);
// Проверяем разрешённые домены
if (allowedOrigins.some(allowed => {
if (allowed.includes('*')) {
const regex = new RegExp(allowed.replace('*', '.*'));
return regex.test(origin);
}
return allowed === origin;
})) {
return callback(null, true);
}
callback(new Error('Not allowed by CORS'));
},
credentials: true
}));
// Rate Limiting - защита от DDoS
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 минут
max: 100, // максимум 100 запросов за 15 минут
message: { error: 'Too many requests, please try again later' },
standardHeaders: true,
legacyHeaders: false
});
// Более строгий лимит для auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // только 5 попыток логина за 15 минут
message: { error: 'Too many login attempts, please try again later' }
});
// Применяем rate limiting
app.use(limiter);
app.use('/function/well-api/api', authLimiter); // Legacy API с auth
// Stripe webhooks need raw body for signature verification // Stripe webhooks need raw body for signature verification
// Must be before express.json() // Must be before express.json()