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
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>
12 KiB
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