gigafibre-fsm/apps/portal/traefik-client-portal.yml
louispaulb 7ac9a582c6 fix(portal): deploy Vue SPA to portal.gigafibre.ca, retire client.gigafibre.ca
Topology clarification:
- portal.gigafibre.ca = standalone nginx container serving /opt/client-app/
  (the actual Vue SPA). This is the real customer portal.
- client.gigafibre.ca = ERPNext frontend (exposes Frappe's password login
  form — dead-end UX, legacy MD5 attack surface).

Changes:
- apps/client/deploy.sh: target /opt/client-app/ directly with DEPLOY_BASE=/
  (was uploading into ERPNext's /assets/client-app/, which nothing serves).
  Atomic stage-and-swap + docker restart so the nginx bind-mount picks up
  the new inode.
- apps/portal/traefik-client-portal.yml: replace per-path /login and /desk
  blocks on client.gigafibre.ca with a catch-all 307 to portal.gigafibre.ca.
  Old bookmarks, old invoice links, and in-flight SMS all end up on the
  Vue SPA instead of Frappe's password page.
- apps/ops/package-lock.json: sync to include html5-qrcode transitive deps
  so `npm ci` in deploy.sh works from a clean checkout.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 15:02:31 -04:00

53 lines
2.2 KiB
YAML

# Traefik dynamic route: client.gigafibre.ca → redirect to portal.gigafibre.ca
#
# Background (2026-04-22):
# The customer portal Vue SPA now lives at portal.gigafibre.ca (served by a
# standalone nginx:alpine container from /opt/client-app/; Traefik label
# defined inline on that container). Customers authenticate with a 24h
# passwordless magic-link JWT — no Authentik, no ERPNext /login.
#
# The legacy host client.gigafibre.ca used to point at the ERPNext frontend
# directly, exposing Frappe's password login form. That's an attack surface
# we don't want (legacy MD5 hashes, enumerable accounts) and a dead-end UX
# (the Vue SPA isn't served there). This config retires client.gigafibre.ca
# by permanent-redirecting all traffic on that host to portal.gigafibre.ca,
# so stale bookmarks, old invoice links, and SMS that went out before the
# cut-over still land customers on the right front door.
#
# Staff continue to use id.gigafibre.ca / Authentik — untouched.
#
# Deploy:
# scp traefik-client-portal.yml root@96.125.196.67:/opt/traefik/dynamic/
# (Traefik auto-reloads dynamic config — no restart needed)
#
# DNS: *.gigafibre.ca wildcard already resolves to 96.125.196.67
# TLS: Let's Encrypt auto-provisions cert for client.gigafibre.ca
http:
routers:
# Catch-all: every request on client.gigafibre.ca → portal.gigafibre.ca
# (preserves path so /invoice/INV-123 still lands somewhere sensible on
# the new SPA, which has a hash router — paths without a leading # just
# land on the SPA root and the user re-navigates from there).
client-portal-legacy-redirect:
rule: "Host(`client.gigafibre.ca`)"
entryPoints:
- web
- websecure
service: noop@internal
middlewares:
- portal-legacy-redirect
tls:
certResolver: letsencrypt
priority: 100
middlewares:
# 302 (not 301) during the test phase so browsers don't cache the
# redirect permanently — we can still iterate on hostnames if needed.
# Flip `permanent: true` once the portal host is stable.
portal-legacy-redirect:
redirectRegex:
regex: "^https?://client\\.gigafibre\\.ca/(.*)"
replacement: "https://portal.gigafibre.ca/$1"
permanent: false