firebase-tools 10.1.3 → 10.2.1

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.
Files changed (61) hide show
  1. package/lib/api.js +1 -0
  2. package/lib/apiv2.js +4 -0
  3. package/lib/auth.js +62 -25
  4. package/lib/commands/auth-import.js +1 -1
  5. package/lib/commands/ext-configure.js +1 -0
  6. package/lib/commands/ext-install.js +1 -0
  7. package/lib/commands/ext-uninstall.js +1 -0
  8. package/lib/commands/ext-update.js +1 -0
  9. package/lib/commands/functions-config-clone.js +1 -1
  10. package/lib/commands/functions-secrets-access.js +17 -0
  11. package/lib/commands/functions-secrets-destroy.js +40 -0
  12. package/lib/commands/functions-secrets-get.js +21 -0
  13. package/lib/commands/functions-secrets-prune.js +50 -0
  14. package/lib/commands/functions-secrets-set.js +46 -0
  15. package/lib/commands/index.js +7 -3
  16. package/lib/commands/login.js +1 -1
  17. package/lib/deploy/functions/backend.js +9 -1
  18. package/lib/deploy/functions/ensure.js +112 -0
  19. package/lib/deploy/functions/ensureCloudBuildEnabled.js +0 -49
  20. package/lib/deploy/functions/prepare.js +12 -18
  21. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +1 -0
  22. package/lib/deploy/functions/runtimes/node/parseTriggers.js +12 -0
  23. package/lib/deploy/functions/validate.js +56 -1
  24. package/lib/deploy/hosting/client.js +9 -0
  25. package/lib/deploy/hosting/convertConfig.js +6 -0
  26. package/lib/deploy/hosting/index.js +5 -5
  27. package/lib/deploy/hosting/prepare.js +25 -25
  28. package/lib/deploy/hosting/release.js +21 -24
  29. package/lib/emulator/commandUtils.js +5 -1
  30. package/lib/emulator/controller.js +3 -1
  31. package/lib/emulator/downloadableEmulators.js +29 -12
  32. package/lib/emulator/emulatorLogger.js +7 -0
  33. package/lib/emulator/functionsEmulator.js +122 -80
  34. package/lib/emulator/functionsEmulatorRuntime.js +100 -83
  35. package/lib/emulator/functionsEmulatorShared.js +51 -1
  36. package/lib/emulator/functionsEmulatorShell.js +1 -2
  37. package/lib/emulator/functionsRuntimeWorker.js +1 -1
  38. package/lib/emulator/storage/apis/gcloud.js +2 -2
  39. package/lib/emulator/storage/files.js +8 -3
  40. package/lib/extensions/askUserForParam.js +1 -1
  41. package/lib/extensions/diagnose.js +56 -0
  42. package/lib/extensions/extensionsHelper.js +10 -17
  43. package/lib/extensions/listExtensions.js +2 -0
  44. package/lib/extensions/resolveSource.js +1 -53
  45. package/lib/extensions/secretsUtils.js +1 -1
  46. package/lib/extensions/updateHelper.js +0 -14
  47. package/lib/extensions/utils.js +4 -2
  48. package/lib/functions/env.js +5 -7
  49. package/lib/functions/secrets.js +112 -0
  50. package/lib/gcp/cloudfunctions.js +2 -2
  51. package/lib/gcp/secretManager.js +128 -46
  52. package/lib/gcp/storage.js +5 -3
  53. package/lib/hosting/functionsProxy.js +15 -5
  54. package/lib/previews.js +1 -1
  55. package/lib/responseToError.js +16 -7
  56. package/lib/serve/functions.js +2 -2
  57. package/lib/serve/hosting.js +1 -1
  58. package/lib/utils.js +6 -1
  59. package/npm-shrinkwrap.json +124 -45
  60. package/package.json +4 -4
  61. package/schema/firebase-config.json +27 -0
package/lib/api.js CHANGED
@@ -67,6 +67,7 @@ var _appendQueryData = function (path, data) {
67
67
  return path;
68
68
  };
