wrangler 0.0.0-e5d3690 → 0.0.0-e88a54e
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/bin/wrangler.js +2 -2
- package/miniflare-dist/index.mjs +79 -0
- package/miniflare-dist/index.mjs.map +7 -0
- package/package.json +32 -16
- package/pages/functions/buildWorker.ts +1 -1
- package/pages/functions/filepath-routing.test.ts +212 -28
- package/pages/functions/filepath-routing.ts +83 -130
- package/pages/functions/routes.ts +11 -18
- package/pages/functions/template-worker.ts +3 -9
- package/src/__tests__/configuration.test.ts +2796 -0
- package/src/__tests__/dev.test.tsx +488 -59
- package/src/__tests__/dev2.test.tsx +154 -0
- package/src/__tests__/guess-worker-format.test.ts +58 -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__/{mock-cfetch.ts → helpers/mock-cfetch.ts} +48 -14
- package/src/__tests__/helpers/mock-console.ts +70 -0
- package/src/__tests__/{mock-dialogs.ts → helpers/mock-dialogs.ts} +1 -1
- 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 +777 -75
- package/src/__tests__/jest.setup.ts +53 -6
- package/src/__tests__/kv.test.ts +674 -491
- package/src/__tests__/logout.test.ts +52 -0
- package/src/__tests__/package-manager.test.ts +206 -0
- package/src/__tests__/pages.test.ts +44 -0
- package/src/__tests__/parse.test.ts +322 -0
- package/src/__tests__/publish.test.ts +2327 -295
- 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 +564 -0
- package/src/__tests__/whoami.test.tsx +26 -56
- package/src/bundle.ts +142 -0
- package/src/cfetch/index.ts +13 -19
- package/src/cfetch/internal.ts +91 -23
- package/src/cli.ts +10 -0
- 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 +486 -0
- package/src/config/validation.ts +1090 -0
- package/src/{api/preview.ts → create-worker-preview.ts} +61 -23
- package/src/create-worker-upload-form.ts +217 -0
- package/src/dev/dev.tsx +364 -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 +2 -2
- package/src/entry.ts +208 -0
- package/src/environment-variables.ts +33 -0
- package/src/https-options.ts +122 -0
- package/src/index.tsx +1175 -777
- package/src/inspect.ts +95 -21
- package/src/{kv.tsx → kv.ts} +104 -61
- 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 +39 -32
- package/src/parse.ts +183 -0
- package/src/paths.ts +26 -0
- package/src/proxy.ts +177 -39
- package/src/publish.ts +274 -309
- package/src/r2.ts +50 -0
- package/src/reporting.ts +131 -0
- package/src/selfsigned.d.ts +29 -0
- package/src/sites.tsx +95 -58
- 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 +233 -134
- package/src/{api/worker.ts → worker.ts} +45 -37
- 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 +130352 -112740
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/run-in-tmp.ts +0 -19
- package/src/__tests__/run-wrangler.ts +0 -32
- package/src/api/form_data.ts +0 -131
- package/src/config.ts +0 -646
- package/src/dev.tsx +0 -779
- package/src/tail.tsx +0 -73
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, Text } 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
|
}
|
|
@@ -268,6 +314,8 @@ const Scopes = {
|
|
|
268
314
|
"workers_scripts:write":
|
|
269
315
|
"See and change Cloudflare Workers scripts, durable objects, subdomains, triggers, and tail data.",
|
|
270
316
|
"workers_tail:read": "See Cloudflare Workers tail and script data.",
|
|
317
|
+
"pages:write":
|
|
318
|
+
"See and change Cloudflare Pages projects, settings and deployments.",
|
|
271
319
|
"zone:read": "Grants read level access to account zone.",
|
|
272
320
|
} as const;
|
|
273
321
|
|
|
@@ -276,14 +324,14 @@ const Scopes = {
|
|
|
276
324
|
*
|
|
277
325
|
* "offline_access" is automatically included.
|
|
278
326
|
*/
|
|
279
|
-
type Scope = keyof typeof Scopes
|
|
327
|
+
type Scope = keyof typeof Scopes;
|
|
280
328
|
|
|
281
329
|
const ScopeKeys = Object.keys(Scopes) as Scope[];
|
|
282
330
|
|
|
283
331
|
export function validateScopeKeys(
|
|
284
332
|
scopes: string[]
|
|
285
333
|
): scopes is typeof ScopeKeys {
|
|
286
|
-
return scopes.every((scope) => Scopes
|
|
334
|
+
return scopes.every((scope) => scope in Scopes);
|
|
287
335
|
}
|
|
288
336
|
|
|
289
337
|
const CLIENT_ID = "54d11594-84e4-41aa-b438-e81b8fa78ee7";
|
|
@@ -292,62 +340,63 @@ const TOKEN_URL = "https://dash.cloudflare.com/oauth2/token";
|
|
|
292
340
|
const CALLBACK_URL = "http://localhost:8976/oauth/callback";
|
|
293
341
|
const REVOKE_URL = "https://dash.cloudflare.com/oauth2/revoke";
|
|
294
342
|
|
|
295
|
-
|
|
296
|
-
let initialised = false;
|
|
343
|
+
let LocalState: State = getAuthTokens();
|
|
297
344
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
345
|
+
/**
|
|
346
|
+
* Compute the current auth tokens.
|
|
347
|
+
*/
|
|
348
|
+
function getAuthTokens(): AuthTokens {
|
|
302
349
|
// get refreshToken/accessToken from fs if exists
|
|
303
350
|
try {
|
|
304
|
-
// if
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
+
},
|
|
309
359
|
};
|
|
310
|
-
initialised = true;
|
|
311
|
-
return;
|
|
312
360
|
}
|
|
313
361
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
);
|
|
319
|
-
const { oauth_token, refresh_token, expiration_time } = toml as {
|
|
320
|
-
oauth_token: string;
|
|
321
|
-
refresh_token: string;
|
|
322
|
-
expiration_time: string;
|
|
323
|
-
};
|
|
362
|
+
// otherwise try loading from the user auth config file.
|
|
363
|
+
const { oauth_token, refresh_token, expiration_time, api_token } =
|
|
364
|
+
readAuthConfigFile();
|
|
365
|
+
|
|
324
366
|
if (oauth_token) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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 {};
|
|
329
379
|
}
|
|
330
|
-
} catch
|
|
331
|
-
|
|
332
|
-
// console.error(err);
|
|
380
|
+
} catch {
|
|
381
|
+
return {};
|
|
333
382
|
}
|
|
334
|
-
initialised = true;
|
|
335
383
|
}
|
|
336
384
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
);
|
|
343
|
-
}
|
|
385
|
+
/**
|
|
386
|
+
* Run the initialisation of the auth state, in the case that something changed.
|
|
387
|
+
*/
|
|
388
|
+
export function reinitialiseAuthTokens(): void {
|
|
389
|
+
LocalState = getAuthTokens();
|
|
344
390
|
}
|
|
345
391
|
|
|
346
392
|
export function getAPIToken(): string | undefined {
|
|
347
|
-
if (
|
|
348
|
-
|
|
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
|
+
);
|
|
349
399
|
}
|
|
350
|
-
throwIfNotInitialised();
|
|
351
400
|
return LocalState.accessToken?.value;
|
|
352
401
|
}
|
|
353
402
|
|
|
@@ -561,6 +610,7 @@ export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
|
|
|
561
610
|
`?response_type=code&` +
|
|
562
611
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
563
612
|
`redirect_uri=${encodeURIComponent(CALLBACK_URL)}&` +
|
|
613
|
+
// @ts-expect-error we add offline_access manually
|
|
564
614
|
`scope=${encodeURIComponent(scopes.concat("offline_access").join(" "))}&` +
|
|
565
615
|
`state=${stateQueryParam}&` +
|
|
566
616
|
`code_challenge=${encodeURIComponent(codeChallenge)}&` +
|
|
@@ -568,6 +618,17 @@ export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
|
|
|
568
618
|
);
|
|
569
619
|
}
|
|
570
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
|
+
|
|
571
632
|
/**
|
|
572
633
|
* Refresh an access token from the remote service.
|
|
573
634
|
*/
|
|
@@ -592,13 +653,12 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
592
653
|
throw await response.json();
|
|
593
654
|
} else {
|
|
594
655
|
try {
|
|
595
|
-
const json = await response.json();
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
};
|
|
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;
|
|
602
662
|
let scopes: Scope[] = [];
|
|
603
663
|
|
|
604
664
|
const accessToken: AccessToken = {
|
|
@@ -626,20 +686,24 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
626
686
|
refreshToken: LocalState.refreshToken,
|
|
627
687
|
};
|
|
628
688
|
return accessContext;
|
|
629
|
-
} catch (
|
|
630
|
-
const error = err?.error || "There was a network error.";
|
|
689
|
+
} catch (error) {
|
|
631
690
|
switch (error) {
|
|
632
691
|
case "invalid_grant":
|
|
633
|
-
console.
|
|
692
|
+
console.warn(
|
|
634
693
|
"Expired! Auth code or refresh token needs to be renewed."
|
|
635
694
|
);
|
|
636
695
|
// alert("Redirecting to auth server to obtain a new auth grant code.");
|
|
637
696
|
// TODO: return refreshAuthCodeOrRefreshToken();
|
|
638
697
|
break;
|
|
639
698
|
default:
|
|
699
|
+
console.error(error);
|
|
640
700
|
break;
|
|
641
701
|
}
|
|
642
|
-
|
|
702
|
+
if (typeof error === "string") {
|
|
703
|
+
throw toErrorClass(error);
|
|
704
|
+
} else {
|
|
705
|
+
throw error;
|
|
706
|
+
}
|
|
643
707
|
}
|
|
644
708
|
}
|
|
645
709
|
}
|
|
@@ -680,13 +744,11 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
680
744
|
}
|
|
681
745
|
throw toErrorClass(error);
|
|
682
746
|
}
|
|
683
|
-
const json = await response.json();
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
scope: string;
|
|
689
|
-
};
|
|
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;
|
|
690
752
|
let scopes: Scope[] = [];
|
|
691
753
|
LocalState.hasAuthCodeBeenExchangedForAccessToken = true;
|
|
692
754
|
|
|
@@ -770,31 +832,37 @@ function generateRandomState(lengthOfState: number): string {
|
|
|
770
832
|
.join("");
|
|
771
833
|
}
|
|
772
834
|
|
|
773
|
-
|
|
774
|
-
|
|
835
|
+
export function writeAuthConfigFile(config: UserAuthConfig) {
|
|
836
|
+
mkdirSync(path.join(os.homedir(), ".wrangler/config/"), {
|
|
775
837
|
recursive: true,
|
|
776
838
|
});
|
|
777
|
-
|
|
778
|
-
path.join(os.homedir(),
|
|
779
|
-
|
|
780
|
-
oauth_token = "${tokenData.token?.value || ""}"
|
|
781
|
-
refresh_token = "${tokenData.refreshToken?.value}"
|
|
782
|
-
expiration_time = "${tokenData.token?.expiry}"
|
|
783
|
-
`,
|
|
839
|
+
writeFileSync(
|
|
840
|
+
path.join(os.homedir(), USER_AUTH_CONFIG_FILE),
|
|
841
|
+
TOML.stringify(config as TOML.JsonMap),
|
|
784
842
|
{ encoding: "utf-8" }
|
|
785
843
|
);
|
|
786
844
|
}
|
|
787
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
|
+
|
|
788
853
|
type LoginProps = {
|
|
789
854
|
scopes?: Scope[];
|
|
790
855
|
};
|
|
791
856
|
|
|
792
|
-
export async function loginOrRefreshIfRequired(
|
|
857
|
+
export async function loginOrRefreshIfRequired(
|
|
858
|
+
isInteractive = true
|
|
859
|
+
): Promise<boolean> {
|
|
793
860
|
// TODO: if there already is a token, then try refreshing
|
|
794
861
|
// TODO: ask permission before opening browser
|
|
795
|
-
if (!
|
|
796
|
-
//
|
|
797
|
-
|
|
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());
|
|
798
866
|
} else if (isAccessTokenExpired()) {
|
|
799
867
|
return await refreshToken();
|
|
800
868
|
} else {
|
|
@@ -803,19 +871,20 @@ export async function loginOrRefreshIfRequired(): Promise<boolean> {
|
|
|
803
871
|
}
|
|
804
872
|
|
|
805
873
|
export async function login(props?: LoginProps): Promise<boolean> {
|
|
874
|
+
console.log("Attempting to login via OAuth...");
|
|
806
875
|
const urlToOpen = await getAuthURL(props?.scopes);
|
|
807
|
-
await
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
let server;
|
|
811
|
-
let loginTimeoutHandle;
|
|
876
|
+
await openInBrowser(urlToOpen);
|
|
877
|
+
let server: http.Server;
|
|
878
|
+
let loginTimeoutHandle: NodeJS.Timeout;
|
|
812
879
|
const timerPromise = new Promise<boolean>((resolve) => {
|
|
813
880
|
loginTimeoutHandle = setTimeout(() => {
|
|
814
|
-
console.error(
|
|
881
|
+
console.error(
|
|
882
|
+
"Timed out waiting for authorization code, please try again."
|
|
883
|
+
);
|
|
815
884
|
server.close();
|
|
816
885
|
clearTimeout(loginTimeoutHandle);
|
|
817
886
|
resolve(false);
|
|
818
|
-
}, 60000); // wait for
|
|
887
|
+
}, 60000); // wait for 60 seconds for the user to authorize
|
|
819
888
|
});
|
|
820
889
|
|
|
821
890
|
const loginPromise = new Promise<boolean>((resolve, reject) => {
|
|
@@ -846,8 +915,9 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
846
915
|
finish(false);
|
|
847
916
|
});
|
|
848
917
|
console.log(
|
|
849
|
-
"Error: Consent denied. You must grant consent to Wrangler in order to login
|
|
850
|
-
|
|
918
|
+
"Error: Consent denied. You must grant consent to Wrangler in order to login.\n" +
|
|
919
|
+
"If you don't want to do this consider passing an API token via the `CLOUDFLARE_API_TOKEN` environment variable"
|
|
920
|
+
);
|
|
851
921
|
|
|
852
922
|
return;
|
|
853
923
|
} else {
|
|
@@ -860,8 +930,12 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
860
930
|
finish(false, new ErrorNoAuthCode());
|
|
861
931
|
return;
|
|
862
932
|
} else {
|
|
863
|
-
const
|
|
864
|
-
|
|
933
|
+
const exchange = await exchangeAuthCodeForAccessToken();
|
|
934
|
+
writeAuthConfigFile({
|
|
935
|
+
oauth_token: exchange.token?.value ?? "",
|
|
936
|
+
expiration_time: exchange.token?.expiry,
|
|
937
|
+
refresh_token: exchange.refreshToken?.value,
|
|
938
|
+
});
|
|
865
939
|
res.writeHead(307, {
|
|
866
940
|
Location:
|
|
867
941
|
"https://welcome.developers.workers.dev/wrangler-oauth-consent-granted",
|
|
@@ -869,9 +943,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
869
943
|
res.end(() => {
|
|
870
944
|
finish(true);
|
|
871
945
|
});
|
|
872
|
-
console.log(
|
|
873
|
-
`Successfully configured. You can find your configuration file at: ${os.homedir()}/.wrangler/config/default.toml`
|
|
874
|
-
);
|
|
946
|
+
console.log(`Successfully logged in.`);
|
|
875
947
|
|
|
876
948
|
return;
|
|
877
949
|
}
|
|
@@ -888,18 +960,22 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
888
960
|
/**
|
|
889
961
|
* Checks to see if the access token has expired.
|
|
890
962
|
*/
|
|
891
|
-
|
|
892
|
-
throwIfNotInitialised();
|
|
963
|
+
function isAccessTokenExpired(): boolean {
|
|
893
964
|
const { accessToken } = LocalState;
|
|
894
965
|
return Boolean(accessToken && new Date() >= new Date(accessToken.expiry));
|
|
895
966
|
}
|
|
896
967
|
|
|
897
|
-
|
|
898
|
-
throwIfNotInitialised();
|
|
968
|
+
async function refreshToken(): Promise<boolean> {
|
|
899
969
|
// refresh
|
|
900
970
|
try {
|
|
901
|
-
const
|
|
902
|
-
|
|
971
|
+
const {
|
|
972
|
+
token: { value: oauth_token, expiry: expiration_time } = {
|
|
973
|
+
value: "",
|
|
974
|
+
expiry: "",
|
|
975
|
+
},
|
|
976
|
+
refreshToken: { value: refresh_token } = {},
|
|
977
|
+
} = await exchangeRefreshTokenForAccessToken();
|
|
978
|
+
writeAuthConfigFile({ oauth_token, expiration_time, refresh_token });
|
|
903
979
|
return true;
|
|
904
980
|
} catch (err) {
|
|
905
981
|
console.error(err);
|
|
@@ -908,10 +984,28 @@ export async function refreshToken(): Promise<boolean> {
|
|
|
908
984
|
}
|
|
909
985
|
|
|
910
986
|
export async function logout(): Promise<void> {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
987
|
+
if (!LocalState.accessToken) {
|
|
988
|
+
if (!LocalState.refreshToken) {
|
|
989
|
+
console.log("Not logged in, exiting...");
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
const body =
|
|
994
|
+
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
995
|
+
`token_type_hint=refresh_token&` +
|
|
996
|
+
`token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
|
|
997
|
+
|
|
998
|
+
const response = await fetch(REVOKE_URL, {
|
|
999
|
+
method: "POST",
|
|
1000
|
+
body,
|
|
1001
|
+
headers: {
|
|
1002
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1003
|
+
},
|
|
1004
|
+
});
|
|
1005
|
+
await response.text(); // blank text? would be nice if it was something meaningful
|
|
1006
|
+
console.log(
|
|
1007
|
+
"💁 Wrangler is configured with an OAuth token. The token has been successfully revoked"
|
|
1008
|
+
);
|
|
915
1009
|
}
|
|
916
1010
|
const body =
|
|
917
1011
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
@@ -926,20 +1020,13 @@ export async function logout(): Promise<void> {
|
|
|
926
1020
|
},
|
|
927
1021
|
});
|
|
928
1022
|
await response.text(); // blank text? would be nice if it was something meaningful
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
);
|
|
932
|
-
// delete the file
|
|
933
|
-
await rm(path.join(os.homedir(), ".wrangler/config/default.toml"));
|
|
934
|
-
console.log(
|
|
935
|
-
`Removing ${os.homedir()}/.wrangler/config/default.toml.. success!`
|
|
936
|
-
);
|
|
1023
|
+
rmSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE));
|
|
1024
|
+
console.log(`Successfully logged out.`);
|
|
937
1025
|
}
|
|
938
1026
|
|
|
939
|
-
export function listScopes(): void {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
const data = ScopeKeys.map((scope) => ({
|
|
1027
|
+
export function listScopes(message = "💁 Available scopes:"): void {
|
|
1028
|
+
console.log(message);
|
|
1029
|
+
const data = ScopeKeys.map((scope: Scope) => ({
|
|
943
1030
|
Scope: scope,
|
|
944
1031
|
Description: Scopes[scope],
|
|
945
1032
|
}));
|
|
@@ -947,17 +1034,20 @@ export function listScopes(): void {
|
|
|
947
1034
|
// TODO: maybe a good idea to show usage here
|
|
948
1035
|
}
|
|
949
1036
|
|
|
950
|
-
export async function getAccountId(
|
|
1037
|
+
export async function getAccountId(
|
|
1038
|
+
isInteractive = true
|
|
1039
|
+
): Promise<string | undefined> {
|
|
951
1040
|
const apiToken = getAPIToken();
|
|
952
1041
|
if (!apiToken) return;
|
|
953
1042
|
|
|
954
|
-
|
|
955
|
-
|
|
1043
|
+
const accountIdFromEnv = getCloudflareAccountIdFromEnv();
|
|
1044
|
+
if (accountIdFromEnv) {
|
|
1045
|
+
return accountIdFromEnv;
|
|
956
1046
|
}
|
|
957
1047
|
|
|
958
1048
|
let response: Response;
|
|
959
1049
|
try {
|
|
960
|
-
response = await fetch(`${
|
|
1050
|
+
response = await fetch(`${getCloudflareApiBaseUrl()}/memberships`, {
|
|
961
1051
|
method: "GET",
|
|
962
1052
|
headers: {
|
|
963
1053
|
Authorization: "Bearer " + apiToken,
|
|
@@ -977,7 +1067,7 @@ export async function getAccountId() {
|
|
|
977
1067
|
if (responseJSON.success === true) {
|
|
978
1068
|
if (responseJSON.result.length === 1) {
|
|
979
1069
|
accountId = responseJSON.result[0].account.id;
|
|
980
|
-
} else {
|
|
1070
|
+
} else if (isInteractive) {
|
|
981
1071
|
accountId = await new Promise((resolve) => {
|
|
982
1072
|
const accounts = responseJSON.result.map((x) => x.account);
|
|
983
1073
|
const { unmount } = render(
|
|
@@ -990,6 +1080,15 @@ export async function getAccountId() {
|
|
|
990
1080
|
/>
|
|
991
1081
|
);
|
|
992
1082
|
});
|
|
1083
|
+
} else {
|
|
1084
|
+
throw new Error(
|
|
1085
|
+
"More than one account available but unable to select one in non-interactive mode.\n" +
|
|
1086
|
+
`Please set the appropriate \`account_id\` in your \`wrangler.toml\` file.\n` +
|
|
1087
|
+
`Available accounts are ("<name>" - "<id>"):\n` +
|
|
1088
|
+
responseJSON.result
|
|
1089
|
+
.map((x) => ` "${x.account.name}" - "${x.account.id}")`)
|
|
1090
|
+
.join("\n")
|
|
1091
|
+
);
|
|
993
1092
|
}
|
|
994
1093
|
}
|
|
995
1094
|
return accountId;
|
|
@@ -1001,7 +1100,7 @@ type ChooseAccountItem = {
|
|
|
1001
1100
|
};
|
|
1002
1101
|
export function ChooseAccount(props: {
|
|
1003
1102
|
accounts: ChooseAccountItem[];
|
|
1004
|
-
onSelect: (item) => void;
|
|
1103
|
+
onSelect: (item: SelectInputItem<ChooseAccountItem>) => void;
|
|
1005
1104
|
}) {
|
|
1006
1105
|
return (
|
|
1007
1106
|
<>
|