wrangler 0.0.0-e22f9d7 → 0.0.0-e2b7352
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 +3 -3
- package/bin/wrangler.js +12 -3
- package/miniflare-dist/index.mjs +79 -0
- package/miniflare-dist/index.mjs.map +7 -0
- package/package.json +33 -16
- package/pages/functions/buildWorker.ts +1 -1
- package/pages/functions/filepath-routing.test.ts +212 -28
- package/pages/functions/filepath-routing.ts +91 -131
- package/pages/functions/routes.ts +11 -18
- package/pages/functions/template-worker.ts +19 -33
- package/src/__tests__/configuration.test.ts +2820 -0
- package/src/__tests__/dev.test.tsx +535 -61
- 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__/{mock-cfetch.ts → helpers/mock-cfetch.ts} +52 -16
- 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 +773 -77
- package/src/__tests__/jest.setup.ts +53 -6
- package/src/__tests__/kv.test.ts +725 -456
- 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 +3509 -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 +638 -0
- package/src/__tests__/whoami.test.tsx +100 -0
- package/src/bundle.ts +155 -0
- package/src/cfetch/index.ts +21 -19
- package/src/cfetch/internal.ts +92 -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} +61 -23
- 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/dialogs.tsx +2 -2
- package/src/entry.ts +240 -0
- package/src/environment-variables.ts +33 -0
- package/src/https-options.ts +122 -0
- package/src/index.tsx +1218 -803
- package/src/inspect.ts +103 -25
- package/src/intl-polyfill.d.ts +139 -0
- 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 +74 -44
- package/src/parse.ts +183 -0
- package/src/paths.ts +26 -0
- package/src/proxy.ts +177 -39
- package/src/publish.ts +286 -300
- package/src/r2.ts +50 -0
- package/src/reporting.ts +131 -0
- package/src/selfsigned.d.ts +29 -0
- package/src/sites.tsx +224 -62
- 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 +278 -178
- package/src/whoami.tsx +69 -0
- 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 +130377 -112126
- 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 -487
- package/src/dev.tsx +0 -784
- 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
|
}
|
|
@@ -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,66 +340,64 @@ 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
|
-
|
|
345
|
+
/**
|
|
346
|
+
* Compute the current auth tokens.
|
|
347
|
+
*/
|
|
348
|
+
function getAuthTokens(): AuthTokens {
|
|
303
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
|
-
// `throwIfNotInitialised()` ensures that the accessToken is guaranteed to be defined.
|
|
354
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
355
|
-
return LocalState.accessToken!.value;
|
|
400
|
+
return LocalState.accessToken?.value;
|
|
356
401
|
}
|
|
357
402
|
|
|
358
403
|
interface AccessContext {
|
|
@@ -550,7 +595,7 @@ function isReturningFromAuthServer(query: ParsedUrlQuery): boolean {
|
|
|
550
595
|
return true;
|
|
551
596
|
}
|
|
552
597
|
|
|
553
|
-
export async function getAuthURL(scopes
|
|
598
|
+
export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
|
|
554
599
|
const { codeChallenge, codeVerifier } = await generatePKCECodes();
|
|
555
600
|
const stateQueryParam = generateRandomState(RECOMMENDED_STATE_LENGTH);
|
|
556
601
|
|
|
@@ -560,22 +605,30 @@ export async function getAuthURL(scopes?: string[]): Promise<string> {
|
|
|
560
605
|
stateQueryParam,
|
|
561
606
|
});
|
|
562
607
|
|
|
563
|
-
// TODO: verify that the scopes passed are legit
|
|
564
|
-
|
|
565
608
|
return (
|
|
566
609
|
AUTH_URL +
|
|
567
610
|
`?response_type=code&` +
|
|
568
611
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
569
612
|
`redirect_uri=${encodeURIComponent(CALLBACK_URL)}&` +
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
)}&` +
|
|
613
|
+
// we add offline_access manually for every request
|
|
614
|
+
`scope=${encodeURIComponent([...scopes, "offline_access"].join(" "))}&` +
|
|
573
615
|
`state=${stateQueryParam}&` +
|
|
574
616
|
`code_challenge=${encodeURIComponent(codeChallenge)}&` +
|
|
575
617
|
`code_challenge_method=S256`
|
|
576
618
|
);
|
|
577
619
|
}
|
|
578
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
|
+
|
|
579
632
|
/**
|
|
580
633
|
* Refresh an access token from the remote service.
|
|
581
634
|
*/
|
|
@@ -600,13 +653,12 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
600
653
|
throw await response.json();
|
|
601
654
|
} else {
|
|
602
655
|
try {
|
|
603
|
-
const json = await response.json();
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
};
|
|
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;
|
|
610
662
|
let scopes: Scope[] = [];
|
|
611
663
|
|
|
612
664
|
const accessToken: AccessToken = {
|
|
@@ -634,20 +686,24 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
634
686
|
refreshToken: LocalState.refreshToken,
|
|
635
687
|
};
|
|
636
688
|
return accessContext;
|
|
637
|
-
} catch (
|
|
638
|
-
const error = err?.error || "There was a network error.";
|
|
689
|
+
} catch (error) {
|
|
639
690
|
switch (error) {
|
|
640
691
|
case "invalid_grant":
|
|
641
|
-
console.
|
|
692
|
+
console.warn(
|
|
642
693
|
"Expired! Auth code or refresh token needs to be renewed."
|
|
643
694
|
);
|
|
644
695
|
// alert("Redirecting to auth server to obtain a new auth grant code.");
|
|
645
696
|
// TODO: return refreshAuthCodeOrRefreshToken();
|
|
646
697
|
break;
|
|
647
698
|
default:
|
|
699
|
+
console.error(error);
|
|
648
700
|
break;
|
|
649
701
|
}
|
|
650
|
-
|
|
702
|
+
if (typeof error === "string") {
|
|
703
|
+
throw toErrorClass(error);
|
|
704
|
+
} else {
|
|
705
|
+
throw error;
|
|
706
|
+
}
|
|
651
707
|
}
|
|
652
708
|
}
|
|
653
709
|
}
|
|
@@ -688,13 +744,11 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
688
744
|
}
|
|
689
745
|
throw toErrorClass(error);
|
|
690
746
|
}
|
|
691
|
-
const json = await response.json();
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
scope: string;
|
|
697
|
-
};
|
|
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;
|
|
698
752
|
let scopes: Scope[] = [];
|
|
699
753
|
LocalState.hasAuthCodeBeenExchangedForAccessToken = true;
|
|
700
754
|
|
|
@@ -778,52 +832,68 @@ function generateRandomState(lengthOfState: number): string {
|
|
|
778
832
|
.join("");
|
|
779
833
|
}
|
|
780
834
|
|
|
781
|
-
|
|
782
|
-
|
|
835
|
+
export function writeAuthConfigFile(config: UserAuthConfig) {
|
|
836
|
+
mkdirSync(path.join(os.homedir(), ".wrangler/config/"), {
|
|
783
837
|
recursive: true,
|
|
784
838
|
});
|
|
785
|
-
|
|
786
|
-
path.join(os.homedir(),
|
|
787
|
-
|
|
788
|
-
oauth_token = "${tokenData.token?.value || ""}"
|
|
789
|
-
refresh_token = "${tokenData.refreshToken?.value}"
|
|
790
|
-
expiration_time = "${tokenData.token?.expiry}"
|
|
791
|
-
`,
|
|
839
|
+
writeFileSync(
|
|
840
|
+
path.join(os.homedir(), USER_AUTH_CONFIG_FILE),
|
|
841
|
+
TOML.stringify(config as TOML.JsonMap),
|
|
792
842
|
{ encoding: "utf-8" }
|
|
793
843
|
);
|
|
794
844
|
}
|
|
795
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
|
+
|
|
796
853
|
type LoginProps = {
|
|
797
|
-
scopes?:
|
|
854
|
+
scopes?: Scope[];
|
|
798
855
|
};
|
|
799
856
|
|
|
800
|
-
export async function loginOrRefreshIfRequired(
|
|
857
|
+
export async function loginOrRefreshIfRequired(
|
|
858
|
+
isInteractive = true
|
|
859
|
+
): Promise<boolean> {
|
|
801
860
|
// TODO: if there already is a token, then try refreshing
|
|
802
861
|
// TODO: ask permission before opening browser
|
|
803
|
-
if (!
|
|
804
|
-
//
|
|
805
|
-
|
|
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());
|
|
806
866
|
} else if (isAccessTokenExpired()) {
|
|
807
|
-
|
|
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
|
+
}
|
|
808
877
|
} else {
|
|
809
878
|
return true;
|
|
810
879
|
}
|
|
811
880
|
}
|
|
812
881
|
|
|
813
882
|
export async function login(props?: LoginProps): Promise<boolean> {
|
|
883
|
+
console.log("Attempting to login via OAuth...");
|
|
814
884
|
const urlToOpen = await getAuthURL(props?.scopes);
|
|
815
|
-
await
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
let server;
|
|
819
|
-
let loginTimeoutHandle;
|
|
885
|
+
await openInBrowser(urlToOpen);
|
|
886
|
+
let server: http.Server;
|
|
887
|
+
let loginTimeoutHandle: NodeJS.Timeout;
|
|
820
888
|
const timerPromise = new Promise<boolean>((resolve) => {
|
|
821
889
|
loginTimeoutHandle = setTimeout(() => {
|
|
822
|
-
console.error(
|
|
890
|
+
console.error(
|
|
891
|
+
"Timed out waiting for authorization code, please try again."
|
|
892
|
+
);
|
|
823
893
|
server.close();
|
|
824
894
|
clearTimeout(loginTimeoutHandle);
|
|
825
895
|
resolve(false);
|
|
826
|
-
}, 60000); // wait for
|
|
896
|
+
}, 60000); // wait for 60 seconds for the user to authorize
|
|
827
897
|
});
|
|
828
898
|
|
|
829
899
|
const loginPromise = new Promise<boolean>((resolve, reject) => {
|
|
@@ -854,8 +924,9 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
854
924
|
finish(false);
|
|
855
925
|
});
|
|
856
926
|
console.log(
|
|
857
|
-
"Error: Consent denied. You must grant consent to Wrangler in order to login
|
|
858
|
-
|
|
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
|
+
);
|
|
859
930
|
|
|
860
931
|
return;
|
|
861
932
|
} else {
|
|
@@ -868,8 +939,12 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
868
939
|
finish(false, new ErrorNoAuthCode());
|
|
869
940
|
return;
|
|
870
941
|
} else {
|
|
871
|
-
const
|
|
872
|
-
|
|
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
|
+
});
|
|
873
948
|
res.writeHead(307, {
|
|
874
949
|
Location:
|
|
875
950
|
"https://welcome.developers.workers.dev/wrangler-oauth-consent-granted",
|
|
@@ -877,9 +952,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
877
952
|
res.end(() => {
|
|
878
953
|
finish(true);
|
|
879
954
|
});
|
|
880
|
-
console.log(
|
|
881
|
-
`Successfully configured. You can find your configuration file at: ${os.homedir()}/.wrangler/config/default.toml`
|
|
882
|
-
);
|
|
955
|
+
console.log(`Successfully logged in.`);
|
|
883
956
|
|
|
884
957
|
return;
|
|
885
958
|
}
|
|
@@ -896,18 +969,22 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
896
969
|
/**
|
|
897
970
|
* Checks to see if the access token has expired.
|
|
898
971
|
*/
|
|
899
|
-
|
|
900
|
-
throwIfNotInitialised();
|
|
972
|
+
function isAccessTokenExpired(): boolean {
|
|
901
973
|
const { accessToken } = LocalState;
|
|
902
974
|
return Boolean(accessToken && new Date() >= new Date(accessToken.expiry));
|
|
903
975
|
}
|
|
904
976
|
|
|
905
|
-
|
|
906
|
-
throwIfNotInitialised();
|
|
977
|
+
async function refreshToken(): Promise<boolean> {
|
|
907
978
|
// refresh
|
|
908
979
|
try {
|
|
909
|
-
const
|
|
910
|
-
|
|
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 });
|
|
911
988
|
return true;
|
|
912
989
|
} catch (err) {
|
|
913
990
|
console.error(err);
|
|
@@ -916,10 +993,28 @@ export async function refreshToken(): Promise<boolean> {
|
|
|
916
993
|
}
|
|
917
994
|
|
|
918
995
|
export async function logout(): Promise<void> {
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
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
|
+
);
|
|
923
1018
|
}
|
|
924
1019
|
const body =
|
|
925
1020
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
@@ -934,38 +1029,34 @@ export async function logout(): Promise<void> {
|
|
|
934
1029
|
},
|
|
935
1030
|
});
|
|
936
1031
|
await response.text(); // blank text? would be nice if it was something meaningful
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
);
|
|
940
|
-
// delete the file
|
|
941
|
-
await rm(path.join(os.homedir(), ".wrangler/config/default.toml"));
|
|
942
|
-
console.log(
|
|
943
|
-
`Removing ${os.homedir()}/.wrangler/config/default.toml.. success!`
|
|
944
|
-
);
|
|
1032
|
+
rmSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE));
|
|
1033
|
+
console.log(`Successfully logged out.`);
|
|
945
1034
|
}
|
|
946
1035
|
|
|
947
|
-
export function listScopes(): void {
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
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) => ({
|
|
951
1039
|
Scope: scope,
|
|
952
|
-
Description:
|
|
1040
|
+
Description: Scopes[scope],
|
|
953
1041
|
}));
|
|
954
1042
|
render(<Table data={data} />);
|
|
955
1043
|
// TODO: maybe a good idea to show usage here
|
|
956
1044
|
}
|
|
957
1045
|
|
|
958
|
-
export async function getAccountId(
|
|
1046
|
+
export async function getAccountId(
|
|
1047
|
+
isInteractive = true
|
|
1048
|
+
): Promise<string | undefined> {
|
|
959
1049
|
const apiToken = getAPIToken();
|
|
960
1050
|
if (!apiToken) return;
|
|
961
1051
|
|
|
962
|
-
|
|
963
|
-
|
|
1052
|
+
const accountIdFromEnv = getCloudflareAccountIdFromEnv();
|
|
1053
|
+
if (accountIdFromEnv) {
|
|
1054
|
+
return accountIdFromEnv;
|
|
964
1055
|
}
|
|
965
1056
|
|
|
966
1057
|
let response: Response;
|
|
967
1058
|
try {
|
|
968
|
-
response = await fetch(`${
|
|
1059
|
+
response = await fetch(`${getCloudflareApiBaseUrl()}/memberships`, {
|
|
969
1060
|
method: "GET",
|
|
970
1061
|
headers: {
|
|
971
1062
|
Authorization: "Bearer " + apiToken,
|
|
@@ -985,7 +1076,7 @@ export async function getAccountId() {
|
|
|
985
1076
|
if (responseJSON.success === true) {
|
|
986
1077
|
if (responseJSON.result.length === 1) {
|
|
987
1078
|
accountId = responseJSON.result[0].account.id;
|
|
988
|
-
} else {
|
|
1079
|
+
} else if (isInteractive) {
|
|
989
1080
|
accountId = await new Promise((resolve) => {
|
|
990
1081
|
const accounts = responseJSON.result.map((x) => x.account);
|
|
991
1082
|
const { unmount } = render(
|
|
@@ -998,6 +1089,15 @@ export async function getAccountId() {
|
|
|
998
1089
|
/>
|
|
999
1090
|
);
|
|
1000
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
|
+
);
|
|
1001
1101
|
}
|
|
1002
1102
|
}
|
|
1003
1103
|
return accountId;
|
|
@@ -1009,7 +1109,7 @@ type ChooseAccountItem = {
|
|
|
1009
1109
|
};
|
|
1010
1110
|
export function ChooseAccount(props: {
|
|
1011
1111
|
accounts: ChooseAccountItem[];
|
|
1012
|
-
onSelect: (item) => void;
|
|
1112
|
+
onSelect: (item: SelectInputItem<ChooseAccountItem>) => void;
|
|
1013
1113
|
}) {
|
|
1014
1114
|
return (
|
|
1015
1115
|
<>
|