Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/cf-auth-injectable-identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@cloudflare/workers-auth": minor
---

Make the OAuth identity and token storage injectable, and add a shared env-credential resolver

`createOAuthFlow` now takes the consumer's OAuth identity (`clientId`, `consent`, `redirectUri`) and token `storage` on its context, so other Cloudflare CLIs can reuse the flow under their own OAuth app and store tokens in their own location/format. Also adds a shared env→credential resolver (`getAuthFromEnv`, `getAPIToken`, `requireApiToken`).
7 changes: 7 additions & 0 deletions .changeset/cf-auth-wrangler-resolver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"wrangler": patch
---

Use the shared env-credential resolver from `@cloudflare/workers-auth`

No user-facing behaviour change. Credential resolution order (global API key + email → `CLOUDFLARE_API_TOKEN` → stored OAuth token) is preserved.
2 changes: 1 addition & 1 deletion .changeset/cf-wrangler-delegate-entrypoint.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

Add an experimental `cf-wrangler` delegate entrypoint for projects that can't use `@cloudflare/vite-plugin` (service workers, old compatibility dates, Python, Rust, etc.).

`cf-wrangler dev` starts the same local dev server as `wrangler dev` — it sits directly on wrangler's internal dev server, so the bundling and runtime behaviour are identical — but exposes a deliberately narrow CLI surface (`--mode`, `--port`, `--host`, `--local`) for a parent CLI to delegate to, and other dev server config options are read from the wrangler config file.
`cf-wrangler dev` starts the same local dev server as `wrangler dev` — it sits directly on wrangler's internal dev server, so the bundling and runtime behaviour are identical — but exposes a deliberately narrow CLI surface (`--mode`, `--port`, `--host`, `--local`) for a parent CLI to delegate to, and other dev server config options are read from the wrangler config file.

This replaces the separate `@cloudflare/wrangler-bundler` package. This is an internal integration point and is not intended to be run directly by users.
12 changes: 12 additions & 0 deletions .changeset/dependabot-update-14256.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"miniflare": patch
"wrangler": patch
---

Update dependencies of "miniflare", "wrangler"

The following dependency versions have been updated:

| Dependency | From | To |
| ---------- | ------------ | ------------ |
| workerd | 1.20260610.1 | 1.20260611.1 |
7 changes: 7 additions & 0 deletions .changeset/gate-network-enable-when-headless.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"wrangler": patch
---

Fix a memory leak that could make long-running headless `wrangler dev` sessions unresponsive

Long-running `wrangler dev` sessions with no DevTools attached (for example using the containers feature under sustained traffic) could gradually consume unbounded memory and eventually stop accepting connections. The inspector proxy now only enables network tracking while a DevTools client is connected, so the buildup no longer happens. Interactive debugging is unaffected. Fixes #14191.
6 changes: 6 additions & 0 deletions .changeset/move-build-before-deploy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"wrangler": patch
"@cloudflare/deploy-helpers": patch
---

