All checks were successful
Build and Push Docker Images / docker (push) Successful in 1m22s
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>
202 lines
7.5 KiB
Plaintext
202 lines
7.5 KiB
Plaintext
# /etc/nginx/sites-available/puffinoffset.com
|
|
|
|
# 1) Redirect all HTTP to HTTPS, except the ACME challenge path
|
|
server {
|
|
listen 80;
|
|
server_name puffinoffset.com;
|
|
|
|
# Allow certbot to do HTTP-01 challenges
|
|
location ^~ /.well-known/acme-challenge/ {
|
|
root /var/www/html; # adjust if your webroot differs
|
|
try_files $uri =404;
|
|
}
|
|
|
|
# Redirect everything else to HTTPS
|
|
location / {
|
|
return 301 https://$host$request_uri;
|
|
}
|
|
}
|
|
|
|
# 2) HTTPS server block: reverse-proxy to your Docker app on localhost:3800
|
|
server {
|
|
listen 443 ssl http2;
|
|
server_name puffinoffset.com;
|
|
|
|
# === SSL certs from Let's Encrypt ===
|
|
# ssl_certificate /etc/letsencrypt/live/puffinoffset.com/fullchain.pem;
|
|
# ssl_certificate_key /etc/letsencrypt/live/puffinoffset.com/privkey.pem;
|
|
include /etc/letsencrypt/options-ssl-nginx.conf; # from certbot
|
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # from certbot
|
|
|
|
# === Proxy for direct image requests from Wren API ===
|
|
location ~* ^/images/(.*)$ {
|
|
proxy_pass https://www.wren.co/images/$1;
|
|
proxy_ssl_server_name on;
|
|
proxy_set_header Host www.wren.co;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_buffers 16 4k;
|
|
proxy_buffer_size 2k;
|
|
|
|
# Add CORS headers for images
|
|
add_header Access-Control-Allow-Origin '*' always;
|
|
add_header Access-Control-Allow-Methods 'GET, OPTIONS' always;
|
|
add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization' always;
|
|
|
|
# Cache control for images
|
|
expires 7d;
|
|
add_header Cache-Control "public, max-age=604800";
|
|
|
|
# Handle OPTIONS requests for CORS preflight
|
|
if ($request_method = 'OPTIONS') {
|
|
add_header Access-Control-Allow-Origin '*';
|
|
add_header Access-Control-Allow-Methods 'GET, OPTIONS';
|
|
add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization';
|
|
add_header Access-Control-Max-Age 1728000;
|
|
add_header Content-Type 'text/plain charset=UTF-8';
|
|
add_header Content-Length 0;
|
|
return 204;
|
|
}
|
|
}
|
|
|
|
# === Proxy for Wren API requests ===
|
|
location /api/wren/ {
|
|
proxy_pass https://www.wren.co/api/;
|
|
proxy_ssl_server_name on;
|
|
proxy_set_header Host www.wren.co;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
# Add CORS headers for API requests
|
|
add_header Access-Control-Allow-Origin '*' always;
|
|
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS' always;
|
|
add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization' always;
|
|
|
|
# Handle OPTIONS requests for CORS preflight
|
|
if ($request_method = 'OPTIONS') {
|
|
add_header Access-Control-Allow-Origin '*';
|
|
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
|
|
add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization';
|
|
add_header Access-Control-Max-Age 1728000;
|
|
add_header Content-Type 'text/plain charset=UTF-8';
|
|
add_header Content-Length 0;
|
|
return 204;
|
|
}
|
|
}
|
|
|
|
# === Backend API - Stripe Webhooks (specific route, no trailing slash) ===
|
|
location = /api/webhooks/stripe {
|
|
proxy_pass http://127.0.0.1:3001/api/webhooks/stripe;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
# Stripe requires raw body for signature verification
|
|
proxy_request_buffering off;
|
|
|
|
# CORS headers
|
|
add_header Access-Control-Allow-Origin '*' always;
|
|
add_header Access-Control-Allow-Methods 'POST, OPTIONS' always;
|
|
add_header Access-Control-Allow-Headers 'Content-Type, Stripe-Signature' always;
|
|
}
|
|
|
|
# === Backend API - All other API routes ===
|
|
location /api/ {
|
|
proxy_pass http://127.0.0.1:3001/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
# CORS headers for API
|
|
add_header Access-Control-Allow-Origin '*' always;
|
|
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS' always;
|
|
add_header Access-Control-Allow-Headers 'Content-Type, Authorization' always;
|
|
|
|
# API timeouts
|
|
proxy_read_timeout 120;
|
|
proxy_connect_timeout 120;
|
|
proxy_send_timeout 120;
|
|
|
|
# Handle OPTIONS for CORS preflight
|
|
if ($request_method = 'OPTIONS') {
|
|
add_header Access-Control-Allow-Origin '*';
|
|
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
|
|
add_header Access-Control-Allow-Headers 'Content-Type, Authorization';
|
|
add_header Access-Control-Max-Age 1728000;
|
|
add_header Content-Length 0;
|
|
return 204;
|
|
}
|
|
}
|
|
|
|
# === Proxy all other traffic to Frontend ===
|
|
location / {
|
|
proxy_pass http://127.0.0.1:3800;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
# Add CORS headers for all responses
|
|
add_header Access-Control-Allow-Origin '*' always;
|
|
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS' always;
|
|
add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization' always;
|
|
|
|
# Increase timeouts for potentially slow API calls
|
|
proxy_read_timeout 120;
|
|
proxy_connect_timeout 120;
|
|
proxy_send_timeout 120;
|
|
}
|
|
|
|
# === Additional common settings ===
|
|
|
|
# Increase client body size for file uploads if needed
|
|
client_max_body_size 10M;
|
|
|
|
# Enable compression for better performance
|
|
gzip on;
|
|
gzip_comp_level 5;
|
|
gzip_min_length 256;
|
|
gzip_proxied any;
|
|
gzip_vary on;
|
|
gzip_types
|
|
application/atom+xml
|
|
application/javascript
|
|
application/json
|
|
application/ld+json
|
|
application/manifest+json
|
|
application/rss+xml
|
|
application/vnd.geo+json
|
|
application/vnd.ms-fontobject
|
|
application/x-font-ttf
|
|
application/x-web-app-manifest+json
|
|
application/xhtml+xml
|
|
application/xml
|
|
font/opentype
|
|
image/bmp
|
|
image/svg+xml
|
|
image/x-icon
|
|
text/cache-manifest
|
|
text/css
|
|
text/plain
|
|
text/vcard
|
|
text/vnd.rim.location.xloc
|
|
text/vtt
|
|
text/x-component
|
|
text/x-cross-domain-policy;
|
|
|
|
# Optional: serve static assets directly if you ever add any here
|
|
# location /static/ {
|
|
# root /var/www/puffinoffset.com;
|
|
# try_files $uri $uri/ =404;
|
|
# }
|
|
}
|