diff --git a/surfsense_web/.env.example b/surfsense_web/.env.example index 2313f3e56f..45f6289e55 100644 --- a/surfsense_web/.env.example +++ b/surfsense_web/.env.example @@ -5,6 +5,14 @@ NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE=LOCAL or GOOGLE NEXT_PUBLIC_OIDC_LOGOUT_URL=https:///logout NEXT_PUBLIC_OIDC_CLIENT_ID= NEXT_PUBLIC_OAUTH2_PROXY_URL=https://auth. + +# Required when AUTH_TYPE=SSO: portal hostname prefix for the SPA +# logout redirect (-..). Must +# match the ForwardAuth portal host. Same env name across every +# devstack app — see sso-rules RULES.md §1 Logout. The container reads +# SMB_NAME; docker-entrypoint.js substitutes the bundle's +# __NEXT_PUBLIC_SMB_NAME__ placeholder at startup. +SMB_NAME= NEXT_PUBLIC_ETL_SERVICE=UNSTRUCTURED or LLAMACLOUD or DOCLING NEXT_PUBLIC_ZERO_CACHE_URL=http://localhost:4848 diff --git a/surfsense_web/Dockerfile b/surfsense_web/Dockerfile index b16b3f066c..0cfa288730 100644 --- a/surfsense_web/Dockerfile +++ b/surfsense_web/Dockerfile @@ -38,6 +38,7 @@ ARG NEXT_PUBLIC_ETL_SERVICE=__NEXT_PUBLIC_ETL_SERVICE__ ARG NEXT_PUBLIC_ZERO_CACHE_URL=__NEXT_PUBLIC_ZERO_CACHE_URL__ ARG NEXT_PUBLIC_DEPLOYMENT_MODE=__NEXT_PUBLIC_DEPLOYMENT_MODE__ ARG NEXT_PUBLIC_OAUTH2_PROXY_URL=__NEXT_PUBLIC_OAUTH2_PROXY_URL__ +ARG NEXT_PUBLIC_SMB_NAME=__NEXT_PUBLIC_SMB_NAME__ # These are baked at build time (not placeholder-substituted). Next.js inlines # them as literal strings and terser dead-code-eliminates branches based on # truthiness; placeholder tokens look truthy and defeat that optimization. @@ -51,6 +52,7 @@ ENV NEXT_PUBLIC_ETL_SERVICE=$NEXT_PUBLIC_ETL_SERVICE ENV NEXT_PUBLIC_ZERO_CACHE_URL=$NEXT_PUBLIC_ZERO_CACHE_URL ENV NEXT_PUBLIC_DEPLOYMENT_MODE=$NEXT_PUBLIC_DEPLOYMENT_MODE ENV NEXT_PUBLIC_OAUTH2_PROXY_URL=$NEXT_PUBLIC_OAUTH2_PROXY_URL +ENV NEXT_PUBLIC_SMB_NAME=$NEXT_PUBLIC_SMB_NAME ENV NEXT_PUBLIC_LOGOUT_REDIRECT_URL=$NEXT_PUBLIC_LOGOUT_REDIRECT_URL ENV NEXT_PUBLIC_OIDC_LOGOUT_URL=$NEXT_PUBLIC_OIDC_LOGOUT_URL ENV NEXT_PUBLIC_OIDC_CLIENT_ID=$NEXT_PUBLIC_OIDC_CLIENT_ID diff --git a/surfsense_web/docker-entrypoint.js b/surfsense_web/docker-entrypoint.js index b71264550f..82181d269c 100644 --- a/surfsense_web/docker-entrypoint.js +++ b/surfsense_web/docker-entrypoint.js @@ -12,6 +12,18 @@ const fs = require("fs"); const path = require("path"); +// SMB_NAME is required when AUTH_TYPE=SSO. No default — fail loudly at +// startup so the SPA never silently rewrites the logout host to the wrong +// domain. Same env name across every devstack app — see sso-rules +// RULES.md §1 Logout. +if (!process.env.SMB_NAME && process.env.NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE === "SSO") { + console.error( + "[entrypoint] ERROR: SMB_NAME env is required when NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE=SSO." + ); + console.error("[entrypoint] Set it to the portal hostname prefix (e.g. 'moneta')."); + process.exit(1); +} + const replacements = [ [ "__NEXT_PUBLIC_FASTAPI_BACKEND_URL__", @@ -28,6 +40,7 @@ const replacements = [ ], ["__NEXT_PUBLIC_DEPLOYMENT_MODE__", process.env.NEXT_PUBLIC_DEPLOYMENT_MODE || "self-hosted"], ["__NEXT_PUBLIC_OAUTH2_PROXY_URL__", process.env.NEXT_PUBLIC_OAUTH2_PROXY_URL || ""], + ["__NEXT_PUBLIC_SMB_NAME__", process.env.SMB_NAME || ""], ]; let filesProcessed = 0; diff --git a/surfsense_web/lib/auth-utils.ts b/surfsense_web/lib/auth-utils.ts index aadc550d09..5ddc702f61 100644 --- a/surfsense_web/lib/auth-utils.ts +++ b/surfsense_web/lib/auth-utils.ts @@ -239,9 +239,10 @@ export async function logout(): Promise { clearAllTokens(); if (typeof window !== "undefined") { - // Rewrite "foss-." → "foss." so we land on the portal - // (outside ForwardAuth) instead of SurfSense's own root, which would silently re-auth. - const portalHost = window.location.hostname.replace(/^[^.]*\./, "moneta."); + // Rewrite "-." → "." so we land on the + // portal (outside ForwardAuth) instead of SurfSense's own root, which would silently re-auth. + const smbName = process.env.NEXT_PUBLIC_SMB_NAME!.trim(); + const portalHost = window.location.hostname.replace(/^[^.]*\./, `${smbName}.`); window.location.href = `${window.location.protocol}//${portalHost}`; return true; }