Skip to content
Merged
472 changes: 471 additions & 1 deletion apps/cloud/src/edge/marketing.test.ts

Large diffs are not rendered by default.

178 changes: 172 additions & 6 deletions apps/cloud/src/edge/marketing.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
// ---------------------------------------------------------------------------
// Marketing routes — proxied to the marketing worker via service binding.
// The `executor.sh` edge — decides which Worker answers a public request.
//
// On the production domain (`executor.sh`), marketing paths and the
// unauthenticated landing page are served by the separate `executor-marketing`
// worker. This module deliberately has no TanStack Start or cloud application
// imports: the Worker entry calls it before loading the Start server graph.
// On the production domain (`executor.sh`):
// - marketing paths and the unauthenticated landing page go to the separate
// `executor-marketing` worker;
// - sign-up goes to v2 (a redirect to v2's sign-up page);
// - a fixed list of v2 paths (sign-in issuer metadata, social sign-in
// callbacks, Git smart HTTP and the agent skills index) is forwarded to
// v2's Worker over a service binding;
// - so is a connected-account OAuth callback whose `state` carries v2's
// prefix.
// v1 owns everything not listed. This module deliberately has no TanStack
// Start or cloud application imports: the Worker entry calls it before
// loading the Start server graph.
// ---------------------------------------------------------------------------

