puffin-app/server/index.js
Matt bc9e2d3782
All checks were successful
Build and Push Docker Images / docker (push) Successful in 1m22s
Implement comprehensive Stripe security fixes and production deployment
CRITICAL SECURITY FIXES:
- Add webhook secret validation to prevent signature bypass
- Implement idempotency protection across all webhook handlers
- Add atomic database updates to prevent race conditions
- Improve CORS security with origin validation and logging
- Remove .env from git tracking to protect secrets

STRIPE INTEGRATION:
- Add support for checkout.session.expired webhook event
- Add Stripe publishable key to environment configuration
- Fix webhook handlers with proper idempotency checks
- Update Order model with atomic updatePaymentAndStatus method
- Add comprehensive logging for webhook processing

DEPLOYMENT ARCHITECTURE:
- Split into two Docker images (frontend-latest, backend-latest)
- Update CI/CD to build separate frontend and backend images
- Configure backend on port 3801 (internal 3001)
- Add production-ready docker-compose.yml
- Remove redundant docker-compose.portainer.yml
- Update nginx configuration for both frontend and backend

DOCUMENTATION:
- Add PRODUCTION-SETUP.md with complete deployment guide
- Add docs/stripe-security-fixes.md with security audit details
- Add docs/stripe-checkout-sessions.md with integration docs
- Add docs/stripe-webhooks.md with webhook configuration
- Update .env.example with all required variables including Stripe publishable key

CONFIGURATION:
- Consolidate to single .env.example template
- Update .gitignore to protect all .env variants
- Add server/Dockerfile for backend container
- Update DEPLOYMENT.md with new architecture

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 12:18:57 +01:00

107 lines
3.5 KiB
JavaScript

import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import { initializeDatabase } from './config/database.js';
import checkoutRoutes from './routes/checkout.js';
import webhookRoutes from './routes/webhooks.js';
const app = express();
const PORT = process.env.PORT || 3001;
// Initialize database
console.log('🗄️ Initializing database...');
initializeDatabase();
// CORS configuration
const corsOptions = {
origin: (origin, callback) => {
const allowedOrigins = [
process.env.FRONTEND_URL || 'http://localhost:5173',
'http://localhost:5173', // Always allow local development
'http://localhost:3800', // Docker frontend
].filter(Boolean);
// Allow requests with no origin (mobile apps, Postman, etc.)
if (!origin) {
return callback(null, true);
}
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
console.warn(`🚫 CORS blocked request from origin: ${origin}`);
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
};
app.use(cors(corsOptions));
// IMPORTANT: Webhook routes must come BEFORE express.json() middleware
// because Stripe webhooks require raw body for signature verification
app.use('/api/webhooks', webhookRoutes);
// JSON body parser for all other routes
app.use(express.json());
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// API Routes
app.use('/api/checkout', checkoutRoutes);
// Error handling middleware
app.use((err, req, res, next) => {
console.error('❌ Unhandled error:', err);
res.status(500).json({
error: 'Internal server error',
message: process.env.NODE_ENV === 'development' ? err.message : undefined,
});
});
// 404 handler
app.use((req, res) => {
res.status(404).json({ error: 'Not found' });
});
// Start server
app.listen(PORT, () => {
console.log('');
console.log('🚀 Puffin App Server');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log(`📡 Server running on port ${PORT}`);
console.log(`🌐 Frontend URL: ${process.env.FRONTEND_URL}`);
console.log(`🔑 Stripe configured: ${!!process.env.STRIPE_SECRET_KEY}`);
console.log(`🌱 Wren API configured: ${!!process.env.WREN_API_TOKEN}`);
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log('');
console.log('📝 Available endpoints:');
console.log(` GET http://localhost:${PORT}/health`);
console.log(` POST http://localhost:${PORT}/api/checkout/create-session`);
console.log(` GET http://localhost:${PORT}/api/checkout/session/:sessionId`);
console.log(` POST http://localhost:${PORT}/api/webhooks/stripe`);
console.log('');
console.log('🎣 Webhook events handled:');
console.log(' - checkout.session.completed');
console.log(' - checkout.session.async_payment_succeeded');
console.log(' - checkout.session.async_payment_failed');
console.log(' - checkout.session.expired');
console.log('');
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('👋 SIGTERM received, shutting down gracefully...');
process.exit(0);
});
process.on('SIGINT', () => {
console.log('👋 SIGINT received, shutting down gracefully...');
process.exit(0);
});