fix(nginx): unblock public API routes powering lead tools/flows (HC sales killer)
api.performancewest.net uses an explicit per-path allowlist; everything else
falls through to a trusted-IP-only catch-all that returns 403. Six browser-
facing routes had no location block, so they 403'd for every public visitor:
/api/v1/npi/ <- THE healthcare sales killer. The 'Free NPI
Compliance Check' tool (top of the HC funnel,
where every HC campaign sends traffic) fetches
/api/v1/npi/lookup. It 403'd -> CORS error in
the browser -> the tool never rendered results
or the upsell CTAs (Revalidation $399 / NPPES
$149 / Bundle $899) -> 0 HC sales despite 17
sessions reaching it in 30d and 0 HC orders
EVER created in the compliance DB.
/api/v1/cdr/ telecom CDR profile tool
/api/v1/icc/ intrastate/ICC profile tool
/api/v1/corp/ corporate foreign-qual check
/api/v1/foreign-qualification/ foreign qualification quote/jurisdictions
/api/v1/lnpa-regions LNPA region lookup
Added explicit proxy_pass blocks (mirroring the existing entities/identity
pattern) before the catch-all. Verified live: all six now reach the app with
proper CORS; the NPI tool renders results + order CTAs end-to-end via a real
browser; npi-revalidation order page -> Stripe confirmed.
The live /etc/nginx/sites-enabled/pw-api.conf was hand-edited and untracked;
committing the current state here so it is version-controlled. (Live backup:
/root/pw-api.conf.bak_20260623.)
This commit is contained in:
parent
a90cdc9066
commit
14357a0223
1 changed files with 313 additions and 0 deletions
313
infra/nginx/sites-enabled/pw-api.conf
Normal file
313
infra/nginx/sites-enabled/pw-api.conf
Normal file
|
|
@ -0,0 +1,313 @@
|
||||||
|
# HTTPS config for api.performancewest.net
|
||||||
|
# Restricted: only specific paths are publicly accessible.
|
||||||
|
# Admin/internal paths require trusted IP.
|
||||||
|
|
||||||
|
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
|
||||||
|
|
||||||
|
geo $api_trusted {
|
||||||
|
default 0;
|
||||||
|
127.0.0.1/32 1;
|
||||||
|
172.16.0.0/12 1;
|
||||||
|
10.0.0.0/8 1;
|
||||||
|
207.174.124.71/32 1;
|
||||||
|
76.228.206.147/32 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name api.performancewest.net;
|
||||||
|
location /.well-known/acme-challenge/ { root /var/www/certbot; }
|
||||||
|
location / { return 301 https://api.performancewest.net$request_uri; }
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
http2 on;
|
||||||
|
server_name api.performancewest.net;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/api.performancewest.net/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/api.performancewest.net/privkey.pem;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_session_cache shared:SSL:10m;
|
||||||
|
ssl_session_timeout 10m;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
||||||
|
client_max_body_size 50m;
|
||||||
|
|
||||||
|
# Shared proxy settings
|
||||||
|
set $api_backend http://127.0.0.1:3001;
|
||||||
|
|
||||||
|
# ── Webhooks: fully public (Stripe, PayPal, SHKeeper) ──
|
||||||
|
location ^~ /api/v1/webhooks/ {
|
||||||
|
limit_req zone=api_limit burst=10 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Public API paths (browser frontend needs these) ──
|
||||||
|
location ^~ /api/v1/fcc/ {
|
||||||
|
limit_req zone=api_limit burst=20 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
proxy_connect_timeout 10s;
|
||||||
|
proxy_send_timeout 60s;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/id-upload/ {
|
||||||
|
limit_req zone=api_limit burst=10 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
client_max_body_size 20M;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/dot/ {
|
||||||
|
limit_req zone=api_limit burst=20 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
proxy_connect_timeout 10s;
|
||||||
|
proxy_send_timeout 60s;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/discount/ {
|
||||||
|
limit_req zone=api_limit burst=10 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/auth/ {
|
||||||
|
limit_req zone=api_limit burst=10 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/subscribe {
|
||||||
|
limit_req zone=api_limit burst=5 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/tickets {
|
||||||
|
limit_req zone=api_limit burst=5 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/checkout/ {
|
||||||
|
limit_req zone=api_limit burst=10 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
proxy_send_timeout 60s;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/compliance-orders {
|
||||||
|
limit_req zone=api_limit burst=10 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
proxy_send_timeout 60s;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/portal {
|
||||||
|
limit_req zone=api_limit burst=10 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/canada-crtc {
|
||||||
|
limit_req zone=api_limit burst=10 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
proxy_send_timeout 60s;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/amb/ {
|
||||||
|
limit_req zone=api_limit burst=10 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/formations {
|
||||||
|
limit_req zone=api_limit burst=10 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
proxy_send_timeout 60s;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/paypal {
|
||||||
|
limit_req zone=api_limit burst=10 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/entities/ {
|
||||||
|
limit_req zone=api_limit burst=10 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/identity {
|
||||||
|
limit_req zone=api_limit burst=5 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Public site routes (browser-facing tools/flows) ──
|
||||||
|
# Added: these power public lead tools + order flows and MUST be reachable by
|
||||||
|
# untrusted visitors. Without an explicit block they fell through to the
|
||||||
|
# trusted-IP-only catch-all and returned 403, silently breaking:
|
||||||
|
# npi -> Healthcare "Free NPI Compliance Check" (top of the HC funnel; the
|
||||||
|
# fetch failed CORS/403 so the tool never rendered results -> 0 HC sales)
|
||||||
|
# cdr/icc/corp/foreign-qualification/lnpa-regions -> telecom/corporate tools.
|
||||||
|
location ^~ /api/v1/npi/ {
|
||||||
|
limit_req zone=api_limit burst=20 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/cdr/ {
|
||||||
|
limit_req zone=api_limit burst=10 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/icc/ {
|
||||||
|
limit_req zone=api_limit burst=10 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/corp/ {
|
||||||
|
limit_req zone=api_limit burst=10 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/foreign-qualification/ {
|
||||||
|
limit_req zone=api_limit burst=10 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/v1/lnpa-regions {
|
||||||
|
limit_req zone=api_limit burst=10 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Everything else: trusted IPs only ──
|
||||||
|
location / {
|
||||||
|
if ($api_trusted = 0) {
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
limit_req zone=api_limit burst=20 nodelay;
|
||||||
|
proxy_pass $api_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;
|
||||||
|
proxy_connect_timeout 10s;
|
||||||
|
proxy_send_timeout 60s;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ { root /var/www/certbot; }
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue