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/remote-bindings-auth-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"wrangler": patch
---

Handle and explain authentication failures from remote bindings during local development

Wrangler now recognizes authentication failures from remote preview sessions and reports that bindings which need to run remotely require Cloudflare authentication even when the rest of the Worker is developed locally.
91 changes: 91 additions & 0 deletions packages/remote-bindings/src/utils/remote.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { APIError } from "@cloudflare/workers-utils";
import { afterEach, describe, it, vi } from "vitest";
import {
handlePreviewSessionCreationError,
RemoteSessionAuthenticationError,
} from "./remote";

afterEach(() => {
vi.unstubAllEnvs();
});

function makeAuthError(code: number, status = 400): APIError {
const error = new APIError({
text: "A request to the Cloudflare API failed.",
status,
telemetryMessage: false,
});
error.code = code;
return error;
}

describe("RemoteSessionAuthenticationError", () => {
it("preserves the cause and telemetry message", ({ expect }) => {
const cause = new Error("original API error");
const error = new RemoteSessionAuthenticationError(cause);

expect(error.cause).toBe(cause);
expect(error.telemetryMessage).toBe("remote dev authentication error");
});

it("explains remote bindings when authenticating with an API token", ({
expect,
}) => {
vi.stubEnv("CLOUDFLARE_API_TOKEN", "test-token");

const error = new RemoteSessionAuthenticationError(new Error("api error"));

expect(error.message).toMatchInlineSnapshot(`
"This Worker uses bindings that need to run remotely, even when developing locally, but the remote session could not be authenticated.
It looks like you are authenticating via a custom API token (\`CLOUDFLARE_API_TOKEN\`) set in an environment variable.
The token may be invalid or lack the required permissions for this operation.

To fix this, verify that your token is valid and has the correct permissions.
You can also run \`wrangler whoami\` to check your current authentication status."
`);
});

it("explains remote bindings when authenticating with OAuth", ({
expect,
}) => {
vi.stubEnv("CLOUDFLARE_API_TOKEN", "");
vi.stubEnv("CLOUDFLARE_API_KEY", "");
vi.stubEnv("CLOUDFLARE_EMAIL", "");

const error = new RemoteSessionAuthenticationError(new Error("api error"));

expect(error.message).toMatchInlineSnapshot(`
"This Worker uses bindings that need to run remotely, even when developing locally, but the remote session could not be authenticated.
Your credentials may have expired or been revoked.

To fix this, try to:
- Run \`wrangler whoami\` to check your current authentication status.
- Run \`wrangler logout\` and then \`wrangler login\` to re-authenticate."
`);
});
});

describe("handlePreviewSessionCreationError", () => {
for (const { code, status } of [
{ code: 9106, status: 400 },
{ code: 10000, status: 400 },
{ code: 10405, status: 405 },
]) {
it(`wraps API authentication error ${code}`, ({ expect }) => {
const cause = makeAuthError(code, status);
let thrown: unknown;

try {
handlePreviewSessionCreationError(cause, "test-account-id");
} catch (error) {
thrown = error;
}

expect(thrown).toBeInstanceOf(RemoteSessionAuthenticationError);
expect((thrown as RemoteSessionAuthenticationError).cause).toBe(cause);
expect((thrown as RemoteSessionAuthenticationError).message).toContain(
"bindings that need to run remotely, even when developing locally"
);
});
}
});
9 changes: 5 additions & 4 deletions packages/remote-bindings/src/utils/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ import type {
export class RemoteSessionAuthenticationError extends UserError {
/**
* @param cause - The original error that triggered the authentication
* failure (e.g. an {@link APIError} with code 9106 or 10000).
* failure (e.g. an {@link APIError} with code 9106, 10000, or 10405).
*/
constructor(cause: unknown) {
const envAuth = getAuthFromEnv();

let errorMessage =
"Failed to establish remote session due to an authentication issue.\n";
"This Worker uses bindings that need to run remotely, even when developing locally, but the remote session could not be authenticated.\n";
if (envAuth !== undefined) {
// The user is authenticating via an environment variable
const method =
Expand Down Expand Up @@ -165,9 +165,10 @@ export function createRemoteWorkerInit(props: {
function handleUserFriendlyError(error: unknown, accountId?: string) {
if (error instanceof APIError) {
switch (error.code) {
// code 9106 and 10000 are authentication errors
// codes 9106, 10000, and 10405 are authentication errors
case 9106:
case 10000: {
case 10000:
case 10405: {
throw new RemoteSessionAuthenticationError(error);
}

Expand Down
Loading