69
69
  var api = {
70
+ authProxyOrigin: utils.envOverride("FIREBASE_AUTHPROXY_URL", "https://auth.firebase.tools"),
70
71
  clientId: utils.envOverride("FIREBASE_CLIENT_ID", "563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com"),
71
72
  clientSecret: utils.envOverride("FIREBASE_CLIENT_SECRET", "j9iVZfS8kkCEFUPaAeJV0sAi"),
72
73
  cloudbillingOrigin: utils.envOverride("FIREBASE_CLOUDBILLING_URL", "https://cloudbilling.googleapis.com"),
package/lib/apiv2.js CHANGED
@@ -254,10 +254,14 @@ class Client {
254
254
  body = JSON.parse(text);
255
255
  }
256
256
  catch (err) {
257
+ this.logResponse(res, text, options);
257
258
  throw new error_1.FirebaseError(`Unable to parse JSON: ${err}`);
258
259
  }
259
260
  }
260
261
  }
262
+ else if (options.responseType === "xml") {
263
+ body = (await res.text());
264
+ }
261
265
  else if (options.responseType === "stream") {
262
266
  body = res.body;
263
267
  }
package/lib/auth.js CHANGED
@@ -19,6 +19,10 @@ const logger_1 = require("./logger");
19
19
  const prompt_1 = require("./prompt");
20
20
  const scopes = require("./scopes");
21
21
  const defaultCredentials_1 = require("./defaultCredentials");
22
+ const uuid_1 = require("uuid");
23
+ const crypto_1 = require("crypto");
24
+ const cli_color_1 = require("cli-color");
25
+ const track_1 = require("./track");
22
26
  portfinder.basePort = 9005;
23
27
  function getGlobalDefaultAccount() {
24
28
  const user = configstore_1.configstore.get("user");
@@ -182,19 +186,23 @@ function getLoginUrl(callbackUrl, userHint) {
182
186
  login_hint: userHint,
183
187
  }));
184
188
  }