import { parseCookie } from "../auth/cookies";
Expand Down Expand Up @@ -32,6 +40,8 @@ const MARKETING_PATHS = [

const SESSION_COOKIE = "wos-session";

const PRODUCTION_HOST = "executor.sh";

/** Whether an exact pathname belongs to the public marketing worker. */
export const isMarketingPath = (pathname: string): boolean =>
MARKETING_PATHS.some((p) => pathname === p || pathname.startsWith(`${p}/`));
Expand All @@ -42,7 +52,7 @@ export const isMarketingPath = (pathname: string): boolean =>
*/
export const marketingProxyRequest = (request: Request): Request | null => {
const url = new URL(request.url);
if (url.hostname !== "executor.sh") return null;
if (url.hostname !== PRODUCTION_HOST) return null;

const shouldProxy =
isMarketingPath(url.pathname) ||
Expand All @@ -52,3 +62,159 @@ export const marketingProxyRequest = (request: Request): Request | null => {
if (url.pathname === "/home") url.pathname = "/";
return new Request(url, request);
};

// ---------------------------------------------------------------------------
// v2 on `executor.sh`
// ---------------------------------------------------------------------------

const SIGN_UP_PATHS: ReadonlySet<string> = new Set(["/sign-up", "/signup"]);

/** A path pattern is an exact path, or `/x/*`, which matches every path
* that starts with `/x/` (and not `/x` itself). */
const matchesPathPattern = (pathname: string, pattern: string): boolean =>
pattern.endsWith("/*") ? pathname.startsWith(pattern.slice(0, -1)) : pathname === pattern;

/** Path patterns v2 answers on `executor.sh`. `/git/*` never matches
* `/gitlab/...`. v2's outbound OAuth client metadata document stays off this
* list: its move to `executor.sh` is pending, and v1's own document
* (`/oauth/client-id-metadata.json`) stays with v1. */
const V2_PATHS: ReadonlyArray<string> = [
// Sign-in issuer metadata for the issuer `https://executor.sh/api/auth`.
"/.well-known/oauth-authorization-server/api/auth",
"/git/*",
"/.well-known/agent-skills/*",
];

/** v2's social sign-in callback is `/api/auth/callback/<provider>`. v1's
* WorkOS callback is the bare `/api/auth/callback`, which stays with v1. */
const SOCIAL_CALLBACK_PREFIX = "/api/auth/callback/";

const isSocialCallbackPath = (pathname: string): boolean => {
if (!pathname.startsWith(SOCIAL_CALLBACK_PREFIX)) return false;
const provider = pathname.slice(SOCIAL_CALLBACK_PREFIX.length);
return provider.length > 0 && !provider.includes("/");
};

/** Whether a pathname is a sign-up entry point that redirects to v2. */
export const isSignUpPath = (pathname: string): boolean => SIGN_UP_PATHS.has(pathname);

/** Whether `executor.sh` forwards a pathname to v2's Worker. */
export const isV2Path = (pathname: string): boolean =>
V2_PATHS.some((pattern) => matchesPathPattern(pathname, pattern)) ||
isSocialCallbackPath(pathname);

/** Request headers v1 never passes to v2. The cookie header carries v1's
* `wos-session` (v2's cookies are host-only on its own hosts, so nothing of
* v2's travels on `executor.sh`). Client-sent forwarding headers are dropped
* so v2 sees no host claim other than the request URL's. */
const V2_STRIPPED_HEADERS = ["cookie", "x-forwarded-host", "x-forwarded-proto"] as const;

/**
* Project an `executor.sh` request onto the request sent to v2's Worker.
*
* Keeps the URL (so v2 sees host `executor.sh`, path and query unchanged),
* method, body stream and every header except {@link V2_STRIPPED_HEADERS},
* including `Authorization`. Redirects are returned to the client, not
* followed: v2 answers callbacks with a redirect to its own host.
*/
export const v2ForwardRequest = (request: Request): Request => {
const headers = new Headers(request.headers);
for (const name of V2_STRIPPED_HEADERS) headers.delete(name);
return new Request(request, { headers, redirect: "manual" });
};

/** The Worker that serves v2, reached over a service binding. */
export interface V2Service {
readonly fetch: (request: Request) => Promise<Response>;
}

/** v1's connected-account OAuth callback. v2 shares it on `executor.sh`:
* a callback whose `state` starts with v2's prefix belongs to v2. */
const OAUTH_CALLBACK_PATH = "/api/oauth/callback";

/** A state prefix is URL-safe as is (so the query value carries it
* unencoded) and includes a character outside base64url. v1's states are
* base64url, so no v1 state can start with such a prefix. */
const OAUTH_STATE_PREFIX_PATTERN = /^[A-Za-z0-9._~-]*[.~][A-Za-z0-9._~-]*$/;

/** The edge's raw v2 settings, as the Worker environment holds them. */
export interface V2EdgeEnv {
/** Service binding to v2's Worker. */
readonly V2?: V2Service;
/** Absolute URL of v2's sign-up page. */
readonly V2_SIGN_UP_URL?: string;
/** The prefix v2 puts on every connected-account OAuth `state`. */
readonly V2_OAUTH_STATE_PREFIX?: string;
}

/** What the edge needs to hand requests to v2. */
export interface V2Edge {
/** v2's Worker. */
readonly service: V2Service;
/** Absolute URL of v2's sign-up page (the `V2_SIGN_UP_URL` var). */
readonly signUpUrl: URL;
/** v2's connected-account OAuth state prefix (the `V2_OAUTH_STATE_PREFIX` var). */
readonly oauthStatePrefix: string;
}

/**
* Parse the edge's v2 settings from the Worker environment. Returns `null`
* when none is set (local dev, test workers), so v1 serves everything, and
* the reason as a string when the deployment is broken: only some of them
* set, a sign-up URL that is not absolute, or a state prefix that could
* match a v1 state.
*/
export const parseV2Edge = (env: V2EdgeEnv): V2Edge | string | null => {
const service = env.V2;
const signUpUrl = env.V2_SIGN_UP_URL;
const oauthStatePrefix = env.V2_OAUTH_STATE_PREFIX;
if (service === undefined && signUpUrl === undefined && oauthStatePrefix === undefined) {
return null;
}
if (service === undefined || signUpUrl === undefined || oauthStatePrefix === undefined) {
return "The V2 binding, V2_SIGN_UP_URL and V2_OAUTH_STATE_PREFIX must be set together";
}
const parsed = URL.parse(signUpUrl);
if (parsed === null || (parsed.protocol !== "https:" && parsed.protocol !== "http:")) {
return "V2_SIGN_UP_URL must be an absolute http(s) URL";
}
if (!OAUTH_STATE_PREFIX_PATTERN.test(oauthStatePrefix)) {
return "V2_OAUTH_STATE_PREFIX must be URL-safe and contain '.' or '~'";
}
return { service, signUpUrl: parsed, oauthStatePrefix };
};

/** Whether a connected-account callback carries v2's state prefix. Reads the
* query only: a callback without `state` in its query stays with v1. */
const isV2OAuthCallback = (url: URL, prefix: string): boolean =>
url.searchParams.get("state")?.startsWith(prefix) === true;

/**
* Answer a production request that belongs to v2: redirect sign-up (`GET`,
* any query) to v2's sign-up page, or forward a {@link isV2Path} request, or
* a connected-account callback whose `state` starts with v2's prefix, to
* v2's Worker and return its response unchanged (status, headers and body
* stream). Returns `null` when v1 owns the request.
*
* Broken settings answer the requests the edge owns with a 500 and leave the
* rest of v1 serving. The callback is the exception: which callbacks are v2's
* depends on the settings, so v1 keeps every callback until they parse.
*/
export const v2EdgeResponse = (request: Request, env: V2EdgeEnv): Promise<Response> | null => {
const url = new URL(request.url);
if (url.hostname !== PRODUCTION_HOST) return null;

const signUp = isSignUpPath(url.pathname) && request.method === "GET";
const callback = url.pathname === OAUTH_CALLBACK_PATH;
if (!signUp && !callback && !isV2Path(url.pathname)) return null;
const edge = parseV2Edge(env);
if (edge === null) return null;
if (typeof edge === "string") {
console.error(`executor.sh v2 edge misconfigured: ${edge}`);
if (callback) return null;
return Promise.resolve(new Response("Service misconfigured", { status: 500 }));
}
if (signUp) return Promise.resolve(Response.redirect(edge.signUpUrl.href, 302));
if (callback && !isV2OAuthCallback(url, edge.oauthStatePrefix)) return null;
return edge.service.fetch(v2ForwardRequest(request));
};
53 changes: 53 additions & 0 deletions apps/cloud/src/edge/production-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { fileURLToPath } from "node:url";

import { describe, expect, it } from "@effect/vitest";
import { Schema } from "effect";
import { unstable_readConfig } from "wrangler";

import { parseV2Edge, v2EdgeResponse, type V2EdgeEnv, type V2Service } from "./marketing";

// The deployed edge reads its v2 settings from wrangler.jsonc. These tests read
// that file the way wrangler does and run the edge with the values it ships.
// wrangler's config arrives untyped here, so it is decoded first.
const WranglerConfig = Schema.Struct({
vars: Schema.Record(Schema.String, Schema.Unknown),
services: Schema.Array(Schema.Struct({ binding: Schema.String, service: Schema.String })),
});
const config = Schema.decodeUnknownSync(WranglerConfig)(
unstable_readConfig({ config: fileURLToPath(new URL("../../wrangler.jsonc", import.meta.url)) }),
);

const stringVar = (name: string): string | undefined => {
const value = config.vars[name];
return typeof value === "string" ? value : undefined;
};

const standIn: V2Service = { fetch: () => Promise.resolve(new Response("v2")) };

/** The shipped settings, with a stand-in for v2's Worker. */
const shipped: V2EdgeEnv = {
V2: standIn,
V2_SIGN_UP_URL: stringVar("V2_SIGN_UP_URL"),
V2_OAUTH_STATE_PREFIX: stringVar("V2_OAUTH_STATE_PREFIX"),
};

describe("production v2 edge settings", () => {
it("binds V2 to exactly one Worker", () => {
const bindings = config.services.filter((service) => service.binding === "V2");
expect(bindings).toHaveLength(1);
expect(bindings[0]?.service).toMatch(/^[a-z0-9-]+$/);
});

it("redirects sign-up to v2's sign-up page on v2.executor.sh", async () => {
const response = await v2EdgeResponse(new Request("https://executor.sh/sign-up"), shipped);

expect(response?.status).toBe(302);
expect(response?.headers.get("location")).toBe("https://v2.executor.sh/login?mode=signup");
});

it("ships v2's connected-account state prefix", () => {
const edge = parseV2Edge(shipped);
if (edge === null || typeof edge === "string") return expect.unreachable("settings must parse");
expect(edge.oauthStatePrefix).toBe("x2.");
});
});
10 changes: 10 additions & 0 deletions apps/cloud/src/env-augment.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ declare global {
MCP_RESIDENT_RUNTIME_SOFT_CAP?: string;
NODE_ENV?: string;

// v2 on executor.sh (wrangler.jsonc `services` + `vars`). Optional so
// local dev and test workers without them serve everything from v1.
/** Service binding to v2's API Worker; `edge/marketing.ts` forwards to it. */
V2?: Fetcher;
/** Absolute URL `/sign-up` and `/signup` redirect to. */
V2_SIGN_UP_URL?: string;
/** Prefix of v2's connected-account OAuth `state`; `/api/oauth/callback`
* requests whose state starts with it go to v2. */
V2_OAUTH_STATE_PREFIX?: string;

// Shared with frontend
VITE_PUBLIC_SITE_URL?: string;
VITE_PUBLIC_OTLP_TRACES_URL?: string;
Expand Down
7 changes: 6 additions & 1 deletion apps/cloud/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import * as Sentry from "@sentry/cloudflare";
import handler from "@tanstack/react-start/server-entry";

import { isAppOwnedPath, servedByAppPlane } from "./app-paths";
import { marketingProxyRequest } from "./edge/marketing";
import { marketingProxyRequest, v2EdgeResponse } from "./edge/marketing";
import { passthroughResponse } from "./edge/passthrough";
import { withPrivateReferrerPolicy } from "./edge/referrer-policy";
import { runWorkOsEventsSync } from "./auth/workos-events-runner";
Expand Down Expand Up @@ -318,6 +318,11 @@ const cloudflareHandler = {
prewarmAppPlane(ctx);
}

// Sign-up, the fixed list of v2 paths and v2's connected-account
// callbacks on `executor.sh` go to v2.
const v2 = v2EdgeResponse(request, env);
if (v2) return v2;

const marketingRequest = marketingProxyRequest(request);
const marketing: Fetcher | undefined = env.MARKETING;
if (marketingRequest && marketing) return marketing.fetch(marketingRequest);
Expand Down
13 changes: 13 additions & 0 deletions apps/cloud/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@
"binding": "MARKETING",
"service": "executor-marketing",
},
// v2's API Worker (the executor-next `v2` stage, same account). The edge
// (src/edge/marketing.ts) forwards a fixed list of executor.sh paths to
// it with the URL unchanged, so v2 sees host `executor.sh`.
{
"binding": "V2",
"service": "executor-next-hosted-api-v2-qs32brgjwvt7ytx4",
},
],
"hyperdrive": [
{
Expand Down Expand Up @@ -133,6 +140,12 @@
},
"vars": {
"VITE_PUBLIC_SITE_URL": "https://executor.sh",
// Where /sign-up and /signup redirect: v2's sign-up page.
"V2_SIGN_UP_URL": "https://v2.executor.sh/login?mode=signup",
// v2 starts every connected-account OAuth `state` with this prefix, and
// /api/oauth/callback requests carrying it go to v2. It must equal the
// prefix v2 mints.
"V2_OAUTH_STATE_PREFIX": "x2.",
// Keeps the /__sentry-otel-verify probe live in production: Sentry
// delivery failed silently for weeks (zero events after ~Jul 30 with an
// active DSN), and without this there is no way to test the pipeline
Expand Down
39 changes: 39 additions & 0 deletions packages/core/api/src/account/org-slug.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,45 @@ describe("isValidOrgSlug", () => {
expect(RESERVED_ORG_SLUGS.has(critical), critical).toBe(true);
}
});

it("reserves the root segments the executor.sh edge gives to v2", () => {
// `/git/<owner>/<repo>` and the other forwarded or redirected roots would
// otherwise read as an org's console URL (`/<org>/...`).
for (const claimed of [
"git",
"apps",
"experiments",
"oauth",
"api",
"app",
"mcp",
"docs",
"sign-up",
"signup",
"pricing",
"blog",
]) {
expect(isValidOrgSlug(claimed), claimed).toBe(false);
}
// Look-alikes stay claimable.
for (const lookalike of [
"gitlab",
"git-team",
"github",
"app-team",
"experiment",
"oauth-co",
"blogs",
]) {
expect(isValidOrgSlug(lookalike), lookalike).toBe(true);
}
});

it("never mints a reserved edge slug for an org name", async () => {
const slug = await generateOrgSlug("Git", async () => false);
expect(slug).not.toBe("git");
expect(slug).toMatch(/^git-[a-z2-9]{4}$/);
});
});

describe("generateOrgSlug", () => {
Expand Down
9 changes: 9 additions & 0 deletions packages/core/api/src/account/org-slug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ const ORG_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){1,47}$/;
* - Marketing worker: home, setup, privacy, terms, blog, pricing, careers,
* changelog, _astro (executor.sh edge routes; the
* non-route names are cheap insurance)
* - v2 on executor.sh: git (Git smart HTTP remotes the executor.sh edge
* forwards to v2), apps and experiments (v2 marketing
* pages; no v1 organization uses them), plus oauth,
* api, app, mcp, docs, sign-up, signup, pricing and
* blog listed elsewhere here
* - Infra: assets (vite build output), cdn-cgi (Cloudflare),
* static, public, favicon.ico, robots.txt, sitemap.xml
* - Auth flows: auth, oauth, callback, logout, signin, signout,
Expand Down Expand Up @@ -69,6 +74,10 @@ export const RESERVED_ORG_SLUGS: ReadonlySet<string> = new Set([
"careers",
"changelog",
"_astro",
// v2 on executor.sh
"git",
"apps",
"experiments",
// infra
"assets",
"cdn-cgi",
Expand Down
Loading