littleshop/DEPLOYMENT_NGINX_GUIDE.md
SysAdmin a2247d7c02
Some checks failed
Build and Deploy LittleShop / Build TeleBot Docker Image (push) Failing after 11s
Build and Deploy LittleShop / Build LittleShop Docker Image (push) Failing after 15s
Build and Deploy LittleShop / Deploy to Production VPS (Manual Only) (push) Has been skipped
Build and Deploy LittleShop / Deploy to Pre-Production (CT109) (push) Has been skipped
feat: Add customer management, payments, and push notifications with security enhancements
Major Feature Additions:
- Customer management: Full CRUD with data export and privacy compliance
- Payment management: Centralized payment tracking and administration
- Push notification subscriptions: Manage and track web push subscriptions

Security Enhancements:
- IP whitelist middleware for administrative endpoints
- Data retention service with configurable policies
- Enhanced push notification security documentation
- Security fixes progress tracking (2025-11-14)

UI/UX Improvements:
- Enhanced navigation with improved mobile responsiveness
- Updated admin dashboard with order status counts
- Improved product CRUD forms
- New customer and payment management interfaces

Backend Improvements:
- Extended customer service with data export capabilities
- Enhanced order service with status count queries
- Improved crypto payment service with better error handling
- Updated validators and configuration

Documentation:
- DEPLOYMENT_NGINX_GUIDE.md: Nginx deployment instructions
- IP_STORAGE_ANALYSIS.md: IP storage security analysis
- PUSH_NOTIFICATION_SECURITY.md: Push notification security guide
- UI_UX_IMPROVEMENT_PLAN.md: Planned UI/UX enhancements
- UI_UX_IMPROVEMENTS_COMPLETED.md: Completed improvements

Cleanup:
- Removed temporary database WAL files
- Removed stale commit message file

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 19:33:02 +00:00

12 KiB

Nginx Deployment Guide - Push Notification Security

This guide explains how to properly configure nginx to isolate the admin panel while keeping push notifications functional.

Architecture Overview