Move worker build step earlier in deploy/upload step, before upload specific config validation
25 changes: 3 additions & 22 deletions packages/deploy-helpers/src/deploy/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import {
} from "./helpers/environments";
import { helpIfErrorIsSizeOrScriptStartup } from "./helpers/friendly-validator-errors";
import { verifyWorkerMatchesCITag } from "./helpers/match-tag";
import { validateNodeCompatMode } from "./helpers/node-compat";
import { parseBulkInputToObject } from "./helpers/parse-bulk-input";
import { parseConfigPlacement } from "./helpers/placement";
import { printBindings } from "./helpers/print-bindings";
Expand All @@ -60,7 +59,7 @@ import {
} from "./helpers/versions-api";
import { isWorkerNotFoundError } from "./helpers/worker-not-found-error";
import { addWorkersSitesBindings } from "./helpers/workers-sites-bindings";
import type { DeployProps, HandleBuild } from "../shared/types";
import type { DeployProps, WorkerBuildResult } from "../shared/types";
import type { RetrieveSourceMapFunction } from "./helpers/sourcemap";
import type {
ApiVersion,
Expand Down Expand Up @@ -144,7 +143,7 @@ export type DeployCallbacks = {
export default async function deploy(
props: DeployProps,
config: Config,
buildWorker: HandleBuild,
buildResult: WorkerBuildResult,
callbacks: DeployCallbacks
): Promise<{
sourceMapSize?: number;
Expand All @@ -165,8 +164,6 @@ export default async function deploy(
compatibilityDate,
compatibilityFlags,
keepVars,
minify,
noBundle,
uploadSourceMaps,
accountId,
} = props;
Expand Down Expand Up @@ -318,19 +315,6 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m

validateRoutes(allDeploymentRoutes, props.assetsOptions);

const nodejsCompatMode = validateNodeCompatMode(
compatibilityDate,
compatibilityFlags,
{ noBundle }
);

// Warn if user tries minify with no-bundle
if (noBundle && minify) {
logger.warn(
"`--minify` and `--no-bundle` can't be used together. If you want to minify your Worker and disable Wrangler's bundling, please minify as part of your own bundling process."
);
}

const scriptName = name;

assert(
Expand Down Expand Up @@ -415,10 +399,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
bundleType,
content,
bundle,
} = await buildWorker.build(props, config, {
nodejsCompatMode,
metafile: props.metafile,
});
} = buildResult;
// durable object migrations
const migrations = !isDryRun
? await getMigrationsToUpload(scriptName, {
Expand Down
24 changes: 3 additions & 21 deletions packages/deploy-helpers/src/deploy/versions-upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {
} from "./helpers/environments";
import { helpIfErrorIsSizeOrScriptStartup } from "./helpers/friendly-validator-errors";
import { verifyWorkerMatchesCITag } from "./helpers/match-tag";
import { validateNodeCompatMode } from "./helpers/node-compat";
import { parseBulkInputToObject } from "./helpers/parse-bulk-input";
import { parseConfigPlacement } from "./helpers/placement";
import { printBindings } from "./helpers/print-bindings";
Expand All @@ -43,7 +42,7 @@ import {
import { useServiceEnvironments as useServiceEnvironmentsConfig } from "./helpers/use-service-environments";
import { patchNonVersionedScriptSettings } from "./helpers/versions-api";
import { isWorkerNotFoundError } from "./helpers/worker-not-found-error";
import type { HandleBuild, VersionsUploadProps } from "../shared/types";
import type { VersionsUploadProps, WorkerBuildResult } from "../shared/types";
import type { DeployCallbacks } from "./deploy";
import type { RetrieveSourceMapFunction } from "./helpers/sourcemap";
import type { CfWorkerInit, Config } from "@cloudflare/workers-utils";
Expand All @@ -57,7 +56,7 @@ export type VersionsUploadCallbacks = Pick<
export default async function versionsUpload(
props: VersionsUploadProps,
config: Config,
buildWorker: HandleBuild,
buildResult: WorkerBuildResult,
callbacks: VersionsUploadCallbacks
): Promise<{
versionId: string | null;
Expand All @@ -77,8 +76,6 @@ export default async function versionsUpload(
compatibilityDate,
compatibilityFlags,
keepVars,
minify,
noBundle,
uploadSourceMaps,
accountId,
} = props;
Expand Down Expand Up @@ -155,19 +152,6 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
);
}

const nodejsCompatMode = validateNodeCompatMode(
compatibilityDate,
compatibilityFlags,
{ noBundle }
);

// Warn if user tries minify or node-compat with no-bundle
if (noBundle && minify) {
logger.warn(
"`--minify` and `--no-bundle` can't be used together. If you want to minify your Worker and disable Wrangler's bundling, please minify as part of your own bundling process."
);
}

const scriptName = name;

if (config.site && !config.site.bucket) {
Expand Down Expand Up @@ -223,9 +207,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
bundleType,
content,
bundle,
} = await buildWorker.build(props, config, {
nodejsCompatMode,
});
} = buildResult;
const bindings = getBindings(config);

// Vars from the CLI (--var) are hidden so their values aren't logged to the terminal
Expand Down
14 changes: 1 addition & 13 deletions packages/deploy-helpers/src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import type {
Route,
Entry,
} from "@cloudflare/workers-utils";
import type { NodeJSCompatMode } from "miniflare";

/**
* client needs to handle logger and fetch/auth implementation
Expand Down Expand Up @@ -143,7 +142,7 @@ export type BuildBundleInfo = {
sourceMapMetadata?: { tmpDir: string; entryDirectory: string } | undefined;
};

export type HandleBuildResult = {
export type WorkerBuildResult = {
modules: CfModule[];
dependencies: Record<string, { bytesInOutput: number }>;
resolvedEntryPointPath: string;
Expand All @@ -152,17 +151,6 @@ export type HandleBuildResult = {
bundle: BuildBundleInfo;
};

export type HandleBuild = {
build: (
props: SharedDeployVersionsProps,
config: Config,
options: {
nodejsCompatMode: NodeJSCompatMode;
metafile?: string | boolean;
}
) => Promise<HandleBuildResult>;
};

export interface TriggerDeployment {
targets: string[];
error?: Error;
Expand Down
2 changes: 1 addition & 1 deletion packages/miniflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"@cspotcode/source-map-support": "0.8.1",
"sharp": "0.34.5",
"undici": "catalog:default",
"workerd": "1.20260610.1",
"workerd": "1.20260611.1",
"ws": "catalog:default",
"youch": "4.1.0-beta.10"
},
Expand Down
25 changes: 19 additions & 6 deletions packages/workers-auth/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ CLIs. Internal-only — published as `prerelease: true`.

- `src/pkce.ts` — PKCE code-verifier / code-challenge generation (RFC 7636)
- `src/errors.ts` — `ErrorOAuth2` class hierarchy + `toErrorClass` mapper
- `src/generate-auth-url.ts` — authorize URL builder + `OAUTH_CALLBACK_URL`
- `src/generate-auth-url.ts` — authorize URL builder
- `src/generate-random-state.ts` — CSRF state generator
- `src/env-vars.ts` — `WRANGLER_*` env-var getters for OAuth endpoints
- `src/access.ts` — Cloudflare Access detection + service-token / `cloudflared` headers
- `src/auth-config-file.ts` — read/write the persisted TOML at `<globalWranglerConfigPath>/config/<env>.toml`
- `src/auth-config-file.ts` — the `AuthConfigStorage` / `UserAuthConfig` storage contract (interfaces only; the default TOML-on-disk implementation lives in the consumer, e.g. wrangler's `src/user/auth-config-file.ts`)
- `src/state.ts` — `readStoredAuthState()` + `StoredAuthState` shape
- `src/token-exchange.ts` — auth-code → token + refresh-token rotation + `fetchAuthToken`
- `src/callback-server.ts` — local HTTP server on `localhost:8976` for the OAuth callback
- `src/callback-server.ts` — local HTTP server for the OAuth callback (listens on the host/port from the consumer's `redirectUri`)
- `src/flow.ts` — `createOAuthFlow(ctx)` factory wiring everything together
- `src/context.ts` — `OAuthFlowContext` interface (DI surface)
- `src/test-helpers/` — MSW handlers for consumers' tests (`@cloudflare/workers-auth/test-helpers`)
Expand All @@ -28,10 +28,22 @@ CLIs. Internal-only — published as `prerelease: true`.
- `isNonInteractiveOrCI()` — whether to suppress interactive prompts
- `openInBrowser(url)` — opens the browser to the OAuth authorize URL
- `hasEnvCredentials()` — short-circuits refresh logic when env-based auth is set
- `purgeOnLoginOrLogout()` — invalidate consumer-side caches after login/logout
- `clientId` (required) — the consumer's registered OAuth app ID; `string` or
`() => string` for lazy (e.g. env-driven prod/staging) resolution
- `consent` (required) — the consumer's branded granted/denied consent pages
- `redirectUri` (required) — the registered redirect URI / local callback URL.
The callback server's listen host/port and route path are all derived from it
(per-call bind overrides via `LoginProps.callbackHost`/`callbackPort`)
- `storage` (required) — the consumer's `AuthConfigStorage` token-persistence
backend (wrangler's TOML-on-disk default lives in `src/user/auth-config-file.ts`)
- `purgeOnLoginOrLogout?()` — invalidate consumer-side caches after login/logout
- `generateAuthUrl?` / `generateRandomState?` — test overrides for deterministic
snapshot tests (defaults pull from `./generate-auth-url` / `./generate-random-state`)

`clientId`, `consent`, `redirectUri`, and `storage` are consumer-specific
(Wrangler's live in `packages/wrangler/src/user/`), so they are required rather
than defaulted here.

Wrangler wires this once in `packages/wrangler/src/user/user.ts`.

## CONVENTIONS
Expand All @@ -46,8 +58,9 @@ Wrangler wires this once in `packages/wrangler/src/user/user.ts`.
These labels are part of the telemetry contract — preserve them verbatim.
- No direct Cloudflare REST API calls. This package talks to OAuth endpoints
(`/oauth2/auth`, `/oauth2/token`, `/oauth2/revoke`) only.
- OAuth callback server listens on `localhost:8976` by default; override via
`LoginProps.callbackHost` / `callbackPort` per-call.
- OAuth callback server listens on the host/port derived from the consumer's
required `ctx.redirectUri`; override the bind address per-call via
`LoginProps.callbackHost` / `callbackPort`.

## BUILD

Expand Down
80 changes: 20 additions & 60 deletions packages/workers-auth/src/auth-config-file.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,3 @@
import { chmodSync, mkdirSync, writeFileSync } from "node:fs";
import path from "node:path";
import {
getCloudflareApiEnvironmentFromEnv,
getGlobalWranglerConfigPath,
parseTOML,
readFileSync,
} from "@cloudflare/workers-utils";
import TOML from "smol-toml";

/**
* The data that may be read from the on-disk user auth config file.
*/
Expand All @@ -21,56 +11,26 @@ export interface UserAuthConfig {
}

/**
* The path to the config file that holds user authentication data,
* relative to the user's home directory.
*/
const USER_AUTH_CONFIG_PATH = "config";

/**
* Returns the absolute path to the auth config TOML file.
*
* The file lives under the global Wrangler config directory and is named
* `default.toml` in production, or `<environment>.toml` for the staging /
* other Cloudflare API environments.
*/
export function getAuthConfigFilePath(): string {
const environment = getCloudflareApiEnvironmentFromEnv();
const filePath = `${USER_AUTH_CONFIG_PATH}/${environment === "production" ? "default.toml" : `${environment}.toml`}`;
return path.join(getGlobalWranglerConfigPath(), filePath);
}

/**
* Writes the user auth config to disk.
*
* No in-memory cache to invalidate — auth state is read on demand by every call
* site that needs it. Callers are responsible for any consumer-side cache
* purging (e.g. via the {@link OAuthFlowContext.purgeOnLoginOrLogout} hook).
*/
export function writeAuthConfigFile(config: UserAuthConfig): void {
const configPath = getAuthConfigFilePath();

mkdirSync(path.dirname(configPath), {
recursive: true,
});
// Write with mode 0o600 on creation and re-`chmod` on every save so
// other local users on shared hosts can't read the OAuth tokens.
// `writeFileSync`'s `mode` option only applies when the file is
// being created — the explicit `chmodSync` ensures that pre-existing
// files (e.g. written by an older Wrangler version with the process
// umask) get tightened on the next save too.
writeFileSync(configPath, TOML.stringify(config), {
encoding: "utf-8",
mode: 0o600,
});
chmodSync(configPath, 0o600);
}

/**
* Reads the user auth config from disk.
* Pluggable persistence for the user auth config.
*
* @throws if the file does not exist or cannot be parsed as TOML. Callers
* typically catch this and treat the failure as "not logged in via local OAuth".
* This package does not ship a default implementation — the consumer injects
* one via {@link OAuthFlowContext.storage} (and into {@link getAPIToken} /
* {@link readStoredAuthState}). Wrangler's default reads/writes a TOML file
* under the global Wrangler config directory; other CLIs can use a different
* location and/or serialization format (e.g. a JSONC file under a different
* CLI's XDG config directory).
*/
export function readAuthConfigFile(): UserAuthConfig {
return parseTOML(readFileSync(getAuthConfigFilePath())) as UserAuthConfig;
export interface AuthConfigStorage {
/**
* Read and parse the stored auth config.
* @throws if the backing store is missing or cannot be parsed. Callers treat
* a throw as "not logged in via local OAuth".
*/
read(): UserAuthConfig;
/** Serialize and persist the auth config. */
write(config: UserAuthConfig): void;
/** Remove the backing store (used on logout). */
clear(): void;
/** Human-readable location of the backing store, for display and warnings. */
path(): string;
}
Loading
Loading