From 5f65f44d06b0c833bd20b1ce8e2b180f202af67a Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Thu, 2 Jul 2026 10:32:35 -0700 Subject: [PATCH 1/3] okta: RFC 8414 path-insert discovery + RFC 7591 dynamic client registration - Serve authorization-server metadata at the path-insert well-known forms (/.well-known/{oauth-authorization-server,openid-configuration}/oauth2/:id) alongside the existing suffix forms; advertise registration_endpoint, S256, and token_endpoint_auth_methods_supported. - Add POST /oauth2/v1/clients and POST /oauth2/:authServerId/v1/clients implementing RFC 7591: persists clients into the shared store so the authorize/token flow accepts DCR-minted clients, public clients get no secret, RFC 6749 error envelopes (invalid_client_metadata, invalid_redirect_uri). - Wire-level tests incl. a simulated client probe -> register -> PKCE code exchange end to end, and a one-shot fault on the registration route. --- .../okta/src/__tests__/okta.test.ts | 242 +++++++++++++++++- packages/@emulators/okta/src/manifest.ts | 36 +++ packages/@emulators/okta/src/routes/oauth.ts | 171 ++++++++++++- 3 files changed, 440 insertions(+), 9 deletions(-) diff --git a/packages/@emulators/okta/src/__tests__/okta.test.ts b/packages/@emulators/okta/src/__tests__/okta.test.ts index c56eae48..899fc35f 100644 --- a/packages/@emulators/okta/src/__tests__/okta.test.ts +++ b/packages/@emulators/okta/src/__tests__/okta.test.ts @@ -2,8 +2,9 @@ import { createHash } from "node:crypto"; import { decodeJwt } from "jose"; import { Hono } from "@emulators/core"; import { beforeEach, describe, expect, it } from "vitest"; -import { Store, WebhookDispatcher, authMiddleware, type TokenMap } from "@emulators/core"; +import { Store, WebhookDispatcher, authMiddleware, createServer, type TokenMap } from "@emulators/core"; import { getOktaStore, oktaPlugin, seedFromConfig } from "../index.js"; +import { manifest } from "../manifest.js"; const base = "http://localhost:4000"; @@ -156,6 +157,89 @@ async function exchangeCode( }); } +function dcrRequestBody(tokenEndpointAuthMethod: "none" | "client_secret_post" = "none") { + return { + redirect_uris: ["http://localhost:3000/dcr-callback"], + client_name: "Executor DCR Client", + grant_types: ["authorization_code", "refresh_token"], + response_types: ["code"], + token_endpoint_auth_method: tokenEndpointAuthMethod, + scope: "openid profile email offline_access", + }; +} + +async function registerClient( + app: Hono, + registrationEndpoint = `${base}/oauth2/default/v1/clients`, + tokenEndpointAuthMethod: "none" | "client_secret_post" = "none", +): Promise> { + const res = await app.request(registrationEndpoint, { + method: "POST", + headers: { "Content-Type": "application/json", Accept: "application/json" }, + body: JSON.stringify(dcrRequestBody(tokenEndpointAuthMethod)), + }); + expect(res.status).toBe(201); + return (await res.json()) as Record; +} + +async function completePkceCodeFlow( + app: Hono, + store: Store, + options: { + authorizationEndpoint?: string; + tokenEndpoint?: string; + clientId: string; + clientSecret?: string; + redirectUri?: string; + includeClientSecret?: boolean; + }, +): Promise> { + const redirectUri = options.redirectUri ?? "http://localhost:3000/dcr-callback"; + const verifier = "executor-pkce-verifier-12345"; + const challenge = createHash("sha256").update(verifier).digest("base64url"); + const authorizationEndpoint = options.authorizationEndpoint ?? `${base}/oauth2/default/v1/authorize`; + + const authorizeUrl = new URL(authorizationEndpoint); + authorizeUrl.searchParams.set("client_id", options.clientId); + authorizeUrl.searchParams.set("redirect_uri", redirectUri); + authorizeUrl.searchParams.set("response_type", "code"); + authorizeUrl.searchParams.set("state", "executor-state"); + authorizeUrl.searchParams.set("code_challenge", challenge); + authorizeUrl.searchParams.set("code_challenge_method", "S256"); + authorizeUrl.searchParams.set("scope", "openid profile email offline_access"); + + const authorizeRes = await app.request(authorizeUrl.toString(), { headers: { Accept: "text/html" } }); + expect(authorizeRes.status).toBe(200); + + const { code, state } = await getAuthCode(app, store, { + authServerId: "default", + clientId: options.clientId, + redirectUri, + scope: "openid profile email offline_access", + state: "executor-state", + codeChallenge: challenge, + codeChallengeMethod: "S256", + }); + expect(code).toBeTruthy(); + expect(state).toBe("executor-state"); + + const tokenUrl = new URL(options.tokenEndpoint ?? `${base}/oauth2/default/v1/token`); + const tokenRes = await app.request(tokenUrl.toString(), { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" }, + body: new URLSearchParams({ + grant_type: "authorization_code", + code, + client_id: options.clientId, + ...(options.includeClientSecret === false ? {} : { client_secret: options.clientSecret ?? "" }), + redirect_uri: redirectUri, + code_verifier: verifier, + }).toString(), + }); + expect(tokenRes.status).toBe(200); + return (await tokenRes.json()) as Record; +} + describe("Okta plugin integration", () => { let app: Hono; let store: Store; @@ -180,7 +264,7 @@ describe("Okta plugin integration", () => { expect(body.introspection_endpoint).toBe(`${base}/oauth2/v1/introspect`); expect(body.registration_endpoint).toBe(`${base}/oauth2/v1/clients`); expect(body.code_challenge_methods_supported).toEqual(["plain", "S256"]); - expect(body.token_endpoint_auth_methods_supported).toEqual(["client_secret_post", "client_secret_basic", "none"]); + expect(body.token_endpoint_auth_methods_supported).toEqual(["client_secret_basic", "client_secret_post", "none"]); expect(body.request_parameter_supported).toBe(false); }); @@ -202,6 +286,40 @@ describe("Okta plugin integration", () => { expect(body.token_endpoint).toBe(`${base}/oauth2/custom-as/v1/token`); }); + it("returns path-insert metadata for both well-known suffixes", async () => { + for (const path of [ + "/.well-known/oauth-authorization-server/oauth2/default", + "/.well-known/openid-configuration/oauth2/default", + ]) { + const res = await app.request(`${base}${path}`, { headers: { Accept: "application/json" } }); + expect(res.status).toBe(200); + const body = (await res.json()) as Record; + expect(body.issuer).toBe(`${base}/oauth2/default`); + expect(body.authorization_endpoint).toBe(`${base}/oauth2/default/v1/authorize`); + expect(body.token_endpoint).toBe(`${base}/oauth2/default/v1/token`); + expect(body.registration_endpoint).toBe(`${base}/oauth2/default/v1/clients`); + expect(body.response_types_supported).toContain("code"); + expect(body.code_challenge_methods_supported).toContain("S256"); + } + }); + + it("returns the same auth-server metadata for suffix and path-insert aliases", async () => { + const paths = [ + "/oauth2/default/.well-known/openid-configuration", + "/oauth2/default/.well-known/oauth-authorization-server", + "/.well-known/openid-configuration/oauth2/default", + "/.well-known/oauth-authorization-server/oauth2/default", + ]; + const documents = await Promise.all( + paths.map(async (path) => { + const res = await app.request(`${base}${path}`, { headers: { Accept: "application/json" } }); + expect(res.status).toBe(200); + return (await res.json()) as Record; + }), + ); + expect(documents.every((doc) => JSON.stringify(doc) === JSON.stringify(documents[0]))).toBe(true); + }); + it("returns 404 for unknown custom auth server", async () => { const res = await app.request(`${base}/oauth2/does-not-exist/.well-known/openid-configuration`); expect(res.status).toBe(404); @@ -346,6 +464,126 @@ describe("Okta plugin integration", () => { }); }); + describe("dynamic client registration", () => { + it("registers public and confidential clients with RFC 7591 responses", async () => { + const publicClient = await registerClient(app, `${base}/oauth2/default/v1/clients`, "none"); + expect(typeof publicClient.client_id).toBe("string"); + expect(publicClient.client_secret).toBeUndefined(); + expect(publicClient.client_id_issued_at).toEqual(expect.any(Number)); + expect(publicClient.client_secret_expires_at).toBe(0); + expect(publicClient.client_name).toBe("Executor DCR Client"); + expect(publicClient.redirect_uris).toEqual(["http://localhost:3000/dcr-callback"]); + expect(publicClient.grant_types).toEqual(["authorization_code", "refresh_token"]); + expect(publicClient.response_types).toEqual(["code"]); + expect(publicClient.token_endpoint_auth_method).toBe("none"); + + const confidentialClient = await registerClient(app, `${base}/oauth2/default/v1/clients`, "client_secret_post"); + expect(typeof confidentialClient.client_id).toBe("string"); + expect(typeof confidentialClient.client_secret).toBe("string"); + expect(confidentialClient.token_endpoint_auth_method).toBe("client_secret_post"); + }); + + it("registered public client completes authorization-code PKCE S256 exchange", async () => { + const client = await registerClient(app, `${base}/oauth2/default/v1/clients`, "none"); + const tokenBody = await completePkceCodeFlow(app, store, { + clientId: client.client_id as string, + includeClientSecret: false, + }); + expect((tokenBody.access_token as string).startsWith("okta_")).toBe(true); + expect((tokenBody.refresh_token as string).startsWith("r_okta_")).toBe(true); + const claims = decodeJwt(tokenBody.id_token as string); + expect(claims.aud).toBe(client.client_id); + expect(claims.iss).toBe(`${base}/oauth2/default`); + }); + + it("rejects invalid metadata with an RFC 6749 error envelope", async () => { + const res = await app.request(`${base}/oauth2/default/v1/clients`, { + method: "POST", + headers: { "Content-Type": "application/json", Accept: "application/json" }, + body: JSON.stringify({ ...dcrRequestBody(), redirect_uris: [] }), + }); + expect(res.status).toBe(400); + const body = (await res.json()) as Record; + expect(body).toEqual({ + error: "invalid_client_metadata", + error_description: "redirect_uris must be a non-empty array of strings.", + }); + }); + + it("rejects invalid redirect URIs with invalid_redirect_uri", async () => { + const res = await app.request(`${base}/oauth2/default/v1/clients`, { + method: "POST", + headers: { "Content-Type": "application/json", Accept: "application/json" }, + body: JSON.stringify({ ...dcrRequestBody(), redirect_uris: ["not a url"] }), + }); + expect(res.status).toBe(400); + const body = (await res.json()) as Record; + expect(body.error).toBe("invalid_redirect_uri"); + expect(body.error_description).toBe("Each redirect_uri must be an absolute HTTP or HTTPS URL."); + }); + + it("satisfies executor metadata probe then registers and completes PKCE", async () => { + const metadataRes = await app.request(`${base}/.well-known/oauth-authorization-server/oauth2/default`, { + headers: { Accept: "application/json" }, + }); + expect(metadataRes.status).toBe(200); + const metadata = (await metadataRes.json()) as Record; + expect(metadata.registration_endpoint).toBe(`${base}/oauth2/default/v1/clients`); + + const client = await registerClient(app, metadata.registration_endpoint as string, "none"); + const tokenBody = await completePkceCodeFlow(app, store, { + authorizationEndpoint: metadata.authorization_endpoint as string, + tokenEndpoint: metadata.token_endpoint as string, + clientId: client.client_id as string, + includeClientSecret: false, + }); + expect(tokenBody.token_type).toBe("Bearer"); + expect(tokenBody.scope).toBe("openid profile email offline_access"); + }); + + it("fires an armed registration fault once and records it in the ledger", async () => { + const server = createServer(oktaPlugin, { baseUrl: base, manifest }); + oktaPlugin.seed?.(server.store, base); + + const arm = await server.app.request(`${base}/_emulate/faults`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + match: { method: "POST", pathPattern: "/oauth2/default/v1/clients" }, + response: { + status: 503, + body: { error: "temporarily_unavailable", error_description: "Injected failure." }, + }, + }), + }); + expect(arm.status).toBe(200); + + const first = await server.app.request(`${base}/oauth2/default/v1/clients`, { + method: "POST", + headers: { "Content-Type": "application/json", Accept: "application/json" }, + body: JSON.stringify(dcrRequestBody()), + }); + expect(first.status).toBe(503); + expect(((await first.json()) as Record).error).toBe("temporarily_unavailable"); + + const second = await server.app.request(`${base}/oauth2/default/v1/clients`, { + method: "POST", + headers: { "Content-Type": "application/json", Accept: "application/json" }, + body: JSON.stringify(dcrRequestBody()), + }); + expect(second.status).toBe(201); + + const ledgerRes = await server.app.request(`${base}/_emulate/ledger`); + expect(ledgerRes.status).toBe(200); + const ledger = (await ledgerRes.json()) as { entries: Array> }; + const faulted = ledger.entries.find( + (entry) => entry.path === "/oauth2/default/v1/clients" && entry.faulted === true, + ); + expect(faulted).toBeDefined(); + expect((faulted?.response as Record).status).toBe(503); + }); + }); + describe("PKCE", () => { it("supports S256 code challenge", async () => { const verifier = "pkce-verifier-12345"; diff --git a/packages/@emulators/okta/src/manifest.ts b/packages/@emulators/okta/src/manifest.ts index 5cfe37ee..238ee45d 100644 --- a/packages/@emulators/okta/src/manifest.ts +++ b/packages/@emulators/okta/src/manifest.ts @@ -38,14 +38,50 @@ export const manifest: ServiceManifest = { path: "/.well-known/openid-configuration", status: "hand-authored", }, + { + operationId: "oauth/authorizationServerMetadata", + method: "GET", + path: "/.well-known/oauth-authorization-server", + status: "hand-authored", + }, { operationId: "oidc/authServerDiscovery", method: "GET", path: "/oauth2/:authServerId/.well-known/openid-configuration", status: "hand-authored", }, + { + operationId: "oauth/authServerAuthorizationServerMetadata", + method: "GET", + path: "/oauth2/:authServerId/.well-known/oauth-authorization-server", + status: "hand-authored", + }, + { + operationId: "oidc/pathInsertAuthServerDiscovery", + method: "GET", + path: "/.well-known/openid-configuration/oauth2/:authServerId", + status: "hand-authored", + }, + { + operationId: "oauth/pathInsertAuthServerAuthorizationServerMetadata", + method: "GET", + path: "/.well-known/oauth-authorization-server/oauth2/:authServerId", + status: "hand-authored", + }, { operationId: "oidc/jwks", method: "GET", path: "/oauth2/v1/keys", status: "hand-authored" }, { operationId: "oauth/authorize", method: "GET", path: "/oauth2/v1/authorize", status: "hand-authored" }, + { + operationId: "oauth/registerClient", + method: "POST", + path: "/oauth2/v1/clients", + status: "hand-authored", + }, + { + operationId: "oauth/registerAuthServerClient", + method: "POST", + path: "/oauth2/:authServerId/v1/clients", + status: "hand-authored", + }, { operationId: "oauth/token", method: "POST", path: "/oauth2/v1/token", status: "hand-authored" }, { operationId: "oauth/userinfo", method: "GET", path: "/oauth2/v1/userinfo", status: "hand-authored" }, { operationId: "oauth/introspect", method: "POST", path: "/oauth2/v1/introspect", status: "hand-authored" }, diff --git a/packages/@emulators/okta/src/routes/oauth.ts b/packages/@emulators/okta/src/routes/oauth.ts index f007728e..5807479a 100644 --- a/packages/@emulators/okta/src/routes/oauth.ts +++ b/packages/@emulators/okta/src/routes/oauth.ts @@ -9,6 +9,7 @@ import { escapeAttr, escapeHtml, matchesRedirectUri, + recordSideEffect, renderCardPage, renderErrorPage, renderFormPostPage, @@ -132,7 +133,7 @@ function resolveServer( function buildOidcConfiguration(baseUrl: string, server: ResolvedServer): Record { const oauthBase = buildOAuthBasePath(server.authServerId); const oauthUrlBase = `${baseUrl}${oauthBase}`; - const tokenEndpointAuthMethods = ["client_secret_post", "client_secret_basic", "none"]; + const tokenEndpointAuthMethods = ["client_secret_basic", "client_secret_post", "none"]; return { issuer: server.issuer, authorization_endpoint: `${oauthUrlBase}/authorize`, @@ -176,6 +177,51 @@ function buildOidcConfiguration(baseUrl: string, server: ResolvedServer): Record }; } +function oauthMetadataResponse( + c: Context, + baseUrl: string, + oktaStore: ReturnType, + authServerId: string, +): Response { + const server = resolveServer(authServerId, baseUrl, oktaStore); + if (!server) return oktaError(c, 404, "E0000007", `Not found: authorization server '${authServerId}'`); + return c.json(buildOidcConfiguration(baseUrl, server)); +} + +type ClientRegistrationBody = { + redirect_uris?: unknown; + client_name?: unknown; + grant_types?: unknown; + response_types?: unknown; + token_endpoint_auth_method?: unknown; + scope?: unknown; +}; + +function registrationError(c: Context, error: string, errorDescription: string): Response { + return c.json({ error, error_description: errorDescription }, 400); +} + +function isStringArray(value: unknown): value is string[] { + return Array.isArray(value) && value.every((entry) => typeof entry === "string"); +} + +function isAllowedRedirectUri(value: string): boolean { + try { + const url = new URL(value); + return url.protocol === "http:" || url.protocol === "https:"; + } catch { + return false; + } +} + +function generateClientId(): string { + return `0oa${randomBytes(16).toString("hex")}`; +} + +function generateClientSecret(): string { + return randomBytes(32).toString("base64url"); +} + async function parseTokenLikeBody(c: Context): Promise> { const contentType = c.req.header("Content-Type") ?? ""; const raw = await c.req.text(); @@ -328,17 +374,128 @@ export function oauthRoutes({ app, store, baseUrl, tokenMap }: RouteContext): vo const SERVICE_LABEL = "Okta"; app.get("/.well-known/openid-configuration", (c) => { - const server = resolveServer(ORG_AUTH_SERVER_ID, baseUrl, oktaStore); - if (!server) return oktaError(c, 404, "E0000007", "Not found: org authorization server"); - return c.json(buildOidcConfiguration(baseUrl, server)); + return oauthMetadataResponse(c, baseUrl, oktaStore, ORG_AUTH_SERVER_ID); + }); + + app.get("/.well-known/oauth-authorization-server", (c) => { + return oauthMetadataResponse(c, baseUrl, oktaStore, ORG_AUTH_SERVER_ID); }); app.get("/oauth2/:authServerId/.well-known/openid-configuration", (c) => { - const authServerId = c.req.param("authServerId"); + return oauthMetadataResponse(c, baseUrl, oktaStore, c.req.param("authServerId")); + }); + + app.get("/oauth2/:authServerId/.well-known/oauth-authorization-server", (c) => { + return oauthMetadataResponse(c, baseUrl, oktaStore, c.req.param("authServerId")); + }); + + app.get("/.well-known/openid-configuration/oauth2/:authServerId", (c) => { + return oauthMetadataResponse(c, baseUrl, oktaStore, c.req.param("authServerId")); + }); + + app.get("/.well-known/oauth-authorization-server/oauth2/:authServerId", (c) => { + return oauthMetadataResponse(c, baseUrl, oktaStore, c.req.param("authServerId")); + }); + + const handleClientRegistration = async (c: Context, authServerId: string): Promise => { const server = resolveServer(authServerId, baseUrl, oktaStore); if (!server) return oktaError(c, 404, "E0000007", `Not found: authorization server '${authServerId}'`); - return c.json(buildOidcConfiguration(baseUrl, server)); - }); + + let body: ClientRegistrationBody; + try { + body = (await c.req.json()) as ClientRegistrationBody; + } catch { + return registrationError(c, "invalid_client_metadata", "Request body must be valid JSON."); + } + + if (!body || typeof body !== "object" || Array.isArray(body)) { + return registrationError(c, "invalid_client_metadata", "Request body must be a JSON object."); + } + + if (!isStringArray(body.redirect_uris) || body.redirect_uris.length === 0) { + return registrationError(c, "invalid_client_metadata", "redirect_uris must be a non-empty array of strings."); + } + + if (!body.redirect_uris.every(isAllowedRedirectUri)) { + return registrationError(c, "invalid_redirect_uri", "Each redirect_uri must be an absolute HTTP or HTTPS URL."); + } + + const responseTypes = + body.response_types === undefined ? ["code"] : isStringArray(body.response_types) ? body.response_types : null; + if (!responseTypes || responseTypes.length === 0 || !responseTypes.includes("code")) { + return registrationError(c, "invalid_client_metadata", "response_types must include code."); + } + + const grantTypes = + body.grant_types === undefined + ? ["authorization_code"] + : isStringArray(body.grant_types) + ? body.grant_types + : null; + if (!grantTypes || grantTypes.length === 0 || !grantTypes.includes("authorization_code")) { + return registrationError(c, "invalid_client_metadata", "grant_types must include authorization_code."); + } + + const tokenEndpointAuthMethod = body.token_endpoint_auth_method ?? "client_secret_basic"; + if ( + tokenEndpointAuthMethod !== "none" && + tokenEndpointAuthMethod !== "client_secret_post" && + tokenEndpointAuthMethod !== "client_secret_basic" + ) { + return registrationError(c, "invalid_client_metadata", "token_endpoint_auth_method is not supported."); + } + + if (body.client_name !== undefined && typeof body.client_name !== "string") { + return registrationError(c, "invalid_client_metadata", "client_name must be a string."); + } + + if (body.scope !== undefined && typeof body.scope !== "string") { + return registrationError(c, "invalid_client_metadata", "scope must be a string."); + } + + let clientId = generateClientId(); + while (oktaStore.oauthClients.findOneBy("client_id", clientId)) { + clientId = generateClientId(); + } + + const clientSecret = tokenEndpointAuthMethod === "none" ? undefined : generateClientSecret(); + const client = oktaStore.oauthClients.insert({ + client_id: clientId, + ...(clientSecret ? { client_secret: clientSecret } : {}), + name: body.client_name || "Dynamic Client", + redirect_uris: body.redirect_uris, + response_types: responseTypes, + grant_types: grantTypes, + token_endpoint_auth_method: tokenEndpointAuthMethod, + auth_server_id: authServerId, + }); + + recordSideEffect(c, { + type: "create", + collection: "okta.oauth_clients", + id: client.id, + summary: `Registered OAuth client ${client.client_id}`, + }); + + const issuedAt = Math.floor(new Date(client.created_at).getTime() / 1000); + return c.json( + { + client_id: client.client_id, + ...(client.client_secret ? { client_secret: client.client_secret } : {}), + client_id_issued_at: issuedAt, + client_secret_expires_at: 0, + client_name: client.name, + redirect_uris: client.redirect_uris, + grant_types: client.grant_types, + response_types: client.response_types, + token_endpoint_auth_method: client.token_endpoint_auth_method, + }, + 201, + ); + }; + + app.post("/oauth2/v1/clients", (c) => handleClientRegistration(c, ORG_AUTH_SERVER_ID)); + app.post("/oauth2/:authServerId/v1/clients", (c) => handleClientRegistration(c, c.req.param("authServerId"))); app.get("/oauth2/v1/keys", async (c) => { const { publicKey } = await keyPairPromise; From 54d686e30d8bbaa407426be5a08a22bc79f7659e Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Thu, 2 Jul 2026 10:41:52 -0700 Subject: [PATCH 2/3] Give the cold-start github api test a CI-tolerant timeout The first createEmulator test absorbs the plugin import cost; it ran at 2.9s on a green main run and deterministically exceeded the 5s default on this PR's runners (failed twice, passes locally in ~0.3s). --- packages/emulate/src/__tests__/api.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/emulate/src/__tests__/api.test.ts b/packages/emulate/src/__tests__/api.test.ts index 97a8f566..c7cf9508 100644 --- a/packages/emulate/src/__tests__/api.test.ts +++ b/packages/emulate/src/__tests__/api.test.ts @@ -2,7 +2,9 @@ import { describe, it, expect } from "vitest"; import { createEmulator } from "../api.js"; describe("createEmulator", () => { - it("starts github and returns a url", async () => { + // The first test in the suite pays the cold-start plugin import cost, which + // can exceed the default 5s on CI runners (it hit 2.9s on a green run). + it("starts github and returns a url", { timeout: 15000 }, async () => { const github = await createEmulator({ service: "github", port: 14000 }); expect(github.url).toBe("http://localhost:14000"); From 605e344d1801e2c7b5a6b59f6ec0525959b3c444 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Thu, 2 Jul 2026 10:57:02 -0700 Subject: [PATCH 3/3] Wait for the ad-hoc TTL test server to listen before fetching serve() returns before the socket is bound; on slow CI runners the first fetch raced the listen and failed with ECONNREFUSED on the test's own port. Pre-existing flake surfaced by this PR's runs. --- packages/@emulators/workos/src/__tests__/workos.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/@emulators/workos/src/__tests__/workos.test.ts b/packages/@emulators/workos/src/__tests__/workos.test.ts index f96aa042..6906b23a 100644 --- a/packages/@emulators/workos/src/__tests__/workos.test.ts +++ b/packages/@emulators/workos/src/__tests__/workos.test.ts @@ -370,6 +370,9 @@ describe("workos emulator with the real @workos-inc/node SDK", () => { oauth: { default_access_token_ttl_seconds: 5 }, }); const server = serve({ fetch: app.fetch, port: PORT + 1 }); + // serve() returns before the socket is listening; without this wait the + // first fetch races the listen and fails with ECONNREFUSED on slow runners. + await new Promise((resolve) => server.once("listening", resolve)); try { const base = `http://localhost:${PORT + 1}`; const registered = (await (