wrangler 0.0.0-e22f9d7 → 0.0.0-e29a241
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 +35 -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 +2754 -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} +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 +779 -75
- package/src/__tests__/jest.setup.ts +53 -6
- package/src/__tests__/kv.test.ts +725 -454
- 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 +2704 -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 +564 -0
- package/src/__tests__/whoami.test.tsx +100 -0
- package/src/bundle.ts +142 -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 +274 -0
- package/src/config/diagnostics.ts +80 -0
- package/src/config/environment.ts +310 -0
- package/src/config/index.ts +56 -0
- package/src/config/validation-helpers.ts +486 -0
- package/src/config/validation.ts +1096 -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 +226 -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 +1196 -797
- package/src/inspect.ts +103 -25
- 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 +49 -35
- package/src/parse.ts +183 -0
- package/src/paths.ts +26 -0
- package/src/proxy.ts +177 -39
- package/src/publish.ts +283 -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 +268 -177
- 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 +130145 -112053
- 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
|
+
// @ts-expect-error we add offline_access manually
|
|
614
|
+
`scope=${encodeURIComponent(scopes.concat("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,31 +832,37 @@ 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
|
return await refreshToken();
|
|
808
868
|
} else {
|
|
@@ -811,19 +871,20 @@ export async function loginOrRefreshIfRequired(): Promise<boolean> {
|
|
|
811
871
|
}
|
|
812
872
|
|
|
813
873
|
export async function login(props?: LoginProps): Promise<boolean> {
|
|
874
|
+
console.log("Attempting to login via OAuth...");
|
|
814
875
|
const urlToOpen = await getAuthURL(props?.scopes);
|
|
815
|
-
await
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
let server;
|
|
819
|
-
let loginTimeoutHandle;
|
|
876
|
+
await openInBrowser(urlToOpen);
|
|
877
|
+
let server: http.Server;
|
|
878
|
+
let loginTimeoutHandle: NodeJS.Timeout;
|
|
820
879
|
const timerPromise = new Promise<boolean>((resolve) => {
|
|
821
880
|
loginTimeoutHandle = setTimeout(() => {
|
|
822
|
-
console.error(
|
|
881
|
+
console.error(
|
|
882
|
+
"Timed out waiting for authorization code, please try again."
|
|
883
|
+
);
|
|
823
884
|
server.close();
|
|
824
885
|
clearTimeout(loginTimeoutHandle);
|
|
825
886
|
resolve(false);
|
|
826
|
-
}, 60000); // wait for
|
|
887
|
+
}, 60000); // wait for 60 seconds for the user to authorize
|
|
827
888
|
});
|
|
828
889
|
|
|
829
890
|
const loginPromise = new Promise<boolean>((resolve, reject) => {
|
|
@@ -854,8 +915,9 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
854
915
|
finish(false);
|
|
855
916
|
});
|
|
856
917
|
console.log(
|
|
857
|
-
"Error: Consent denied. You must grant consent to Wrangler in order to login
|
|
858
|
-
|
|
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
|
+
);
|
|
859
921
|
|
|
860
922
|
return;
|
|
861
923
|
} else {
|
|
@@ -868,8 +930,12 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
868
930
|
finish(false, new ErrorNoAuthCode());
|
|
869
931
|
return;
|
|
870
932
|
} else {
|
|
871
|
-
const
|
|
872
|
-
|
|
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
|
+
});
|
|
873
939
|
res.writeHead(307, {
|
|
874
940
|
Location:
|
|
875
941
|
"https://welcome.developers.workers.dev/wrangler-oauth-consent-granted",
|
|
@@ -877,9 +943,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
877
943
|
res.end(() => {
|
|
878
944
|
finish(true);
|
|
879
945
|
});
|
|
880
|
-
console.log(
|
|
881
|
-
`Successfully configured. You can find your configuration file at: ${os.homedir()}/.wrangler/config/default.toml`
|
|
882
|
-
);
|
|
946
|
+
console.log(`Successfully logged in.`);
|
|
883
947
|
|
|
884
948
|
return;
|
|
885
949
|
}
|
|
@@ -896,18 +960,22 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
896
960
|
/**
|
|
897
961
|
* Checks to see if the access token has expired.
|
|
898
962
|
*/
|
|
899
|
-
|
|
900
|
-
throwIfNotInitialised();
|
|
963
|
+
function isAccessTokenExpired(): boolean {
|
|
901
964
|
const { accessToken } = LocalState;
|
|
902
965
|
return Boolean(accessToken && new Date() >= new Date(accessToken.expiry));
|
|
903
966
|
}
|
|
904
967
|
|
|
905
|
-
|
|
906
|
-
throwIfNotInitialised();
|
|
968
|
+
async function refreshToken(): Promise<boolean> {
|
|
907
969
|
// refresh
|
|
908
970
|
try {
|
|
909
|
-
const
|
|
910
|
-
|
|
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 });
|
|
911
979
|
return true;
|
|
912
980
|
} catch (err) {
|
|
913
981
|
console.error(err);
|
|
@@ -916,10 +984,28 @@ export async function refreshToken(): Promise<boolean> {
|
|
|
916
984
|
}
|
|
917
985
|
|
|
918
986
|
export async function logout(): Promise<void> {
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
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
|
+
);
|
|
923
1009
|
}
|
|
924
1010
|
const body =
|
|
925
1011
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
@@ -934,38 +1020,34 @@ export async function logout(): Promise<void> {
|
|
|
934
1020
|
},
|
|
935
1021
|
});
|
|
936
1022
|
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
|
-
);
|
|
1023
|
+
rmSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE));
|
|
1024
|
+
console.log(`Successfully logged out.`);
|
|
945
1025
|
}
|
|
946
1026
|
|
|
947
|
-
export function listScopes(): void {
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
const data = Scopes.map((scope, index) => ({
|
|
1027
|
+
export function listScopes(message = "💁 Available scopes:"): void {
|
|
1028
|
+
console.log(message);
|
|
1029
|
+
const data = ScopeKeys.map((scope: Scope) => ({
|
|
951
1030
|
Scope: scope,
|
|
952
|
-
Description:
|
|
1031
|
+
Description: Scopes[scope],
|
|
953
1032
|
}));
|
|
954
1033
|
render(<Table data={data} />);
|
|
955
1034
|
// TODO: maybe a good idea to show usage here
|
|
956
1035
|
}
|
|
957
1036
|
|
|
958
|
-
export async function getAccountId(
|
|
1037
|
+
export async function getAccountId(
|
|
1038
|
+
isInteractive = true
|
|
1039
|
+
): Promise<string | undefined> {
|
|
959
1040
|
const apiToken = getAPIToken();
|
|
960
1041
|
if (!apiToken) return;
|
|
961
1042
|
|
|
962
|
-
|
|
963
|
-
|
|
1043
|
+
const accountIdFromEnv = getCloudflareAccountIdFromEnv();
|
|
1044
|
+
if (accountIdFromEnv) {
|
|
1045
|
+
return accountIdFromEnv;
|
|
964
1046
|
}
|
|
965
1047
|
|
|
966
1048
|
let response: Response;
|
|
967
1049
|
try {
|
|
968
|
-
response = await fetch(`${
|
|
1050
|
+
response = await fetch(`${getCloudflareApiBaseUrl()}/memberships`, {
|
|
969
1051
|
method: "GET",
|
|
970
1052
|
headers: {
|
|
971
1053
|
Authorization: "Bearer " + apiToken,
|
|
@@ -985,7 +1067,7 @@ export async function getAccountId() {
|
|
|
985
1067
|
if (responseJSON.success === true) {
|
|
986
1068
|
if (responseJSON.result.length === 1) {
|
|
987
1069
|
accountId = responseJSON.result[0].account.id;
|
|
988
|
-
} else {
|
|
1070
|
+
} else if (isInteractive) {
|
|
989
1071
|
accountId = await new Promise((resolve) => {
|
|
990
1072
|
const accounts = responseJSON.result.map((x) => x.account);
|
|
991
1073
|
const { unmount } = render(
|
|
@@ -998,6 +1080,15 @@ export async function getAccountId() {
|
|
|
998
1080
|
/>
|
|
999
1081
|
);
|
|
1000
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
|
+
);
|
|
1001
1092
|
}
|
|
1002
1093
|
}
|
|
1003
1094
|
return accountId;
|
|
@@ -1009,7 +1100,7 @@ type ChooseAccountItem = {
|
|
|
1009
1100
|
};
|
|
1010
1101
|
export function ChooseAccount(props: {
|
|
1011
1102
|
accounts: ChooseAccountItem[];
|
|
1012
|
-
onSelect: (item) => void;
|
|
1103
|
+
onSelect: (item: SelectInputItem<ChooseAccountItem>) => void;
|
|
1013
1104
|
}) {
|
|
1014
1105
|
return (
|
|
1015
1106
|
<>
|