fire-planner: dual ingress — /api/* unprotected, / behind Authentik

The SPA can't carry an Authentik session on its own fetch() XHRs in
all cases (cross-origin redirect to authentik.viktorbarzin.me on a
stale cookie returns HTML, fetch().json() parse fails). Splitting
the ingress so /api/ paths skip forward-auth lets the React app talk
to its API end-to-end. The browser still has to log in via
Authentik to load the SPA at /.

Verified end-to-end via chrome-service Playwright: dashboard load,
scenario list, what-if run with real Monte Carlo, save-as-scenario
round-trip, run-now on detail, delete — all pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-05-10 00:06:40 +00:00
parent 58fd4025f8
commit 2d6812f951

View file

@ -420,6 +420,26 @@ module "ingress" {
}
}
# Second ingress at the same host for the /api/ prefix WITHOUT Authentik
# forward-auth. The SPA loads under Authentik (main ingress at /), then its
# fetch() XHRs hit /api/* directly forward-auth on /api/* would 302 the
# XHR to a cross-origin Authentik login page, which fetch().json() can't
# parse. App-layer bearer auth still gates writes (POST/PATCH/DELETE on
# scenarios, /recompute, /simulate); read endpoints are open. Acceptable
# for a personal tool whose only data is anonymous numeric projections.
module "ingress_api" {
source = "../../modules/kubernetes/ingress_factory"
dns_type = "none"
namespace = kubernetes_namespace.fire_planner.metadata[0].name
name = "fire-planner-api"
host = "fire-planner" # share effective_host with main ingress
service_name = "fire-planner"
port = 8080
ingress_path = ["/api/"]
tls_secret_name = var.tls_secret_name
protected = false
}
# Plan-time read of the ESO-created K8s Secret for Grafana datasource
# password. First-apply gotcha: must
# `terragrunt apply -target=kubernetes_manifest.db_external_secret` so