wrangler 0.0.0-e2a26a0 → 0.0.0-e3e3243
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 +10 -1
- package/miniflare-dist/index.mjs +79 -0
- package/miniflare-dist/index.mjs.map +7 -0
- package/package.json +24 -12
- package/pages/functions/filepath-routing.test.ts +212 -36
- package/pages/functions/filepath-routing.ts +69 -119
- package/pages/functions/routes.ts +8 -17
- package/pages/functions/template-worker.ts +3 -9
- package/src/__tests__/configuration.test.ts +2820 -0
- package/src/__tests__/dev.test.tsx +535 -65
- package/src/__tests__/guess-worker-format.test.ts +68 -0
- package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
- package/src/__tests__/helpers/mock-account-id.ts +26 -8
- package/src/__tests__/helpers/mock-bin.ts +4 -1
- package/src/__tests__/helpers/mock-cfetch.ts +5 -5
- package/src/__tests__/helpers/mock-console.ts +21 -7
- package/src/__tests__/helpers/mock-kv.ts +9 -16
- 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 +3 -0
- package/src/__tests__/helpers/run-wrangler.ts +5 -3
- 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 +467 -27
- package/src/__tests__/jest.setup.ts +37 -5
- package/src/__tests__/kv.test.ts +442 -201
- package/src/__tests__/logout.test.ts +15 -13
- package/src/__tests__/package-manager.test.ts +2 -2
- package/src/__tests__/pages.test.ts +82 -0
- package/src/__tests__/parse.test.ts +322 -0
- package/src/__tests__/publish.test.ts +2717 -430
- package/src/__tests__/r2.test.ts +198 -0
- package/src/__tests__/route.test.ts +45 -0
- package/src/__tests__/secret.test.ts +301 -86
- package/src/__tests__/sentry.test.ts +146 -0
- package/src/__tests__/tail.test.ts +562 -0
- package/src/__tests__/whoami.test.tsx +21 -7
- package/src/bundle.ts +155 -0
- package/src/cfetch/index.ts +8 -6
- package/src/cfetch/internal.ts +60 -22
- 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 +371 -0
- package/src/config/index.ts +62 -0
- package/src/config/validation-helpers.ts +554 -0
- package/src/config/validation.ts +1232 -0
- package/src/{api/preview.ts → create-worker-preview.ts} +59 -21
- package/src/create-worker-upload-form.ts +217 -0
- package/src/dev/dev.tsx +392 -0
- package/src/dev/local.tsx +232 -0
- package/src/dev/remote.tsx +203 -0
- package/src/dev/use-esbuild.ts +104 -0
- package/src/entry.ts +240 -0
- package/src/environment-variables.ts +33 -0
- package/src/https-options.ts +122 -0
- package/src/index.tsx +1067 -779
- package/src/inspect.ts +25 -4
- package/src/intl-polyfill.d.ts +139 -0
- package/src/{kv.tsx → kv.ts} +95 -60
- 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 +226 -31
- package/src/open-in-browser.ts +25 -0
- package/src/package-manager.ts +21 -15
- package/src/pages.tsx +35 -20
- package/src/parse.ts +183 -0
- package/src/proxy.ts +115 -45
- package/src/publish.ts +268 -328
- package/src/r2.ts +50 -0
- package/src/reporting.ts +131 -0
- package/src/selfsigned.d.ts +29 -0
- package/src/sites.tsx +83 -43
- 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 +234 -126
- package/src/{api/worker.ts → worker.ts} +44 -36
- package/templates/new-worker.js +1 -1
- package/templates/new-worker.ts +1 -1
- package/templates/tsconfig.json +105 -0
- package/wrangler-dist/cli.js +131201 -114242
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/helpers/mock-user.ts +0 -27
- package/src/api/form_data.ts +0 -134
- package/src/config.ts +0 -641
- package/src/dev.tsx +0 -834
- 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
|
|
@@ -207,24 +207,42 @@
|
|
|
207
207
|
|
|
208
208
|
import assert from "node:assert";
|
|
209
209
|
import { webcrypto as crypto } from "node:crypto";
|
|
210
|
-
import {
|
|
210
|
+
import { writeFileSync, mkdirSync, rmSync } from "node:fs";
|
|
211
211
|
import http from "node:http";
|
|
212
212
|
import os from "node:os";
|
|
213
213
|
import path from "node:path";
|
|
214
|
-
import process from "node:process";
|
|
215
214
|
import url from "node:url";
|
|
216
215
|
import { TextEncoder } from "node:util";
|
|
217
216
|
import TOML from "@iarna/toml";
|
|
218
217
|
import { render, Text } from "ink";
|
|
219
218
|
import SelectInput from "ink-select-input";
|
|
220
219
|
import Table from "ink-table";
|
|
221
|
-
import open from "open";
|
|
222
220
|
import React from "react";
|
|
223
221
|
import { fetch } from "undici";
|
|
224
|
-
import {
|
|
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";
|
|
225
227
|
import type { ParsedUrlQuery } from "node:querystring";
|
|
226
228
|
import type { Response } from "undici";
|
|
227
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
|
+
});
|
|
245
|
+
|
|
228
246
|
/**
|
|
229
247
|
* An implementation of rfc6749#section-4.1 and rfc7636.
|
|
230
248
|
*/
|
|
@@ -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,13 +610,25 @@ 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)}&` +
|
|
564
|
-
|
|
613
|
+
// we add offline_access manually for every request
|
|
614
|
+
`scope=${encodeURIComponent([...scopes, "offline_access"].join(" "))}&` +
|
|
565
615
|
`state=${stateQueryParam}&` +
|
|
566
616
|
`code_challenge=${encodeURIComponent(codeChallenge)}&` +
|
|
567
617
|
`code_challenge_method=S256`
|
|
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,52 +832,68 @@ 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
|
+
// 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
|
+
}
|
|
800
877
|
} else {
|
|
801
878
|
return true;
|
|
802
879
|
}
|
|
803
880
|
}
|
|
804
881
|
|
|
805
882
|
export async function login(props?: LoginProps): Promise<boolean> {
|
|
883
|
+
console.log("Attempting to login via OAuth...");
|
|
806
884
|
const urlToOpen = await getAuthURL(props?.scopes);
|
|
807
|
-
await
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
let server;
|
|
811
|
-
let loginTimeoutHandle;
|
|
885
|
+
await openInBrowser(urlToOpen);
|
|
886
|
+
let server: http.Server;
|
|
887
|
+
let loginTimeoutHandle: NodeJS.Timeout;
|
|
812
888
|
const timerPromise = new Promise<boolean>((resolve) => {
|
|
813
889
|
loginTimeoutHandle = setTimeout(() => {
|
|
814
|
-
console.error(
|
|
890
|
+
console.error(
|
|
891
|
+
"Timed out waiting for authorization code, please try again."
|
|
892
|
+
);
|
|
815
893
|
server.close();
|
|
816
894
|
clearTimeout(loginTimeoutHandle);
|
|
817
895
|
resolve(false);
|
|
818
|
-
}, 60000); // wait for
|
|
896
|
+
}, 60000); // wait for 60 seconds for the user to authorize
|
|
819
897
|
});
|
|
820
898
|
|
|
821
899
|
const loginPromise = new Promise<boolean>((resolve, reject) => {
|
|
@@ -846,8 +924,9 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
846
924
|
finish(false);
|
|
847
925
|
});
|
|
848
926
|
console.log(
|
|
849
|
-
"Error: Consent denied. You must grant consent to Wrangler in order to login
|
|
850
|
-
|
|
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
|
+
);
|
|
851
930
|
|
|
852
931
|
return;
|
|
853
932
|
} else {
|
|
@@ -860,8 +939,12 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
860
939
|
finish(false, new ErrorNoAuthCode());
|
|
861
940
|
return;
|
|
862
941
|
} else {
|
|
863
|
-
const
|
|
864
|
-
|
|
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
|
+
});
|
|
865
948
|
res.writeHead(307, {
|
|
866
949
|
Location:
|
|
867
950
|
"https://welcome.developers.workers.dev/wrangler-oauth-consent-granted",
|
|
@@ -869,9 +952,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
869
952
|
res.end(() => {
|
|
870
953
|
finish(true);
|
|
871
954
|
});
|
|
872
|
-
console.log(
|
|
873
|
-
`Successfully configured. You can find your configuration file at: ${os.homedir()}/.wrangler/config/default.toml`
|
|
874
|
-
);
|
|
955
|
+
console.log(`Successfully logged in.`);
|
|
875
956
|
|
|
876
957
|
return;
|
|
877
958
|
}
|
|
@@ -888,18 +969,22 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
888
969
|
/**
|
|
889
970
|
* Checks to see if the access token has expired.
|
|
890
971
|
*/
|
|
891
|
-
|
|
892
|
-
throwIfNotInitialised();
|
|
972
|
+
function isAccessTokenExpired(): boolean {
|
|
893
973
|
const { accessToken } = LocalState;
|
|
894
974
|
return Boolean(accessToken && new Date() >= new Date(accessToken.expiry));
|
|
895
975
|
}
|
|
896
976
|
|
|
897
|
-
|
|
898
|
-
throwIfNotInitialised();
|
|
977
|
+
async function refreshToken(): Promise<boolean> {
|
|
899
978
|
// refresh
|
|
900
979
|
try {
|
|
901
|
-
const
|
|
902
|
-
|
|
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 });
|
|
903
988
|
return true;
|
|
904
989
|
} catch (err) {
|
|
905
990
|
console.error(err);
|
|
@@ -908,10 +993,28 @@ export async function refreshToken(): Promise<boolean> {
|
|
|
908
993
|
}
|
|
909
994
|
|
|
910
995
|
export async function logout(): Promise<void> {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
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
|
+
);
|
|
915
1018
|
}
|
|
916
1019
|
const body =
|
|
917
1020
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
@@ -926,20 +1029,13 @@ export async function logout(): Promise<void> {
|
|
|
926
1029
|
},
|
|
927
1030
|
});
|
|
928
1031
|
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
|
-
);
|
|
1032
|
+
rmSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE));
|
|
1033
|
+
console.log(`Successfully logged out.`);
|
|
937
1034
|
}
|
|
938
1035
|
|
|
939
|
-
export function listScopes(): void {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
const data = ScopeKeys.map((scope) => ({
|
|
1036
|
+
export function listScopes(message = "💁 Available scopes:"): void {
|
|
1037
|
+
console.log(message);
|
|
1038
|
+
const data = ScopeKeys.map((scope: Scope) => ({
|
|
943
1039
|
Scope: scope,
|
|
944
1040
|
Description: Scopes[scope],
|
|
945
1041
|
}));
|
|
@@ -947,17 +1043,20 @@ export function listScopes(): void {
|
|
|
947
1043
|
// TODO: maybe a good idea to show usage here
|
|
948
1044
|
}
|
|
949
1045
|
|
|
950
|
-
export async function getAccountId(
|
|
1046
|
+
export async function getAccountId(
|
|
1047
|
+
isInteractive = true
|
|
1048
|
+
): Promise<string | undefined> {
|
|
951
1049
|
const apiToken = getAPIToken();
|
|
952
1050
|
if (!apiToken) return;
|
|
953
1051
|
|
|
954
|
-
|
|
955
|
-
|
|
1052
|
+
const accountIdFromEnv = getCloudflareAccountIdFromEnv();
|
|
1053
|
+
if (accountIdFromEnv) {
|
|
1054
|
+
return accountIdFromEnv;
|
|
956
1055
|
}
|
|
957
1056
|
|
|
958
1057
|
let response: Response;
|
|
959
1058
|
try {
|
|
960
|
-
response = await fetch(`${
|
|
1059
|
+
response = await fetch(`${getCloudflareApiBaseUrl()}/memberships`, {
|
|
961
1060
|
method: "GET",
|
|
962
1061
|
headers: {
|
|
963
1062
|
Authorization: "Bearer " + apiToken,
|
|
@@ -977,7 +1076,7 @@ export async function getAccountId() {
|
|
|
977
1076
|
if (responseJSON.success === true) {
|
|
978
1077
|
if (responseJSON.result.length === 1) {
|
|
979
1078
|
accountId = responseJSON.result[0].account.id;
|
|
980
|
-
} else {
|
|
1079
|
+
} else if (isInteractive) {
|
|
981
1080
|
accountId = await new Promise((resolve) => {
|
|
982
1081
|
const accounts = responseJSON.result.map((x) => x.account);
|
|
983
1082
|
const { unmount } = render(
|
|
@@ -990,6 +1089,15 @@ export async function getAccountId() {
|
|
|
990
1089
|
/>
|
|
991
1090
|
);
|
|
992
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
|
+
);
|
|
993
1101
|
}
|
|
994
1102
|
}
|
|
995
1103
|
return accountId;
|
|
@@ -1001,7 +1109,7 @@ type ChooseAccountItem = {
|
|
|
1001
1109
|
};
|
|
1002
1110
|
export function ChooseAccount(props: {
|
|
1003
1111
|
accounts: ChooseAccountItem[];
|
|
1004
|
-
onSelect: (item) => void;
|
|
1112
|
+
onSelect: (item: SelectInputItem<ChooseAccountItem>) => void;
|
|
1005
1113
|
}) {
|
|
1006
1114
|
return (
|
|
1007
1115
|
<>
|