diff --git a/apps/Backend/.env b/apps/Backend/.env index 637f1fed..b26a93d4 100755 --- a/apps/Backend/.env +++ b/apps/Backend/.env @@ -2,7 +2,7 @@ NODE_ENV="development" HOST=0.0.0.0 PORT=5000 CLOUDFLARE_HOST= -FRONTEND_URLS=http://localhost:3000 +FRONTEND_URLS=http://localhost:3000,https://local-summit.mydentalofficemanagement.com SELENIUM_AGENT_BASE_URL=http://localhost:5002 JWT_SECRET = 'dentalsecret' LICENSE_SECRET=3aa4ab937e46c6863b9e3c2b591a595b31ea3af1060bf5e7961ad722a8b54f92 diff --git a/apps/Frontend/.gitignore b/apps/Frontend/.gitignore index a547bf36..07975b4c 100755 --- a/apps/Frontend/.gitignore +++ b/apps/Frontend/.gitignore @@ -12,6 +12,9 @@ dist dist-ssr *.local +# Local HTTPS dev certs (machine-specific, generated via mkcert) +certs/ + # Editor directories and files .vscode/* !.vscode/extensions.json diff --git a/apps/Frontend/src/lib/socket.ts b/apps/Frontend/src/lib/socket.ts index 964ceb96..18e03758 100644 --- a/apps/Frontend/src/lib/socket.ts +++ b/apps/Frontend/src/lib/socket.ts @@ -6,13 +6,17 @@ */ import { io, Socket } from "socket.io-client"; -// Connect directly to backend to avoid Vite's WS proxy failing on upgrade, -// which causes an unhandled AggregateError from engine.io's Promise.any() probe. -// Use the env var when set; otherwise derive the backend URL from the current -// page's hostname so remote browsers (non-localhost) reach the server correctly. +// When served through nginx (any port other than Vite's own 3000), nginx already +// proxies /socket.io/ with WS upgrade support — connect same-origin so TLS and +// CORS are handled consistently with the rest of the app. +// When hitting Vite's dev server directly on :3000, connect straight to the +// backend instead, since Vite's own WS proxy fails on upgrade (unhandled +// AggregateError from engine.io's Promise.any() probe). const SOCKET_URL = import.meta.env.VITE_API_BASE_URL_BACKEND || - `${window.location.protocol}//${window.location.hostname}:5000`; + (window.location.port === "3000" + ? `${window.location.protocol}//${window.location.hostname}:5000` + : window.location.origin); export const socket: Socket = io(SOCKET_URL, { withCredentials: true, diff --git a/apps/Frontend/vite.config.ts b/apps/Frontend/vite.config.ts index 74ac4ca8..9ae6d238 100755 --- a/apps/Frontend/vite.config.ts +++ b/apps/Frontend/vite.config.ts @@ -16,6 +16,7 @@ export default defineConfig(({ mode }) => { allowedHosts: [ ...(env.VITE_CLOUDFLARE_HOST ? [env.VITE_CLOUDFLARE_HOST] : []), "192.168.0.94", + "local-summit.mydentalofficemanagement.com", ], proxy: { "/api": { diff --git a/nginx.conf b/nginx.conf index 83d8a305..108da622 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,6 +1,17 @@ +# ── LAN-only app access (staff) ────────────────────────────────────────── +# https://local-summit.mydentalofficemanagement.com +# DNS A record points at this office's private LAN IP; cert is a real +# Let's Encrypt cert (issued via certbot + Cloudflare DNS-01), so no CA +# needs to be installed on any staff PC. Restricted to the office subnet. server { - listen 80; - server_name _; + listen 443 ssl; + server_name local-summit.mydentalofficemanagement.com; + + ssl_certificate /etc/letsencrypt/live/local-summit.mydentalofficemanagement.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/local-summit.mydentalofficemanagement.com/privkey.pem; + + allow 192.168.0.0/24; + deny all; client_max_body_size 50m; @@ -33,3 +44,40 @@ server { proxy_set_header Host $host; } } + +# ── Public Twilio webhooks only ────────────────────────────────────────── +# https://summit.mydentalofficemanagement.com, reached via Cloudflare Tunnel. +# Cloudflare terminates TLS at its edge and cloudflared forwards plain HTTP +# to this block, so no certificate is needed here. Nothing except the +# Twilio webhook path is exposed on this hostname. +server { + listen 80; + server_name summit.mydentalofficemanagement.com; + + client_max_body_size 50m; + + location /api/twilio/ { + proxy_pass http://localhost:5000; + 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; + } + + location / { + return 403; + } +} + +# Catch-all: reject anything not matching the two hostnames above +# (e.g. plain http:// or an unrecognized Host header). +server { + listen 80 default_server; + listen 443 ssl default_server; + server_name _; + + ssl_certificate /etc/letsencrypt/live/local-summit.mydentalofficemanagement.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/local-summit.mydentalofficemanagement.com/privkey.pem; + + return 403; +}