WellNuo/backend/src/index.js
Sergei bcb444f5ba 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>
2025-12-19 09:50:27 -08:00

122 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 apiRouter = require('./routes/api');
const stripeRouter = require('./routes/stripe');
const webhookRouter = require('./routes/webhook');
const adminRouter = require('./routes/admin');
const app = express();
const PORT = process.env.PORT || 3000;
// ============ SECURITY ============
// 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
// Must be before express.json()
app.use('/api/webhook/stripe', express.raw({ type: 'application/json' }));
// JSON body parser for other routes
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// ============ ROUTES ============
// Legacy API route - matches old API structure
// POST /function/well-api/api with function parameter
app.use('/function/well-api/api', apiRouter);
// New REST API routes
app.use('/api/stripe', stripeRouter);
app.use('/api/webhook', webhookRouter);
app.use('/api/admin', adminRouter);
// Admin UI
app.get('/admin', (req, res) => {
res.sendFile(path.join(__dirname, 'admin', 'index.html'));
});
// Health check
app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
stripe: process.env.STRIPE_SECRET_KEY ? 'configured' : 'missing'
});
});
// API info
app.get('/api', (req, res) => {
res.json({
name: 'WellNuo API',
version: '1.0.0',
endpoints: {
legacy: '/function/well-api/api',
stripe: '/api/stripe',
webhook: '/api/webhook/stripe'
}
});
});
app.listen(PORT, () => {
console.log(`WellNuo API running on port ${PORT}`);
console.log(`Stripe: ${process.env.STRIPE_SECRET_KEY ? '✓ configured' : '✗ missing'}`);
});