wrangler 0.0.0-fb43821 → 0.0.0-fb53fda
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/wrangler.js +12 -3
- package/miniflare-dist/index.mjs +79 -0
- package/miniflare-dist/index.mjs.map +7 -0
- package/package.json +46 -17
- package/pages/functions/buildWorker.ts +62 -0
- package/pages/functions/filepath-routing.test.ts +223 -0
- package/pages/functions/filepath-routing.ts +181 -0
- package/pages/functions/identifiers.ts +78 -0
- package/pages/functions/routes.ts +151 -0
- package/pages/functions/template-worker.ts +130 -0
- package/src/__tests__/configuration.test.ts +2790 -0
- package/src/__tests__/dev.test.tsx +532 -0
- package/src/__tests__/guess-worker-format.test.ts +68 -0
- package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
- package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
- package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
- package/src/__tests__/helpers/mock-account-id.ts +48 -0
- package/src/__tests__/helpers/mock-bin.ts +36 -0
- package/src/__tests__/helpers/mock-cfetch.ts +207 -0
- package/src/__tests__/helpers/mock-console.ts +70 -0
- package/src/__tests__/helpers/mock-dialogs.ts +65 -0
- package/src/__tests__/helpers/mock-kv.ts +33 -0
- package/src/__tests__/helpers/mock-stdin.ts +103 -0
- package/src/__tests__/helpers/mock-web-socket.ts +29 -0
- package/src/__tests__/helpers/run-in-tmp.ts +32 -0
- package/src/__tests__/helpers/run-wrangler.ts +16 -0
- package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
- package/src/__tests__/https-options.test.ts +127 -0
- package/src/__tests__/index.test.ts +770 -209
- package/src/__tests__/jest.setup.ts +69 -0
- package/src/__tests__/kv.test.ts +1380 -0
- package/src/__tests__/logout.test.ts +52 -0
- package/src/__tests__/package-manager.test.ts +206 -0
- package/src/__tests__/pages.test.ts +82 -0
- package/src/__tests__/parse.test.ts +322 -0
- package/src/__tests__/publish.test.ts +3431 -0
- package/src/__tests__/r2.test.ts +198 -0
- package/src/__tests__/route.test.ts +45 -0
- package/src/__tests__/secret.test.ts +425 -0
- package/src/__tests__/sentry.test.ts +146 -0
- package/src/__tests__/tail.test.ts +562 -0
- package/src/__tests__/whoami.test.tsx +100 -0
- package/src/bundle.ts +142 -0
- package/src/cfetch/index.ts +104 -0
- package/src/cfetch/internal.ts +139 -0
- package/src/cli.ts +16 -7
- package/src/config/README.md +107 -0
- package/src/config/config.ts +218 -0
- package/src/config/diagnostics.ts +80 -0
- package/src/config/environment.ts +366 -0
- package/src/config/index.ts +62 -0
- package/src/config/validation-helpers.ts +554 -0
- package/src/config/validation.ts +1191 -0
- package/src/{api/preview.ts → create-worker-preview.ts} +63 -25
- package/src/create-worker-upload-form.ts +217 -0
- package/src/dev/dev.tsx +390 -0
- package/src/dev/local.tsx +232 -0
- package/src/dev/remote.tsx +203 -0
- package/src/dev/use-esbuild.ts +100 -0
- package/src/dialogs.tsx +4 -4
- package/src/entry.ts +237 -0
- package/src/environment-variables.ts +33 -0
- package/src/https-options.ts +122 -0
- package/src/index.tsx +1417 -915
- package/src/inspect.ts +617 -0
- package/src/intl-polyfill.d.ts +139 -0
- package/src/kv.ts +319 -0
- package/src/miniflare-cli/README.md +30 -0
- package/src/miniflare-cli/enum-keys.ts +17 -0
- package/src/miniflare-cli/index.ts +37 -0
- package/src/module-collection.ts +229 -35
- package/src/open-in-browser.ts +25 -0
- package/src/package-manager.ts +126 -0
- package/src/pages.tsx +470 -227
- package/src/parse.ts +183 -0
- package/src/paths.ts +26 -0
- package/src/proxy.ts +440 -0
- package/src/publish.ts +292 -290
- package/src/r2.ts +50 -0
- package/src/reporting.ts +131 -0
- package/src/selfsigned.d.ts +29 -0
- package/src/sites.tsx +224 -59
- package/src/tail/filters.ts +279 -0
- package/src/tail/index.ts +299 -0
- package/src/tail/printing.ts +62 -0
- package/src/user.tsx +301 -203
- package/src/whoami.tsx +69 -0
- package/src/worker.ts +163 -0
- package/templates/new-worker.js +15 -0
- package/templates/new-worker.ts +15 -0
- package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
- package/templates/tsconfig.json +105 -0
- package/wrangler-dist/cli.js +134241 -116339
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/mock-cfetch.js +0 -42
- package/src/api/form_data.ts +0 -141
- package/src/api/inspect.ts +0 -430
- package/src/api/worker.ts +0 -161
- package/src/cfetch.ts +0 -72
- package/src/config.ts +0 -122
- package/src/dev.tsx +0 -776
- package/src/kv.tsx +0 -211
- package/src/tail.tsx +0 -71
package/src/user.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Based heavily on code from https://github.com/BitySA/oauth2-auth-code-pkce */
|
|
2
2
|
|
|
3
|
-
/*
|
|
3
|
+
/*
|
|
4
4
|
|
|
5
5
|
Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -205,25 +205,43 @@
|
|
|
205
205
|
limitations under the License.
|
|
206
206
|
*/
|
|
207
207
|
|
|
208
|
-
import
|
|
209
|
-
import { render } from "ink";
|
|
210
|
-
import Table from "ink-table";
|
|
211
|
-
import SelectInput from "ink-select-input";
|
|
212
|
-
import fetch from "node-fetch";
|
|
208
|
+
import assert from "node:assert";
|
|
213
209
|
import { webcrypto as crypto } from "node:crypto";
|
|
214
|
-
import {
|
|
215
|
-
import open from "open";
|
|
216
|
-
import url from "node:url";
|
|
210
|
+
import { writeFileSync, mkdirSync, rmSync } from "node:fs";
|
|
217
211
|
import http from "node:http";
|
|
218
|
-
import { readFile, writeFile, rm, mkdir } from "node:fs/promises";
|
|
219
|
-
import path from "node:path";
|
|
220
|
-
import process from "node:process";
|
|
221
212
|
import os from "node:os";
|
|
213
|
+
import path from "node:path";
|
|
214
|
+
import url from "node:url";
|
|
215
|
+
import { TextEncoder } from "node:util";
|
|
222
216
|
import TOML from "@iarna/toml";
|
|
223
|
-
import
|
|
217
|
+
import { render, Text } from "ink";
|
|
218
|
+
import SelectInput from "ink-select-input";
|
|
219
|
+
import Table from "ink-table";
|
|
220
|
+
import React from "react";
|
|
221
|
+
import { fetch } from "undici";
|
|
222
|
+
import { getCloudflareApiBaseUrl } from "./cfetch";
|
|
223
|
+
import { getEnvironmentVariableFactory } from "./environment-variables";
|
|
224
|
+
import openInBrowser from "./open-in-browser";
|
|
225
|
+
import { readFileSync } from "./parse";
|
|
226
|
+
import type { Item as SelectInputItem } from "ink-select-input/build/SelectInput";
|
|
224
227
|
import type { ParsedUrlQuery } from "node:querystring";
|
|
225
|
-
import {
|
|
226
|
-
|
|
228
|
+
import type { Response } from "undici";
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Try to read the API token from the environment.
|
|
232
|
+
*/
|
|
233
|
+
const getCloudflareAPITokenFromEnv = getEnvironmentVariableFactory({
|
|
234
|
+
variableName: "CLOUDFLARE_API_TOKEN",
|
|
235
|
+
deprecatedName: "CF_API_TOKEN",
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Try to read the account ID from the environment.
|
|
240
|
+
*/
|
|
241
|
+
const getCloudflareAccountIdFromEnv = getEnvironmentVariableFactory({
|
|
242
|
+
variableName: "CLOUDFLARE_ACCOUNT_ID",
|
|
243
|
+
deprecatedName: "CF_ACCOUNT_ID",
|
|
244
|
+
});
|
|
227
245
|
|
|
228
246
|
/**
|
|
229
247
|
* An implementation of rfc6749#section-4.1 and rfc7636.
|
|
@@ -234,17 +252,45 @@ interface PKCECodes {
|
|
|
234
252
|
codeVerifier: string;
|
|
235
253
|
}
|
|
236
254
|
|
|
237
|
-
|
|
238
|
-
|
|
255
|
+
/**
|
|
256
|
+
* The module level state of the authentication flow.
|
|
257
|
+
*/
|
|
258
|
+
interface State extends AuthTokens {
|
|
239
259
|
authorizationCode?: string;
|
|
240
260
|
codeChallenge?: string;
|
|
241
261
|
codeVerifier?: string;
|
|
242
262
|
hasAuthCodeBeenExchangedForAccessToken?: boolean;
|
|
243
|
-
refreshToken?: RefreshToken; // persist
|
|
244
263
|
stateQueryParam?: string;
|
|
245
264
|
scopes?: Scope[];
|
|
246
265
|
}
|
|
247
266
|
|
|
267
|
+
/**
|
|
268
|
+
* The tokens related to authentication.
|
|
269
|
+
*/
|
|
270
|
+
interface AuthTokens {
|
|
271
|
+
accessToken?: AccessToken;
|
|
272
|
+
refreshToken?: RefreshToken;
|
|
273
|
+
/** @deprecated - this field was only provided by the deprecated `wrangler1 config` command. */
|
|
274
|
+
apiToken?: string;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The path to the config file that holds user authentication data,
|
|
279
|
+
* relative to the user's home directory.
|
|
280
|
+
*/
|
|
281
|
+
export const USER_AUTH_CONFIG_FILE = ".wrangler/config/default.toml";
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* The data that may be read from the `USER_CONFIG_FILE`.
|
|
285
|
+
*/
|
|
286
|
+
export interface UserAuthConfig {
|
|
287
|
+
oauth_token?: string;
|
|
288
|
+
refresh_token?: string;
|
|
289
|
+
expiration_time?: string;
|
|
290
|
+
/** @deprecated - this field was only provided by the deprecated `wrangler1 config` command. */
|
|
291
|
+
api_token?: string;
|
|
292
|
+
}
|
|
293
|
+
|
|
248
294
|
interface RefreshToken {
|
|
249
295
|
value: string;
|
|
250
296
|
}
|
|
@@ -254,38 +300,39 @@ interface AccessToken {
|
|
|
254
300
|
expiry: string;
|
|
255
301
|
}
|
|
256
302
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
"
|
|
271
|
-
"
|
|
272
|
-
|
|
273
|
-
"
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
303
|
+
const Scopes = {
|
|
304
|
+
"account:read":
|
|
305
|
+
"See your account info such as account details, analytics, and memberships.",
|
|
306
|
+
"user:read":
|
|
307
|
+
"See your user info such as name, email address, and account memberships.",
|
|
308
|
+
"workers:write":
|
|
309
|
+
"See and change Cloudflare Workers data such as zones, KV storage, namespaces, scripts, and routes.",
|
|
310
|
+
"workers_kv:write":
|
|
311
|
+
"See and change Cloudflare Workers KV Storage data such as keys and namespaces.",
|
|
312
|
+
"workers_routes:write":
|
|
313
|
+
"See and change Cloudflare Workers data such as filters and routes.",
|
|
314
|
+
"workers_scripts:write":
|
|
315
|
+
"See and change Cloudflare Workers scripts, durable objects, subdomains, triggers, and tail data.",
|
|
316
|
+
"workers_tail:read": "See Cloudflare Workers tail and script data.",
|
|
317
|
+
"pages:write":
|
|
318
|
+
"See and change Cloudflare Pages projects, settings and deployments.",
|
|
319
|
+
"zone:read": "Grants read level access to account zone.",
|
|
320
|
+
} as const;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* The possible keys for a Scope.
|
|
324
|
+
*
|
|
325
|
+
* "offline_access" is automatically included.
|
|
326
|
+
*/
|
|
327
|
+
type Scope = keyof typeof Scopes;
|
|
328
|
+
|
|
329
|
+
const ScopeKeys = Object.keys(Scopes) as Scope[];
|
|
330
|
+
|
|
331
|
+
export function validateScopeKeys(
|
|
332
|
+
scopes: string[]
|
|
333
|
+
): scopes is typeof ScopeKeys {
|
|
334
|
+
return scopes.every((scope) => scope in Scopes);
|
|
335
|
+
}
|
|
289
336
|
|
|
290
337
|
const CLIENT_ID = "54d11594-84e4-41aa-b438-e81b8fa78ee7";
|
|
291
338
|
const AUTH_URL = "https://dash.cloudflare.com/oauth2/auth";
|
|
@@ -293,63 +340,63 @@ const TOKEN_URL = "https://dash.cloudflare.com/oauth2/token";
|
|
|
293
340
|
const CALLBACK_URL = "http://localhost:8976/oauth/callback";
|
|
294
341
|
const REVOKE_URL = "https://dash.cloudflare.com/oauth2/revoke";
|
|
295
342
|
|
|
296
|
-
|
|
297
|
-
let initialised = false;
|
|
343
|
+
let LocalState: State = getAuthTokens();
|
|
298
344
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
// get
|
|
345
|
+
/**
|
|
346
|
+
* Compute the current auth tokens.
|
|
347
|
+
*/
|
|
348
|
+
function getAuthTokens(): AuthTokens {
|
|
349
|
+
// get refreshToken/accessToken from fs if exists
|
|
304
350
|
try {
|
|
305
|
-
// if
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
351
|
+
// if the environment variable is available, use that
|
|
352
|
+
const apiTokenFromEnv = getCloudflareAPITokenFromEnv();
|
|
353
|
+
if (apiTokenFromEnv) {
|
|
354
|
+
return {
|
|
355
|
+
accessToken: {
|
|
356
|
+
value: apiTokenFromEnv,
|
|
357
|
+
expiry: "3021-12-31T23:59:59+00:00",
|
|
358
|
+
},
|
|
310
359
|
};
|
|
311
|
-
initialised = true;
|
|
312
|
-
return;
|
|
313
360
|
}
|
|
314
361
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
);
|
|
320
|
-
const { oauth_token, refresh_token, expiration_time } = toml as {
|
|
321
|
-
oauth_token: string;
|
|
322
|
-
refresh_token: string;
|
|
323
|
-
expiration_time: string;
|
|
324
|
-
};
|
|
362
|
+
// otherwise try loading from the user auth config file.
|
|
363
|
+
const { oauth_token, refresh_token, expiration_time, api_token } =
|
|
364
|
+
readAuthConfigFile();
|
|
365
|
+
|
|
325
366
|
if (oauth_token) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
367
|
+
return {
|
|
368
|
+
accessToken: {
|
|
369
|
+
value: oauth_token,
|
|
370
|
+
// If there is no `expiration_time` field then set it to an old date, to cause it to expire immediately.
|
|
371
|
+
expiry: expiration_time ?? "2000-01-01:00:00:00+00:00",
|
|
372
|
+
},
|
|
373
|
+
refreshToken: { value: refresh_token ?? "" },
|
|
374
|
+
};
|
|
375
|
+
} else if (api_token) {
|
|
376
|
+
return { apiToken: api_token };
|
|
377
|
+
} else {
|
|
378
|
+
return {};
|
|
330
379
|
}
|
|
331
|
-
} catch
|
|
332
|
-
|
|
333
|
-
// console.error(err);
|
|
380
|
+
} catch {
|
|
381
|
+
return {};
|
|
334
382
|
}
|
|
335
|
-
initialised = true;
|
|
336
383
|
}
|
|
337
384
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
);
|
|
344
|
-
}
|
|
385
|
+
/**
|
|
386
|
+
* Run the initialisation of the auth state, in the case that something changed.
|
|
387
|
+
*/
|
|
388
|
+
export function reinitialiseAuthTokens(): void {
|
|
389
|
+
LocalState = getAuthTokens();
|
|
345
390
|
}
|
|
346
391
|
|
|
347
|
-
export function getAPIToken(): string {
|
|
348
|
-
if (
|
|
349
|
-
|
|
392
|
+
export function getAPIToken(): string | undefined {
|
|
393
|
+
if (LocalState.apiToken) {
|
|
394
|
+
console.warn(
|
|
395
|
+
"It looks like you have used Wrangler 1's `config` command to login with an API token.\n" +
|
|
396
|
+
"This is no longer supported in the current version of Wrangler.\n" +
|
|
397
|
+
"If you wish to authenticate via an API token then please set the `CLOUDFLARE_API_TOKEN` environment variable."
|
|
398
|
+
);
|
|
350
399
|
}
|
|
351
|
-
|
|
352
|
-
throwIfNotInitialised();
|
|
353
400
|
return LocalState.accessToken?.value;
|
|
354
401
|
}
|
|
355
402
|
|
|
@@ -548,7 +595,7 @@ function isReturningFromAuthServer(query: ParsedUrlQuery): boolean {
|
|
|
548
595
|
return true;
|
|
549
596
|
}
|
|
550
597
|
|
|
551
|
-
export async function getAuthURL(scopes
|
|
598
|
+
export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
|
|
552
599
|
const { codeChallenge, codeVerifier } = await generatePKCECodes();
|
|
553
600
|
const stateQueryParam = generateRandomState(RECOMMENDED_STATE_LENGTH);
|
|
554
601
|
|
|
@@ -558,39 +605,44 @@ export async function getAuthURL(scopes?: string[]): Promise<string> {
|
|
|
558
605
|
stateQueryParam,
|
|
559
606
|
});
|
|
560
607
|
|
|
561
|
-
// TODO: verify that the scopes passed are legit
|
|
562
|
-
|
|
563
608
|
return (
|
|
564
609
|
AUTH_URL +
|
|
565
610
|
`?response_type=code&` +
|
|
566
611
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
567
612
|
`redirect_uri=${encodeURIComponent(CALLBACK_URL)}&` +
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
)}&` +
|
|
613
|
+
// we add offline_access manually for every request
|
|
614
|
+
`scope=${encodeURIComponent([...scopes, "offline_access"].join(" "))}&` +
|
|
571
615
|
`state=${stateQueryParam}&` +
|
|
572
616
|
`code_challenge=${encodeURIComponent(codeChallenge)}&` +
|
|
573
617
|
`code_challenge_method=S256`
|
|
574
618
|
);
|
|
575
619
|
}
|
|
576
620
|
|
|
621
|
+
type TokenResponse =
|
|
622
|
+
| {
|
|
623
|
+
access_token: string;
|
|
624
|
+
expires_in: number;
|
|
625
|
+
refresh_token: string;
|
|
626
|
+
scope: string;
|
|
627
|
+
}
|
|
628
|
+
| {
|
|
629
|
+
error: string;
|
|
630
|
+
};
|
|
631
|
+
|
|
577
632
|
/**
|
|
578
633
|
* Refresh an access token from the remote service.
|
|
579
634
|
*/
|
|
580
635
|
async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
if (!refreshToken) {
|
|
636
|
+
if (!LocalState.refreshToken) {
|
|
584
637
|
console.warn("No refresh token is present.");
|
|
585
638
|
}
|
|
586
639
|
|
|
587
|
-
const url = TOKEN_URL;
|
|
588
640
|
const body =
|
|
589
641
|
`grant_type=refresh_token&` +
|
|
590
|
-
`refresh_token=${refreshToken?.value}&` +
|
|
642
|
+
`refresh_token=${LocalState.refreshToken?.value}&` +
|
|
591
643
|
`client_id=${CLIENT_ID}`;
|
|
592
644
|
|
|
593
|
-
const response = await fetch(
|
|
645
|
+
const response = await fetch(TOKEN_URL, {
|
|
594
646
|
method: "POST",
|
|
595
647
|
body,
|
|
596
648
|
headers: {
|
|
@@ -601,13 +653,12 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
601
653
|
throw await response.json();
|
|
602
654
|
} else {
|
|
603
655
|
try {
|
|
604
|
-
const json = await response.json();
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
};
|
|
656
|
+
const json = (await response.json()) as TokenResponse;
|
|
657
|
+
if ("error" in json) {
|
|
658
|
+
throw json.error;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const { access_token, expires_in, refresh_token, scope } = json;
|
|
611
662
|
let scopes: Scope[] = [];
|
|
612
663
|
|
|
613
664
|
const accessToken: AccessToken = {
|
|
@@ -617,10 +668,9 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
617
668
|
LocalState.accessToken = accessToken;
|
|
618
669
|
|
|
619
670
|
if (refresh_token) {
|
|
620
|
-
|
|
671
|
+
LocalState.refreshToken = {
|
|
621
672
|
value: refresh_token,
|
|
622
673
|
};
|
|
623
|
-
LocalState.refreshToken = refreshToken;
|
|
624
674
|
}
|
|
625
675
|
|
|
626
676
|
if (scope) {
|
|
@@ -636,20 +686,24 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
636
686
|
refreshToken: LocalState.refreshToken,
|
|
637
687
|
};
|
|
638
688
|
return accessContext;
|
|
639
|
-
} catch (
|
|
640
|
-
const error = err?.error || "There was a network error.";
|
|
689
|
+
} catch (error) {
|
|
641
690
|
switch (error) {
|
|
642
691
|
case "invalid_grant":
|
|
643
|
-
console.
|
|
692
|
+
console.warn(
|
|
644
693
|
"Expired! Auth code or refresh token needs to be renewed."
|
|
645
694
|
);
|
|
646
695
|
// alert("Redirecting to auth server to obtain a new auth grant code.");
|
|
647
696
|
// TODO: return refreshAuthCodeOrRefreshToken();
|
|
648
697
|
break;
|
|
649
698
|
default:
|
|
699
|
+
console.error(error);
|
|
650
700
|
break;
|
|
651
701
|
}
|
|
652
|
-
|
|
702
|
+
if (typeof error === "string") {
|
|
703
|
+
throw toErrorClass(error);
|
|
704
|
+
} else {
|
|
705
|
+
throw error;
|
|
706
|
+
}
|
|
653
707
|
}
|
|
654
708
|
}
|
|
655
709
|
}
|
|
@@ -666,7 +720,6 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
666
720
|
console.warn("No authorization grant code is being passed.");
|
|
667
721
|
}
|
|
668
722
|
|
|
669
|
-
const url = TOKEN_URL;
|
|
670
723
|
const body =
|
|
671
724
|
`grant_type=authorization_code&` +
|
|
672
725
|
`code=${encodeURIComponent(authorizationCode || "")}&` +
|
|
@@ -674,7 +727,7 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
674
727
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
675
728
|
`code_verifier=${codeVerifier}`;
|
|
676
729
|
|
|
677
|
-
const response = await fetch(
|
|
730
|
+
const response = await fetch(TOKEN_URL, {
|
|
678
731
|
method: "POST",
|
|
679
732
|
body,
|
|
680
733
|
headers: {
|
|
@@ -691,13 +744,11 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
691
744
|
}
|
|
692
745
|
throw toErrorClass(error);
|
|
693
746
|
}
|
|
694
|
-
const json = await response.json();
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
scope: string;
|
|
700
|
-
};
|
|
747
|
+
const json = (await response.json()) as TokenResponse;
|
|
748
|
+
if ("error" in json) {
|
|
749
|
+
throw new Error(json.error);
|
|
750
|
+
}
|
|
751
|
+
const { access_token, expires_in, refresh_token, scope } = json;
|
|
701
752
|
let scopes: Scope[] = [];
|
|
702
753
|
LocalState.hasAuthCodeBeenExchangedForAccessToken = true;
|
|
703
754
|
|
|
@@ -709,10 +760,9 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
709
760
|
LocalState.accessToken = accessToken;
|
|
710
761
|
|
|
711
762
|
if (refresh_token) {
|
|
712
|
-
|
|
763
|
+
LocalState.refreshToken = {
|
|
713
764
|
value: refresh_token,
|
|
714
765
|
};
|
|
715
|
-
LocalState.refreshToken = refreshToken;
|
|
716
766
|
}
|
|
717
767
|
|
|
718
768
|
if (scope) {
|
|
@@ -782,52 +832,68 @@ function generateRandomState(lengthOfState: number): string {
|
|
|
782
832
|
.join("");
|
|
783
833
|
}
|
|
784
834
|
|
|
785
|
-
|
|
786
|
-
|
|
835
|
+
export function writeAuthConfigFile(config: UserAuthConfig) {
|
|
836
|
+
mkdirSync(path.join(os.homedir(), ".wrangler/config/"), {
|
|
787
837
|
recursive: true,
|
|
788
838
|
});
|
|
789
|
-
|
|
790
|
-
path.join(os.homedir(),
|
|
791
|
-
|
|
792
|
-
oauth_token = "${tokenData.token?.value || ""}"
|
|
793
|
-
refresh_token = "${tokenData.refreshToken?.value}"
|
|
794
|
-
expiration_time = "${tokenData.token?.expiry}"
|
|
795
|
-
`,
|
|
839
|
+
writeFileSync(
|
|
840
|
+
path.join(os.homedir(), USER_AUTH_CONFIG_FILE),
|
|
841
|
+
TOML.stringify(config as TOML.JsonMap),
|
|
796
842
|
{ encoding: "utf-8" }
|
|
797
843
|
);
|
|
798
844
|
}
|
|
799
845
|
|
|
846
|
+
function readAuthConfigFile(): UserAuthConfig {
|
|
847
|
+
const toml = TOML.parse(
|
|
848
|
+
readFileSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE))
|
|
849
|
+
);
|
|
850
|
+
return toml;
|
|
851
|
+
}
|
|
852
|
+
|
|
800
853
|
type LoginProps = {
|
|
801
|
-
scopes?:
|
|
854
|
+
scopes?: Scope[];
|
|
802
855
|
};
|
|
803
856
|
|
|
804
|
-
export async function loginOrRefreshIfRequired(
|
|
857
|
+
export async function loginOrRefreshIfRequired(
|
|
858
|
+
isInteractive = true
|
|
859
|
+
): Promise<boolean> {
|
|
805
860
|
// TODO: if there already is a token, then try refreshing
|
|
806
861
|
// TODO: ask permission before opening browser
|
|
807
|
-
if (!
|
|
808
|
-
//
|
|
809
|
-
|
|
862
|
+
if (!getAPIToken()) {
|
|
863
|
+
// Not logged in.
|
|
864
|
+
// If we are not interactive, we cannot ask the user to login
|
|
865
|
+
return isInteractive && (await login());
|
|
810
866
|
} else if (isAccessTokenExpired()) {
|
|
811
|
-
|
|
867
|
+
// We're logged in, but the refresh token seems to have expired,
|
|
868
|
+
// so let's try to refresh it
|
|
869
|
+
const didRefresh = await refreshToken();
|
|
870
|
+
if (didRefresh) {
|
|
871
|
+
// The token was refreshed, so we're done here
|
|
872
|
+
return true;
|
|
873
|
+
} else {
|
|
874
|
+
// If the refresh token isn't valid, then we ask the user to login again
|
|
875
|
+
return isInteractive && (await login());
|
|
876
|
+
}
|
|
812
877
|
} else {
|
|
813
878
|
return true;
|
|
814
879
|
}
|
|
815
880
|
}
|
|
816
881
|
|
|
817
882
|
export async function login(props?: LoginProps): Promise<boolean> {
|
|
883
|
+
console.log("Attempting to login via OAuth...");
|
|
818
884
|
const urlToOpen = await getAuthURL(props?.scopes);
|
|
819
|
-
await
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
let server;
|
|
823
|
-
let loginTimeoutHandle;
|
|
885
|
+
await openInBrowser(urlToOpen);
|
|
886
|
+
let server: http.Server;
|
|
887
|
+
let loginTimeoutHandle: NodeJS.Timeout;
|
|
824
888
|
const timerPromise = new Promise<boolean>((resolve) => {
|
|
825
889
|
loginTimeoutHandle = setTimeout(() => {
|
|
826
|
-
console.error(
|
|
890
|
+
console.error(
|
|
891
|
+
"Timed out waiting for authorization code, please try again."
|
|
892
|
+
);
|
|
827
893
|
server.close();
|
|
828
894
|
clearTimeout(loginTimeoutHandle);
|
|
829
895
|
resolve(false);
|
|
830
|
-
}, 60000); // wait for
|
|
896
|
+
}, 60000); // wait for 60 seconds for the user to authorize
|
|
831
897
|
});
|
|
832
898
|
|
|
833
899
|
const loginPromise = new Promise<boolean>((resolve, reject) => {
|
|
@@ -858,8 +924,9 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
858
924
|
finish(false);
|
|
859
925
|
});
|
|
860
926
|
console.log(
|
|
861
|
-
"Error: Consent denied. You must grant consent to Wrangler in order to login
|
|
862
|
-
|
|
927
|
+
"Error: Consent denied. You must grant consent to Wrangler in order to login.\n" +
|
|
928
|
+
"If you don't want to do this consider passing an API token via the `CLOUDFLARE_API_TOKEN` environment variable"
|
|
929
|
+
);
|
|
863
930
|
|
|
864
931
|
return;
|
|
865
932
|
} else {
|
|
@@ -872,8 +939,12 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
872
939
|
finish(false, new ErrorNoAuthCode());
|
|
873
940
|
return;
|
|
874
941
|
} else {
|
|
875
|
-
const
|
|
876
|
-
|
|
942
|
+
const exchange = await exchangeAuthCodeForAccessToken();
|
|
943
|
+
writeAuthConfigFile({
|
|
944
|
+
oauth_token: exchange.token?.value ?? "",
|
|
945
|
+
expiration_time: exchange.token?.expiry,
|
|
946
|
+
refresh_token: exchange.refreshToken?.value,
|
|
947
|
+
});
|
|
877
948
|
res.writeHead(307, {
|
|
878
949
|
Location:
|
|
879
950
|
"https://welcome.developers.workers.dev/wrangler-oauth-consent-granted",
|
|
@@ -881,9 +952,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
881
952
|
res.end(() => {
|
|
882
953
|
finish(true);
|
|
883
954
|
});
|
|
884
|
-
console.log(
|
|
885
|
-
`Successfully configured. You can find your configuration file at: ${os.homedir()}/.wrangler/config/default.toml`
|
|
886
|
-
);
|
|
955
|
+
console.log(`Successfully logged in.`);
|
|
887
956
|
|
|
888
957
|
return;
|
|
889
958
|
}
|
|
@@ -900,18 +969,22 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
900
969
|
/**
|
|
901
970
|
* Checks to see if the access token has expired.
|
|
902
971
|
*/
|
|
903
|
-
|
|
904
|
-
throwIfNotInitialised();
|
|
972
|
+
function isAccessTokenExpired(): boolean {
|
|
905
973
|
const { accessToken } = LocalState;
|
|
906
974
|
return Boolean(accessToken && new Date() >= new Date(accessToken.expiry));
|
|
907
975
|
}
|
|
908
976
|
|
|
909
|
-
|
|
910
|
-
throwIfNotInitialised();
|
|
977
|
+
async function refreshToken(): Promise<boolean> {
|
|
911
978
|
// refresh
|
|
912
979
|
try {
|
|
913
|
-
const
|
|
914
|
-
|
|
980
|
+
const {
|
|
981
|
+
token: { value: oauth_token, expiry: expiration_time } = {
|
|
982
|
+
value: "",
|
|
983
|
+
expiry: "",
|
|
984
|
+
},
|
|
985
|
+
refreshToken: { value: refresh_token } = {},
|
|
986
|
+
} = await exchangeRefreshTokenForAccessToken();
|
|
987
|
+
writeAuthConfigFile({ oauth_token, expiration_time, refresh_token });
|
|
915
988
|
return true;
|
|
916
989
|
} catch (err) {
|
|
917
990
|
console.error(err);
|
|
@@ -920,16 +993,33 @@ export async function refreshToken(): Promise<boolean> {
|
|
|
920
993
|
}
|
|
921
994
|
|
|
922
995
|
export async function logout(): Promise<void> {
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
996
|
+
if (!LocalState.accessToken) {
|
|
997
|
+
if (!LocalState.refreshToken) {
|
|
998
|
+
console.log("Not logged in, exiting...");
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
const body =
|
|
1003
|
+
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
1004
|
+
`token_type_hint=refresh_token&` +
|
|
1005
|
+
`token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
|
|
1006
|
+
|
|
1007
|
+
const response = await fetch(REVOKE_URL, {
|
|
1008
|
+
method: "POST",
|
|
1009
|
+
body,
|
|
1010
|
+
headers: {
|
|
1011
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1012
|
+
},
|
|
1013
|
+
});
|
|
1014
|
+
await response.text(); // blank text? would be nice if it was something meaningful
|
|
1015
|
+
console.log(
|
|
1016
|
+
"💁 Wrangler is configured with an OAuth token. The token has been successfully revoked"
|
|
1017
|
+
);
|
|
928
1018
|
}
|
|
929
1019
|
const body =
|
|
930
1020
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
931
1021
|
`token_type_hint=refresh_token&` +
|
|
932
|
-
`token=${encodeURIComponent(refreshToken?.value || "")}`;
|
|
1022
|
+
`token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
|
|
933
1023
|
|
|
934
1024
|
const response = await fetch(REVOKE_URL, {
|
|
935
1025
|
method: "POST",
|
|
@@ -939,38 +1029,34 @@ export async function logout(): Promise<void> {
|
|
|
939
1029
|
},
|
|
940
1030
|
});
|
|
941
1031
|
await response.text(); // blank text? would be nice if it was something meaningful
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
);
|
|
945
|
-
// delete the file
|
|
946
|
-
await rm(path.join(os.homedir(), ".wrangler/config/default.toml"));
|
|
947
|
-
console.log(
|
|
948
|
-
`Removing ${os.homedir()}/.wrangler/config/default.toml.. success!`
|
|
949
|
-
);
|
|
1032
|
+
rmSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE));
|
|
1033
|
+
console.log(`Successfully logged out.`);
|
|
950
1034
|
}
|
|
951
1035
|
|
|
952
|
-
export function listScopes(): void {
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
const data = Scopes.map((scope, index) => ({
|
|
1036
|
+
export function listScopes(message = "💁 Available scopes:"): void {
|
|
1037
|
+
console.log(message);
|
|
1038
|
+
const data = ScopeKeys.map((scope: Scope) => ({
|
|
956
1039
|
Scope: scope,
|
|
957
|
-
Description:
|
|
1040
|
+
Description: Scopes[scope],
|
|
958
1041
|
}));
|
|
959
1042
|
render(<Table data={data} />);
|
|
960
1043
|
// TODO: maybe a good idea to show usage here
|
|
961
1044
|
}
|
|
962
1045
|
|
|
963
|
-
export async function getAccountId(
|
|
1046
|
+
export async function getAccountId(
|
|
1047
|
+
isInteractive = true
|
|
1048
|
+
): Promise<string | undefined> {
|
|
964
1049
|
const apiToken = getAPIToken();
|
|
965
1050
|
if (!apiToken) return;
|
|
966
1051
|
|
|
967
|
-
|
|
968
|
-
|
|
1052
|
+
const accountIdFromEnv = getCloudflareAccountIdFromEnv();
|
|
1053
|
+
if (accountIdFromEnv) {
|
|
1054
|
+
return accountIdFromEnv;
|
|
969
1055
|
}
|
|
970
1056
|
|
|
971
1057
|
let response: Response;
|
|
972
1058
|
try {
|
|
973
|
-
response = await fetch(`${
|
|
1059
|
+
response = await fetch(`${getCloudflareApiBaseUrl()}/memberships`, {
|
|
974
1060
|
method: "GET",
|
|
975
1061
|
headers: {
|
|
976
1062
|
Authorization: "Bearer " + apiToken,
|
|
@@ -978,19 +1064,19 @@ export async function getAccountId() {
|
|
|
978
1064
|
});
|
|
979
1065
|
} catch (err) {
|
|
980
1066
|
// probably offline
|
|
1067
|
+
return;
|
|
981
1068
|
}
|
|
982
|
-
|
|
983
|
-
let accountId: string;
|
|
984
|
-
|
|
985
|
-
const responseJSON: {
|
|
1069
|
+
|
|
1070
|
+
let accountId: string | undefined;
|
|
1071
|
+
const responseJSON = (await response.json()) as {
|
|
986
1072
|
success: boolean;
|
|
987
1073
|
result: { id: string; account: { id: string; name: string } }[];
|
|
988
|
-
}
|
|
1074
|
+
};
|
|
989
1075
|
|
|
990
1076
|
if (responseJSON.success === true) {
|
|
991
1077
|
if (responseJSON.result.length === 1) {
|
|
992
1078
|
accountId = responseJSON.result[0].account.id;
|
|
993
|
-
} else {
|
|
1079
|
+
} else if (isInteractive) {
|
|
994
1080
|
accountId = await new Promise((resolve) => {
|
|
995
1081
|
const accounts = responseJSON.result.map((x) => x.account);
|
|
996
1082
|
const { unmount } = render(
|
|
@@ -1003,6 +1089,15 @@ export async function getAccountId() {
|
|
|
1003
1089
|
/>
|
|
1004
1090
|
);
|
|
1005
1091
|
});
|
|
1092
|
+
} else {
|
|
1093
|
+
throw new Error(
|
|
1094
|
+
"More than one account available but unable to select one in non-interactive mode.\n" +
|
|
1095
|
+
`Please set the appropriate \`account_id\` in your \`wrangler.toml\` file.\n` +
|
|
1096
|
+
`Available accounts are ("<name>" - "<id>"):\n` +
|
|
1097
|
+
responseJSON.result
|
|
1098
|
+
.map((x) => ` "${x.account.name}" - "${x.account.id}")`)
|
|
1099
|
+
.join("\n")
|
|
1100
|
+
);
|
|
1006
1101
|
}
|
|
1007
1102
|
}
|
|
1008
1103
|
return accountId;
|
|
@@ -1014,16 +1109,19 @@ type ChooseAccountItem = {
|
|
|
1014
1109
|
};
|
|
1015
1110
|
export function ChooseAccount(props: {
|
|
1016
1111
|
accounts: ChooseAccountItem[];
|
|
1017
|
-
onSelect: (item) => void;
|
|
1112
|
+
onSelect: (item: SelectInputItem<ChooseAccountItem>) => void;
|
|
1018
1113
|
}) {
|
|
1019
1114
|
return (
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1115
|
+
<>
|
|
1116
|
+
<Text bold>Select an account from below:</Text>
|
|
1117
|
+
<SelectInput
|
|
1118
|
+
items={props.accounts.map((item) => ({
|
|
1119
|
+
key: item.id,
|
|
1120
|
+
label: item.name,
|
|
1121
|
+
value: item,
|
|
1122
|
+
}))}
|
|
1123
|
+
onSelect={props.onSelect}
|
|
1124
|
+
/>
|
|
1125
|
+
</>
|
|
1028
1126
|
);
|
|
1029
1127
|
}
|