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>
259 lines
6.2 KiB
Markdown
259 lines
6.2 KiB
Markdown
# Production Setup Guide
|
|
|
|
## Quick Start
|
|
|
|
### 1. Create Your .env File
|
|
|
|
Copy the template and fill in your secrets:
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
Then edit `.env` with your actual values:
|
|
|
|
```bash
|
|
# === Frontend Variables ===
|
|
VITE_API_BASE_URL=https://puffinoffset.com/api
|
|
VITE_WREN_API_TOKEN=35c025d9-5dbb-404b-85aa-19b09da0578d
|
|
VITE_FORMSPREE_CONTACT_ID=xkgovnby
|
|
VITE_FORMSPREE_OFFSET_ID=xvgzbory
|
|
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_51SJc52Pdj1mnVT5k8a2NsdyywF6jlpR2VTAMeHOSoXskOQBNRyKpA35G6sJ2ckgv6UPXq9LbiIspFC6E4Yrppk9m00yAMX8K9Z
|
|
|
|
# === Backend Variables ===
|
|
NODE_ENV=production
|
|
PORT=3001
|
|
FRONTEND_URL=https://puffinoffset.com
|
|
|
|
# === Stripe Configuration (Test Mode) ===
|
|
STRIPE_SECRET_KEY=sk_test_51SJc52Pdj1mnVT5kkkJQgPpjQPkrf8D6Ik0yvdHgCYHOjZXwdRo3wCMZ4YjqaMDEQL0gyNhUgZZ0sAo4YIGTn6f500Or1vuuxJ
|
|
STRIPE_WEBHOOK_SECRET=whsec_6hNtwjRPUvxY3MKOJYfyOZRDZW7mlIsB
|
|
|
|
# === Wren API Configuration ===
|
|
WREN_API_TOKEN=35c025d9-5dbb-404b-85aa-19b09da0578d
|
|
WREN_DRY_RUN=true
|
|
|
|
# === Database Configuration ===
|
|
DATABASE_PATH=/app/data/orders.db
|
|
```
|
|
|
|
### 2. Deploy with Docker Compose
|
|
|
|
```bash
|
|
# Pull latest images
|
|
docker compose pull
|
|
|
|
# Start containers
|
|
docker compose up -d
|
|
|
|
# View logs
|
|
docker compose logs -f
|
|
|
|
# Check status
|
|
docker compose ps
|
|
```
|
|
|
|
### 3. Configure Nginx on Host
|
|
|
|
Update your `/etc/nginx/sites-available/puffinoffset.com` configuration:
|
|
|
|
```nginx
|
|
# Frontend
|
|
server {
|
|
listen 443 ssl http2;
|
|
server_name puffinoffset.com;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/puffinoffset.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/puffinoffset.com/privkey.pem;
|
|
|
|
location / {
|
|
proxy_pass http://localhost:3800;
|
|
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;
|
|
}
|
|
}
|
|
|
|
# Backend API
|
|
server {
|
|
listen 443 ssl http2;
|
|
server_name puffinoffset.com;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/puffinoffset.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/puffinoffset.com/privkey.pem;
|
|
|
|
# Stripe webhooks (MUST use raw body)
|
|
location /api/webhooks/stripe {
|
|
proxy_pass http://localhost:3801;
|
|
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;
|
|
|
|
# CRITICAL: Don't buffer request body for webhook signature verification
|
|
proxy_request_buffering off;
|
|
proxy_buffering off;
|
|
client_max_body_size 10m;
|
|
}
|
|
|
|
# All other API routes
|
|
location /api/ {
|
|
proxy_pass http://localhost:3801;
|
|
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;
|
|
}
|
|
}
|
|
|
|
# HTTP redirect
|
|
server {
|
|
listen 80;
|
|
server_name puffinoffset.com;
|
|
|
|
location /.well-known/acme-challenge/ {
|
|
root /var/www/html;
|
|
}
|
|
|
|
location / {
|
|
return 301 https://$host$request_uri;
|
|
}
|
|
}
|
|
```
|
|
|
|
Reload nginx:
|
|
```bash
|
|
sudo nginx -t
|
|
sudo systemctl reload nginx
|
|
```
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Internet
|
|
↓
|
|
Host Nginx (SSL) :443
|
|
↓
|
|
├─→ Frontend Container :3800
|
|
└─→ Backend Container :3801
|
|
```
|
|
|
|
**Port Mapping:**
|
|
- Frontend: Host 3800 → Container 3000
|
|
- Backend: Host 3801 → Container 3001
|
|
|
|
## Stripe Configuration
|
|
|
|
### Required Stripe Keys:
|
|
1. **Publishable Key** (Frontend): `pk_test_*` for test mode
|
|
2. **Secret Key** (Backend): `sk_test_*` for test mode
|
|
3. **Webhook Secret** (Backend): `whsec_*` from Stripe Dashboard
|
|
|
|
### Configure Webhooks in Stripe Dashboard:
|
|
|
|
1. Go to: https://dashboard.stripe.com/test/webhooks
|
|
2. Click "Add endpoint"
|
|
3. URL: `https://puffinoffset.com/api/webhooks/stripe`
|
|
4. Select these events:
|
|
- `checkout.session.completed`
|
|
- `checkout.session.async_payment_succeeded`
|
|
- `checkout.session.async_payment_failed`
|
|
- `checkout.session.expired`
|
|
|
|
### Test Mode Configuration:
|
|
- ✅ Using `sk_test_*` and `pk_test_*` keys
|
|
- ✅ Using `WREN_DRY_RUN=true` (no real offsets purchased)
|
|
- ✅ Test card: 4242 4242 4242 4242
|
|
- ✅ No real charges will occur
|
|
|
|
### Going Live:
|
|
When ready for production:
|
|
1. Get live Stripe keys (`sk_live_*` and `pk_live_*`)
|
|
2. Configure production webhook endpoint
|
|
3. Set `WREN_DRY_RUN=false`
|
|
4. Update `.env` with live keys
|
|
|
|
## Testing
|
|
|
|
### Test Stripe Integration:
|
|
```bash
|
|
# Use test card in checkout:
|
|
Card: 4242 4242 4242 4242
|
|
Date: Any future date
|
|
CVC: Any 3 digits
|
|
ZIP: Any 5 digits
|
|
```
|
|
|
|
### Verify Webhooks:
|
|
```bash
|
|
# Check backend logs
|
|
docker compose logs -f backend
|
|
|
|
# Should see:
|
|
# ✅ Checkout session completed
|
|
# 💳 Payment confirmed
|
|
# 🌱 Fulfilling order via Wren API
|
|
# ⚠️ DRY RUN MODE: No real offset will be created
|
|
```
|
|
|
|
### Check Health:
|
|
```bash
|
|
curl https://puffinoffset.com/api/health
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Container won't start:
|
|
```bash
|
|
docker compose logs backend
|
|
docker compose logs web
|
|
```
|
|
|
|
### Webhook errors:
|
|
- Verify `STRIPE_WEBHOOK_SECRET` matches Stripe Dashboard
|
|
- Check nginx is not buffering webhook requests
|
|
- View webhook attempts in Stripe Dashboard
|
|
|
|
### Database issues:
|
|
```bash
|
|
# Check database volume
|
|
docker volume ls
|
|
docker volume inspect puffin-app_puffin-data
|
|
|
|
# Access database
|
|
docker compose exec backend sh
|
|
sqlite3 /app/data/orders.db
|
|
```
|
|
|
|
## Security Checklist
|
|
|
|
- ✅ `.env` excluded from git
|
|
- ✅ Webhook signature verification enabled
|
|
- ✅ CORS restricted to allowed origins
|
|
- ✅ Idempotency protection on webhooks
|
|
- ✅ SSL/TLS on host nginx
|
|
- ⚠️ Rotate Wren API token (it was previously committed to git)
|
|
|
|
## CI/CD Pipeline
|
|
|
|
Gitea Actions automatically builds and pushes images on push to `main`:
|
|
- Frontend: `code.puffinoffset.com/matt/puffin-app:frontend-latest`
|
|
- Backend: `code.puffinoffset.com/matt/puffin-app:backend-latest`
|
|
|
|
To deploy updates:
|
|
```bash
|
|
docker compose pull
|
|
docker compose up -d
|
|
```
|
|
|
|
## Files Overview
|
|
|
|
| File | Purpose | In Git? |
|
|
|------|---------|---------|
|
|
| `.env.example` | Template with placeholders | ✅ Yes |
|
|
| `.env` | Your actual secrets | ❌ No (gitignored) |
|
|
| `docker-compose.yml` | Production deployment | ✅ Yes |
|
|
| `nginx-host.conf` | Example nginx config | ✅ Yes |
|
|
|
|
**NEVER commit `.env` with real secrets!**
|