diff --git a/.changeset/temporary-account-multi-auth.md b/.changeset/temporary-account-multi-auth.md new file mode 100644 index 00000000000..debae728c4c --- /dev/null +++ b/.changeset/temporary-account-multi-auth.md @@ -0,0 +1,10 @@ +--- +"@cloudflare/workers-auth": patch +"wrangler": patch +--- + +Fix the `--temporary` error on commands that authenticate more than one time + +`wrangler d1 migrations apply --remote --temporary` failed with this error: `You're already authenticated with Cloudflare, so --temporary can't be used`. The failure occurred with no login and with no `CLOUDFLARE_API_TOKEN`. This command authenticates one time for each statement that it runs. The first authentication makes a temporary preview account. The second authentication read the token of this new account as an earlier login. + +Wrangler now uses again the temporary account from the same command run. Commands that authenticate more than one time now work as `wrangler deploy --temporary` works. If real credentials are available, `--temporary` is still an error. diff --git a/packages/workers-auth/src/core/factory.ts b/packages/workers-auth/src/core/factory.ts index f63f1981b65..e492599f68b 100644 --- a/packages/workers-auth/src/core/factory.ts +++ b/packages/workers-auth/src/core/factory.ts @@ -570,6 +570,15 @@ ${accounts ); } + // This command run made this temporary account. It is not an earlier + // login, so use it again. Some commands call `requireAuth` more than + // one time, for example `d1 migrations apply --remote`. Without this + // check, the second call fails at the test below. + const latchedTemporaryAccount = oauthFlow.getActiveTemporaryAccount(); + if (latchedTemporaryAccount) { + return latchedTemporaryAccount.account.id; + } + // `--temporary` is only for unauthenticated use. If any credentials are // already available (env, global key, or a stored OAuth token), refuse // rather than silently provisioning a throwaway account alongside them. diff --git a/packages/wrangler/src/__tests__/d1/migrate.test.ts b/packages/wrangler/src/__tests__/d1/migrate.test.ts index 38e39308053..8066a4803b5 100644 --- a/packages/wrangler/src/__tests__/d1/migrate.test.ts +++ b/packages/wrangler/src/__tests__/d1/migrate.test.ts @@ -519,6 +519,127 @@ Your database may not be available to serve requests during the migration, conti ] `); }); + + describe("with a temporary preview account", () => { + mockAccountId({ accountId: null }); + mockApiToken({ apiToken: null }); + + const temporaryPreviewAccountUrl = + "https://api.cloudflare.com/client/v4/provisioning/previews"; + + function mockTemporaryPreviewAccount() { + let mintRequests = 0; + msw.use( + http.post(`${temporaryPreviewAccountUrl}/challenge`, () => + HttpResponse.json({ + success: true, + result: { + challengeToken: "challenge-token", + seed: Buffer.alloc(32, 1).toString("base64url"), + k: 2, + g: 2, + s: 16, + expiresAt: 9999999999, + }, + errors: [], + messages: [], + }) + ), + http.post(temporaryPreviewAccountUrl, () => { + mintRequests += 1; + return HttpResponse.json({ + success: true, + result: { + account: { + id: "preview-account-id", + name: "Preview Account Alpha", + type: "standard", + apiToken: "preview-account-token", + tokenId: "preview-token-id", + expiresAt: "2027-01-01T00:00:00.000Z", + }, + claim: { + token: "claim-token", + url: "https://dash.cloudflare.com/claim-preview?claimToken=claim-token", + expiresAt: "2027-01-02T00:00:00.000Z", + }, + }, + errors: [], + messages: [], + }); + }) + ); + return () => mintRequests; + } + + // `d1 migrations apply --remote` calls `requireAuth` one time for + // each statement. The account from the first call must also satisfy + // the later calls. + it("should apply migrations against the account it minted", async ({ + expect, + }) => { + setIsTTY(false); + const std = mockConsoleMethods(); + const migrationsDir = path.join(process.cwd(), "migrations"); + writeWranglerConfig({ + d1_databases: [ + { + binding: "DATABASE", + database_name: "db", + database_id: "xxxx", + migrations_dir: migrationsDir, + }, + ], + }); + let queryRequests = 0; + msw.use( + http.get("*/accounts/:accountId/d1/database", () => + HttpResponse.json({ + success: true, + result: [ + { uuid: "xxxx", name: "db", created_at: "", version: "alpha" }, + ], + errors: [], + messages: [], + }) + ), + http.post( + "*/accounts/:accountId/d1/database/:databaseId/query", + () => { + queryRequests += 1; + return HttpResponse.json({ + success: true, + result: [{ results: [], success: true, meta: {} }], + errors: [], + messages: [], + }); + } + ) + ); + const mintRequests = mockTemporaryPreviewAccount(); + + mockConfirm({ + text: `No migrations folder found. +Ok to create ${migrationsDir}?`, + result: true, + }); + await runWrangler("d1 migrations create db test"); + mockConfirm({ + text: `About to apply 1 migration(s) +Your database may not be available to serve requests during the migration, continue?`, + result: true, + }); + + await runWrangler("d1 migrations apply db --remote --temporary"); + + expect(std.err).toBe(""); + expect(std.out).toContain("0001_test.sql"); + // More than one query proves that the command authenticated more + // than one time and did not stop at the first authentication. + expect(queryRequests).toBeGreaterThan(1); + expect(mintRequests()).toBe(1); + }); + }); }); describe("list", () => {