From b2692457232f734d10d4f51623cc2cccc57bc090 Mon Sep 17 00:00:00 2001 From: btopro Date: Thu, 6 Aug 2026 14:30:16 -0400 Subject: [PATCH 1/2] Standardize system API path in connection-settings (parity with PHP) The system API base path is now always absolute (/{basePath}system/api/) mirroring PHP (HAXCMS.php appJWTConnectionSettings forces a leading slash). Previously Node emitted a relative systemRequestBase which resolved against the current page URL, producing a site-scoped system endpoint (/_sites//system/api/v1/...) when called from a site context. That worked on Node (which registers site-scoped system routes) but diverged from PHP and was not standardized. Regression noticed during post-merge testing: the appStore URL generated by connection-settings resolved site-scoped on Node vs root-level on PHP. Co-Authored-By: Oz --- .../v1/routes/connectionSettings.js | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/systemRoutes/v1/routes/connectionSettings.js b/src/systemRoutes/v1/routes/connectionSettings.js index f1382e93..776a21bf 100644 --- a/src/systemRoutes/v1/routes/connectionSettings.js +++ b/src/systemRoutes/v1/routes/connectionSettings.js @@ -142,20 +142,24 @@ async function connectionSettings(req, res) { res.setHeader('Expires', '0'); res.setHeader('Surrogate-Control', 'no-store'); res.setHeader('Content-Type', 'application/javascript'); - const isDashboardRequest = ( - HAXCMS && - HAXCMS.operatingContext !== 'single' && - req.headers && - req.headers.referer && - !req.headers.referer.includes(`/${HAXCMS.sitesDirectory}/`) - ); - // default to relative API paths so calls in site context resolve correctly - // and mirror PHP behavior for appStore-generated endpoint paths. - let baseAPIPath = HAXCMS.systemRequestBase; - // in non-root installs, preserve basePath for site-context API routing. - if (!isDashboardRequest && HAXCMS.basePath && HAXCMS.basePath !== '/') { - baseAPIPath = `${HAXCMS.basePath}${HAXCMS.systemRequestBase}`; + // System API base path is always absolute (root-level), mirroring PHP + // (HAXCMS.php appJWTConnectionSettings forces a leading slash). System + // routes are not site-scoped; both backends serve them at + // /system/api/v1/ so calls resolve identically whether the page is a + // site context (/_sites//) or the system dashboard. A relative + // path would resolve against the current page URL and produce a + // site-scoped system endpoint (/_sites//system/api/v1/...) which + // works on Node but is not standardized with PHP. + let systemNormalizedBasePath = String(HAXCMS.basePath || '/'); + if (systemNormalizedBasePath.charAt(0) !== '/') { + systemNormalizedBasePath = '/' + systemNormalizedBasePath; + } + if ( + systemNormalizedBasePath.charAt(systemNormalizedBasePath.length - 1) !== '/' + ) { + systemNormalizedBasePath += '/'; } + let baseAPIPath = `${systemNormalizedBasePath}${HAXCMS.systemRequestBase}`; var sitename = ''; // name parsed from a multisite site-context URL (/_sites//...). // tracked separately because it must drive the site API base path, whereas From 9c0d7d7eb6fb072956d825440feb026d03efa39a Mon Sep 17 00:00:00 2001 From: btopro Date: Thu, 6 Aug 2026 14:31:56 -0400 Subject: [PATCH 2/2] Restore app-store site-token-only auth stance (fix regression) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit normalizeSiteApiSecurityPolicy now distinguishes siteTokenHeader-only ('site-token-only') from bearerAuth+siteTokenHeader ('authenticated-site'). The app-store GET declares siteTokenHeader alone, so it maps to 'site-token-only' — the handler (generateAppStore.js) validates the site token against the server-side active user, no bearer JWT required. Previously the conformance work mapped siteTokenHeader to 'authenticated-site' unconditionally, which required a bearer-derived userName at the router (enforceSystemApiSiteTokenPolicy). The front-end appStore fetch sends the site token but not a bearer JWT, so both backends rejected the call (Node 403, PHP 401). This restores the original handler-validated behavior. provider-search (bearerAuth+siteTokenHeader) stays 'authenticated-site' and is unaffected. No site-spec routes declare siteTokenHeader-only, so the site API is unaffected. Co-Authored-By: Oz --- src/app.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/app.js b/src/app.js index 93019c29..9233d7b8 100644 --- a/src/app.js +++ b/src/app.js @@ -1650,7 +1650,17 @@ function normalizeSiteApiSecurityPolicy(securityConfig = null) { return 'public'; } if (Object.prototype.hasOwnProperty.call(requirement, 'siteTokenHeader')) { - return 'authenticated-site'; + // siteTokenHeader paired with bearerAuth (in the same requirement) is + // 'authenticated-site' (bearer JWT + site token, e.g. provider-search, + // site API mutations). siteTokenHeader alone is 'site-token-only' — + // the site token is validated against the server-side active user by + // the handler (e.g. generateAppStore), no bearer JWT required. This + // mirrors the original handler-validated behavior and avoids requiring + // a bearer JWT the front-end appStore fetch does not send. + if (Object.prototype.hasOwnProperty.call(requirement, 'bearerAuth')) { + return 'authenticated-site'; + } + return 'site-token-only'; } if (Object.prototype.hasOwnProperty.call(requirement, 'userTokenHeader')) { requiresUserToken = true;