wrangler 0.0.0-e2a26a0 → 0.0.0-e3278fa
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/Cloudflare_CA.pem +18 -0
- package/README.md +27 -14
- package/bin/wrangler.js +49 -5
- package/miniflare-dist/index.mjs +4886 -0
- package/package.json +51 -27
- package/pages/functions/buildPlugin.ts +105 -0
- package/pages/functions/buildWorker.ts +93 -4
- package/pages/functions/filepath-routing.test.ts +223 -36
- package/pages/functions/filepath-routing.ts +78 -120
- package/pages/functions/routes.ts +13 -17
- package/pages/functions/template-plugin.ts +147 -0
- package/pages/functions/template-worker.ts +24 -17
- package/src/__tests__/config-cache.test.ts +36 -0
- package/src/__tests__/configuration.test.ts +3408 -0
- package/src/__tests__/dev.test.tsx +977 -65
- package/src/__tests__/guess-worker-format.test.ts +85 -0
- package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
- package/src/__tests__/helpers/mock-account-id.ts +26 -8
- package/src/__tests__/helpers/mock-bin.ts +4 -1
- package/src/__tests__/helpers/mock-cfetch.ts +10 -6
- package/src/__tests__/helpers/mock-console.ts +39 -8
- package/src/__tests__/helpers/mock-dialogs.ts +67 -2
- package/src/__tests__/helpers/mock-http-server.ts +46 -0
- package/src/__tests__/helpers/mock-istty.ts +27 -0
- package/src/__tests__/helpers/mock-kv.ts +9 -16
- package/src/__tests__/helpers/mock-oauth-flow.ts +255 -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 +10 -0
- package/src/__tests__/helpers/run-wrangler.ts +5 -3
- package/src/__tests__/helpers/write-worker-source.ts +31 -0
- package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
- package/src/__tests__/https-options.test.ts +130 -0
- package/src/__tests__/index.test.ts +148 -435
- package/src/__tests__/init.test.ts +1637 -0
- package/src/__tests__/jest.setup.ts +45 -5
- package/src/__tests__/kv.test.ts +590 -248
- package/src/__tests__/logger.test.ts +115 -0
- package/src/__tests__/package-manager.test.ts +156 -9
- package/src/__tests__/pages.test.ts +754 -0
- package/src/__tests__/parse.test.ts +330 -0
- package/src/__tests__/publish.test.ts +5471 -939
- package/src/__tests__/r2.test.ts +233 -0
- package/src/__tests__/route.test.ts +45 -0
- package/src/__tests__/secret.test.ts +362 -91
- package/src/__tests__/tail.test.ts +690 -0
- package/src/__tests__/user.test.ts +131 -0
- package/src/__tests__/whoami.test.tsx +60 -7
- package/src/abort.d.ts +3 -0
- package/src/bundle.ts +197 -0
- package/src/cfetch/index.ts +27 -8
- package/src/cfetch/internal.ts +73 -32
- package/src/cli.ts +6 -2
- package/src/config/README.md +107 -0
- package/src/config/config.ts +234 -0
- package/src/config/diagnostics.ts +80 -0
- package/src/config/environment.ts +420 -0
- package/src/config/index.ts +225 -0
- package/src/config/validation-helpers.ts +584 -0
- package/src/config/validation.ts +1539 -0
- package/src/config-cache.ts +45 -0
- package/src/create-worker-preview.ts +185 -0
- package/src/create-worker-upload-form.ts +254 -0
- package/src/dev/dev-vars.ts +36 -0
- package/src/dev/dev.tsx +412 -0
- package/src/dev/local.tsx +322 -0
- package/src/dev/remote.tsx +237 -0
- package/src/dev/use-esbuild.ts +118 -0
- package/src/dialogs.tsx +64 -6
- package/src/durable.ts +102 -0
- package/src/entry.ts +322 -0
- package/src/environment-variables.ts +35 -0
- package/src/errors.ts +11 -0
- package/src/https-options.ts +123 -0
- package/src/index.tsx +1747 -1002
- package/src/inspect.ts +80 -14
- package/src/intl-polyfill.d.ts +139 -0
- package/src/{kv.tsx → kv.ts} +192 -68
- package/src/logger.ts +66 -0
- package/src/miniflare-cli/README.md +30 -0
- package/src/miniflare-cli/enum-keys.ts +17 -0
- package/src/miniflare-cli/index.ts +41 -0
- package/src/miniflare-cli/request-context.ts +40 -0
- package/src/module-collection.ts +226 -31
- package/src/open-in-browser.ts +19 -0
- package/src/package-manager.ts +79 -25
- package/src/pages.tsx +1131 -84
- package/src/parse.ts +202 -0
- package/src/proxy.ts +170 -62
- package/src/publish.ts +612 -306
- package/src/r2.ts +50 -0
- package/src/selfsigned.d.ts +29 -0
- package/src/sites.tsx +143 -57
- package/src/tail/filters.ts +279 -0
- package/src/tail/index.ts +299 -0
- package/src/tail/printing.ts +67 -0
- package/src/update-check.ts +19 -0
- package/src/user.tsx +341 -146
- package/src/whoami.tsx +5 -3
- package/src/{api/worker.ts → worker.ts} +65 -39
- package/src/zones.ts +73 -0
- package/templates/gitignore +171 -0
- package/templates/new-worker-scheduled.js +17 -0
- package/templates/new-worker-scheduled.ts +32 -0
- package/templates/new-worker.js +1 -1
- package/templates/new-worker.ts +17 -2
- package/templates/static-asset-facade.js +1 -5
- package/templates/tsconfig.json +105 -0
- package/wrangler-dist/cli.js +72295 -71006
- package/src/__tests__/helpers/mock-user.ts +0 -27
- package/src/__tests__/logout.test.ts +0 -50
- package/src/api/form_data.ts +0 -134
- package/src/api/preview.ts +0 -128
- package/src/config.ts +0 -641
- package/src/dev.tsx +0 -834
- package/src/tail.tsx +0 -73
- package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
- package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
- package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
- package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
- package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
- package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
- package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
- package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
- package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
- package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
- package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
- package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
- package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
- package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
- package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
- package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
- package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
- package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
- package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
- package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
- package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
- package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
- package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
- package/vendor/wrangler-mime/CHANGELOG.md +0 -289
- package/vendor/wrangler-mime/LICENSE +0 -21
- package/vendor/wrangler-mime/Mime.js +0 -97
- package/vendor/wrangler-mime/README.md +0 -187
- package/vendor/wrangler-mime/cli.js +0 -46
- package/vendor/wrangler-mime/index.js +0 -4
- package/vendor/wrangler-mime/lite.js +0 -4
- package/vendor/wrangler-mime/package.json +0 -52
- package/vendor/wrangler-mime/types/other.js +0 -1
- package/vendor/wrangler-mime/types/standard.js +0 -1
- package/wrangler-dist/cli.js.map +0 -7
package/src/user.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Based heavily on code from https://github.com/BitySA/oauth2-auth-code-pkce */
|
|
2
2
|
|
|
3
|
-
/*
|
|
3
|
+
/*
|
|
4
4
|
|
|
5
5
|
Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -207,24 +207,45 @@
|
|
|
207
207
|
|
|
208
208
|
import assert from "node:assert";
|
|
209
209
|
import { webcrypto as crypto } from "node:crypto";
|
|
210
|
-
import {
|
|
210
|
+
import { writeFileSync, mkdirSync, rmSync } from "node:fs";
|
|
211
211
|
import http from "node:http";
|
|
212
212
|
import os from "node:os";
|
|
213
213
|
import path from "node:path";
|
|
214
|
-
import process from "node:process";
|
|
215
214
|
import url from "node:url";
|
|
216
215
|
import { TextEncoder } from "node:util";
|
|
217
216
|
import TOML from "@iarna/toml";
|
|
217
|
+
import { HostURL } from "@webcontainer/env";
|
|
218
218
|
import { render, Text } from "ink";
|
|
219
219
|
import SelectInput from "ink-select-input";
|
|
220
220
|
import Table from "ink-table";
|
|
221
|
-
import open from "open";
|
|
222
221
|
import React from "react";
|
|
223
222
|
import { fetch } from "undici";
|
|
224
|
-
import {
|
|
223
|
+
import { getCloudflareApiBaseUrl } from "./cfetch";
|
|
224
|
+
import { purgeConfigCaches } from "./config-cache";
|
|
225
|
+
import { getEnvironmentVariableFactory } from "./environment-variables";
|
|
226
|
+
import { logger } from "./logger";
|
|
227
|
+
import openInBrowser from "./open-in-browser";
|
|
228
|
+
import { parseTOML, readFileSync } from "./parse";
|
|
229
|
+
import type { Item as SelectInputItem } from "ink-select-input/build/SelectInput";
|
|
225
230
|
import type { ParsedUrlQuery } from "node:querystring";
|
|
226
231
|
import type { Response } from "undici";
|
|
227
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Try to read the API token from the environment.
|
|
235
|
+
*/
|
|
236
|
+
export const getCloudflareAPITokenFromEnv = getEnvironmentVariableFactory({
|
|
237
|
+
variableName: "CLOUDFLARE_API_TOKEN",
|
|
238
|
+
deprecatedName: "CF_API_TOKEN",
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Try to read the account ID from the environment.
|
|
243
|
+
*/
|
|
244
|
+
const getCloudflareAccountIdFromEnv = getEnvironmentVariableFactory({
|
|
245
|
+
variableName: "CLOUDFLARE_ACCOUNT_ID",
|
|
246
|
+
deprecatedName: "CF_ACCOUNT_ID",
|
|
247
|
+
});
|
|
248
|
+
|
|
228
249
|
/**
|
|
229
250
|
* An implementation of rfc6749#section-4.1 and rfc7636.
|
|
230
251
|
*/
|
|
@@ -234,17 +255,45 @@ interface PKCECodes {
|
|
|
234
255
|
codeVerifier: string;
|
|
235
256
|
}
|
|
236
257
|
|
|
237
|
-
|
|
238
|
-
|
|
258
|
+
/**
|
|
259
|
+
* The module level state of the authentication flow.
|
|
260
|
+
*/
|
|
261
|
+
interface State extends AuthTokens {
|
|
239
262
|
authorizationCode?: string;
|
|
240
263
|
codeChallenge?: string;
|
|
241
264
|
codeVerifier?: string;
|
|
242
265
|
hasAuthCodeBeenExchangedForAccessToken?: boolean;
|
|
243
|
-
refreshToken?: RefreshToken; // persist
|
|
244
266
|
stateQueryParam?: string;
|
|
245
267
|
scopes?: Scope[];
|
|
246
268
|
}
|
|
247
269
|
|
|
270
|
+
/**
|
|
271
|
+
* The tokens related to authentication.
|
|
272
|
+
*/
|
|
273
|
+
interface AuthTokens {
|
|
274
|
+
accessToken?: AccessToken;
|
|
275
|
+
refreshToken?: RefreshToken;
|
|
276
|
+
/** @deprecated - this field was only provided by the deprecated `wrangler1 config` command. */
|
|
277
|
+
apiToken?: string;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* The path to the config file that holds user authentication data,
|
|
282
|
+
* relative to the user's home directory.
|
|
283
|
+
*/
|
|
284
|
+
export const USER_AUTH_CONFIG_FILE = ".wrangler/config/default.toml";
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* The data that may be read from the `USER_CONFIG_FILE`.
|
|
288
|
+
*/
|
|
289
|
+
export interface UserAuthConfig {
|
|
290
|
+
oauth_token?: string;
|
|
291
|
+
refresh_token?: string;
|
|
292
|
+
expiration_time?: string;
|
|
293
|
+
/** @deprecated - this field was only provided by the deprecated `wrangler1 config` command. */
|
|
294
|
+
api_token?: string;
|
|
295
|
+
}
|
|
296
|
+
|
|
248
297
|
interface RefreshToken {
|
|
249
298
|
value: string;
|
|
250
299
|
}
|
|
@@ -268,6 +317,8 @@ const Scopes = {
|
|
|
268
317
|
"workers_scripts:write":
|
|
269
318
|
"See and change Cloudflare Workers scripts, durable objects, subdomains, triggers, and tail data.",
|
|
270
319
|
"workers_tail:read": "See Cloudflare Workers tail and script data.",
|
|
320
|
+
"pages:write":
|
|
321
|
+
"See and change Cloudflare Pages projects, settings and deployments.",
|
|
271
322
|
"zone:read": "Grants read level access to account zone.",
|
|
272
323
|
} as const;
|
|
273
324
|
|
|
@@ -276,79 +327,113 @@ const Scopes = {
|
|
|
276
327
|
*
|
|
277
328
|
* "offline_access" is automatically included.
|
|
278
329
|
*/
|
|
279
|
-
type Scope = keyof typeof Scopes
|
|
330
|
+
type Scope = keyof typeof Scopes;
|
|
280
331
|
|
|
281
332
|
const ScopeKeys = Object.keys(Scopes) as Scope[];
|
|
282
333
|
|
|
283
334
|
export function validateScopeKeys(
|
|
284
335
|
scopes: string[]
|
|
285
336
|
): scopes is typeof ScopeKeys {
|
|
286
|
-
return scopes.every((scope) => Scopes
|
|
337
|
+
return scopes.every((scope) => scope in Scopes);
|
|
287
338
|
}
|
|
288
339
|
|
|
289
340
|
const CLIENT_ID = "54d11594-84e4-41aa-b438-e81b8fa78ee7";
|
|
290
341
|
const AUTH_URL = "https://dash.cloudflare.com/oauth2/auth";
|
|
291
342
|
const TOKEN_URL = "https://dash.cloudflare.com/oauth2/token";
|
|
292
|
-
const CALLBACK_URL = "http://localhost:8976/oauth/callback";
|
|
293
343
|
const REVOKE_URL = "https://dash.cloudflare.com/oauth2/revoke";
|
|
294
344
|
|
|
295
|
-
|
|
296
|
-
|
|
345
|
+
/**
|
|
346
|
+
* To allow OAuth callbacks in environments such as WebContainer we need to
|
|
347
|
+
* create a host URL which only resolves `localhost` to a WebContainer
|
|
348
|
+
* hostname if the process is running in a WebContainer. On local this will
|
|
349
|
+
* be a no-op and it leaves the URL unmodified.
|
|
350
|
+
*
|
|
351
|
+
* @see https://www.npmjs.com/package/@webcontainer/env
|
|
352
|
+
*/
|
|
353
|
+
const CALLBACK_URL = HostURL.parse("http://localhost:8976/oauth/callback").href;
|
|
354
|
+
|
|
355
|
+
let LocalState: State = getAuthTokens();
|
|
297
356
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
357
|
+
/**
|
|
358
|
+
* Compute the current auth tokens.
|
|
359
|
+
*/
|
|
360
|
+
function getAuthTokens(config?: UserAuthConfig): AuthTokens {
|
|
302
361
|
// get refreshToken/accessToken from fs if exists
|
|
303
362
|
try {
|
|
304
|
-
// if
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
363
|
+
// if the environment variable is available, use that
|
|
364
|
+
const apiTokenFromEnv = getCloudflareAPITokenFromEnv();
|
|
365
|
+
if (apiTokenFromEnv) {
|
|
366
|
+
return {
|
|
367
|
+
accessToken: {
|
|
368
|
+
value: apiTokenFromEnv,
|
|
369
|
+
expiry: "3021-12-31T23:59:59+00:00",
|
|
370
|
+
},
|
|
309
371
|
};
|
|
310
|
-
initialised = true;
|
|
311
|
-
return;
|
|
312
372
|
}
|
|
313
373
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
);
|
|
319
|
-
const { oauth_token, refresh_token, expiration_time } = toml as {
|
|
320
|
-
oauth_token: string;
|
|
321
|
-
refresh_token: string;
|
|
322
|
-
expiration_time: string;
|
|
323
|
-
};
|
|
374
|
+
// otherwise try loading from the user auth config file.
|
|
375
|
+
const { oauth_token, refresh_token, expiration_time, api_token } =
|
|
376
|
+
config || readAuthConfigFile();
|
|
377
|
+
|
|
324
378
|
if (oauth_token) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
379
|
+
return {
|
|
380
|
+
accessToken: {
|
|
381
|
+
value: oauth_token,
|
|
382
|
+
// If there is no `expiration_time` field then set it to an old date, to cause it to expire immediately.
|
|
383
|
+
expiry: expiration_time ?? "2000-01-01:00:00:00+00:00",
|
|
384
|
+
},
|
|
385
|
+
refreshToken: { value: refresh_token ?? "" },
|
|
386
|
+
};
|
|
387
|
+
} else if (api_token) {
|
|
388
|
+
return { apiToken: api_token };
|
|
389
|
+
} else {
|
|
390
|
+
return {};
|
|
329
391
|
}
|
|
330
|
-
} catch
|
|
331
|
-
|
|
332
|
-
// console.error(err);
|
|
392
|
+
} catch {
|
|
393
|
+
return {};
|
|
333
394
|
}
|
|
334
|
-
initialised = true;
|
|
335
395
|
}
|
|
336
396
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
397
|
+
/**
|
|
398
|
+
* Run the initialisation of the auth state, in the case that something changed.
|
|
399
|
+
*
|
|
400
|
+
* This runs automatically whenever `writeAuthConfigFile` is run, so generally
|
|
401
|
+
* you won't need to call it yourself.
|
|
402
|
+
*/
|
|
403
|
+
export function reinitialiseAuthTokens(): void;
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Reinitialise auth state from an in-memory config, skipping
|
|
407
|
+
* over the part where we write a file and then read it back into memory
|
|
408
|
+
*/
|
|
409
|
+
export function reinitialiseAuthTokens(config: UserAuthConfig): void;
|
|
410
|
+
|
|
411
|
+
export function reinitialiseAuthTokens(config?: UserAuthConfig): void {
|
|
412
|
+
LocalState = getAuthTokens(config);
|
|
344
413
|
}
|
|
345
414
|
|
|
346
415
|
export function getAPIToken(): string | undefined {
|
|
347
|
-
if (
|
|
348
|
-
|
|
416
|
+
if (LocalState.apiToken) {
|
|
417
|
+
logger.warn(
|
|
418
|
+
"It looks like you have used Wrangler 1's `config` command to login with an API token.\n" +
|
|
419
|
+
"This is no longer supported in the current version of Wrangler.\n" +
|
|
420
|
+
"If you wish to authenticate via an API token then please set the `CLOUDFLARE_API_TOKEN` environment variable."
|
|
421
|
+
);
|
|
349
422
|
}
|
|
350
|
-
|
|
351
|
-
|
|
423
|
+
|
|
424
|
+
const localAPIToken = getCloudflareAPITokenFromEnv();
|
|
425
|
+
|
|
426
|
+
if (
|
|
427
|
+
!process.stdout.isTTY &&
|
|
428
|
+
!localAPIToken &&
|
|
429
|
+
!LocalState.accessToken?.value
|
|
430
|
+
) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
"In a non-interactive environment, it's necessary to set a CLOUDFLARE_API_TOKEN environment variable for wrangler to work. Please go to https://developers.cloudflare.com/api/tokens/create/ for instructions on how to create an api token, and assign its value to CLOUDFLARE_API_TOKEN."
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return localAPIToken ?? LocalState.accessToken?.value;
|
|
352
437
|
}
|
|
353
438
|
|
|
354
439
|
interface AccessContext {
|
|
@@ -535,8 +620,8 @@ function isReturningFromAuthServer(query: ParsedUrlQuery): boolean {
|
|
|
535
620
|
|
|
536
621
|
const stateQueryParam = query.state;
|
|
537
622
|
if (stateQueryParam !== state.stateQueryParam) {
|
|
538
|
-
|
|
539
|
-
"
|
|
623
|
+
logger.warn(
|
|
624
|
+
"Received query string parameter doesn't match the one sent! Possible malicious activity somewhere."
|
|
540
625
|
);
|
|
541
626
|
throw new ErrorInvalidReturnedStateParam();
|
|
542
627
|
}
|
|
@@ -561,19 +646,31 @@ export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
|
|
|
561
646
|
`?response_type=code&` +
|
|
562
647
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
563
648
|
`redirect_uri=${encodeURIComponent(CALLBACK_URL)}&` +
|
|
564
|
-
|
|
649
|
+
// we add offline_access manually for every request
|
|
650
|
+
`scope=${encodeURIComponent([...scopes, "offline_access"].join(" "))}&` +
|
|
565
651
|
`state=${stateQueryParam}&` +
|
|
566
652
|
`code_challenge=${encodeURIComponent(codeChallenge)}&` +
|
|
567
653
|
`code_challenge_method=S256`
|
|
568
654
|
);
|
|
569
655
|
}
|
|
570
656
|
|
|
657
|
+
type TokenResponse =
|
|
658
|
+
| {
|
|
659
|
+
access_token: string;
|
|
660
|
+
expires_in: number;
|
|
661
|
+
refresh_token: string;
|
|
662
|
+
scope: string;
|
|
663
|
+
}
|
|
664
|
+
| {
|
|
665
|
+
error: string;
|
|
666
|
+
};
|
|
667
|
+
|
|
571
668
|
/**
|
|
572
669
|
* Refresh an access token from the remote service.
|
|
573
670
|
*/
|
|
574
671
|
async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
575
672
|
if (!LocalState.refreshToken) {
|
|
576
|
-
|
|
673
|
+
logger.warn("No refresh token is present.");
|
|
577
674
|
}
|
|
578
675
|
|
|
579
676
|
const body =
|
|
@@ -588,17 +685,35 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
588
685
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
589
686
|
},
|
|
590
687
|
});
|
|
688
|
+
|
|
591
689
|
if (response.status >= 400) {
|
|
592
|
-
|
|
690
|
+
let tokenExchangeResErr = undefined;
|
|
691
|
+
|
|
692
|
+
try {
|
|
693
|
+
tokenExchangeResErr = await response.text();
|
|
694
|
+
tokenExchangeResErr = JSON.parse(tokenExchangeResErr);
|
|
695
|
+
} catch (e) {
|
|
696
|
+
// If it can't parse to JSON ignore the error
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if (tokenExchangeResErr !== undefined) {
|
|
700
|
+
// We will throw the parsed error if it parsed correctly, otherwise we throw an unknown error.
|
|
701
|
+
throw typeof tokenExchangeResErr === "string"
|
|
702
|
+
? new Error(tokenExchangeResErr)
|
|
703
|
+
: tokenExchangeResErr;
|
|
704
|
+
} else {
|
|
705
|
+
throw new ErrorUnknown(
|
|
706
|
+
"Failed to parse Error from exchangeRefreshTokenForAccessToken"
|
|
707
|
+
);
|
|
708
|
+
}
|
|
593
709
|
} else {
|
|
594
710
|
try {
|
|
595
|
-
const json = await response.json();
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
};
|
|
711
|
+
const json = (await response.json()) as TokenResponse;
|
|
712
|
+
if ("error" in json) {
|
|
713
|
+
throw json.error;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const { access_token, expires_in, refresh_token, scope } = json;
|
|
602
717
|
let scopes: Scope[] = [];
|
|
603
718
|
|
|
604
719
|
const accessToken: AccessToken = {
|
|
@@ -626,20 +741,12 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
626
741
|
refreshToken: LocalState.refreshToken,
|
|
627
742
|
};
|
|
628
743
|
return accessContext;
|
|
629
|
-
} catch (
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
"Expired! Auth code or refresh token needs to be renewed."
|
|
635
|
-
);
|
|
636
|
-
// alert("Redirecting to auth server to obtain a new auth grant code.");
|
|
637
|
-
// TODO: return refreshAuthCodeOrRefreshToken();
|
|
638
|
-
break;
|
|
639
|
-
default:
|
|
640
|
-
break;
|
|
744
|
+
} catch (error) {
|
|
745
|
+
if (typeof error === "string") {
|
|
746
|
+
throw toErrorClass(error);
|
|
747
|
+
} else {
|
|
748
|
+
throw error;
|
|
641
749
|
}
|
|
642
|
-
throw toErrorClass(error);
|
|
643
750
|
}
|
|
644
751
|
}
|
|
645
752
|
}
|
|
@@ -651,9 +758,9 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
651
758
|
const { authorizationCode, codeVerifier = "" } = LocalState;
|
|
652
759
|
|
|
653
760
|
if (!codeVerifier) {
|
|
654
|
-
|
|
761
|
+
logger.warn("No code verifier is being sent.");
|
|
655
762
|
} else if (!authorizationCode) {
|
|
656
|
-
|
|
763
|
+
logger.warn("No authorization grant code is being passed.");
|
|
657
764
|
}
|
|
658
765
|
|
|
659
766
|
const body =
|
|
@@ -674,19 +781,17 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
674
781
|
const { error } = (await response.json()) as { error: string };
|
|
675
782
|
// .catch((_) => ({ error: "invalid_json" }));
|
|
676
783
|
if (error === "invalid_grant") {
|
|
677
|
-
|
|
784
|
+
logger.log("Expired! Auth code or refresh token needs to be renewed.");
|
|
678
785
|
// alert("Redirecting to auth server to obtain a new auth grant code.");
|
|
679
786
|
// TODO: return refreshAuthCodeOrRefreshToken();
|
|
680
787
|
}
|
|
681
788
|
throw toErrorClass(error);
|
|
682
789
|
}
|
|
683
|
-
const json = await response.json();
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
scope: string;
|
|
689
|
-
};
|
|
790
|
+
const json = (await response.json()) as TokenResponse;
|
|
791
|
+
if ("error" in json) {
|
|
792
|
+
throw new Error(json.error);
|
|
793
|
+
}
|
|
794
|
+
const { access_token, expires_in, refresh_token, scope } = json;
|
|
690
795
|
let scopes: Scope[] = [];
|
|
691
796
|
LocalState.hasAuthCodeBeenExchangedForAccessToken = true;
|
|
692
797
|
|
|
@@ -770,52 +875,73 @@ function generateRandomState(lengthOfState: number): string {
|
|
|
770
875
|
.join("");
|
|
771
876
|
}
|
|
772
877
|
|
|
773
|
-
|
|
774
|
-
|
|
878
|
+
/**
|
|
879
|
+
* Writes a a wrangler config file (auth credentials) to disk,
|
|
880
|
+
* and updates the user auth state with the new credentials.
|
|
881
|
+
*/
|
|
882
|
+
export function writeAuthConfigFile(config: UserAuthConfig) {
|
|
883
|
+
mkdirSync(path.join(os.homedir(), ".wrangler/config/"), {
|
|
775
884
|
recursive: true,
|
|
776
885
|
});
|
|
777
|
-
|
|
778
|
-
path.join(os.homedir(),
|
|
779
|
-
|
|
780
|
-
oauth_token = "${tokenData.token?.value || ""}"
|
|
781
|
-
refresh_token = "${tokenData.refreshToken?.value}"
|
|
782
|
-
expiration_time = "${tokenData.token?.expiry}"
|
|
783
|
-
`,
|
|
886
|
+
writeFileSync(
|
|
887
|
+
path.join(os.homedir(), USER_AUTH_CONFIG_FILE),
|
|
888
|
+
TOML.stringify(config as TOML.JsonMap),
|
|
784
889
|
{ encoding: "utf-8" }
|
|
785
890
|
);
|
|
891
|
+
|
|
892
|
+
reinitialiseAuthTokens();
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
export function readAuthConfigFile(): UserAuthConfig {
|
|
896
|
+
const toml = parseTOML(
|
|
897
|
+
readFileSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE))
|
|
898
|
+
);
|
|
899
|
+
return toml;
|
|
786
900
|
}
|
|
787
901
|
|
|
788
902
|
type LoginProps = {
|
|
789
903
|
scopes?: Scope[];
|
|
790
904
|
};
|
|
791
905
|
|
|
792
|
-
export async function loginOrRefreshIfRequired(
|
|
906
|
+
export async function loginOrRefreshIfRequired(
|
|
907
|
+
isInteractive = true
|
|
908
|
+
): Promise<boolean> {
|
|
793
909
|
// TODO: if there already is a token, then try refreshing
|
|
794
910
|
// TODO: ask permission before opening browser
|
|
795
|
-
if (!
|
|
796
|
-
//
|
|
797
|
-
|
|
911
|
+
if (!getAPIToken()) {
|
|
912
|
+
// Not logged in.
|
|
913
|
+
// If we are not interactive, we cannot ask the user to login
|
|
914
|
+
return isInteractive && (await login());
|
|
798
915
|
} else if (isAccessTokenExpired()) {
|
|
799
|
-
|
|
916
|
+
// We're logged in, but the refresh token seems to have expired,
|
|
917
|
+
// so let's try to refresh it
|
|
918
|
+
const didRefresh = await refreshToken();
|
|
919
|
+
if (didRefresh) {
|
|
920
|
+
// The token was refreshed, so we're done here
|
|
921
|
+
return true;
|
|
922
|
+
} else {
|
|
923
|
+
// If the refresh token isn't valid, then we ask the user to login again
|
|
924
|
+
return isInteractive && (await login());
|
|
925
|
+
}
|
|
800
926
|
} else {
|
|
801
927
|
return true;
|
|
802
928
|
}
|
|
803
929
|
}
|
|
804
930
|
|
|
805
931
|
export async function login(props?: LoginProps): Promise<boolean> {
|
|
932
|
+
logger.log("Attempting to login via OAuth...");
|
|
806
933
|
const urlToOpen = await getAuthURL(props?.scopes);
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
// console.log(`💁 Opened ${urlToOpen}`);
|
|
810
|
-
let server;
|
|
811
|
-
let loginTimeoutHandle;
|
|
934
|
+
let server: http.Server;
|
|
935
|
+
let loginTimeoutHandle: NodeJS.Timeout;
|
|
812
936
|
const timerPromise = new Promise<boolean>((resolve) => {
|
|
813
937
|
loginTimeoutHandle = setTimeout(() => {
|
|
814
|
-
|
|
938
|
+
logger.error(
|
|
939
|
+
"Timed out waiting for authorization code, please try again."
|
|
940
|
+
);
|
|
815
941
|
server.close();
|
|
816
942
|
clearTimeout(loginTimeoutHandle);
|
|
817
943
|
resolve(false);
|
|
818
|
-
}, 60000); // wait for
|
|
944
|
+
}, 60000); // wait for 60 seconds for the user to authorize
|
|
819
945
|
});
|
|
820
946
|
|
|
821
947
|
const loginPromise = new Promise<boolean>((resolve, reject) => {
|
|
@@ -845,9 +971,10 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
845
971
|
res.end(() => {
|
|
846
972
|
finish(false);
|
|
847
973
|
});
|
|
848
|
-
|
|
849
|
-
"Error: Consent denied. You must grant consent to Wrangler in order to login
|
|
850
|
-
|
|
974
|
+
logger.error(
|
|
975
|
+
"Error: Consent denied. You must grant consent to Wrangler in order to login.\n" +
|
|
976
|
+
"If you don't want to do this consider passing an API token via the `CLOUDFLARE_API_TOKEN` environment variable"
|
|
977
|
+
);
|
|
851
978
|
|
|
852
979
|
return;
|
|
853
980
|
} else {
|
|
@@ -860,8 +987,12 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
860
987
|
finish(false, new ErrorNoAuthCode());
|
|
861
988
|
return;
|
|
862
989
|
} else {
|
|
863
|
-
const
|
|
864
|
-
|
|
990
|
+
const exchange = await exchangeAuthCodeForAccessToken();
|
|
991
|
+
writeAuthConfigFile({
|
|
992
|
+
oauth_token: exchange.token?.value ?? "",
|
|
993
|
+
expiration_time: exchange.token?.expiry,
|
|
994
|
+
refresh_token: exchange.refreshToken?.value,
|
|
995
|
+
});
|
|
865
996
|
res.writeHead(307, {
|
|
866
997
|
Location:
|
|
867
998
|
"https://welcome.developers.workers.dev/wrangler-oauth-consent-granted",
|
|
@@ -869,9 +1000,9 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
869
1000
|
res.end(() => {
|
|
870
1001
|
finish(true);
|
|
871
1002
|
});
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
);
|
|
1003
|
+
logger.log(`Successfully logged in.`);
|
|
1004
|
+
|
|
1005
|
+
purgeConfigCaches();
|
|
875
1006
|
|
|
876
1007
|
return;
|
|
877
1008
|
}
|
|
@@ -882,36 +1013,59 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
882
1013
|
server.listen(8976);
|
|
883
1014
|
});
|
|
884
1015
|
|
|
1016
|
+
await openInBrowser(urlToOpen);
|
|
1017
|
+
|
|
885
1018
|
return Promise.race([timerPromise, loginPromise]);
|
|
886
1019
|
}
|
|
887
1020
|
|
|
888
1021
|
/**
|
|
889
1022
|
* Checks to see if the access token has expired.
|
|
890
1023
|
*/
|
|
891
|
-
|
|
892
|
-
throwIfNotInitialised();
|
|
1024
|
+
function isAccessTokenExpired(): boolean {
|
|
893
1025
|
const { accessToken } = LocalState;
|
|
894
1026
|
return Boolean(accessToken && new Date() >= new Date(accessToken.expiry));
|
|
895
1027
|
}
|
|
896
1028
|
|
|
897
|
-
|
|
898
|
-
throwIfNotInitialised();
|
|
1029
|
+
async function refreshToken(): Promise<boolean> {
|
|
899
1030
|
// refresh
|
|
900
1031
|
try {
|
|
901
|
-
const
|
|
902
|
-
|
|
1032
|
+
const {
|
|
1033
|
+
token: { value: oauth_token, expiry: expiration_time } = {
|
|
1034
|
+
value: "",
|
|
1035
|
+
expiry: "",
|
|
1036
|
+
},
|
|
1037
|
+
refreshToken: { value: refresh_token } = {},
|
|
1038
|
+
} = await exchangeRefreshTokenForAccessToken();
|
|
1039
|
+
writeAuthConfigFile({ oauth_token, expiration_time, refresh_token });
|
|
903
1040
|
return true;
|
|
904
1041
|
} catch (err) {
|
|
905
|
-
console.error(err);
|
|
906
1042
|
return false;
|
|
907
1043
|
}
|
|
908
1044
|
}
|
|
909
1045
|
|
|
910
1046
|
export async function logout(): Promise<void> {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1047
|
+
if (!LocalState.accessToken) {
|
|
1048
|
+
if (!LocalState.refreshToken) {
|
|
1049
|
+
logger.log("Not logged in, exiting...");
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
const body =
|
|
1054
|
+
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
1055
|
+
`token_type_hint=refresh_token&` +
|
|
1056
|
+
`token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
|
|
1057
|
+
|
|
1058
|
+
const response = await fetch(REVOKE_URL, {
|
|
1059
|
+
method: "POST",
|
|
1060
|
+
body,
|
|
1061
|
+
headers: {
|
|
1062
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1063
|
+
},
|
|
1064
|
+
});
|
|
1065
|
+
await response.text(); // blank text? would be nice if it was something meaningful
|
|
1066
|
+
logger.log(
|
|
1067
|
+
"💁 Wrangler is configured with an OAuth token. The token has been successfully revoked"
|
|
1068
|
+
);
|
|
915
1069
|
}
|
|
916
1070
|
const body =
|
|
917
1071
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
@@ -926,20 +1080,13 @@ export async function logout(): Promise<void> {
|
|
|
926
1080
|
},
|
|
927
1081
|
});
|
|
928
1082
|
await response.text(); // blank text? would be nice if it was something meaningful
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
);
|
|
932
|
-
// delete the file
|
|
933
|
-
await rm(path.join(os.homedir(), ".wrangler/config/default.toml"));
|
|
934
|
-
console.log(
|
|
935
|
-
`Removing ${os.homedir()}/.wrangler/config/default.toml.. success!`
|
|
936
|
-
);
|
|
1083
|
+
rmSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE));
|
|
1084
|
+
logger.log(`Successfully logged out.`);
|
|
937
1085
|
}
|
|
938
1086
|
|
|
939
|
-
export function listScopes(): void {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
const data = ScopeKeys.map((scope) => ({
|
|
1087
|
+
export function listScopes(message = "💁 Available scopes:"): void {
|
|
1088
|
+
logger.log(message);
|
|
1089
|
+
const data = ScopeKeys.map((scope: Scope) => ({
|
|
943
1090
|
Scope: scope,
|
|
944
1091
|
Description: Scopes[scope],
|
|
945
1092
|
}));
|
|
@@ -947,17 +1094,20 @@ export function listScopes(): void {
|
|
|
947
1094
|
// TODO: maybe a good idea to show usage here
|
|
948
1095
|
}
|
|
949
1096
|
|
|
950
|
-
export async function getAccountId(
|
|
1097
|
+
export async function getAccountId(
|
|
1098
|
+
isInteractive = true
|
|
1099
|
+
): Promise<string | undefined> {
|
|
951
1100
|
const apiToken = getAPIToken();
|
|
952
1101
|
if (!apiToken) return;
|
|
953
1102
|
|
|
954
|
-
|
|
955
|
-
|
|
1103
|
+
const accountIdFromEnv = getCloudflareAccountIdFromEnv();
|
|
1104
|
+
if (accountIdFromEnv) {
|
|
1105
|
+
return accountIdFromEnv;
|
|
956
1106
|
}
|
|
957
1107
|
|
|
958
1108
|
let response: Response;
|
|
959
1109
|
try {
|
|
960
|
-
response = await fetch(`${
|
|
1110
|
+
response = await fetch(`${getCloudflareApiBaseUrl()}/memberships`, {
|
|
961
1111
|
method: "GET",
|
|
962
1112
|
headers: {
|
|
963
1113
|
Authorization: "Bearer " + apiToken,
|
|
@@ -977,7 +1127,7 @@ export async function getAccountId() {
|
|
|
977
1127
|
if (responseJSON.success === true) {
|
|
978
1128
|
if (responseJSON.result.length === 1) {
|
|
979
1129
|
accountId = responseJSON.result[0].account.id;
|
|
980
|
-
} else {
|
|
1130
|
+
} else if (isInteractive) {
|
|
981
1131
|
accountId = await new Promise((resolve) => {
|
|
982
1132
|
const accounts = responseJSON.result.map((x) => x.account);
|
|
983
1133
|
const { unmount } = render(
|
|
@@ -990,7 +1140,21 @@ export async function getAccountId() {
|
|
|
990
1140
|
/>
|
|
991
1141
|
);
|
|
992
1142
|
});
|
|
1143
|
+
} else {
|
|
1144
|
+
throw new Error(
|
|
1145
|
+
"More than one account available but unable to select one in non-interactive mode.\n" +
|
|
1146
|
+
`Please set the appropriate \`account_id\` in your \`wrangler.toml\` file.\n` +
|
|
1147
|
+
`Available accounts are ("<name>" - "<id>"):\n` +
|
|
1148
|
+
responseJSON.result
|
|
1149
|
+
.map((x) => ` "${x.account.name}" - "${x.account.id}")`)
|
|
1150
|
+
.join("\n")
|
|
1151
|
+
);
|
|
993
1152
|
}
|
|
1153
|
+
} else {
|
|
1154
|
+
if (!isInteractive)
|
|
1155
|
+
throw new Error(
|
|
1156
|
+
`Failed to automatically retrieve account IDs for the logged in user. In a non-interactive environment, it is mandatory to specify an account ID, either by assigning its value to CLOUDFLARE_ACCOUNT_ID, or as \`account_id\` in your \`wrangler.toml\` file.`
|
|
1157
|
+
);
|
|
994
1158
|
}
|
|
995
1159
|
return accountId;
|
|
996
1160
|
}
|
|
@@ -1001,7 +1165,7 @@ type ChooseAccountItem = {
|
|
|
1001
1165
|
};
|
|
1002
1166
|
export function ChooseAccount(props: {
|
|
1003
1167
|
accounts: ChooseAccountItem[];
|
|
1004
|
-
onSelect: (item) => void;
|
|
1168
|
+
onSelect: (item: SelectInputItem<ChooseAccountItem>) => void;
|
|
1005
1169
|
}) {
|
|
1006
1170
|
return (
|
|
1007
1171
|
<>
|
|
@@ -1017,3 +1181,34 @@ export function ChooseAccount(props: {
|
|
|
1017
1181
|
</>
|
|
1018
1182
|
);
|
|
1019
1183
|
}
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* Ensure that a user is logged in, and a valid account_id is available.
|
|
1187
|
+
*/
|
|
1188
|
+
export async function requireAuth(config: {
|
|
1189
|
+
account_id?: string;
|
|
1190
|
+
}): Promise<string> {
|
|
1191
|
+
const isInteractive = process.stdin.isTTY;
|
|
1192
|
+
const loggedIn = await loginOrRefreshIfRequired(isInteractive);
|
|
1193
|
+
if (!loggedIn) {
|
|
1194
|
+
// didn't login, let's just quit
|
|
1195
|
+
throw new Error("Did not login, quitting...");
|
|
1196
|
+
}
|
|
1197
|
+
const accountId = config.account_id || (await getAccountId(isInteractive));
|
|
1198
|
+
if (!accountId) {
|
|
1199
|
+
throw new Error("No account id found, quitting...");
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
return accountId;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/**
|
|
1206
|
+
* Throw an error if there is no API token available.
|
|
1207
|
+
*/
|
|
1208
|
+
export function requireApiToken(): string {
|
|
1209
|
+
const authToken = getAPIToken();
|
|
1210
|
+
if (!authToken) {
|
|
1211
|
+
throw new Error("No API token found.");
|
|
1212
|
+
}
|
|
1213
|
+
return authToken;
|
|
1214
|
+
}
|