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 0d13082..ffeef86 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 @@ -153,6 +153,7 @@ The frontend behavior coverage now also explicitly pins: - login redirect preservation for pathname, query, and hash deep links - safe `next`-path normalization across custom auth pages and SuperTokens post-auth redirect handoff - browser auth-bootstrap normalization when the account payload reports email/fallback posture +- login/register page continuation behavior plus the public `/download` page rule that configured release targets still route through the protected dashboard instead of exposing raw URLs - dashboard launch-readiness visibility plus generated desktop-link verify URL behavior The first-party auth server now also supports bounded same-origin public serving diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md index 27f8164..d101512 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md @@ -264,7 +264,7 @@ repo. | Feature | Status | Primary authority | Notes | |---|---|---|---| | Public `hypertwist.app` marketing shell | Implemented now | first-party `website/` app + feature registry/roadmap authority | HyperTwist now has a dedicated first-party public web surface for homepage, about, resources, pricing, download, support, and legal routes. This lane is separate from the embedded Unreal browser runtime under `Content/Browser/` and does not claim browser-simulator parity. The same package now also carries a first-party external runtime-readiness verifier so deploy-time env and live health posture can be checked outside the dashboard, plus separated local-versus-production env templates whose placeholder values are intentionally rejected until real launch config is in place, bootstrap CI now validates both the frontend and auth-server website commands directly, and the auth server can now auto-serve the built `website/dist` bundle with bounded SPA fallback for same-origin public deployment. Request-level server coverage now also proves that public/app shell delivery does not shadow `/api/*`, `/auth*`, `/health`, or missing asset paths. | -| Browser-based operator/account dashboard | Implemented now | first-party `website/` app + shared auth/dashboard packet | A protected browser dashboard is now live for operator access, account state, download posture, browser-access boundary explanation, notices review, and bounded billing/entitlement status. It reuses the shared SuperTokens auth posture proven in FamiliarOS and ScriptoriumAI while remaining HyperTwist-specific in product content and boundary claims, the current auth-health surface now truthfully distinguishes configured versus reachable or ready shared-core posture while exposing fallback-active reason instead of hardcoding readiness, and the same dashboard now also surfaces launch-readiness truth for download URLs, checkout links, source/notices URLs, billing-secret/map configuration, and local-versus-public runtime deployment posture. Focused frontend coverage now also protects deep-link login redirect preservation, safe `next`-path normalization across auth entry points, fallback/email auth-bootstrap normalization, and desktop-link verify-url/dashboard readiness behavior. | +| Browser-based operator/account dashboard | Implemented now | first-party `website/` app + shared auth/dashboard packet | A protected browser dashboard is now live for operator access, account state, download posture, browser-access boundary explanation, notices review, and bounded billing/entitlement status. It reuses the shared SuperTokens auth posture proven in FamiliarOS and ScriptoriumAI while remaining HyperTwist-specific in product content and boundary claims, the current auth-health surface now truthfully distinguishes configured versus reachable or ready shared-core posture while exposing fallback-active reason instead of hardcoding readiness, and the same dashboard now also surfaces launch-readiness truth for download URLs, checkout links, source/notices URLs, billing-secret/map configuration, and local-versus-public runtime deployment posture. Focused frontend coverage now also protects deep-link login redirect preservation, safe `next`-path normalization across auth entry points, fallback/email auth-bootstrap normalization, login/register continuation behavior, public download-gating behavior, and desktop-link verify-url/dashboard readiness behavior. | | Desktop download posture and browser-to-desktop pairing | Implemented now | first-party `website/` app + `website/server` desktop-link endpoints | Public download targets, dashboard-side release posture, and short-lived desktop-link token generation/verification are now first-party owned. The current server posture now enforces exact website-origin matching, bounded per-user issuance, one-time token consumption, and billing-backed plan/download entitlement resolution with focused `website/server` tests green on `2026-06-22`, and the verify handshake now returns the same resolved download-entitlement posture the dashboard sees instead of only identity plus plan/role. The public `/download` page now keeps raw download URLs behind the protected dashboard instead of exposing them directly. Actual release URLs remain deployment configuration rather than hardcoded product truth. | | Paddle-ready pricing and billing webhook seam | Implemented now | first-party `website/` app + `website/server` billing endpoint | The public pricing surface now exists with plan structure, checkout-link configuration seams, and the same `/api/billing/paddle/webhook` endpoint family used by the broader product website lane. The current server now verifies `Paddle-Signature` against `PADDLE_WEBHOOK_SECRET` using the documented raw-body HMAC flow, persists a bounded first-party billing state file, and applies verified Paddle events into account/download entitlement state that the browser dashboard consumes, with focused `website/server` tests green on `2026-06-22`. Production checkout URLs, secret management, and broader operator/admin billing workflows remain deployment/application tasks, not shipped-code omissions. | | Public open-source notices and corresponding-source surface | Implemented now | first-party `website/` app + `HYPERTWIST_MPL_DISTRIBUTION_PLACEMENT_CHECKLIST_2026-05-25.md` | HyperTwist now has a stable public `Open Source Notices` route linked from pricing, download, and footer surfaces, satisfying the requirement that public distribution surfaces expose notice and corresponding-source guidance when shipped builds contain `MPL`-covered material. The exact public corresponding-source URL still must be configured before external launch. | diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md index c013a9c..a598346 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md @@ -220,7 +220,8 @@ Current consolidated milestone snapshot: explicitly rejected by that verifier, and the bootstrap CI lane now also runs the website/frontend plus website/server validation commands directly, while focused frontend coverage now also pins deep-link login redirects, safe - `next`-path normalization, fallback auth-bootstrap normalization, and + `next`-path normalization, fallback auth-bootstrap normalization, + login/register continuation behavior, public download-gating behavior, 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` diff --git a/website/README.md b/website/README.md index a40475a..098b68d 100644 --- a/website/README.md +++ b/website/README.md @@ -95,4 +95,5 @@ The focused frontend test coverage now also pins: - route-guard redirect preservation for pathname, query, and hash deep links - safe `next`-path normalization across custom auth pages and SuperTokens redirect handoff - auth-bootstrap normalization when fallback/email sessions are re-hydrated +- login/register page continuation behavior and protected-dashboard download gating on the public download page - dashboard launch-readiness plus desktop-link verify-url behavior diff --git a/website/src/__tests__/public-auth-pages.test.tsx b/website/src/__tests__/public-auth-pages.test.tsx new file mode 100644 index 0000000..c62b2bc --- /dev/null +++ b/website/src/__tests__/public-auth-pages.test.tsx @@ -0,0 +1,140 @@ +import { cleanup, render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { MemoryRouter, Route, Routes, useLocation } from 'react-router-dom' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { ROUTER_FUTURE_FLAGS } from '../router/router-future' + +const mockUsePlatformAuth = vi.fn() +const mockLogin = vi.fn() +const mockRegister = vi.fn() + +vi.mock('../auth/platform-auth', () => ({ + usePlatformAuth: () => mockUsePlatformAuth(), +})) + +vi.mock('../auth/supertokens-client', () => ({ + getSuperTokensAuthRuntimeValidation: () => ({ + ready: true, + missing: [], + warnings: [], + }), + isGitHubOAuthEnabled: () => false, + isGoogleOAuthEnabled: () => false, + isOrcidOAuthEnabled: () => false, +})) + +vi.mock('../site-config', async () => { + const actual = await vi.importActual('../site-config') + return { + ...actual, + downloadTargets: [ + { + platform: 'Windows', + subtitle: 'Primary shipping lane', + href: 'https://downloads.hypertwist.app/windows.exe', + details: 'Current packaged validation is strongest on the Windows Unreal lane.', + }, + { + platform: 'macOS', + subtitle: 'Planned distribution surface', + href: '', + details: 'List a signed desktop build here when the package lane is opened.', + }, + { + platform: 'Linux', + subtitle: 'Operator-targeted later lane', + href: '', + details: 'Use for future package publication after the bounded release lane is widened.', + }, + ], + } +}) + +import { DownloadPage } from '../pages/public-pages' +import { LoginPage, RegisterPage } from '../pages/auth-pages' + +function LocationEcho() { + const location = useLocation() + return
{location.pathname}{location.search}
+} + +function renderAuthRoutes(initialEntry: string) { + return render( + + + } /> + } /> + } /> + } /> + + , + ) +} + +describe('public auth and download pages', () => { + beforeEach(() => { + cleanup() + mockLogin.mockReset() + mockRegister.mockReset() + mockUsePlatformAuth.mockReset() + mockUsePlatformAuth.mockReturnValue({ + isAuthenticated: false, + login: (...args: unknown[]) => mockLogin(...args), + register: (...args: unknown[]) => mockRegister(...args), + }) + }) + + it('falls back unsafe login next targets to /app and preserves that safe fallback in the register link', async () => { + mockLogin.mockResolvedValue({ ok: true }) + + renderAuthRoutes('/login?next=%2F%2Fevil.example') + + const registerLink = screen.getByRole('link', { name: /create an account/i }) + expect(registerLink.getAttribute('href')).toBe('/register?next=%2Fapp') + + await userEvent.type(screen.getByLabelText('Email'), 'operator@hypertwist.app') + await userEvent.type(screen.getByLabelText('Password'), 'password123') + await userEvent.click(screen.getByRole('button', { name: 'Log in' })) + + expect((await screen.findByTestId('location')).textContent).toBe('/app') + expect(mockLogin).toHaveBeenCalledWith({ + method: 'email', + email: 'operator@hypertwist.app', + password: 'password123', + }) + }) + + it('preserves safe register next targets across footer links and successful account creation', async () => { + mockRegister.mockResolvedValue({ ok: true }) + + renderAuthRoutes('/register?next=%2Fapp%2Fdownloads%3Fplatform%3Dwindows') + + const loginLink = screen.getByRole('link', { name: 'Log in' }) + expect(loginLink.getAttribute('href')).toBe('/login?next=%2Fapp%2Fdownloads%3Fplatform%3Dwindows') + + await userEvent.type(screen.getByLabelText('Name'), 'Operator') + await userEvent.type(screen.getByLabelText('Email'), 'operator@hypertwist.app') + await userEvent.type(screen.getByLabelText('Password'), 'password123') + await userEvent.click(screen.getByRole('button', { name: 'Create account' })) + + expect((await screen.findByTestId('location')).textContent).toBe('/app/downloads?platform=windows') + expect(mockRegister).toHaveBeenCalledWith({ + method: 'email', + email: 'operator@hypertwist.app', + password: 'password123', + name: 'Operator', + }) + }) + + it('keeps configured public download targets behind the protected dashboard instead of exposing raw URLs', () => { + render( + + + , + ) + + const windowsAccessLink = screen.getByRole('link', { name: /sign in for windows access/i }) + expect(windowsAccessLink.getAttribute('href')).toBe('/app/downloads') + expect(screen.queryByText('https://downloads.hypertwist.app/windows.exe')).toBeNull() + }) +})