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