From d11bf59a7016108fd23f86b37ad7ec3f0ec0a185 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Mon, 22 Jun 2026 03:13:32 +0000 Subject: [PATCH] Test website auth error and oauth posture --- ...LING_AND_DISTRIBUTION_PACKET_2026-06-22.md | 1 + .../HyperTwist/FEATURE_REGISTRY.md | 2 +- .../HyperTwist/ROADMAP.md | 4 +- website/README.md | 1 + .../src/__tests__/public-auth-pages.test.tsx | 86 +++++++++++++++++-- website/src/pages/auth-pages.tsx | 78 +++++++---------- 6 files changed, 116 insertions(+), 56 deletions(-) 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 8a233ed..d729246 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 @@ -157,6 +157,7 @@ The frontend behavior coverage now also explicitly pins: - protected app-route loading/redirect behavior plus auth-aware marketing/app shell actions - real `AppRouteTree` smoke coverage for homepage, pricing, download, login, dashboard, and dashboard-download routes through the lazy public/protected router itself - top-level `App` bootstrap coverage for unknown-route redirect plus SuperTokens wrapper enabled-versus-fallback posture +- login/register unhappy-path coverage for returned form errors, auth-runtime warning callouts, and OAuth-button visibility/invocation - 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 09ebf80..c6eb8d0 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, login/register continuation behavior, public download-gating behavior, protected-route/shell behavior, real lazy-route tree behavior for key public and protected paths, top-level app-bootstrap and SuperTokens-wrapper posture, 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, protected-route/shell behavior, real lazy-route tree behavior for key public and protected paths, top-level app-bootstrap and SuperTokens-wrapper posture, login/register unhappy-path and OAuth-button 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 9c7530d..895197e 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md @@ -226,7 +226,9 @@ Current consolidated milestone snapshot: plus desktop-link verify-url behavior, with real lazy-route smoke coverage now pinned for `/`, `/pricing`, `/download`, `/login`, `/app`, and `/app/downloads`, plus top-level `App` bootstrap proof for unknown-route redirect and - SuperTokens-wrapper posture, + SuperTokens-wrapper posture, plus login/register unhappy-path proof for + returned form errors, auth-runtime warning posture, and OAuth-button + 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, while the env templates and diff --git a/website/README.md b/website/README.md index 4778c1e..0748aa4 100644 --- a/website/README.md +++ b/website/README.md @@ -99,4 +99,5 @@ The focused frontend test coverage now also pins: - protected-route loading/redirect behavior plus auth-aware marketing/app shell actions - real `AppRouteTree` smoke coverage for `/`, `/pricing`, `/download`, `/login`, `/app`, and `/app/downloads` - top-level `App` bootstrap coverage for unknown-route redirect and SuperTokens wrapper on/off posture +- login/register unhappy-path coverage for returned form errors, auth-runtime warning callouts, and OAuth-button visibility/invocation - 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 index c62b2bc..b7c49f6 100644 --- a/website/src/__tests__/public-auth-pages.test.tsx +++ b/website/src/__tests__/public-auth-pages.test.tsx @@ -7,20 +7,20 @@ import { ROUTER_FUTURE_FLAGS } from '../router/router-future' const mockUsePlatformAuth = vi.fn() const mockLogin = vi.fn() const mockRegister = vi.fn() +const mockGetSuperTokensAuthRuntimeValidation = vi.fn() +const mockIsGitHubOAuthEnabled = vi.fn(() => false) +const mockIsGoogleOAuthEnabled = vi.fn(() => false) +const mockIsOrcidOAuthEnabled = vi.fn(() => false) vi.mock('../auth/platform-auth', () => ({ usePlatformAuth: () => mockUsePlatformAuth(), })) vi.mock('../auth/supertokens-client', () => ({ - getSuperTokensAuthRuntimeValidation: () => ({ - ready: true, - missing: [], - warnings: [], - }), - isGitHubOAuthEnabled: () => false, - isGoogleOAuthEnabled: () => false, - isOrcidOAuthEnabled: () => false, + getSuperTokensAuthRuntimeValidation: () => mockGetSuperTokensAuthRuntimeValidation(), + isGitHubOAuthEnabled: () => mockIsGitHubOAuthEnabled(), + isGoogleOAuthEnabled: () => mockIsGoogleOAuthEnabled(), + isOrcidOAuthEnabled: () => mockIsOrcidOAuthEnabled(), })) vi.mock('../site-config', async () => { @@ -77,6 +77,18 @@ describe('public auth and download pages', () => { mockLogin.mockReset() mockRegister.mockReset() mockUsePlatformAuth.mockReset() + mockGetSuperTokensAuthRuntimeValidation.mockReset() + mockIsGitHubOAuthEnabled.mockReset() + mockIsGoogleOAuthEnabled.mockReset() + mockIsOrcidOAuthEnabled.mockReset() + mockGetSuperTokensAuthRuntimeValidation.mockReturnValue({ + ready: true, + missing: [], + warnings: [], + }) + mockIsGitHubOAuthEnabled.mockReturnValue(false) + mockIsGoogleOAuthEnabled.mockReturnValue(false) + mockIsOrcidOAuthEnabled.mockReturnValue(false) mockUsePlatformAuth.mockReturnValue({ isAuthenticated: false, login: (...args: unknown[]) => mockLogin(...args), @@ -126,6 +138,64 @@ describe('public auth and download pages', () => { }) }) + it('shows login errors without navigating away from the auth page', async () => { + mockLogin.mockResolvedValue({ ok: false, error: 'Incorrect email or password.' }) + + renderAuthRoutes('/login?next=%2Fapp') + + await userEvent.type(screen.getByLabelText('Email'), 'operator@hypertwist.app') + await userEvent.type(screen.getByLabelText('Password'), 'wrong-password') + await userEvent.click(screen.getByRole('button', { name: 'Log in' })) + + expect(await screen.findByText('Incorrect email or password.')).toBeTruthy() + expect(screen.queryByTestId('location')).toBeNull() + }) + + it('shows register errors without navigating away from the auth page', async () => { + mockRegister.mockResolvedValue({ ok: false, error: 'Sign-up is not allowed right now.' }) + + renderAuthRoutes('/register?next=%2Fapp%2Fdownloads') + + 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.findByText('Sign-up is not allowed right now.')).toBeTruthy() + expect(screen.queryByTestId('location')).toBeNull() + }) + + it('surfaces auth-runtime missing and warning posture on the login page', () => { + mockGetSuperTokensAuthRuntimeValidation.mockReturnValue({ + ready: false, + missing: ['VITE_SUPERTOKENS_API_DOMAIN'], + warnings: ['VITE_AUTH_API_BASE_URL not configured; same-origin auth fallback remains active.'], + }) + + renderAuthRoutes('/login') + + expect(screen.getByText(/Shared browser auth is not fully in production posture yet./i)).toBeTruthy() + expect(screen.getByText('Missing auth env: VITE_SUPERTOKENS_API_DOMAIN')).toBeTruthy() + expect(screen.getByText('VITE_AUTH_API_BASE_URL not configured; same-origin auth fallback remains active.')).toBeTruthy() + }) + + it('shows enabled OAuth buttons and routes provider clicks through the login handler', async () => { + mockIsGoogleOAuthEnabled.mockReturnValue(true) + mockIsGitHubOAuthEnabled.mockReturnValue(true) + mockIsOrcidOAuthEnabled.mockReturnValue(true) + mockLogin.mockResolvedValue({ ok: true }) + + renderAuthRoutes('/login') + + await userEvent.click(screen.getByRole('button', { name: 'Continue with Google' })) + await userEvent.click(screen.getByRole('button', { name: 'Continue with GitHub' })) + await userEvent.click(screen.getByRole('button', { name: 'Continue with ORCID' })) + + expect(mockLogin).toHaveBeenCalledWith({ method: 'google', email: '' }) + expect(mockLogin).toHaveBeenCalledWith({ method: 'github', email: '' }) + expect(mockLogin).toHaveBeenCalledWith({ method: 'orcid', email: '' }) + }) + it('keeps configured public download targets behind the protected dashboard instead of exposing raw URLs', () => { render( diff --git a/website/src/pages/auth-pages.tsx b/website/src/pages/auth-pages.tsx index 8984df9..d6b754e 100644 --- a/website/src/pages/auth-pages.tsx +++ b/website/src/pages/auth-pages.tsx @@ -9,8 +9,6 @@ import { isOrcidOAuthEnabled, } from '../auth/supertokens-client' -const authRuntimeValidation = getSuperTokensAuthRuntimeValidation() - function AuthShell({ title, subtitle, @@ -40,6 +38,36 @@ function useNextPath() { return normalizeNextPath(searchParams.get('next')) } +function AuthRuntimeNotice() { + const authRuntimeValidation = getSuperTokensAuthRuntimeValidation() + if (authRuntimeValidation.ready && authRuntimeValidation.warnings.length === 0) { + return null + } + + return ( +
+

+ Shared browser auth is not fully in production posture yet. + {!authRuntimeValidation.ready ? ' Local fallback mode may activate until the required auth env vars are configured.' : ''} +

+ {authRuntimeValidation.missing.length > 0 ? ( +
    + {authRuntimeValidation.missing.map((item) => ( +
  • Missing auth env: {item}
  • + ))} +
+ ) : null} + {authRuntimeValidation.warnings.length > 0 ? ( +
    + {authRuntimeValidation.warnings.map((item) => ( +
  • {item}
  • + ))} +
+ ) : null} +
+ ) +} + export function LoginPage() { const navigate = useNavigate() const { login, isAuthenticated } = usePlatformAuth() @@ -74,28 +102,7 @@ export function LoginPage() { subtitle="Use the same browser auth posture as FamiliarOS and ScriptoriumAI, then hand off to the desktop runtime when needed." footer={

Need access? Create an account.

} > - {!authRuntimeValidation.ready || authRuntimeValidation.warnings.length > 0 ? ( -
-

- Shared browser auth is not fully in production posture yet. - {!authRuntimeValidation.ready ? ' Local fallback mode may activate until the required auth env vars are configured.' : ''} -

- {authRuntimeValidation.missing.length > 0 ? ( -
    - {authRuntimeValidation.missing.map((item) => ( -
  • Missing auth env: {item}
  • - ))} -
- ) : null} - {authRuntimeValidation.warnings.length > 0 ? ( -
    - {authRuntimeValidation.warnings.map((item) => ( -
  • {item}
  • - ))} -
- ) : null} -
- ) : null} +
setEmail(event.target.value)} /> @@ -162,28 +169,7 @@ export function RegisterPage() { subtitle="This unlocks the browser dashboard, release posture, and desktop-link pairing for the simulator lane." footer={

Already have access? Log in.

} > - {!authRuntimeValidation.ready || authRuntimeValidation.warnings.length > 0 ? ( -
-

- Shared browser auth is not fully in production posture yet. - {!authRuntimeValidation.ready ? ' Local fallback mode may activate until the required auth env vars are configured.' : ''} -

- {authRuntimeValidation.missing.length > 0 ? ( -
    - {authRuntimeValidation.missing.map((item) => ( -
  • Missing auth env: {item}
  • - ))} -
- ) : null} - {authRuntimeValidation.warnings.length > 0 ? ( -
    - {authRuntimeValidation.warnings.map((item) => ( -
  • {item}
  • - ))} -
- ) : null} -
- ) : null} + setName(event.target.value)} />