wrangler 0.0.0-ece06ea → 0.0.0-ed4f2ef
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 +49 -22
- package/pages/functions/buildWorker.ts +62 -0
- package/pages/functions/filepath-routing.test.ts +223 -0
- package/pages/functions/filepath-routing.ts +181 -0
- package/pages/functions/identifiers.ts +78 -0
- package/pages/functions/routes.ts +151 -0
- package/pages/functions/template-worker.ts +130 -0
- package/src/__tests__/configuration.test.ts +2874 -0
- package/src/__tests__/dev.test.tsx +588 -0
- package/src/__tests__/guess-worker-format.test.ts +83 -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__/helpers/mock-cfetch.ts +207 -0
- package/src/__tests__/helpers/mock-console.ts +70 -0
- package/src/__tests__/helpers/mock-dialogs.ts +65 -0
- package/src/__tests__/helpers/mock-kv.ts +33 -0
- package/src/__tests__/helpers/mock-oauth-flow.ts +178 -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 +953 -119
- package/src/__tests__/jest.setup.ts +74 -0
- package/src/__tests__/kv.test.ts +1380 -0
- package/src/__tests__/package-manager.test.ts +206 -0
- package/src/__tests__/pages.test.ts +180 -0
- package/src/__tests__/parse.test.ts +322 -0
- package/src/__tests__/publish.test.ts +3946 -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__/user.test.ts +83 -0
- package/src/__tests__/whoami.test.tsx +95 -0
- package/src/bundle.ts +155 -0
- package/src/cfetch/index.ts +104 -0
- package/src/cfetch/internal.ts +139 -0
- package/src/cli.ts +18 -7
- package/src/config/README.md +107 -0
- package/src/config/config.ts +229 -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 +557 -0
- package/src/config/validation.ts +1240 -0
- package/src/create-worker-preview.ts +173 -0
- package/src/create-worker-upload-form.ts +240 -0
- package/src/dev/dev.tsx +413 -0
- package/src/dev/local.tsx +287 -0
- package/src/dev/remote.tsx +209 -0
- package/src/dev/use-esbuild.ts +109 -0
- package/src/dialogs.tsx +6 -14
- package/src/entry.ts +256 -0
- package/src/environment-variables.ts +33 -0
- package/src/errors.ts +11 -0
- package/src/https-options.ts +122 -0
- package/src/index.tsx +1503 -959
- package/src/inspect.ts +624 -0
- package/src/intl-polyfill.d.ts +139 -0
- package/src/kv.ts +319 -0
- package/src/miniflare-cli/README.md +30 -0
- package/src/miniflare-cli/enum-keys.ts +17 -0
- package/src/miniflare-cli/index.ts +38 -0
- package/src/module-collection.ts +257 -0
- package/src/open-in-browser.ts +25 -0
- package/src/package-manager.ts +126 -0
- package/src/pages.tsx +1074 -212
- package/src/parse.ts +183 -0
- package/src/paths.ts +26 -0
- package/src/proxy.ts +455 -0
- package/src/publish.ts +326 -295
- package/src/r2.ts +50 -0
- package/src/reporting.ts +131 -0
- package/src/selfsigned.d.ts +29 -0
- package/src/sites.tsx +224 -59
- 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 +340 -203
- package/src/whoami.tsx +69 -0
- package/src/worker.ts +172 -0
- package/templates/new-worker.js +15 -0
- package/templates/new-worker.ts +15 -0
- package/{static-asset-facade.js → templates/static-asset-facade.js} +1 -1
- package/templates/tsconfig.json +105 -0
- package/wrangler-dist/cli.js +136988 -131174
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/fixtures/init/.gitkeep +0 -0
- package/src/__tests__/mock-cfetch.js +0 -46
- package/src/api/form_data.ts +0 -158
- package/src/api/inspect.ts +0 -441
- package/src/api/preview.ts +0 -123
- package/src/api/worker.ts +0 -161
- package/src/cfetch.ts +0 -72
- package/src/config.ts +0 -124
- package/src/dev.tsx +0 -736
- package/src/kv.tsx +0 -211
- package/src/tail.tsx +0 -71
- package/src/util/fetch.ts +0 -74
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,44 @@
|
|
|
205
205
|
limitations under the License.
|
|
206
206
|
*/
|
|
207
207
|
|
|
208
|
-
import
|
|
209
|
-
import { render } 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 { Config } from "./config";
|
|
227
|
+
import type { Item as SelectInputItem } from "ink-select-input/build/SelectInput";
|
|
224
228
|
import type { ParsedUrlQuery } from "node:querystring";
|
|
225
|
-
import {
|
|
226
|
-
|
|
229
|
+
import type { Response } from "undici";
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Try to read the API token from the environment.
|
|
233
|
+
*/
|
|
234
|
+
const getCloudflareAPITokenFromEnv = getEnvironmentVariableFactory({
|
|
235
|
+
variableName: "CLOUDFLARE_API_TOKEN",
|
|
236
|
+
deprecatedName: "CF_API_TOKEN",
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Try to read the account ID from the environment.
|
|
241
|
+
*/
|
|
242
|
+
const getCloudflareAccountIdFromEnv = getEnvironmentVariableFactory({
|
|
243
|
+
variableName: "CLOUDFLARE_ACCOUNT_ID",
|
|
244
|
+
deprecatedName: "CF_ACCOUNT_ID",
|
|
245
|
+
});
|
|
227
246
|
|
|
228
247
|
/**
|
|
229
248
|
* An implementation of rfc6749#section-4.1 and rfc7636.
|
|
@@ -234,17 +253,45 @@ interface PKCECodes {
|
|
|
234
253
|
codeVerifier: string;
|
|
235
254
|
}
|
|
236
255
|
|
|
237
|
-
|
|
238
|
-
|
|
256
|
+
/**
|
|
257
|
+
* The module level state of the authentication flow.
|
|
258
|
+
*/
|
|
259
|
+
interface State extends AuthTokens {
|
|
239
260
|
authorizationCode?: string;
|
|
240
261
|
codeChallenge?: string;
|
|
241
262
|
codeVerifier?: string;
|
|
242
263
|
hasAuthCodeBeenExchangedForAccessToken?: boolean;
|
|
243
|
-
refreshToken?: RefreshToken; // persist
|
|
244
264
|
stateQueryParam?: string;
|
|
245
265
|
scopes?: Scope[];
|
|
246
266
|
}
|
|
247
267
|
|
|
268
|
+
/**
|
|
269
|
+
* The tokens related to authentication.
|
|
270
|
+
*/
|
|
271
|
+
interface AuthTokens {
|
|
272
|
+
accessToken?: AccessToken;
|
|
273
|
+
refreshToken?: RefreshToken;
|
|
274
|
+
/** @deprecated - this field was only provided by the deprecated `wrangler1 config` command. */
|
|
275
|
+
apiToken?: string;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The path to the config file that holds user authentication data,
|
|
280
|
+
* relative to the user's home directory.
|
|
281
|
+
*/
|
|
282
|
+
export const USER_AUTH_CONFIG_FILE = ".wrangler/config/default.toml";
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* The data that may be read from the `USER_CONFIG_FILE`.
|
|
286
|
+
*/
|
|
287
|
+
export interface UserAuthConfig {
|
|
288
|
+
oauth_token?: string;
|
|
289
|
+
refresh_token?: string;
|
|
290
|
+
expiration_time?: string;
|
|
291
|
+
/** @deprecated - this field was only provided by the deprecated `wrangler1 config` command. */
|
|
292
|
+
api_token?: string;
|
|
293
|
+
}
|
|
294
|
+
|
|
248
295
|
interface RefreshToken {
|
|
249
296
|
value: string;
|
|
250
297
|
}
|
|
@@ -254,38 +301,39 @@ interface AccessToken {
|
|
|
254
301
|
expiry: string;
|
|
255
302
|
}
|
|
256
303
|
|
|
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
|
-
|
|
304
|
+
const Scopes = {
|
|
305
|
+
"account:read":
|
|
306
|
+
"See your account info such as account details, analytics, and memberships.",
|
|
307
|
+
"user:read":
|
|
308
|
+
"See your user info such as name, email address, and account memberships.",
|
|
309
|
+
"workers:write":
|
|
310
|
+
"See and change Cloudflare Workers data such as zones, KV storage, namespaces, scripts, and routes.",
|
|
311
|
+
"workers_kv:write":
|
|
312
|
+
"See and change Cloudflare Workers KV Storage data such as keys and namespaces.",
|
|
313
|
+
"workers_routes:write":
|
|
314
|
+
"See and change Cloudflare Workers data such as filters and routes.",
|
|
315
|
+
"workers_scripts:write":
|
|
316
|
+
"See and change Cloudflare Workers scripts, durable objects, subdomains, triggers, and tail data.",
|
|
317
|
+
"workers_tail:read": "See Cloudflare Workers tail and script data.",
|
|
318
|
+
"pages:write":
|
|
319
|
+
"See and change Cloudflare Pages projects, settings and deployments.",
|
|
320
|
+
"zone:read": "Grants read level access to account zone.",
|
|
321
|
+
} as const;
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* The possible keys for a Scope.
|
|
325
|
+
*
|
|
326
|
+
* "offline_access" is automatically included.
|
|
327
|
+
*/
|
|
328
|
+
type Scope = keyof typeof Scopes;
|
|
329
|
+
|
|
330
|
+
const ScopeKeys = Object.keys(Scopes) as Scope[];
|
|
331
|
+
|
|
332
|
+
export function validateScopeKeys(
|
|
333
|
+
scopes: string[]
|
|
334
|
+
): scopes is typeof ScopeKeys {
|
|
335
|
+
return scopes.every((scope) => scope in Scopes);
|
|
336
|
+
}
|
|
289
337
|
|
|
290
338
|
const CLIENT_ID = "54d11594-84e4-41aa-b438-e81b8fa78ee7";
|
|
291
339
|
const AUTH_URL = "https://dash.cloudflare.com/oauth2/auth";
|
|
@@ -293,63 +341,74 @@ const TOKEN_URL = "https://dash.cloudflare.com/oauth2/token";
|
|
|
293
341
|
const CALLBACK_URL = "http://localhost:8976/oauth/callback";
|
|
294
342
|
const REVOKE_URL = "https://dash.cloudflare.com/oauth2/revoke";
|
|
295
343
|
|
|
296
|
-
|
|
297
|
-
let initialised = false;
|
|
344
|
+
let LocalState: State = getAuthTokens();
|
|
298
345
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
// get
|
|
346
|
+
/**
|
|
347
|
+
* Compute the current auth tokens.
|
|
348
|
+
*/
|
|
349
|
+
function getAuthTokens(config?: UserAuthConfig): AuthTokens {
|
|
350
|
+
// get refreshToken/accessToken from fs if exists
|
|
304
351
|
try {
|
|
305
|
-
// if
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
352
|
+
// if the environment variable is available, use that
|
|
353
|
+
const apiTokenFromEnv = getCloudflareAPITokenFromEnv();
|
|
354
|
+
if (apiTokenFromEnv) {
|
|
355
|
+
return {
|
|
356
|
+
accessToken: {
|
|
357
|
+
value: apiTokenFromEnv,
|
|
358
|
+
expiry: "3021-12-31T23:59:59+00:00",
|
|
359
|
+
},
|
|
310
360
|
};
|
|
311
|
-
initialised = true;
|
|
312
|
-
return;
|
|
313
361
|
}
|
|
314
362
|
|
|
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
|
-
};
|
|
363
|
+
// otherwise try loading from the user auth config file.
|
|
364
|
+
const { oauth_token, refresh_token, expiration_time, api_token } =
|
|
365
|
+
config || readAuthConfigFile();
|
|
366
|
+
|
|
325
367
|
if (oauth_token) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
368
|
+
return {
|
|
369
|
+
accessToken: {
|
|
370
|
+
value: oauth_token,
|
|
371
|
+
// If there is no `expiration_time` field then set it to an old date, to cause it to expire immediately.
|
|
372
|
+
expiry: expiration_time ?? "2000-01-01:00:00:00+00:00",
|
|
373
|
+
},
|
|
374
|
+
refreshToken: { value: refresh_token ?? "" },
|
|
375
|
+
};
|
|
376
|
+
} else if (api_token) {
|
|
377
|
+
return { apiToken: api_token };
|
|
378
|
+
} else {
|
|
379
|
+
return {};
|
|
330
380
|
}
|
|
331
|
-
} catch
|
|
332
|
-
|
|
333
|
-
// console.error(err);
|
|
381
|
+
} catch {
|
|
382
|
+
return {};
|
|
334
383
|
}
|
|
335
|
-
initialised = true;
|
|
336
384
|
}
|
|
337
385
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
386
|
+
/**
|
|
387
|
+
* Run the initialisation of the auth state, in the case that something changed.
|
|
388
|
+
*
|
|
389
|
+
* This runs automatically whenever `writeAuthConfigFile` is run, so generally
|
|
390
|
+
* you won't need to call it yourself.
|
|
391
|
+
*/
|
|
392
|
+
export function reinitialiseAuthTokens(): void;
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Reinitialise auth state from an in-memory config, skipping
|
|
396
|
+
* over the part where we write a file and then read it back into memory
|
|
397
|
+
*/
|
|
398
|
+
export function reinitialiseAuthTokens(config: UserAuthConfig): void;
|
|
399
|
+
|
|
400
|
+
export function reinitialiseAuthTokens(config?: UserAuthConfig): void {
|
|
401
|
+
LocalState = getAuthTokens(config);
|
|
345
402
|
}
|
|
346
403
|
|
|
347
|
-
export function getAPIToken(): string {
|
|
348
|
-
if (
|
|
349
|
-
|
|
404
|
+
export function getAPIToken(): string | undefined {
|
|
405
|
+
if (LocalState.apiToken) {
|
|
406
|
+
console.warn(
|
|
407
|
+
"It looks like you have used Wrangler 1's `config` command to login with an API token.\n" +
|
|
408
|
+
"This is no longer supported in the current version of Wrangler.\n" +
|
|
409
|
+
"If you wish to authenticate via an API token then please set the `CLOUDFLARE_API_TOKEN` environment variable."
|
|
410
|
+
);
|
|
350
411
|
}
|
|
351
|
-
|
|
352
|
-
throwIfNotInitialised();
|
|
353
412
|
return LocalState.accessToken?.value;
|
|
354
413
|
}
|
|
355
414
|
|
|
@@ -548,7 +607,7 @@ function isReturningFromAuthServer(query: ParsedUrlQuery): boolean {
|
|
|
548
607
|
return true;
|
|
549
608
|
}
|
|
550
609
|
|
|
551
|
-
export async function getAuthURL(scopes
|
|
610
|
+
export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
|
|
552
611
|
const { codeChallenge, codeVerifier } = await generatePKCECodes();
|
|
553
612
|
const stateQueryParam = generateRandomState(RECOMMENDED_STATE_LENGTH);
|
|
554
613
|
|
|
@@ -558,39 +617,44 @@ export async function getAuthURL(scopes?: string[]): Promise<string> {
|
|
|
558
617
|
stateQueryParam,
|
|
559
618
|
});
|
|
560
619
|
|
|
561
|
-
// TODO: verify that the scopes passed are legit
|
|
562
|
-
|
|
563
620
|
return (
|
|
564
621
|
AUTH_URL +
|
|
565
622
|
`?response_type=code&` +
|
|
566
623
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
567
624
|
`redirect_uri=${encodeURIComponent(CALLBACK_URL)}&` +
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
)}&` +
|
|
625
|
+
// we add offline_access manually for every request
|
|
626
|
+
`scope=${encodeURIComponent([...scopes, "offline_access"].join(" "))}&` +
|
|
571
627
|
`state=${stateQueryParam}&` +
|
|
572
628
|
`code_challenge=${encodeURIComponent(codeChallenge)}&` +
|
|
573
629
|
`code_challenge_method=S256`
|
|
574
630
|
);
|
|
575
631
|
}
|
|
576
632
|
|
|
633
|
+
type TokenResponse =
|
|
634
|
+
| {
|
|
635
|
+
access_token: string;
|
|
636
|
+
expires_in: number;
|
|
637
|
+
refresh_token: string;
|
|
638
|
+
scope: string;
|
|
639
|
+
}
|
|
640
|
+
| {
|
|
641
|
+
error: string;
|
|
642
|
+
};
|
|
643
|
+
|
|
577
644
|
/**
|
|
578
645
|
* Refresh an access token from the remote service.
|
|
579
646
|
*/
|
|
580
647
|
async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
if (!refreshToken) {
|
|
648
|
+
if (!LocalState.refreshToken) {
|
|
584
649
|
console.warn("No refresh token is present.");
|
|
585
650
|
}
|
|
586
651
|
|
|
587
|
-
const url = TOKEN_URL;
|
|
588
652
|
const body =
|
|
589
653
|
`grant_type=refresh_token&` +
|
|
590
|
-
`refresh_token=${refreshToken?.value}&` +
|
|
654
|
+
`refresh_token=${LocalState.refreshToken?.value}&` +
|
|
591
655
|
`client_id=${CLIENT_ID}`;
|
|
592
656
|
|
|
593
|
-
const response = await fetch(
|
|
657
|
+
const response = await fetch(TOKEN_URL, {
|
|
594
658
|
method: "POST",
|
|
595
659
|
body,
|
|
596
660
|
headers: {
|
|
@@ -601,13 +665,12 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
601
665
|
throw await response.json();
|
|
602
666
|
} else {
|
|
603
667
|
try {
|
|
604
|
-
const json = await response.json();
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
};
|
|
668
|
+
const json = (await response.json()) as TokenResponse;
|
|
669
|
+
if ("error" in json) {
|
|
670
|
+
throw json.error;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const { access_token, expires_in, refresh_token, scope } = json;
|
|
611
674
|
let scopes: Scope[] = [];
|
|
612
675
|
|
|
613
676
|
const accessToken: AccessToken = {
|
|
@@ -617,10 +680,9 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
617
680
|
LocalState.accessToken = accessToken;
|
|
618
681
|
|
|
619
682
|
if (refresh_token) {
|
|
620
|
-
|
|
683
|
+
LocalState.refreshToken = {
|
|
621
684
|
value: refresh_token,
|
|
622
685
|
};
|
|
623
|
-
LocalState.refreshToken = refreshToken;
|
|
624
686
|
}
|
|
625
687
|
|
|
626
688
|
if (scope) {
|
|
@@ -636,20 +698,24 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
636
698
|
refreshToken: LocalState.refreshToken,
|
|
637
699
|
};
|
|
638
700
|
return accessContext;
|
|
639
|
-
} catch (
|
|
640
|
-
const error = err?.error || "There was a network error.";
|
|
701
|
+
} catch (error) {
|
|
641
702
|
switch (error) {
|
|
642
703
|
case "invalid_grant":
|
|
643
|
-
console.
|
|
704
|
+
console.warn(
|
|
644
705
|
"Expired! Auth code or refresh token needs to be renewed."
|
|
645
706
|
);
|
|
646
707
|
// alert("Redirecting to auth server to obtain a new auth grant code.");
|
|
647
708
|
// TODO: return refreshAuthCodeOrRefreshToken();
|
|
648
709
|
break;
|
|
649
710
|
default:
|
|
711
|
+
console.error(error);
|
|
650
712
|
break;
|
|
651
713
|
}
|
|
652
|
-
|
|
714
|
+
if (typeof error === "string") {
|
|
715
|
+
throw toErrorClass(error);
|
|
716
|
+
} else {
|
|
717
|
+
throw error;
|
|
718
|
+
}
|
|
653
719
|
}
|
|
654
720
|
}
|
|
655
721
|
}
|
|
@@ -666,7 +732,6 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
666
732
|
console.warn("No authorization grant code is being passed.");
|
|
667
733
|
}
|
|
668
734
|
|
|
669
|
-
const url = TOKEN_URL;
|
|
670
735
|
const body =
|
|
671
736
|
`grant_type=authorization_code&` +
|
|
672
737
|
`code=${encodeURIComponent(authorizationCode || "")}&` +
|
|
@@ -674,7 +739,7 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
674
739
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
675
740
|
`code_verifier=${codeVerifier}`;
|
|
676
741
|
|
|
677
|
-
const response = await fetch(
|
|
742
|
+
const response = await fetch(TOKEN_URL, {
|
|
678
743
|
method: "POST",
|
|
679
744
|
body,
|
|
680
745
|
headers: {
|
|
@@ -691,13 +756,11 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
691
756
|
}
|
|
692
757
|
throw toErrorClass(error);
|
|
693
758
|
}
|
|
694
|
-
const json = await response.json();
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
scope: string;
|
|
700
|
-
};
|
|
759
|
+
const json = (await response.json()) as TokenResponse;
|
|
760
|
+
if ("error" in json) {
|
|
761
|
+
throw new Error(json.error);
|
|
762
|
+
}
|
|
763
|
+
const { access_token, expires_in, refresh_token, scope } = json;
|
|
701
764
|
let scopes: Scope[] = [];
|
|
702
765
|
LocalState.hasAuthCodeBeenExchangedForAccessToken = true;
|
|
703
766
|
|
|
@@ -709,10 +772,9 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
709
772
|
LocalState.accessToken = accessToken;
|
|
710
773
|
|
|
711
774
|
if (refresh_token) {
|
|
712
|
-
|
|
775
|
+
LocalState.refreshToken = {
|
|
713
776
|
value: refresh_token,
|
|
714
777
|
};
|
|
715
|
-
LocalState.refreshToken = refreshToken;
|
|
716
778
|
}
|
|
717
779
|
|
|
718
780
|
if (scope) {
|
|
@@ -782,52 +844,73 @@ function generateRandomState(lengthOfState: number): string {
|
|
|
782
844
|
.join("");
|
|
783
845
|
}
|
|
784
846
|
|
|
785
|
-
|
|
786
|
-
|
|
847
|
+
/**
|
|
848
|
+
* Writes a a wrangler config file (auth credentials) to disk,
|
|
849
|
+
* and updates the user auth state with the new credentials.
|
|
850
|
+
*/
|
|
851
|
+
export function writeAuthConfigFile(config: UserAuthConfig) {
|
|
852
|
+
mkdirSync(path.join(os.homedir(), ".wrangler/config/"), {
|
|
787
853
|
recursive: true,
|
|
788
854
|
});
|
|
789
|
-
|
|
790
|
-
path.join(os.homedir(),
|
|
791
|
-
|
|
792
|
-
oauth_token = "${tokenData.token?.value || ""}"
|
|
793
|
-
refresh_token = "${tokenData.refreshToken?.value}"
|
|
794
|
-
expiration_time = "${tokenData.token?.expiry}"
|
|
795
|
-
`,
|
|
855
|
+
writeFileSync(
|
|
856
|
+
path.join(os.homedir(), USER_AUTH_CONFIG_FILE),
|
|
857
|
+
TOML.stringify(config as TOML.JsonMap),
|
|
796
858
|
{ encoding: "utf-8" }
|
|
797
859
|
);
|
|
860
|
+
|
|
861
|
+
reinitialiseAuthTokens();
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
export function readAuthConfigFile(): UserAuthConfig {
|
|
865
|
+
const toml = TOML.parse(
|
|
866
|
+
readFileSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE))
|
|
867
|
+
);
|
|
868
|
+
return toml;
|
|
798
869
|
}
|
|
799
870
|
|
|
800
871
|
type LoginProps = {
|
|
801
|
-
scopes?:
|
|
872
|
+
scopes?: Scope[];
|
|
802
873
|
};
|
|
803
874
|
|
|
804
|
-
export async function loginOrRefreshIfRequired(
|
|
875
|
+
export async function loginOrRefreshIfRequired(
|
|
876
|
+
isInteractive = true
|
|
877
|
+
): Promise<boolean> {
|
|
805
878
|
// TODO: if there already is a token, then try refreshing
|
|
806
879
|
// TODO: ask permission before opening browser
|
|
807
|
-
if (!
|
|
808
|
-
//
|
|
809
|
-
|
|
880
|
+
if (!getAPIToken()) {
|
|
881
|
+
// Not logged in.
|
|
882
|
+
// If we are not interactive, we cannot ask the user to login
|
|
883
|
+
return isInteractive && (await login());
|
|
810
884
|
} else if (isAccessTokenExpired()) {
|
|
811
|
-
|
|
885
|
+
// We're logged in, but the refresh token seems to have expired,
|
|
886
|
+
// so let's try to refresh it
|
|
887
|
+
const didRefresh = await refreshToken();
|
|
888
|
+
if (didRefresh) {
|
|
889
|
+
// The token was refreshed, so we're done here
|
|
890
|
+
return true;
|
|
891
|
+
} else {
|
|
892
|
+
// If the refresh token isn't valid, then we ask the user to login again
|
|
893
|
+
return isInteractive && (await login());
|
|
894
|
+
}
|
|
812
895
|
} else {
|
|
813
896
|
return true;
|
|
814
897
|
}
|
|
815
898
|
}
|
|
816
899
|
|
|
817
900
|
export async function login(props?: LoginProps): Promise<boolean> {
|
|
901
|
+
console.log("Attempting to login via OAuth...");
|
|
818
902
|
const urlToOpen = await getAuthURL(props?.scopes);
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
// console.log(`💁 Opened ${urlToOpen}`);
|
|
822
|
-
let server;
|
|
823
|
-
let loginTimeoutHandle;
|
|
903
|
+
let server: http.Server;
|
|
904
|
+
let loginTimeoutHandle: NodeJS.Timeout;
|
|
824
905
|
const timerPromise = new Promise<boolean>((resolve) => {
|
|
825
906
|
loginTimeoutHandle = setTimeout(() => {
|
|
826
|
-
console.error(
|
|
907
|
+
console.error(
|
|
908
|
+
"Timed out waiting for authorization code, please try again."
|
|
909
|
+
);
|
|
827
910
|
server.close();
|
|
828
911
|
clearTimeout(loginTimeoutHandle);
|
|
829
912
|
resolve(false);
|
|
830
|
-
}, 60000); // wait for
|
|
913
|
+
}, 60000); // wait for 60 seconds for the user to authorize
|
|
831
914
|
});
|
|
832
915
|
|
|
833
916
|
const loginPromise = new Promise<boolean>((resolve, reject) => {
|
|
@@ -858,8 +941,9 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
858
941
|
finish(false);
|
|
859
942
|
});
|
|
860
943
|
console.log(
|
|
861
|
-
"Error: Consent denied. You must grant consent to Wrangler in order to login
|
|
862
|
-
|
|
944
|
+
"Error: Consent denied. You must grant consent to Wrangler in order to login.\n" +
|
|
945
|
+
"If you don't want to do this consider passing an API token via the `CLOUDFLARE_API_TOKEN` environment variable"
|
|
946
|
+
);
|
|
863
947
|
|
|
864
948
|
return;
|
|
865
949
|
} else {
|
|
@@ -872,8 +956,12 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
872
956
|
finish(false, new ErrorNoAuthCode());
|
|
873
957
|
return;
|
|
874
958
|
} else {
|
|
875
|
-
const
|
|
876
|
-
|
|
959
|
+
const exchange = await exchangeAuthCodeForAccessToken();
|
|
960
|
+
writeAuthConfigFile({
|
|
961
|
+
oauth_token: exchange.token?.value ?? "",
|
|
962
|
+
expiration_time: exchange.token?.expiry,
|
|
963
|
+
refresh_token: exchange.refreshToken?.value,
|
|
964
|
+
});
|
|
877
965
|
res.writeHead(307, {
|
|
878
966
|
Location:
|
|
879
967
|
"https://welcome.developers.workers.dev/wrangler-oauth-consent-granted",
|
|
@@ -881,9 +969,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
881
969
|
res.end(() => {
|
|
882
970
|
finish(true);
|
|
883
971
|
});
|
|
884
|
-
console.log(
|
|
885
|
-
`Successfully configured. You can find your configuration file at: ${os.homedir()}/.wrangler/config/default.toml`
|
|
886
|
-
);
|
|
972
|
+
console.log(`Successfully logged in.`);
|
|
887
973
|
|
|
888
974
|
return;
|
|
889
975
|
}
|
|
@@ -894,24 +980,30 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
894
980
|
server.listen(8976);
|
|
895
981
|
});
|
|
896
982
|
|
|
983
|
+
await openInBrowser(urlToOpen);
|
|
984
|
+
|
|
897
985
|
return Promise.race([timerPromise, loginPromise]);
|
|
898
986
|
}
|
|
899
987
|
|
|
900
988
|
/**
|
|
901
989
|
* Checks to see if the access token has expired.
|
|
902
990
|
*/
|
|
903
|
-
|
|
904
|
-
throwIfNotInitialised();
|
|
991
|
+
function isAccessTokenExpired(): boolean {
|
|
905
992
|
const { accessToken } = LocalState;
|
|
906
993
|
return Boolean(accessToken && new Date() >= new Date(accessToken.expiry));
|
|
907
994
|
}
|
|
908
995
|
|
|
909
|
-
|
|
910
|
-
throwIfNotInitialised();
|
|
996
|
+
async function refreshToken(): Promise<boolean> {
|
|
911
997
|
// refresh
|
|
912
998
|
try {
|
|
913
|
-
const
|
|
914
|
-
|
|
999
|
+
const {
|
|
1000
|
+
token: { value: oauth_token, expiry: expiration_time } = {
|
|
1001
|
+
value: "",
|
|
1002
|
+
expiry: "",
|
|
1003
|
+
},
|
|
1004
|
+
refreshToken: { value: refresh_token } = {},
|
|
1005
|
+
} = await exchangeRefreshTokenForAccessToken();
|
|
1006
|
+
writeAuthConfigFile({ oauth_token, expiration_time, refresh_token });
|
|
915
1007
|
return true;
|
|
916
1008
|
} catch (err) {
|
|
917
1009
|
console.error(err);
|
|
@@ -920,16 +1012,33 @@ export async function refreshToken(): Promise<boolean> {
|
|
|
920
1012
|
}
|
|
921
1013
|
|
|
922
1014
|
export async function logout(): Promise<void> {
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1015
|
+
if (!LocalState.accessToken) {
|
|
1016
|
+
if (!LocalState.refreshToken) {
|
|
1017
|
+
console.log("Not logged in, exiting...");
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const body =
|
|
1022
|
+
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
1023
|
+
`token_type_hint=refresh_token&` +
|
|
1024
|
+
`token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
|
|
1025
|
+
|
|
1026
|
+
const response = await fetch(REVOKE_URL, {
|
|
1027
|
+
method: "POST",
|
|
1028
|
+
body,
|
|
1029
|
+
headers: {
|
|
1030
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1031
|
+
},
|
|
1032
|
+
});
|
|
1033
|
+
await response.text(); // blank text? would be nice if it was something meaningful
|
|
1034
|
+
console.log(
|
|
1035
|
+
"💁 Wrangler is configured with an OAuth token. The token has been successfully revoked"
|
|
1036
|
+
);
|
|
928
1037
|
}
|
|
929
1038
|
const body =
|
|
930
1039
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
931
1040
|
`token_type_hint=refresh_token&` +
|
|
932
|
-
`token=${encodeURIComponent(refreshToken?.value || "")}`;
|
|
1041
|
+
`token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
|
|
933
1042
|
|
|
934
1043
|
const response = await fetch(REVOKE_URL, {
|
|
935
1044
|
method: "POST",
|
|
@@ -939,38 +1048,34 @@ export async function logout(): Promise<void> {
|
|
|
939
1048
|
},
|
|
940
1049
|
});
|
|
941
1050
|
await response.text(); // blank text? would be nice if it was something meaningful
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
);
|
|
945
|
-
// delete the file
|
|
946
|
-
await rm(path.join(os.homedir(), ".wrangler/config/default.toml"));
|
|
947
|
-
console.log(
|
|
948
|
-
`Removing ${os.homedir()}/.wrangler/config/default.toml.. success!`
|
|
949
|
-
);
|
|
1051
|
+
rmSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE));
|
|
1052
|
+
console.log(`Successfully logged out.`);
|
|
950
1053
|
}
|
|
951
1054
|
|
|
952
|
-
export function listScopes(): void {
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
const data = Scopes.map((scope, index) => ({
|
|
1055
|
+
export function listScopes(message = "💁 Available scopes:"): void {
|
|
1056
|
+
console.log(message);
|
|
1057
|
+
const data = ScopeKeys.map((scope: Scope) => ({
|
|
956
1058
|
Scope: scope,
|
|
957
|
-
Description:
|
|
1059
|
+
Description: Scopes[scope],
|
|
958
1060
|
}));
|
|
959
1061
|
render(<Table data={data} />);
|
|
960
1062
|
// TODO: maybe a good idea to show usage here
|
|
961
1063
|
}
|
|
962
1064
|
|
|
963
|
-
export async function getAccountId(
|
|
1065
|
+
export async function getAccountId(
|
|
1066
|
+
isInteractive = true
|
|
1067
|
+
): Promise<string | undefined> {
|
|
964
1068
|
const apiToken = getAPIToken();
|
|
965
1069
|
if (!apiToken) return;
|
|
966
1070
|
|
|
967
|
-
|
|
968
|
-
|
|
1071
|
+
const accountIdFromEnv = getCloudflareAccountIdFromEnv();
|
|
1072
|
+
if (accountIdFromEnv) {
|
|
1073
|
+
return accountIdFromEnv;
|
|
969
1074
|
}
|
|
970
1075
|
|
|
971
1076
|
let response: Response;
|
|
972
1077
|
try {
|
|
973
|
-
response = await fetch(`${
|
|
1078
|
+
response = await fetch(`${getCloudflareApiBaseUrl()}/memberships`, {
|
|
974
1079
|
method: "GET",
|
|
975
1080
|
headers: {
|
|
976
1081
|
Authorization: "Bearer " + apiToken,
|
|
@@ -978,19 +1083,19 @@ export async function getAccountId() {
|
|
|
978
1083
|
});
|
|
979
1084
|
} catch (err) {
|
|
980
1085
|
// probably offline
|
|
1086
|
+
return;
|
|
981
1087
|
}
|
|
982
|
-
|
|
983
|
-
let accountId: string;
|
|
984
|
-
|
|
985
|
-
const responseJSON: {
|
|
1088
|
+
|
|
1089
|
+
let accountId: string | undefined;
|
|
1090
|
+
const responseJSON = (await response.json()) as {
|
|
986
1091
|
success: boolean;
|
|
987
1092
|
result: { id: string; account: { id: string; name: string } }[];
|
|
988
|
-
}
|
|
1093
|
+
};
|
|
989
1094
|
|
|
990
1095
|
if (responseJSON.success === true) {
|
|
991
1096
|
if (responseJSON.result.length === 1) {
|
|
992
1097
|
accountId = responseJSON.result[0].account.id;
|
|
993
|
-
} else {
|
|
1098
|
+
} else if (isInteractive) {
|
|
994
1099
|
accountId = await new Promise((resolve) => {
|
|
995
1100
|
const accounts = responseJSON.result.map((x) => x.account);
|
|
996
1101
|
const { unmount } = render(
|
|
@@ -1003,6 +1108,15 @@ export async function getAccountId() {
|
|
|
1003
1108
|
/>
|
|
1004
1109
|
);
|
|
1005
1110
|
});
|
|
1111
|
+
} else {
|
|
1112
|
+
throw new Error(
|
|
1113
|
+
"More than one account available but unable to select one in non-interactive mode.\n" +
|
|
1114
|
+
`Please set the appropriate \`account_id\` in your \`wrangler.toml\` file.\n` +
|
|
1115
|
+
`Available accounts are ("<name>" - "<id>"):\n` +
|
|
1116
|
+
responseJSON.result
|
|
1117
|
+
.map((x) => ` "${x.account.name}" - "${x.account.id}")`)
|
|
1118
|
+
.join("\n")
|
|
1119
|
+
);
|
|
1006
1120
|
}
|
|
1007
1121
|
}
|
|
1008
1122
|
return accountId;
|
|
@@ -1014,16 +1128,39 @@ type ChooseAccountItem = {
|
|
|
1014
1128
|
};
|
|
1015
1129
|
export function ChooseAccount(props: {
|
|
1016
1130
|
accounts: ChooseAccountItem[];
|
|
1017
|
-
onSelect: (item) => void;
|
|
1131
|
+
onSelect: (item: SelectInputItem<ChooseAccountItem>) => void;
|
|
1018
1132
|
}) {
|
|
1019
1133
|
return (
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1134
|
+
<>
|
|
1135
|
+
<Text bold>Select an account from below:</Text>
|
|
1136
|
+
<SelectInput
|
|
1137
|
+
items={props.accounts.map((item) => ({
|
|
1138
|
+
key: item.id,
|
|
1139
|
+
label: item.name,
|
|
1140
|
+
value: item,
|
|
1141
|
+
}))}
|
|
1142
|
+
onSelect={props.onSelect}
|
|
1143
|
+
/>
|
|
1144
|
+
</>
|
|
1028
1145
|
);
|
|
1029
1146
|
}
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Ensure that a user is logged in, and a valid account_id is available.
|
|
1150
|
+
*/
|
|
1151
|
+
export async function requireAuth(
|
|
1152
|
+
config: Config,
|
|
1153
|
+
isInteractive = true
|
|
1154
|
+
): Promise<string> {
|
|
1155
|
+
const loggedIn = await loginOrRefreshIfRequired(isInteractive);
|
|
1156
|
+
if (!loggedIn) {
|
|
1157
|
+
// didn't login, let's just quit
|
|
1158
|
+
throw new Error("Did not login, quitting...");
|
|
1159
|
+
}
|
|
1160
|
+
const accountId = config.account_id || (await getAccountId(isInteractive));
|
|
1161
|
+
if (!accountId) {
|
|
1162
|
+
throw new Error("No account id found, quitting...");
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
return accountId;
|
|
1166
|
+
}
|