wrangler 0.0.0-e6733a3 → 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 +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 +2796 -0
- package/src/__tests__/dev.test.tsx +488 -58
- 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} +55 -19
- 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 +738 -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 +94 -24
- 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 +1198 -799
- package/src/inspect.ts +159 -66
- package/src/{kv.tsx → kv.ts} +117 -65
- 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 +58 -42
- package/src/parse.ts +183 -0
- package/src/paths.ts +26 -0
- package/src/proxy.ts +224 -78
- package/src/publish.ts +281 -292
- package/src/r2.ts +50 -0
- package/src/reporting.ts +131 -0
- package/src/selfsigned.d.ts +29 -0
- package/src/sites.tsx +224 -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 +273 -180
- 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 +130612 -112486
- 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 -771
- 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,63 +340,63 @@ const TOKEN_URL = "https://dash.cloudflare.com/oauth2/token";
|
|
|
293
340
|
const CALLBACK_URL = "http://localhost:8976/oauth/callback";
|
|
294
341
|
const REVOKE_URL = "https://dash.cloudflare.com/oauth2/revoke";
|
|
295
342
|
|
|
296
|
-
|
|
297
|
-
let initialised = false;
|
|
343
|
+
let LocalState: State = getAuthTokens();
|
|
298
344
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
// get
|
|
345
|
+
/**
|
|
346
|
+
* Compute the current auth tokens.
|
|
347
|
+
*/
|
|
348
|
+
function getAuthTokens(): AuthTokens {
|
|
349
|
+
// get refreshToken/accessToken from fs if exists
|
|
304
350
|
try {
|
|
305
|
-
// if
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
351
|
+
// if the environment variable is available, use that
|
|
352
|
+
const apiTokenFromEnv = getCloudflareAPITokenFromEnv();
|
|
353
|
+
if (apiTokenFromEnv) {
|
|
354
|
+
return {
|
|
355
|
+
accessToken: {
|
|
356
|
+
value: apiTokenFromEnv,
|
|
357
|
+
expiry: "3021-12-31T23:59:59+00:00",
|
|
358
|
+
},
|
|
310
359
|
};
|
|
311
|
-
initialised = true;
|
|
312
|
-
return;
|
|
313
360
|
}
|
|
314
361
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
);
|
|
320
|
-
const { oauth_token, refresh_token, expiration_time } = toml as {
|
|
321
|
-
oauth_token: string;
|
|
322
|
-
refresh_token: string;
|
|
323
|
-
expiration_time: string;
|
|
324
|
-
};
|
|
362
|
+
// otherwise try loading from the user auth config file.
|
|
363
|
+
const { oauth_token, refresh_token, expiration_time, api_token } =
|
|
364
|
+
readAuthConfigFile();
|
|
365
|
+
|
|
325
366
|
if (oauth_token) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
367
|
+
return {
|
|
368
|
+
accessToken: {
|
|
369
|
+
value: oauth_token,
|
|
370
|
+
// If there is no `expiration_time` field then set it to an old date, to cause it to expire immediately.
|
|
371
|
+
expiry: expiration_time ?? "2000-01-01:00:00:00+00:00",
|
|
372
|
+
},
|
|
373
|
+
refreshToken: { value: refresh_token ?? "" },
|
|
374
|
+
};
|
|
375
|
+
} else if (api_token) {
|
|
376
|
+
return { apiToken: api_token };
|
|
377
|
+
} else {
|
|
378
|
+
return {};
|
|
330
379
|
}
|
|
331
|
-
} catch
|
|
332
|
-
|
|
333
|
-
// console.error(err);
|
|
380
|
+
} catch {
|
|
381
|
+
return {};
|
|
334
382
|
}
|
|
335
|
-
initialised = true;
|
|
336
383
|
}
|
|
337
384
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
);
|
|
344
|
-
}
|
|
385
|
+
/**
|
|
386
|
+
* Run the initialisation of the auth state, in the case that something changed.
|
|
387
|
+
*/
|
|
388
|
+
export function reinitialiseAuthTokens(): void {
|
|
389
|
+
LocalState = getAuthTokens();
|
|
345
390
|
}
|
|
346
391
|
|
|
347
|
-
export function getAPIToken(): string {
|
|
348
|
-
if (
|
|
349
|
-
|
|
392
|
+
export function getAPIToken(): string | undefined {
|
|
393
|
+
if (LocalState.apiToken) {
|
|
394
|
+
console.warn(
|
|
395
|
+
"It looks like you have used Wrangler 1's `config` command to login with an API token.\n" +
|
|
396
|
+
"This is no longer supported in the current version of Wrangler.\n" +
|
|
397
|
+
"If you wish to authenticate via an API token then please set the `CLOUDFLARE_API_TOKEN` environment variable."
|
|
398
|
+
);
|
|
350
399
|
}
|
|
351
|
-
|
|
352
|
-
throwIfNotInitialised();
|
|
353
400
|
return LocalState.accessToken?.value;
|
|
354
401
|
}
|
|
355
402
|
|
|
@@ -548,7 +595,7 @@ function isReturningFromAuthServer(query: ParsedUrlQuery): boolean {
|
|
|
548
595
|
return true;
|
|
549
596
|
}
|
|
550
597
|
|
|
551
|
-
export async function getAuthURL(scopes
|
|
598
|
+
export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
|
|
552
599
|
const { codeChallenge, codeVerifier } = await generatePKCECodes();
|
|
553
600
|
const stateQueryParam = generateRandomState(RECOMMENDED_STATE_LENGTH);
|
|
554
601
|
|
|
@@ -558,22 +605,30 @@ export async function getAuthURL(scopes?: string[]): Promise<string> {
|
|
|
558
605
|
stateQueryParam,
|
|
559
606
|
});
|
|
560
607
|
|
|
561
|
-
// TODO: verify that the scopes passed are legit
|
|
562
|
-
|
|
563
608
|
return (
|
|
564
609
|
AUTH_URL +
|
|
565
610
|
`?response_type=code&` +
|
|
566
611
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
567
612
|
`redirect_uri=${encodeURIComponent(CALLBACK_URL)}&` +
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
)}&` +
|
|
613
|
+
// @ts-expect-error we add offline_access manually
|
|
614
|
+
`scope=${encodeURIComponent(scopes.concat("offline_access").join(" "))}&` +
|
|
571
615
|
`state=${stateQueryParam}&` +
|
|
572
616
|
`code_challenge=${encodeURIComponent(codeChallenge)}&` +
|
|
573
617
|
`code_challenge_method=S256`
|
|
574
618
|
);
|
|
575
619
|
}
|
|
576
620
|
|
|
621
|
+
type TokenResponse =
|
|
622
|
+
| {
|
|
623
|
+
access_token: string;
|
|
624
|
+
expires_in: number;
|
|
625
|
+
refresh_token: string;
|
|
626
|
+
scope: string;
|
|
627
|
+
}
|
|
628
|
+
| {
|
|
629
|
+
error: string;
|
|
630
|
+
};
|
|
631
|
+
|
|
577
632
|
/**
|
|
578
633
|
* Refresh an access token from the remote service.
|
|
579
634
|
*/
|
|
@@ -598,13 +653,12 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
598
653
|
throw await response.json();
|
|
599
654
|
} else {
|
|
600
655
|
try {
|
|
601
|
-
const json = await response.json();
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
};
|
|
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;
|
|
608
662
|
let scopes: Scope[] = [];
|
|
609
663
|
|
|
610
664
|
const accessToken: AccessToken = {
|
|
@@ -632,20 +686,24 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
632
686
|
refreshToken: LocalState.refreshToken,
|
|
633
687
|
};
|
|
634
688
|
return accessContext;
|
|
635
|
-
} catch (
|
|
636
|
-
const error = err?.error || "There was a network error.";
|
|
689
|
+
} catch (error) {
|
|
637
690
|
switch (error) {
|
|
638
691
|
case "invalid_grant":
|
|
639
|
-
console.
|
|
692
|
+
console.warn(
|
|
640
693
|
"Expired! Auth code or refresh token needs to be renewed."
|
|
641
694
|
);
|
|
642
695
|
// alert("Redirecting to auth server to obtain a new auth grant code.");
|
|
643
696
|
// TODO: return refreshAuthCodeOrRefreshToken();
|
|
644
697
|
break;
|
|
645
698
|
default:
|
|
699
|
+
console.error(error);
|
|
646
700
|
break;
|
|
647
701
|
}
|
|
648
|
-
|
|
702
|
+
if (typeof error === "string") {
|
|
703
|
+
throw toErrorClass(error);
|
|
704
|
+
} else {
|
|
705
|
+
throw error;
|
|
706
|
+
}
|
|
649
707
|
}
|
|
650
708
|
}
|
|
651
709
|
}
|
|
@@ -686,13 +744,11 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
686
744
|
}
|
|
687
745
|
throw toErrorClass(error);
|
|
688
746
|
}
|
|
689
|
-
const json = await response.json();
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
scope: string;
|
|
695
|
-
};
|
|
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;
|
|
696
752
|
let scopes: Scope[] = [];
|
|
697
753
|
LocalState.hasAuthCodeBeenExchangedForAccessToken = true;
|
|
698
754
|
|
|
@@ -776,31 +832,37 @@ function generateRandomState(lengthOfState: number): string {
|
|
|
776
832
|
.join("");
|
|
777
833
|
}
|
|
778
834
|
|
|
779
|
-
|
|
780
|
-
|
|
835
|
+
export function writeAuthConfigFile(config: UserAuthConfig) {
|
|
836
|
+
mkdirSync(path.join(os.homedir(), ".wrangler/config/"), {
|
|
781
837
|
recursive: true,
|
|
782
838
|
});
|
|
783
|
-
|
|
784
|
-
path.join(os.homedir(),
|
|
785
|
-
|
|
786
|
-
oauth_token = "${tokenData.token?.value || ""}"
|
|
787
|
-
refresh_token = "${tokenData.refreshToken?.value}"
|
|
788
|
-
expiration_time = "${tokenData.token?.expiry}"
|
|
789
|
-
`,
|
|
839
|
+
writeFileSync(
|
|
840
|
+
path.join(os.homedir(), USER_AUTH_CONFIG_FILE),
|
|
841
|
+
TOML.stringify(config as TOML.JsonMap),
|
|
790
842
|
{ encoding: "utf-8" }
|
|
791
843
|
);
|
|
792
844
|
}
|
|
793
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
|
+
|
|
794
853
|
type LoginProps = {
|
|
795
|
-
scopes?:
|
|
854
|
+
scopes?: Scope[];
|
|
796
855
|
};
|
|
797
856
|
|
|
798
|
-
export async function loginOrRefreshIfRequired(
|
|
857
|
+
export async function loginOrRefreshIfRequired(
|
|
858
|
+
isInteractive = true
|
|
859
|
+
): Promise<boolean> {
|
|
799
860
|
// TODO: if there already is a token, then try refreshing
|
|
800
861
|
// TODO: ask permission before opening browser
|
|
801
|
-
if (!
|
|
802
|
-
//
|
|
803
|
-
|
|
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());
|
|
804
866
|
} else if (isAccessTokenExpired()) {
|
|
805
867
|
return await refreshToken();
|
|
806
868
|
} else {
|
|
@@ -809,19 +871,20 @@ export async function loginOrRefreshIfRequired(): Promise<boolean> {
|
|
|
809
871
|
}
|
|
810
872
|
|
|
811
873
|
export async function login(props?: LoginProps): Promise<boolean> {
|
|
874
|
+
console.log("Attempting to login via OAuth...");
|
|
812
875
|
const urlToOpen = await getAuthURL(props?.scopes);
|
|
813
|
-
await
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
let server;
|
|
817
|
-
let loginTimeoutHandle;
|
|
876
|
+
await openInBrowser(urlToOpen);
|
|
877
|
+
let server: http.Server;
|
|
878
|
+
let loginTimeoutHandle: NodeJS.Timeout;
|
|
818
879
|
const timerPromise = new Promise<boolean>((resolve) => {
|
|
819
880
|
loginTimeoutHandle = setTimeout(() => {
|
|
820
|
-
console.error(
|
|
881
|
+
console.error(
|
|
882
|
+
"Timed out waiting for authorization code, please try again."
|
|
883
|
+
);
|
|
821
884
|
server.close();
|
|
822
885
|
clearTimeout(loginTimeoutHandle);
|
|
823
886
|
resolve(false);
|
|
824
|
-
}, 60000); // wait for
|
|
887
|
+
}, 60000); // wait for 60 seconds for the user to authorize
|
|
825
888
|
});
|
|
826
889
|
|
|
827
890
|
const loginPromise = new Promise<boolean>((resolve, reject) => {
|
|
@@ -852,8 +915,9 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
852
915
|
finish(false);
|
|
853
916
|
});
|
|
854
917
|
console.log(
|
|
855
|
-
"Error: Consent denied. You must grant consent to Wrangler in order to login
|
|
856
|
-
|
|
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
|
+
);
|
|
857
921
|
|
|
858
922
|
return;
|
|
859
923
|
} else {
|
|
@@ -866,8 +930,12 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
866
930
|
finish(false, new ErrorNoAuthCode());
|
|
867
931
|
return;
|
|
868
932
|
} else {
|
|
869
|
-
const
|
|
870
|
-
|
|
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
|
+
});
|
|
871
939
|
res.writeHead(307, {
|
|
872
940
|
Location:
|
|
873
941
|
"https://welcome.developers.workers.dev/wrangler-oauth-consent-granted",
|
|
@@ -875,9 +943,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
875
943
|
res.end(() => {
|
|
876
944
|
finish(true);
|
|
877
945
|
});
|
|
878
|
-
console.log(
|
|
879
|
-
`Successfully configured. You can find your configuration file at: ${os.homedir()}/.wrangler/config/default.toml`
|
|
880
|
-
);
|
|
946
|
+
console.log(`Successfully logged in.`);
|
|
881
947
|
|
|
882
948
|
return;
|
|
883
949
|
}
|
|
@@ -894,18 +960,22 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
894
960
|
/**
|
|
895
961
|
* Checks to see if the access token has expired.
|
|
896
962
|
*/
|
|
897
|
-
|
|
898
|
-
throwIfNotInitialised();
|
|
963
|
+
function isAccessTokenExpired(): boolean {
|
|
899
964
|
const { accessToken } = LocalState;
|
|
900
965
|
return Boolean(accessToken && new Date() >= new Date(accessToken.expiry));
|
|
901
966
|
}
|
|
902
967
|
|
|
903
|
-
|
|
904
|
-
throwIfNotInitialised();
|
|
968
|
+
async function refreshToken(): Promise<boolean> {
|
|
905
969
|
// refresh
|
|
906
970
|
try {
|
|
907
|
-
const
|
|
908
|
-
|
|
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 });
|
|
909
979
|
return true;
|
|
910
980
|
} catch (err) {
|
|
911
981
|
console.error(err);
|
|
@@ -914,10 +984,28 @@ export async function refreshToken(): Promise<boolean> {
|
|
|
914
984
|
}
|
|
915
985
|
|
|
916
986
|
export async function logout(): Promise<void> {
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
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
|
+
);
|
|
921
1009
|
}
|
|
922
1010
|
const body =
|
|
923
1011
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
@@ -932,38 +1020,34 @@ export async function logout(): Promise<void> {
|
|
|
932
1020
|
},
|
|
933
1021
|
});
|
|
934
1022
|
await response.text(); // blank text? would be nice if it was something meaningful
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
);
|
|
938
|
-
// delete the file
|
|
939
|
-
await rm(path.join(os.homedir(), ".wrangler/config/default.toml"));
|
|
940
|
-
console.log(
|
|
941
|
-
`Removing ${os.homedir()}/.wrangler/config/default.toml.. success!`
|
|
942
|
-
);
|
|
1023
|
+
rmSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE));
|
|
1024
|
+
console.log(`Successfully logged out.`);
|
|
943
1025
|
}
|
|
944
1026
|
|
|
945
|
-
export function listScopes(): void {
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
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) => ({
|
|
949
1030
|
Scope: scope,
|
|
950
|
-
Description:
|
|
1031
|
+
Description: Scopes[scope],
|
|
951
1032
|
}));
|
|
952
1033
|
render(<Table data={data} />);
|
|
953
1034
|
// TODO: maybe a good idea to show usage here
|
|
954
1035
|
}
|
|
955
1036
|
|
|
956
|
-
export async function getAccountId(
|
|
1037
|
+
export async function getAccountId(
|
|
1038
|
+
isInteractive = true
|
|
1039
|
+
): Promise<string | undefined> {
|
|
957
1040
|
const apiToken = getAPIToken();
|
|
958
1041
|
if (!apiToken) return;
|
|
959
1042
|
|
|
960
|
-
|
|
961
|
-
|
|
1043
|
+
const accountIdFromEnv = getCloudflareAccountIdFromEnv();
|
|
1044
|
+
if (accountIdFromEnv) {
|
|
1045
|
+
return accountIdFromEnv;
|
|
962
1046
|
}
|
|
963
1047
|
|
|
964
1048
|
let response: Response;
|
|
965
1049
|
try {
|
|
966
|
-
response = await fetch(`${
|
|
1050
|
+
response = await fetch(`${getCloudflareApiBaseUrl()}/memberships`, {
|
|
967
1051
|
method: "GET",
|
|
968
1052
|
headers: {
|
|
969
1053
|
Authorization: "Bearer " + apiToken,
|
|
@@ -971,19 +1055,19 @@ export async function getAccountId() {
|
|
|
971
1055
|
});
|
|
972
1056
|
} catch (err) {
|
|
973
1057
|
// probably offline
|
|
1058
|
+
return;
|
|
974
1059
|
}
|
|
975
|
-
|
|
976
|
-
let accountId: string;
|
|
977
|
-
|
|
978
|
-
const responseJSON: {
|
|
1060
|
+
|
|
1061
|
+
let accountId: string | undefined;
|
|
1062
|
+
const responseJSON = (await response.json()) as {
|
|
979
1063
|
success: boolean;
|
|
980
1064
|
result: { id: string; account: { id: string; name: string } }[];
|
|
981
|
-
}
|
|
1065
|
+
};
|
|
982
1066
|
|
|
983
1067
|
if (responseJSON.success === true) {
|
|
984
1068
|
if (responseJSON.result.length === 1) {
|
|
985
1069
|
accountId = responseJSON.result[0].account.id;
|
|
986
|
-
} else {
|
|
1070
|
+
} else if (isInteractive) {
|
|
987
1071
|
accountId = await new Promise((resolve) => {
|
|
988
1072
|
const accounts = responseJSON.result.map((x) => x.account);
|
|
989
1073
|
const { unmount } = render(
|
|
@@ -996,6 +1080,15 @@ export async function getAccountId() {
|
|
|
996
1080
|
/>
|
|
997
1081
|
);
|
|
998
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
|
+
);
|
|
999
1092
|
}
|
|
1000
1093
|
}
|
|
1001
1094
|
return accountId;
|
|
@@ -1007,7 +1100,7 @@ type ChooseAccountItem = {
|
|
|
1007
1100
|
};
|
|
1008
1101
|
export function ChooseAccount(props: {
|
|
1009
1102
|
accounts: ChooseAccountItem[];
|
|
1010
|
-
onSelect: (item) => void;
|
|
1103
|
+
onSelect: (item: SelectInputItem<ChooseAccountItem>) => void;
|
|
1011
1104
|
}) {
|
|
1012
1105
|
return (
|
|
1013
1106
|
<>
|