185
- async function getTokensFromAuthorizationCode(code, callbackUrl) {
189
+ async function getTokensFromAuthorizationCode(code, callbackUrl, verifier) {
186
190
  var _a, _b;
187
191
  let res;
192
+ const params = {
193
+ code: code,
194
+ client_id: api.clientId,
195
+ client_secret: api.clientSecret,
196
+ redirect_uri: callbackUrl,
197
+ grant_type: "authorization_code",
198
+ };
199
+ if (verifier) {
200
+ params["code_verifier"] = verifier;
201
+ }
188
202
  try {
189
203
  res = await api.request("POST", "/o/oauth2/token", {
190
204
  origin: api.authOrigin,
191
- form: {
192
- code: code,
193
- client_id: api.clientId,
194
- client_secret: api.clientSecret,
195
- redirect_uri: callbackUrl,
196
- grant_type: "authorization_code",
197
- },
205
+ form: params,
198
206
  });
199
207
  }
200
208
  catch (err) {
@@ -248,31 +256,58 @@ async function respondWithFile(req, res, statusCode, filename) {
248
256
  res.end(response);
249
257
  req.socket.destroy();
250
258
  }
251
- async function loginWithoutLocalhost(userHint) {
252
- const callbackUrl = getCallbackUrl();
253
- const authUrl = getLoginUrl(callbackUrl, userHint);
259
+ function urlsafeBase64(base64string) {
260
+ return base64string.replace(/\+/g, "-").replace(/=+$/, "").replace(/\//g, "_");
261
+ }
262
+ async function loginRemotely(userHint) {
263
+ var _a;
264
+ const authProxyClient = new apiv2.Client({
265
+ urlPrefix: api.authProxyOrigin,
266
+ auth: false,
267
+ });
268
+ const sessionId = (0, uuid_1.v4)();
269
+ const codeVerifier = (0, crypto_1.randomBytes)(32).toString("hex");
270
+ const codeChallenge = urlsafeBase64((0, crypto_1.createHash)("sha256").update(codeVerifier).digest("base64"));
271
+ const attestToken = (_a = (await authProxyClient.post("/attest", {
272
+ session_id: sessionId,
273
+ })).body) === null || _a === void 0 ? void 0 : _a.token;
274
+ const loginUrl = `${api.authProxyOrigin}/login?code_challenge=${codeChallenge}&session=${sessionId}&attest=${attestToken}`;
275
+ logger_1.logger.info();
276
+ logger_1.logger.info("To sign in to the Firebase CLI:");
254
277
  logger_1.logger.info();
255
- logger_1.logger.info("Visit this URL on any device to log in:");
256
- logger_1.logger.info(clc.bold.underline(authUrl));
278
+ logger_1.logger.info("1. Take note of your session ID:");
279
+ logger_1.logger.info();
280
+ logger_1.logger.info(` ${(0, cli_color_1.bold)(sessionId.substring(0, 5).toUpperCase())}`);
281
+ logger_1.logger.info();
282
+ logger_1.logger.info("2. Visit the URL below on any device and follow the instructions to get your code:");
283
+ logger_1.logger.info();
284
+ logger_1.logger.info(` ${loginUrl}`);
285
+ logger_1.logger.info();
286
+ logger_1.logger.info("3. Paste or enter the authorization code below once you have it:");
257
287
  logger_1.logger.info();
258
- open(authUrl);
259
288
  const code = await (0, prompt_1.promptOnce)({
260
289
  type: "input",
261
- name: "code",
262
- message: "Paste authorization code here:",
290
+ message: "Enter authorization code:",
263
291
  });
264
- const tokens = await getTokensFromAuthorizationCode(code, callbackUrl);
265
- return {
266
- user: jwt.decode(tokens.id_token),
267
- tokens: tokens,
268
- scopes: SCOPES,
269
- };
292
+ try {
293
+ const tokens = await getTokensFromAuthorizationCode(code, `${api.authProxyOrigin}/complete`, codeVerifier);
294
+ (0, track_1.track)("login", "google_remote");
295
+ return {
296
+ user: jwt.decode(tokens.id_token),
297
+ tokens: tokens,
298
+ scopes: SCOPES,
299
+ };
300
+ }
301
+ catch (e) {
302
+ throw new error_1.FirebaseError("Unable to authenticate using the provided code. Please try again.");
303
+ }
270
304
  }
271
305
  async function loginWithLocalhostGoogle(port, userHint) {
272
306
  const callbackUrl = getCallbackUrl(port);
273
307
  const authUrl = getLoginUrl(callbackUrl, userHint);
274
308
  const successTemplate = "../templates/loginSuccess.html";
275
309
  const tokens = await loginWithLocalhost(port, callbackUrl, authUrl, successTemplate, getTokensFromAuthorizationCode);
310
+ (0, track_1.track)("login", "google_localhost");
276
311
  return {
277
312
  user: jwt.decode(tokens.id_token),
278
313
  tokens: tokens,
@@ -283,7 +318,9 @@ async function loginWithLocalhostGitHub(port) {
283
318
  const callbackUrl = getCallbackUrl(port);
284
319
  const authUrl = getGithubLoginUrl(callbackUrl);
285
320
  const successTemplate = "../templates/loginSuccessGithub.html";
286
- return loginWithLocalhost(port, callbackUrl, authUrl, successTemplate, getGithubTokensFromAuthorizationCode);
321
+ const tokens = await loginWithLocalhost(port, callbackUrl, authUrl, successTemplate, getGithubTokensFromAuthorizationCode);
322
+ (0, track_1.track)("login", "google_localhost");
323
+ return tokens;
287
324
  }
288
325
  async function loginWithLocalhost(port, callbackUrl, authUrl, successTemplate, getTokens) {
289
326
  return new Promise((resolve, reject) => {
@@ -331,10 +368,10 @@ async function loginGoogle(localhost, userHint) {
331
368
  return await loginWithLocalhostGoogle(port, userHint);
332
369
  }
333
370
  catch (_a) {
334
- return await loginWithoutLocalhost(userHint);
371
+ return await loginRemotely(userHint);
335
372
  }
336
373
  }
337
- return await loginWithoutLocalhost(userHint);
374
+ return await loginRemotely(userHint);
338
375
  }
339
376
  exports.loginGoogle = loginGoogle;
340
377
  async function loginGithub() {
@@ -93,7 +93,7 @@ module.exports = new command_1.Command("auth:import [dataFile]")
93
93
  if (err) {
94
94
  throw new error_1.FirebaseError(`Validation Error: ${err}`);
95
95
  }
96
- currentBatch.push(user);
96
+ currentBatch.push(value);
97
97
  if (currentBatch.length === MAX_BATCH_SIZE) {
98
98
  batches.push(currentBatch);
99
99
  currentBatch = [];
@@ -27,6 +27,7 @@ exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
27
27
  "firebaseextensions.instances.get",
28
28
  ])
29
29
  .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
30
+ .before(extensionsHelper_1.diagnoseAndFixProject)
30
31
  .action(async (instanceId, options) => {
31
32
  const spinner = ora(`Configuring ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...`);
32
33
  try {
@@ -201,6 +201,7 @@ exports.default = new command_1.Command("ext:install [extensionName]")
201
201
  .before(requirePermissions_1.requirePermissions, ["firebaseextensions.instances.create"])
202
202
  .before(extensionsHelper_1.ensureExtensionsApiEnabled)
203
203
  .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
204
+ .before(extensionsHelper_1.diagnoseAndFixProject)
204
205
  .action(async (extensionName, options) => {
205
206
  const projectId = (0, projectUtils_1.needProjectId)(options);
206
207
  const paramsEnvPath = options.params;
@@ -33,6 +33,7 @@ exports.default = new command_1.Command("ext:uninstall <extensionInstanceId>")
33
33
  .before(requirePermissions_1.requirePermissions, ["firebaseextensions.instances.delete"])
34
34
  .before(extensionsHelper_1.ensureExtensionsApiEnabled)
35
35
  .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
36
+ .before(extensionsHelper_1.diagnoseAndFixProject)
36
37
  .action(async (instanceId, options) => {
37
38
  const projectId = (0, projectUtils_1.needProjectId)(options);
38
39
  let instance;
@@ -44,6 +44,7 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
44
44
  ])
45
45
  .before(extensionsHelper_1.ensureExtensionsApiEnabled)
46
46
  .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
47
+ .before(extensionsHelper_1.diagnoseAndFixProject)
47
48
  .withForce()
48
49
  .option("--params <paramsFile>", "name of params variables file with .env format.")
49
50
  .action(async (instanceId, updateSource, options) => {
@@ -38,7 +38,7 @@ exports.default = new command_1.Command("functions:config:clone")
38
38
  else if (options.only && options.except) {
39
39
  throw new error_1.FirebaseError("Cannot use both --only and --except at the same time.");
40
40
  }
41
- let only = [];
41
+ let only;
42
42
  let except = [];
43
43
  if (options.only) {
44
44
  only = options.only.split(",");
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const command_1 = require("../command");
4
+ const logger_1 = require("../logger");
5
+ const projectUtils_1 = require("../projectUtils");
6
+ const secretManager_1 = require("../gcp/secretManager");
7
+ exports.default = new command_1.Command("functions:secrets:access <KEY>[@version]")
8
+ .description("Access secret value given secret and its version. Defaults to accessing the latest version.")
9
+ .action(async (key, options) => {
10
+ const projectId = (0, projectUtils_1.needProjectId)(options);
11
+ let [name, version] = key.split("@");
12
+ if (!version) {
13
+ version = "latest";
14
+ }
15
+ const value = await (0, secretManager_1.accessSecretVersion)(projectId, name, version);
16
+ logger_1.logger.info(value);
17
+ });
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const command_1 = require("../command");
4
+ const logger_1 = require("../logger");
5
+ const projectUtils_1 = require("../projectUtils");
6
+ const secretManager_1 = require("../gcp/secretManager");
7
+ const prompt_1 = require("../prompt");
8
+ const secrets = require("../functions/secrets");
9
+ exports.default = new command_1.Command("functions:secrets:destroy <KEY>[@version]")
10
+ .description("Destroy a secret. Defaults to destroying the latest version.")
11
+ .withForce("Destroys a secret without confirmation.")
12
+ .action(async (key, options) => {
13
+ const projectId = (0, projectUtils_1.needProjectId)(options);
14
+ let [name, version] = key.split("@");
15
+ if (!version) {
16
+ version = "latest";
17
+ }
18
+ const sv = await (0, secretManager_1.getSecretVersion)(projectId, name, version);
19
+ if (!options.force) {
20
+ const confirm = await (0, prompt_1.promptOnce)({
21
+ name: "destroy",
22
+ type: "confirm",
23
+ default: true,
24
+ message: `Are you sure you want to destroy ${sv.secret.name}@${sv.versionId}`,
25
+ }, options);
26
+ if (!confirm) {
27
+ return;
28
+ }
29
+ }
30
+ await (0, secretManager_1.destroySecretVersion)(projectId, name, version);
31
+ logger_1.logger.info(`Destroyed secret version ${name}@${sv.versionId}`);
32
+ const secret = await (0, secretManager_1.getSecret)(projectId, name);
33
+ if (secrets.isFirebaseManaged(secret)) {
34
+ const versions = await (0, secretManager_1.listSecretVersions)(projectId, name);
35
+ if (versions.filter((v) => v.state === "ENABLED").length === 0) {
36
+ logger_1.logger.info(`No active secret versions left. Destroying secret ${name}`);
37
+ await (0, secretManager_1.deleteSecret)(projectId, name);
38
+ }
39
+ }
40
+ });
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const Table = require("cli-table");
4
+ const command_1 = require("../command");
5
+ const logger_1 = require("../logger");
6
+ const projectUtils_1 = require("../projectUtils");
7
+ const secretManager_1 = require("../gcp/secretManager");
8
+ exports.default = new command_1.Command("functions:secrets:get <KEY>")
9
+ .description("Get metadata for secret and its versions")
10
+ .action(async (key, options) => {
11
+ const projectId = (0, projectUtils_1.needProjectId)(options);
12
+ const versions = await (0, secretManager_1.listSecretVersions)(projectId, key);
13
+ const table = new Table({
14
+ head: ["Version", "State"],
15
+ style: { head: ["yellow"] },
16
+ });
17
+ for (const version of versions) {
18
+ table.push([version.versionId, version.state]);
19
+ }
20
+ logger_1.logger.info(table.toString());
21
+ });
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const backend = require("../deploy/functions/backend");
4
+ const command_1 = require("../command");
5
+ const projectUtils_1 = require("../projectUtils");
6
+ const secrets_1 = require("../functions/secrets");
7
+ const requirePermissions_1 = require("../requirePermissions");
8
+ const deploymentTool_1 = require("../deploymentTool");
9
+ const utils_1 = require("../utils");
10
+ const prompt_1 = require("../prompt");
11
+ const secretManager_1 = require("../gcp/secretManager");
12
+ exports.default = new command_1.Command("functions:secrets:prune")
13
+ .description("Destroys unused secrets")
14
+ .before(requirePermissions_1.requirePermissions, [
15
+ "cloudfunctions.functions.list",
16
+ "secretmanager.secrets.list",
17
+ "secretmanager.versions.list",
18
+ "secretmanager.versions.destroy",
19
+ ])
20
+ .action(async (options) => {
21
+ const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
22
+ const projectId = (0, projectUtils_1.needProjectId)(options);
23
+ (0, utils_1.logBullet)("Loading secrets...");
24
+ const haveBackend = await backend.existingBackend({ projectId });
25
+ const haveEndpoints = backend
26
+ .allEndpoints(haveBackend)
27
+ .filter((e) => (0, deploymentTool_1.isFirebaseManaged)(e.labels || []));
28
+ const pruned = await (0, secrets_1.pruneSecrets)({ projectNumber, projectId }, haveEndpoints);
29
+ if (pruned.length === 0) {
30
+ (0, utils_1.logBullet)("All secrets are in use. Nothing to prune today.");
31
+ return;
32
+ }
33
+ (0, utils_1.logBullet)(`Found ${pruned.length} unused active secret versions:\n\t` +
34
+ pruned.map((sv) => `${sv.secret}@${sv.version}`).join("\n\t"));
35
+ const confirm = await (0, prompt_1.promptOnce)({
36
+ name: "destroy",
37
+ type: "confirm",
38
+ default: true,
39
+ message: `Do you want to destroy unused secret versions?`,
40
+ }, options);
41
+ if (!confirm) {
42
+ (0, utils_1.logBullet)("Run the following commands to destroy each unused secret version:\n\t" +
43
+ pruned
44
+ .map((sv) => `firebase functions:secrets:destroy ${sv.secret}@${sv.version}`)
45
+ .join("\n\t"));
46
+ return;
47
+ }
48
+ await Promise.all(pruned.map((sv) => (0, secretManager_1.destroySecretVersion)(projectId, sv.secret, sv.version)));
49
+ (0, utils_1.logSuccess)("Destroyed all unused secrets!");
50
+ });
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tty = require("tty");
4
+ const fs = require("fs");
5
+ const clc = require("cli-color");
6
+ const secrets_1 = require("../functions/secrets");
7
+ const command_1 = require("../command");
8
+ const requirePermissions_1 = require("../requirePermissions");
9
+ const prompt_1 = require("../prompt");
10
+ const utils_1 = require("../utils");
11
+ const projectUtils_1 = require("../projectUtils");
12
+ const secretManager_1 = require("../gcp/secretManager");
13
+ exports.default = new command_1.Command("functions:secrets:set <KEY>")
14
+ .description("Create or update a secret for use in Cloud Functions for Firebase")
15
+ .withForce("Does not ensure input keys are valid or upgrade existing secrets to have Firebase manage them.")
16
+ .before(requirePermissions_1.requirePermissions, [
17
+ "secretmanager.secrets.create",
18
+ "secretmanager.secrets.get",
19
+ "secretmanager.secrets.update",
20
+ "secretmanager.versions.add",
21
+ ])
22
+ .option("--data-file <dataFile>", 'File path from which to read secret data. Set to "-" to read the secret data from stdin.')
23
+ .action(async (unvalidatedKey, options) => {
24
+ const projectId = (0, projectUtils_1.needProjectId)(options);
25
+ const key = await (0, secrets_1.ensureValidKey)(unvalidatedKey, options);
26
+ const secret = await (0, secrets_1.ensureSecret)(projectId, key, options);
27
+ let secretValue;
28
+ if ((!options.dataFile || options.dataFile === "-") && tty.isatty(0)) {
29
+ secretValue = await (0, prompt_1.promptOnce)({
30
+ name: key,
31
+ type: "password",
32
+ message: `Enter a value for ${key}`,
33
+ });
34
+ }
35
+ else {
36
+ let dataFile = 0;
37
+ if (options.dataFile && options.dataFile !== "-") {
38
+ dataFile = options.dataFile;
39
+ }
40
+ secretValue = fs.readFileSync(dataFile, "utf-8");
41
+ }
42
+ const secretVersion = await (0, secretManager_1.addVersion)(projectId, key, secretValue);
43
+ (0, utils_1.logSuccess)(`Created a new secret version ${(0, secretManager_1.toSecretVersionResourceName)(secretVersion)}`);
44
+ (0, utils_1.logBullet)("Please deploy your functions for the change to take effect by running:\n\t" +
45
+ clc.bold("firebase deploy --only functions"));
46
+ });
@@ -91,9 +91,7 @@ module.exports = function (client) {
91
91
  client.functions = {};
92
92
  client.functions.config = {};
93
93
  client.functions.config.clone = loadCommand("functions-config-clone");
94
- if (previews.dotenv) {
95
- client.functions.config.export = loadCommand("functions-config-export");
96
- }
94
+ client.functions.config.export = loadCommand("functions-config-export");
97
95
  client.functions.config.get = loadCommand("functions-config-get");
98
96
  client.functions.config.set = loadCommand("functions-config-set");
99
97
  client.functions.config.unset = loadCommand("functions-config-unset");
@@ -104,6 +102,12 @@ module.exports = function (client) {
104
102
  if (previews.deletegcfartifacts) {
105
103
  client.functions.deletegcfartifacts = loadCommand("functions-deletegcfartifacts");
106
104
  }
105
+ client.functions.secrets = {};
106
+ client.functions.secrets.access = loadCommand("functions-secrets-access");
107
+ client.functions.secrets.destroy = loadCommand("functions-secrets-destroy");
108
+ client.functions.secrets.get = loadCommand("functions-secrets-get");
109
+ client.functions.secrets.prune = loadCommand("functions-secrets-prune");
110
+ client.functions.secrets.set = loadCommand("functions-secrets-set");
107
111
  client.help = loadCommand("help");
108
112
  client.hosting = {};
109
113
  client.hosting.channel = {};
@@ -12,7 +12,7 @@ const auth = require("../auth");
12
12
  const utils_1 = require("../utils");
13
13
  module.exports = new command_1.Command("login")
14
14
  .description("log the CLI into Firebase")
15
- .option("--no-localhost", "copy and paste a code instead of starting a local server for authentication")
15
+ .option("--no-localhost", "login from a device without an accessible localhost")
16
16
  .option("--reauth", "force reauthentication even if already logged in")
17
17
  .action(async (options) => {
18
18
  if (options.nonInteractive) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.of = exports.empty = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isHttpsTriggered = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_MEMORY_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.memoryOptionDisplayName = exports.endpointTriggerType = void 0;
3
+ exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.findEndpoint = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.of = exports.empty = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isHttpsTriggered = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_MEMORY_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.memoryOptionDisplayName = exports.endpointTriggerType = void 0;
4
4
  const gcf = require("../../gcp/cloudfunctions");
5
5
  const gcfV2 = require("../../gcp/cloudfunctionsv2");
6
6
  const utils = require("../../utils");
@@ -186,6 +186,14 @@ function someEndpoint(backend, predicate) {
186
186
  return false;
187
187
  }
188
188
  exports.someEndpoint = someEndpoint;
189
+ function findEndpoint(backend, predicate) {
190
+ for (const endpoints of Object.values(backend.endpoints)) {
191
+ const endpoint = Object.values(endpoints).find(predicate);
192
+ if (endpoint)
193
+ return endpoint;
194
+ }
195
+ }
196
+ exports.findEndpoint = findEndpoint;
189
197
  function matchingBackend(backend, predicate) {
190
198
  const filtered = Object.assign({}, empty());
191
199
  for (const endpoint of allEndpoints(backend)) {
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.secretAccess = exports.maybeEnableAR = exports.cloudBuildEnabled = exports.defaultServiceAccount = void 0;
4
+ const clc = require("cli-color");
5
+ const ensureApiEnabled_1 = require("../../ensureApiEnabled");
6
+ const error_1 = require("../../error");
7
+ const utils_1 = require("../../utils");
8
+ const secretManager_1 = require("../../gcp/secretManager");
9
+ const previews_1 = require("../../previews");
10
+ const projects_1 = require("../../management/projects");
11
+ const functional_1 = require("../../functional");
12
+ const track = require("../../track");
13
+ const backend = require("./backend");
14
+ const ensureApiEnabled = require("../../ensureApiEnabled");
15
+ const FAQ_URL = "https://firebase.google.com/support/faq#functions-runtime";
16
+ const CLOUD_BUILD_API = "cloudbuild.googleapis.com";
17
+ async function defaultServiceAccount(e) {
18
+ const metadata = await (0, projects_1.getFirebaseProject)(e.project);
19
+ if (e.platform === "gcfv1") {
20
+ return `${metadata.projectId}@appspot.gserviceaccount.com`;
21
+ }
22
+ else if (e.platform === "gcfv2") {
23
+ return `${metadata.projectNumber}-compute@developer.gserviceaccount.com`;
24
+ }
25
+ (0, functional_1.assertExhaustive)(e.platform);
26
+ }
27
+ exports.defaultServiceAccount = defaultServiceAccount;
28
+ function nodeBillingError(projectId) {
29
+ track("functions_runtime_notices", "nodejs10_billing_error");
30
+ return new error_1.FirebaseError(`Cloud Functions deployment requires the pay-as-you-go (Blaze) billing plan. To upgrade your project, visit the following URL:
31
+
32
+ https://console.firebase.google.com/project/${projectId}/usage/details
33
+
34
+ For additional information about this requirement, see Firebase FAQs:
35
+
36
+ ${FAQ_URL}`, { exit: 1 });
37
+ }
38
+ function nodePermissionError(projectId) {
39
+ track("functions_runtime_notices", "nodejs10_permission_error");
40
+ return new error_1.FirebaseError(`Cloud Functions deployment requires the Cloud Build API to be enabled. The current credentials do not have permission to enable APIs for project ${clc.bold(projectId)}.
41
+
42
+ Please ask a project owner to visit the following URL to enable Cloud Build:
43
+
44
+ https://console.cloud.google.com/apis/library/cloudbuild.googleapis.com?project=${projectId}
45
+
46
+ For additional information about this requirement, see Firebase FAQs:
47
+ ${FAQ_URL}
48
+ `);
49
+ }
50
+ function isPermissionError(e) {
51
+ var _a, _b, _c;
52
+ return ((_c = (_b = (_a = e.context) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.status) === "PERMISSION_DENIED";
53
+ }
54
+ async function cloudBuildEnabled(projectId) {
55
+ try {
56
+ await (0, ensureApiEnabled_1.ensure)(projectId, CLOUD_BUILD_API, "functions");
57
+ }
58
+ catch (e) {
59
+ if ((0, error_1.isBillingError)(e)) {
60
+ throw nodeBillingError(projectId);
61
+ }
62
+ else if (isPermissionError(e)) {
63
+ throw nodePermissionError(projectId);
64
+ }
65
+ throw e;
66
+ }
67
+ }
68
+ exports.cloudBuildEnabled = cloudBuildEnabled;
69
+ async function maybeEnableAR(projectId) {
70
+ if (!previews_1.previews.artifactregistry) {
71
+ return ensureApiEnabled.check(projectId, "artifactregistry.googleapis.com", "functions", true);
72
+ }
73
+ await ensureApiEnabled.ensure(projectId, "artifactregistry.googleapis.com", "functions");
74
+ return true;
75
+ }
76
+ exports.maybeEnableAR = maybeEnableAR;
77
+ async function secretsToServiceAccounts(b) {
78
+ const secretsToSa = {};
79
+ for (const e of backend.allEndpoints(b)) {
80
+ const sa = e.serviceAccountEmail || (await module.exports.defaultServiceAccount(e));
81
+ for (const s of e.secretEnvironmentVariables || []) {
82
+ const serviceAccounts = secretsToSa[s.secret] || new Set();
83
+ serviceAccounts.add(sa);
84
+ secretsToSa[s.secret] = serviceAccounts;
85
+ }
86
+ }
87
+ return secretsToSa;
88
+ }
89
+ async function secretAccess(projectId, wantBackend, haveBackend) {
90
+ var _a, _b;
91
+ const ensureAccess = async (secret, serviceAccounts) => {
92
+ (0, utils_1.logLabeledBullet)("functions", `ensuring ${clc.bold(serviceAccounts.join(", "))} access to secret ${clc.bold(secret)}.`);
93
+ await (0, secretManager_1.ensureServiceAgentRole)({ name: secret, projectId }, serviceAccounts, "roles/secretmanager.secretAccessor");
94
+ (0, utils_1.logLabeledSuccess)("functions", `ensured ${clc.bold(serviceAccounts.join(", "))} access to ${clc.bold(secret)}.`);
95
+ };
96
+ const wantSecrets = await secretsToServiceAccounts(wantBackend);
97
+ const haveSecrets = await secretsToServiceAccounts(haveBackend);
98
+ for (const [secret, serviceAccounts] of Object.entries(haveSecrets)) {
99
+ for (const serviceAccount of serviceAccounts) {
100
+ (_a = wantSecrets[secret]) === null || _a === void 0 ? void 0 : _a.delete(serviceAccount);
101
+ }
102
+ if (((_b = wantSecrets[secret]) === null || _b === void 0 ? void 0 : _b.size) == 0) {
103
+ delete wantSecrets[secret];
104
+ }
105
+ }
106
+ const ensure = [];
107
+ for (const [secret, serviceAccounts] of Object.entries(wantSecrets)) {
108
+ ensure.push(ensureAccess(secret, Array.from(serviceAccounts)));
109
+ }
110
+ await Promise.all(ensure);
111
+ }
112
+ exports.secretAccess = secretAccess;
@@ -1,50 +1 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ensureCloudBuildEnabled = void 0;
4
- const cli_color_1 = require("cli-color");
5
- const track = require("../../track");
6
- const ensureApiEnabled_1 = require("../../ensureApiEnabled");
7
- const error_1 = require("../../error");
8
- const FAQ_URL = "https://firebase.google.com/support/faq#functions-runtime";
9
- const CLOUD_BUILD_API = "cloudbuild.googleapis.com";
10
- function nodeBillingError(projectId) {
11
- track("functions_runtime_notices", "nodejs10_billing_error");
12
- return new error_1.FirebaseError(`Cloud Functions deployment requires the pay-as-you-go (Blaze) billing plan. To upgrade your project, visit the following URL:
13
-
14
- https://console.firebase.google.com/project/${projectId}/usage/details
15
-
16
- For additional information about this requirement, see Firebase FAQs:
17
-
18
- ${FAQ_URL}`, { exit: 1 });
19
- }
20
- function nodePermissionError(projectId) {
21
- track("functions_runtime_notices", "nodejs10_permission_error");
22
- return new error_1.FirebaseError(`Cloud Functions deployment requires the Cloud Build API to be enabled. The current credentials do not have permission to enable APIs for project ${(0, cli_color_1.bold)(projectId)}.
23
-
24
- Please ask a project owner to visit the following URL to enable Cloud Build:
25
-
26
- https://console.cloud.google.com/apis/library/cloudbuild.googleapis.com?project=${projectId}
27
-
28
- For additional information about this requirement, see Firebase FAQs:
29
- ${FAQ_URL}
30
- `);
31
- }
32
- function isPermissionError(e) {
33
- var _a, _b, _c;
34
- return ((_c = (_b = (_a = e.context) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.status) === "PERMISSION_DENIED";
35
- }
36
- async function ensureCloudBuildEnabled(projectId) {
37
- try {
38
- await (0, ensureApiEnabled_1.ensure)(projectId, CLOUD_BUILD_API, "functions");
39
- }
40
- catch (e) {
41
- if ((0, error_1.isBillingError)(e)) {
42
- throw nodeBillingError(projectId);
43
- }
44
- else if (isPermissionError(e)) {
45
- throw nodePermissionError(projectId);
46
- }
47
- throw e;
48
- }
49
- }
50
- exports.ensureCloudBuildEnabled = ensureCloudBuildEnabled;