Internet
    │
    ├─── api.littleshop.com (Public API)
    │    └─── Only: /api/push/vapid-key, /api/push/subscribe/customer, /api/push/unsubscribe
    │
    └─── admin.dark.side (LAN-Only Admin Panel)
         └─── Full access: /Admin/*, /api/*, /blazor/*

LittleShop Backend (littleshop:5000)

Complete Nginx Configuration

Step 1: Create upstream backend

# /etc/nginx/conf.d/littleshop-upstream.conf
upstream littleshop_backend {
    server littleshop:5000;
    keepalive 32;
}

Step 2: Public API Server (Internet-Accessible)

# /etc/nginx/sites-available/littleshop-public-api.conf

# Rate limiting zones
limit_req_zone $binary_remote_addr zone=push_vapid:10m rate=60r/m;
limit_req_zone $binary_remote_addr zone=push_subscribe:10m rate=10r/m;
limit_req_zone $binary_remote_addr zone=push_unsubscribe:10m rate=20r/m;

server {
    listen 80;
    listen [::]:80;
    server_name api.littleshop.com;

    # Redirect to HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name api.littleshop.com;

    # SSL configuration
    ssl_certificate /etc/letsencrypt/live/api.littleshop.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.littleshop.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    # Security headers
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # VAPID public key endpoint (required for push notifications)
    location = /api/push/vapid-key {
        limit_req zone=push_vapid burst=10;

        proxy_pass http://littleshop_backend;
        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 push notifications
        add_header Access-Control-Allow-Origin * always;
        add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Content-Type" always;

        # Preflight requests
        if ($request_method = OPTIONS) {
            return 204;
        }
    }

    # Customer subscription endpoint
    location = /api/push/subscribe/customer {
        limit_req zone=push_subscribe burst=5;

        proxy_pass http://littleshop_backend;
        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
        add_header Access-Control-Allow-Origin * always;
        add_header Access-Control-Allow-Methods "POST, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Content-Type" always;

        if ($request_method = OPTIONS) {
            return 204;
        }
    }

    # Unsubscribe endpoint
    location = /api/push/unsubscribe {
        limit_req zone=push_unsubscribe burst=10;

        proxy_pass http://littleshop_backend;
        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
        add_header Access-Control-Allow-Origin * always;
        add_header Access-Control-Allow-Methods "POST, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Content-Type" always;

        if ($request_method = OPTIONS) {
            return 204;
        }
    }

    # Block all other endpoints
    location / {
        return 403 '{"error": "Access denied", "message": "Admin access is restricted to authorized networks"}';
        add_header Content-Type application/json;
    }

    # Custom error pages
    error_page 403 /403.json;
    location = /403.json {
        internal;
        return 403 '{"error": "Access denied", "message": "This endpoint is not publicly accessible"}';
        add_header Content-Type application/json;
    }
}

Step 3: Admin Panel Server (LAN-Only)

# /etc/nginx/sites-available/littleshop-admin.conf

server {
    listen 80;
    listen [::]:80;
    server_name admin.dark.side admin.littleshop.local;

    # Redirect to HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name admin.dark.side admin.littleshop.local;

    # SSL configuration
    ssl_certificate /etc/letsencrypt/live/admin.dark.side/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/admin.dark.side/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    # LAN-only IP whitelist
    # Private IPv4 ranges
    allow 10.0.0.0/8;         # Class A private network
    allow 172.16.0.0/12;      # Class B private network
    allow 192.168.0.0/16;     # Class C private network
    allow 127.0.0.1;          # Localhost

    # Private IPv6 ranges
    allow fc00::/7;           # Unique local addresses
    allow ::1;                # IPv6 localhost

    # Add your specific office/VPN IPs here
    # allow 203.0.113.50;     # Example: Office static IP
    # allow 198.51.100.0/24;  # Example: VPN subnet

    # Deny all other IPs
    deny all;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;

    # Proxy settings
    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;
    proxy_cache_bypass $http_upgrade;
    proxy_buffering off;

    # Admin panel
    location /Admin/ {
        proxy_pass http://littleshop_backend;
    }

    # API endpoints
    location /api/ {
        proxy_pass http://littleshop_backend;
    }

    # Blazor
    location /blazor/ {
        proxy_pass http://littleshop_backend;
    }

    # SignalR hubs
    location /activityHub {
        proxy_pass http://littleshop_backend;
    }

    location /notificationHub {
        proxy_pass http://littleshop_backend;
    }

    # Static files
    location /css/ {
        proxy_pass http://littleshop_backend;
    }

    location /js/ {
        proxy_pass http://littleshop_backend;
    }

    location /lib/ {
        proxy_pass http://littleshop_backend;
    }

    location /uploads/ {
        proxy_pass http://littleshop_backend;
    }

    # Favicon and manifest
    location ~ ^/(favicon\.ico|site\.webmanifest|manifest\.json)$ {
        proxy_pass http://littleshop_backend;
    }

    # Health check endpoint
    location /health {
        proxy_pass http://littleshop_backend;
        access_log off;
    }

    # Root
    location / {
        proxy_pass http://littleshop_backend;
    }

    # Logging
    access_log /var/log/nginx/littleshop-admin-access.log;
    error_log /var/log/nginx/littleshop-admin-error.log warn;
}

Step 4: Enable sites

# Create symbolic links
sudo ln -s /etc/nginx/sites-available/littleshop-public-api.conf /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/littleshop-admin.conf /etc/nginx/sites-enabled/

# Test configuration
sudo nginx -t

# Reload nginx
sudo systemctl reload nginx

Testing the Configuration

Test Public Endpoints (Should Work)

# VAPID key endpoint
curl https://api.littleshop.com/api/push/vapid-key

# Customer subscription (with valid customerId)
curl -X POST https://api.littleshop.com/api/push/subscribe/customer?customerId=<guid> \
  -H "Content-Type: application/json" \
  -d '{"endpoint":"...","keys":{"p256dh":"...","auth":"..."}}'

# Unsubscribe
curl -X POST https://api.littleshop.com/api/push/unsubscribe \
  -H "Content-Type: application/json" \
  -d '{"endpoint":"..."}'

Test Blocked Endpoints (Should Return 403)

# Admin API (should be blocked from internet)
curl https://api.littleshop.com/api/push/subscribe

# Admin panel (should be blocked from internet)
curl https://api.littleshop.com/Admin/Dashboard

Test Admin Panel from LAN (Should Work)

# From inside LAN
curl https://admin.dark.side/Admin/Dashboard
curl https://admin.dark.side/api/push/subscriptions

Docker Compose Integration

If using Docker Compose with Nginx Proxy Manager:

# docker-compose.yml
version: '3.8'

services:
  littleshop:
    image: littleshop:latest
    container_name: littleshop
    networks:
      - littleshop-network
      - nginx-proxy-network
    environment:
      - ASPNETCORE_URLS=http://+:5000
    ports:
      - "5000:5000"  # Only accessible from docker network

  nginx-proxy-manager:
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: nginx-proxy-manager
    networks:
      - nginx-proxy-network
    ports:
      - '80:80'
      - '443:443'
      - '81:81'  # Admin UI
    volumes:
      - ./nginx-data:/data
      - ./nginx-letsencrypt:/etc/letsencrypt

networks:
  littleshop-network:
    driver: bridge
  nginx-proxy-network:
    driver: bridge

Monitoring and Alerting

Monitor Failed Access Attempts

# Add to admin server block
location /Admin/ {
    access_log /var/log/nginx/admin-access.log combined;
    error_log /var/log/nginx/admin-error.log warn;

    # Log denied IPs
    if ($remote_addr !~* "^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)") {
        access_log /var/log/nginx/admin-denied.log combined;
    }

    proxy_pass http://littleshop_backend;
}

Set up fail2ban for repeated access attempts

# /etc/fail2ban/filter.d/nginx-littleshop-admin.conf
[Definition]
failregex = ^<HOST> .* "GET /Admin/
            ^<HOST> .* "POST /api/push/(subscribe|test|broadcast)
ignoreregex =
# /etc/fail2ban/jail.local
[nginx-littleshop-admin]
enabled = true
port = http,https
filter = nginx-littleshop-admin
logpath = /var/log/nginx/admin-denied.log
maxretry = 5
bantime = 3600
findtime = 600

Production Checklist

  • SSL certificates installed and configured
  • Firewall rules updated to allow 80/443
  • Rate limiting configured for public endpoints
  • IP whitelist configured for admin panel
  • Monitoring and logging enabled
  • fail2ban configured for intrusion detection
  • Health checks working
  • DNS records pointing to correct servers
  • Backup procedures in place
  • Team has VPN access for admin panel

Troubleshooting

Issue: "502 Bad Gateway"

Solution: Check that LittleShop backend is running on port 5000

docker ps | grep littleshop
curl http://localhost:5000/health

Issue: "403 Forbidden" from LAN

Solution: Check IP whitelist includes your LAN subnet

# Check your IP
ip addr show
# Or on Windows
ipconfig

Issue: Push notifications not working

Solution: Verify public endpoints are accessible

curl -v https://api.littleshop.com/api/push/vapid-key
# Should return 200 OK with public key

Issue: CORS errors in browser

Solution: Check CORS headers are present in nginx config

curl -H "Origin: https://example.com" https://api.littleshop.com/api/push/vapid-key -v
# Look for Access-Control-Allow-Origin header