diff --git a/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_AUTH_BILLING_AND_DISTRIBUTION_PACKET_2026-06-22.md b/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_AUTH_BILLING_AND_DISTRIBUTION_PACKET_2026-06-22.md index 0ca20a3..87a2afc 100644 --- a/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_AUTH_BILLING_AND_DISTRIBUTION_PACKET_2026-06-22.md +++ b/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_AUTH_BILLING_AND_DISTRIBUTION_PACKET_2026-06-22.md @@ -160,6 +160,7 @@ for `hypertwist.app` when a built `website/dist/index.html` is present: - static delivery auto-enables from `website/server -> ../dist` - SPA fallback remains limited to public/app routes and excludes `/api/*`, `/auth*`, and `/health` - `WEBSITE_DIST_PATH` and `SERVE_STATIC_WEBSITE` now expose explicit deployment control for that lane +- the runtime-readiness verifier now warns when same-origin public posture leaves that static-serving mode implicit This is browser-based user access for the operator/account surface. diff --git a/docs/ops/HYPERTWIST_WEBSITE_RUNTIME_CONFIGURATION_GUIDE_2026-06-22.md b/docs/ops/HYPERTWIST_WEBSITE_RUNTIME_CONFIGURATION_GUIDE_2026-06-22.md index 88f02bc..a90d4b8 100644 --- a/docs/ops/HYPERTWIST_WEBSITE_RUNTIME_CONFIGURATION_GUIDE_2026-06-22.md +++ b/docs/ops/HYPERTWIST_WEBSITE_RUNTIME_CONFIGURATION_GUIDE_2026-06-22.md @@ -31,6 +31,7 @@ Recommended server env posture: - `API_BASE_PATH=/auth` - `WEBSITE_BASE_PATH=/auth` - `COOKIE_SECURE=true` +- `SERVE_STATIC_WEBSITE=true` when the auth server owns same-origin public delivery - real `PADDLE_WEBHOOK_SECRET` - real `PADDLE_PRODUCT_PLAN_MAP` and/or `PADDLE_PRICE_PLAN_MAP` @@ -52,6 +53,8 @@ The repo now carries two distinct env-template families: The production examples intentionally include `replace-me` placeholder values so the readiness command still fails until real launch values are inserted. +They now also carry the explicit static-serving control variables for the +same-origin lane. ## Why same-origin is the clean default @@ -73,6 +76,7 @@ when a built website bundle exists: - override with `WEBSITE_DIST_PATH` when deployment layout differs - set `SERVE_STATIC_WEBSITE=false` to keep api-only mode explicit - set `SERVE_STATIC_WEBSITE=true` to require bundle presence instead of silently staying api-only +- the readiness verifier now warns when same-origin public posture leaves this static-serving mode ambiguous ## If a split-host auth topology is chosen later diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md index 73998c2..65cbe7e 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md @@ -223,7 +223,9 @@ Current consolidated milestone snapshot: auth-bootstrap normalization, and dashboard launch-readiness plus desktop-link verify-url behavior, and the auth server can now auto-serve the built `website/dist` bundle with bounded SPA fallback for same-origin `hypertwist.app` - deployment when that build output is present, + deployment when that build output is present, while the env templates and + runtime-readiness verifier now also make that static-serving posture explicit + instead of leaving it implicit, persists a bounded first-party billing-state file, applies verified Paddle events into account/download entitlement state, and surfaces that resolved billing/download posture back through `/api/auth/me`, the protected browser diff --git a/website/README.md b/website/README.md index d6583b4..30ad686 100644 --- a/website/README.md +++ b/website/README.md @@ -82,6 +82,7 @@ Use the runtime-readiness command before public launch or deployment approval: - it fails if required public launch values are still missing - it can optionally verify live `/api/auth/health` posture from the deployed site - the auth server can now also serve the built `website/dist` bundle directly for same-origin `hypertwist.app` deployment when that build output is present +- it now warns when same-origin public deployment leaves static website serving mode ambiguous The repo bootstrap CI now also validates this lane through: diff --git a/website/scripts/runtime-readiness-lib.mjs b/website/scripts/runtime-readiness-lib.mjs index 225b197..458714e 100644 --- a/website/scripts/runtime-readiness-lib.mjs +++ b/website/scripts/runtime-readiness-lib.mjs @@ -222,6 +222,24 @@ function evaluateServerConfig(serverEnv) { pushWarning(bucket, 'API_DOMAIN and WEBSITE_DOMAIN differ; same-origin public posture is the clean default.') } + const serveStaticWebsite = normalizeTrimmed(serverEnv.SERVE_STATIC_WEBSITE).toLowerCase() + const websiteDistPath = normalizeTrimmed(serverEnv.WEBSITE_DIST_PATH) || '../dist' + const sameOriginPublicPosture = Boolean( + apiDomain + && websiteDomain + && apiDomain.origin === websiteDomain.origin + && !isLoopbackHostname(apiDomain.hostname) + && !isLoopbackHostname(websiteDomain.hostname), + ) + + if (sameOriginPublicPosture) { + if (serveStaticWebsite === 'false' || serveStaticWebsite === '0' || serveStaticWebsite === 'no') { + pushWarning(bucket, 'SERVE_STATIC_WEBSITE=false; ensure a separate same-origin web server serves the built website bundle.') + } else if (serveStaticWebsite !== 'true' && serveStaticWebsite !== '1' && serveStaticWebsite !== 'yes') { + pushWarning(bucket, `SERVE_STATIC_WEBSITE is not explicitly set; confirm ${websiteDistPath} is present for first-party same-origin serving or that an external same-origin web server serves the frontend.`) + } + } + if ( Boolean(normalizeTrimmed(serverEnv.GITHUB_CLIENT_ID)) !== Boolean(normalizeTrimmed(serverEnv.GITHUB_CLIENT_SECRET)) diff --git a/website/scripts/runtime-readiness-lib.test.mjs b/website/scripts/runtime-readiness-lib.test.mjs index fbb785d..64a7788 100644 --- a/website/scripts/runtime-readiness-lib.test.mjs +++ b/website/scripts/runtime-readiness-lib.test.mjs @@ -41,6 +41,7 @@ describe('buildRuntimeReadinessReport', () => { WEBSITE_DOMAIN: 'https://hypertwist.app', SUPERTOKENS_CORE_URI: 'https://auth-core.internal', COOKIE_SECURE: 'true', + SERVE_STATIC_WEBSITE: 'true', PADDLE_WEBHOOK_SECRET: 'secret', PADDLE_PRICE_PLAN_MAP: '{"pri_operator":"operator"}', }, @@ -65,6 +66,31 @@ describe('buildRuntimeReadinessReport', () => { expect(report.failures).toEqual([]) }) + it('warns when same-origin public posture leaves static website serving ambiguous', () => { + const report = buildRuntimeReadinessReport({ + frontendEnv: { + VITE_SUPERTOKENS_API_DOMAIN: 'https://hypertwist.app', + VITE_SUPERTOKENS_WEBSITE_DOMAIN: 'https://hypertwist.app', + VITE_AUTH_API_BASE_URL: 'https://hypertwist.app', + VITE_WINDOWS_DOWNLOAD_URL: 'https://downloads.hypertwist.app/windows.exe', + VITE_PADDLE_CHECKOUT_URL_OPERATOR: 'https://buy.paddle.com/operator', + VITE_MPL_SOURCE_URL: 'https://hypertwist.app/open-source/source', + VITE_OPEN_SOURCE_REPO_URL: 'https://git.scriptoriumai.io/scriptoriumadmin/hypertwist', + }, + serverEnv: { + API_DOMAIN: 'https://hypertwist.app', + WEBSITE_DOMAIN: 'https://hypertwist.app', + SUPERTOKENS_CORE_URI: 'https://auth-core.internal', + COOKIE_SECURE: 'true', + PADDLE_WEBHOOK_SECRET: 'secret', + PADDLE_PRICE_PLAN_MAP: '{"pri_operator":"operator"}', + }, + liveHealth: null, + }) + + expect(report.warnings).toContain('SERVE_STATIC_WEBSITE is not explicitly set; confirm ../dist is present for first-party same-origin serving or that an external same-origin web server serves the frontend.') + }) + it('fails localhost-grade posture and missing public-launch configuration', () => { const report = buildRuntimeReadinessReport({ frontendEnv: { diff --git a/website/server/.env.example b/website/server/.env.example index 9ef8620..9a341cf 100644 --- a/website/server/.env.example +++ b/website/server/.env.example @@ -5,6 +5,8 @@ WEBSITE_DOMAIN=http://localhost:4273 API_BASE_PATH=/auth WEBSITE_BASE_PATH=/auth COOKIE_SECURE=false +SERVE_STATIC_WEBSITE=false +WEBSITE_DIST_PATH= GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= GOOGLE_CLIENT_ID= diff --git a/website/server/.env.production.example b/website/server/.env.production.example index 58c6449..c33370d 100644 --- a/website/server/.env.production.example +++ b/website/server/.env.production.example @@ -5,6 +5,8 @@ WEBSITE_DOMAIN=https://hypertwist.app API_BASE_PATH=/auth WEBSITE_BASE_PATH=/auth COOKIE_SECURE=true +SERVE_STATIC_WEBSITE=true +WEBSITE_DIST_PATH= GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= GOOGLE_CLIENT_ID= diff --git a/website/server/README.md b/website/server/README.md index d75afd7..e503d21 100644 --- a/website/server/README.md +++ b/website/server/README.md @@ -81,6 +81,7 @@ The server now also supports a bounded first-party same-origin deployment mode: - SPA fallback is limited to non-file public/app routes and does not intercept `/api/*`, `/auth*`, or `/health` - `WEBSITE_DIST_PATH` can override the bundle location when deployment layout differs - `SERVE_STATIC_WEBSITE=true` forces the server to expect a built bundle, while `SERVE_STATIC_WEBSITE=false` keeps api-only mode explicit +- the example env files now carry those static-serving controls directly so deployment posture is not implicit The website package now also ships a deploy-time verification command: