diff --git a/docker/.env.example b/docker/.env.example index 4bfe74abd6..2ab6b4b7ec 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -20,8 +20,12 @@ SECRET_KEY=replace_me_with_a_random_string # modes are not registered in this fork. AUTH_TYPE=SSO -# First DNS label for post-logout portal redirect (web container reads SMB_NAME; default moneta) -SMB_NAME=moneta +# Required under SSO: portal hostname prefix for the SPA logout +# redirect (-..). Same env name +# across every devstack app — see sso-rules RULES.md §1 Logout. No +# default — unset crashes the web container at startup rather than +# silently rewriting to the wrong host. +SMB_NAME= # Allow new user registrations (TRUE or FALSE) # REGISTRATION_ENABLED=TRUE diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index b717369d15..d3ca14a803 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -208,7 +208,10 @@ services: NEXT_PUBLIC_ETL_SERVICE: ${NEXT_PUBLIC_ETL_SERVICE:-DOCLING} NEXT_PUBLIC_ZERO_CACHE_URL: ${NEXT_PUBLIC_ZERO_CACHE_URL:-http://localhost:${ZERO_CACHE_PORT:-4848}} NEXT_PUBLIC_DEPLOYMENT_MODE: ${NEXT_PUBLIC_DEPLOYMENT_MODE:-self-hosted} - NEXT_PUBLIC_SMB_NAME: ${SMB_NAME:-${NEXT_PUBLIC_SMB_NAME:-moneta}} + # Required under SSO. Baked into the dev image at build time + # (this override bypasses the placeholder pattern used in prod). + # See sso-rules RULES.md §1 Logout. + NEXT_PUBLIC_SMB_NAME: ${SMB_NAME:?SMB_NAME is required (portal hostname prefix, e.g. moneta)} ports: - "${FRONTEND_PORT:-3000}:3000" env_file: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index d4eed9e820..91292e940a 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -198,7 +198,9 @@ services: NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE: ${AUTH_TYPE:-SSO} NEXT_PUBLIC_ETL_SERVICE: ${ETL_SERVICE:-DOCLING} NEXT_PUBLIC_DEPLOYMENT_MODE: ${DEPLOYMENT_MODE:-self-hosted} - SMB_NAME: ${SMB_NAME:-moneta} + # Required under SSO. docker-entrypoint.js exits non-zero at startup + # if AUTH_TYPE=SSO and SMB_NAME is unset. See sso-rules RULES.md §1. + SMB_NAME: ${SMB_NAME:?SMB_NAME is required (portal hostname prefix, e.g. moneta)} labels: - "com.centurylinklabs.watchtower.enable=true" depends_on: diff --git a/surfsense_web/.env.example b/surfsense_web/.env.example index 08ac2ec8d3..bde4595f11 100644 --- a/surfsense_web/.env.example +++ b/surfsense_web/.env.example @@ -1,8 +1,15 @@ NEXT_PUBLIC_FASTAPI_BACKEND_URL=http://localhost:8000 NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE=LOCAL or GOOGLE -# Platform portal first DNS label for post-logout redirect (SMB_NAME in Docker; default moneta) -NEXT_PUBLIC_SMB_NAME=moneta +# Required under SSO: portal hostname prefix for the SPA logout +# redirect (-..). 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. For local +# `next dev` set NEXT_PUBLIC_SMB_NAME directly here. No default — +# unset under SSO crashes startup loudly instead of silently rewriting +# to the wrong host. +NEXT_PUBLIC_SMB_NAME= # mPass proxy auth — set when deployed behind oauth2-proxy + Traefik ForwardAuth NEXT_PUBLIC_OIDC_LOGOUT_URL=https:///logout diff --git a/surfsense_web/docker-entrypoint.js b/surfsense_web/docker-entrypoint.js index 1b8ebe54a5..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,10 +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 || process.env.NEXT_PUBLIC_SMB_NAME || "moneta").trim() || "moneta", - ], + ["__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 7dcdcb7862..8be985c690 100644 --- a/surfsense_web/lib/auth-utils.ts +++ b/surfsense_web/lib/auth-utils.ts @@ -239,12 +239,9 @@ export async function logout(): Promise { clearAllTokens(); if (typeof window !== "undefined") { - // Rewrite "." → "." so we land on the platform portal - // (outside ForwardAuth) instead of SurfSense's own root, which would silently re-auth. - // Docker: set SMB_NAME on the container; docker-entrypoint substitutes NEXT_PUBLIC_SMB_NAME. - // Local dev: set NEXT_PUBLIC_SMB_NAME in .env (default moneta). - const smbLabel = process.env.NEXT_PUBLIC_SMB_NAME?.trim() || "moneta"; - const portalHost = window.location.hostname.replace(/^[^.]*\./, `${smbLabel}.`); + // Rewrite "-." → "." 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(/^([^-]+)-[^.]+\.(.+)/, "$1.$2"); window.location.href = `${window.location.protocol}//${portalHost}`; return true; }