From de8af5680eef37ad45d19bcb8caad10dec9b29cc Mon Sep 17 00:00:00 2001 From: Peter Amiri Date: Fri, 12 Jun 2026 10:14:02 -0700 Subject: [PATCH] fix(view): render debug bar env quick-switch links only when switching can work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since #2082 the ?reload= switch requires a non-empty reloadPassword plus a matching password parameter (and is gated by allowEnvironmentSwitchViaUrl), but the debug bar still rendered the Testing/Maintenance/Production quick-switch anchors only when NO reloadPassword was set — the exact configuration where switching is impossible. Clicking one restarted the app and silently stayed in the current environment. The Environment panel now renders the links only when a reloadPassword is configured AND allowEnvironmentSwitchViaUrl resolves true. The links never embed the password: a new wdbEnvSwitch() handler prompts for it at click time and issues the documented ?reload=&password=... request. The plain ?reload=true reload anchor keeps its existing no-password gate. Guides (debug-panel.mdx, environments-and-configuration.mdx) updated to describe the working behavior instead of the dead-link caveat. Fixes #3060 Co-Authored-By: Claude Fable 5 Signed-off-by: Peter Amiri --- changelog.d/debugbar-env-quickswitch.fixed.md | 1 + vendor/wheels/events/onrequestend/debug.cfm | 20 +++- .../events/DebugBarEnvQuickSwitchSpec.cfc | 98 +++++++++++++++++++ .../environments-and-configuration.mdx | 5 +- .../v4-0-0/digging-deeper/debug-panel.mdx | 4 +- 5 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 changelog.d/debugbar-env-quickswitch.fixed.md create mode 100644 vendor/wheels/tests/specs/events/DebugBarEnvQuickSwitchSpec.cfc diff --git a/changelog.d/debugbar-env-quickswitch.fixed.md b/changelog.d/debugbar-env-quickswitch.fixed.md new file mode 100644 index 0000000000..588263b937 --- /dev/null +++ b/changelog.d/debugbar-env-quickswitch.fixed.md @@ -0,0 +1 @@ +- Debug bar: environment quick-switch links (Testing / Maintenance / Production) now render only when switching can actually work — a non-empty `reloadPassword` is configured and `allowEnvironmentSwitchViaUrl` allows it — and prompt for the reload password at click time instead of rendering dead anchors when no password is set (the configuration where `?reload=` has been a no-op since #2082). The password is never embedded in the page, and the plain `?reload=true` reload anchor keeps its existing behavior ([#3060](https://github.com/wheels-dev/wheels/issues/3060)) diff --git a/vendor/wheels/events/onrequestend/debug.cfm b/vendor/wheels/events/onrequestend/debug.cfm index 18a32541a9..00c262b70e 100644 --- a/vendor/wheels/events/onrequestend/debug.cfm +++ b/vendor/wheels/events/onrequestend/debug.cfm @@ -301,12 +301,20 @@ OR (StructKeyExists(url, "format") AND ListFindNoCase("json,xml,csv,pdf", url.fo
#capitalize($get("environment"))# - + + - #capitalize(local.ei)# + #capitalize(local.ei)# @@ -545,6 +553,14 @@ OR (StructKeyExists(url, "format") AND ListFindNoCase("json,xml,csv,pdf", url.fo document.getElementById('wdb-minimized').style.display='none'; try{sessionStorage.removeItem('wdb-hidden');}catch(e){} }; + window.wdbEnvSwitch=function(el){ + var target=el.getAttribute('data-wdb-reload'); + if(!target)return false; + var pw=window.prompt('Enter the reload password to switch environments:'); + if(pw===null||pw==='')return false; + window.location.href=target+'&password='+encodeURIComponent(pw); + return false; + }; try{if(sessionStorage.getItem('wdb-hidden')==='1')wdbMinimize();}catch(e){} })(); diff --git a/vendor/wheels/tests/specs/events/DebugBarEnvQuickSwitchSpec.cfc b/vendor/wheels/tests/specs/events/DebugBarEnvQuickSwitchSpec.cfc new file mode 100644 index 0000000000..6be50a993d --- /dev/null +++ b/vendor/wheels/tests/specs/events/DebugBarEnvQuickSwitchSpec.cfc @@ -0,0 +1,98 @@ +component extends="wheels.WheelsTest" { + + function run() { + describe("debug.cfm environment quick-switch links (issue 3060)", () => { + // Since ##2082, switching environments via ?reload= requires a + // non-empty reloadPassword plus a matching password parameter, and is + // additionally gated by allowEnvironmentSwitchViaUrl. The debug bar's + // Environment panel used to render the quick-switch anchors only when + // NO reloadPassword was set — the exact configuration where switching + // is impossible — and the anchors embedded no password, so clicking + // one silently restarted the app in the same environment. The fixed + // behavior renders the links only when switching CAN work, as + // prompt-based links that never embed the password in the page. + + it("renders no quick-switch links when reloadPassword is empty", () => { + var output = $renderDebugBar(reloadPassword = "", allowSwitch = true); + expect(output contains 'data-wdb-reload="').toBeFalse( + "quick-switch links must not render without a reloadPassword (switching cannot work)" + ); + expect(output contains "margin-left:4px").toBeFalse( + "the legacy dead quick-switch anchors must not render when reloadPassword is empty" + ); + // The plain one-click reload anchor keeps its pre-existing no-password gate. + expect(output contains 'title="Reload Application"').toBeTrue( + "the plain ?reload=true anchor must keep rendering when no reloadPassword is set" + ); + }); + + it("renders prompt-based quick-switch links when a reloadPassword is set and switching is allowed", () => { + var output = $renderDebugBar(reloadPassword = "spec-secret-pw-3060", allowSwitch = true); + expect(output contains 'data-wdb-reload="').toBeTrue( + "quick-switch links must render when a reloadPassword is configured and allowEnvironmentSwitchViaUrl is true" + ); + expect(output contains "wdbEnvSwitch").toBeTrue( + "quick-switch links must go through the password prompt handler" + ); + expect(output contains "spec-secret-pw-3060").toBeFalse( + "the reload password must never be embedded in the rendered page" + ); + // The plain one-click reload anchor keeps its pre-existing password gate. + expect(output contains 'title="Reload Application"').toBeFalse( + "the plain ?reload=true anchor must stay hidden when a reloadPassword is set" + ); + }); + + it("renders no quick-switch links when allowEnvironmentSwitchViaUrl is false even with a password set", () => { + var output = $renderDebugBar(reloadPassword = "spec-secret-pw-3060", allowSwitch = false); + expect(output contains 'data-wdb-reload="').toBeFalse( + "quick-switch links must not render when environment switching via URL is disallowed" + ); + }); + }); + } + + /** + * Renders the debug bar template with the given reloadPassword and + * allowEnvironmentSwitchViaUrl applied, restoring all touched state. + * Modeled on debugBarEncodingSpec.cfc. + */ + private string function $renderDebugBar(required string reloadPassword, required boolean allowSwitch) { + var priorReloadPassword = application.wheels.reloadPassword; + var hadAllowSwitch = StructKeyExists(application.wheels, "allowEnvironmentSwitchViaUrl"); + var priorAllowSwitch = hadAllowSwitch ? application.wheels.allowEnvironmentSwitchViaUrl : true; + var priorReqWheels = StructKeyExists(request, "wheels") ? Duplicate(request.wheels) : {}; + // debug.cfm bails out (cfexit) when url.format is one of json/xml/csv/pdf + // so it never breaks an API response. The test runner is hit with + // format=json — clear it for the duration of the include. + var hadUrlFormat = StructKeyExists(url, "format"); + var priorUrlFormat = hadUrlFormat ? url.format : ""; + var output = ""; + try { + application.wheels.reloadPassword = arguments.reloadPassword; + application.wheels.allowEnvironmentSwitchViaUrl = arguments.allowSwitch; + if (!StructKeyExists(request, "wheels")) { + request.wheels = {}; + } + request.wheels.execution = {total = 0}; + request.wheels.params = {controller = "wheels", action = "tests", route = ""}; + if (hadUrlFormat) { + StructDelete(url, "format"); + } + output = application.wo.$includeAndReturnOutput($template = "/wheels/events/onrequestend/debug.cfm"); + } finally { + application.wheels.reloadPassword = priorReloadPassword; + if (hadAllowSwitch) { + application.wheels.allowEnvironmentSwitchViaUrl = priorAllowSwitch; + } else { + StructDelete(application.wheels, "allowEnvironmentSwitchViaUrl"); + } + request.wheels = priorReqWheels; + if (hadUrlFormat) { + url.format = priorUrlFormat; + } + } + return output; + } + +} diff --git a/web/sites/guides/src/content/docs/v4-0-0/core-concepts/environments-and-configuration.mdx b/web/sites/guides/src/content/docs/v4-0-0/core-concepts/environments-and-configuration.mdx index acae8808f4..06729c6044 100644 --- a/web/sites/guides/src/content/docs/v4-0-0/core-concepts/environments-and-configuration.mdx +++ b/web/sites/guides/src/content/docs/v4-0-0/core-concepts/environments-and-configuration.mdx @@ -87,9 +87,10 @@ A running app can switch environments without a redeploy: request `?reload=&password=` request. When no `reloadPassword` is set, or switching via the URL is disallowed, the links do not render because the switch cannot work. -To actually switch environments at runtime, set a `reloadPassword` in `config/settings.cfm` and request `?reload=&password=`. The switch is additionally gated by the `allowEnvironmentSwitchViaUrl` setting: an explicit boolean you set is honored in every environment, and when unset it defaults to `false` in `production`, `testing`, and `maintenance` and `true` everywhere else. Switching to the environment you are already in is a no-op, and after a successful switch the redirect strips the reload parameters from the URL. +You can also switch environments manually: set a `reloadPassword` in `config/settings.cfm` and request `?reload=&password=`. The switch is additionally gated by the `allowEnvironmentSwitchViaUrl` setting: an explicit boolean you set is honored in every environment, and when unset it defaults to `false` in `production`, `testing`, and `maintenance` and `true` everywhere else. Switching to the environment you are already in is a no-op, and after a successful switch the redirect strips the reload parameters from the URL.