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:
parent
e1b32560ff
commit
3a20d5cc08
38
backend/package-lock.json
generated
38
backend/package-lock.json
generated
@ -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",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user