firebase-tools 9.19.0 → 9.23.0
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/CHANGELOG.md +1 -3
- package/lib/api.js +3 -0
- package/lib/apiv2.js +7 -4
- package/lib/commands/crashlytics-symbols-upload.js +146 -0
- package/lib/commands/deploy.js +9 -1
- package/lib/commands/ext-configure.js +3 -1
- package/lib/commands/ext-dev-deprecate.js +63 -0
- package/lib/commands/ext-dev-undeprecate.js +56 -0
- package/lib/commands/ext-dev-unpublish.js +10 -3
- package/lib/commands/ext-export.js +44 -0
- package/lib/commands/ext-install.js +24 -3
- package/lib/commands/ext-uninstall.js +6 -0
- package/lib/commands/ext-update.js +10 -3
- package/lib/commands/functions-config-export.js +115 -0
- package/lib/commands/functions-delete.js +47 -25
- package/lib/commands/functions-list.js +12 -12
- package/lib/commands/index.js +9 -0
- package/lib/commands/init.js +3 -0
- package/lib/config.js +3 -2
- package/lib/deploy/extensions/args.js +2 -0
- package/lib/deploy/extensions/deploy.js +49 -0
- package/lib/deploy/extensions/deploymentSummary.js +52 -0
- package/lib/deploy/extensions/errors.js +31 -0
- package/lib/deploy/extensions/index.js +8 -0
- package/lib/deploy/extensions/params.js +39 -0
- package/lib/deploy/extensions/planner.js +94 -0
- package/lib/deploy/extensions/prepare.js +111 -0
- package/lib/deploy/extensions/release.js +43 -0
- package/lib/deploy/extensions/secrets.js +150 -0
- package/lib/deploy/extensions/tasks.js +98 -0
- package/lib/deploy/extensions/validate.js +17 -0
- package/lib/deploy/functions/backend.js +93 -115
- package/lib/deploy/functions/checkIam.js +8 -8
- package/lib/deploy/functions/containerCleaner.js +71 -14
- package/lib/deploy/functions/deploy.js +4 -10
- package/lib/deploy/functions/functionsDeployHelper.js +3 -68
- package/lib/deploy/functions/prepare.js +63 -27
- package/lib/deploy/functions/pricing.js +17 -17
- package/lib/deploy/functions/prompts.js +22 -21
- package/lib/deploy/functions/release/executor.js +39 -0
- package/lib/deploy/functions/release/fabricator.js +422 -0
- package/lib/deploy/functions/release/index.js +73 -0
- package/lib/deploy/functions/release/planner.js +162 -0
- package/lib/deploy/functions/release/reporter.js +165 -0
- package/lib/deploy/functions/release/sourceTokenScraper.js +28 -0
- package/lib/deploy/functions/release/timer.js +14 -0
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +129 -126
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +41 -45
- package/lib/deploy/functions/triggerRegionHelper.js +40 -0
- package/lib/deploy/functions/validate.js +1 -24
- package/lib/deploy/index.js +10 -1
- package/lib/downloadUtils.js +37 -0
- package/lib/emulator/auth/apiSpec.js +549 -6
- package/lib/emulator/auth/handlers.js +4 -3
- package/lib/emulator/auth/operations.js +154 -14
- package/lib/emulator/auth/server.js +26 -15
- package/lib/emulator/auth/state.js +151 -13
- package/lib/emulator/download.js +2 -31
- package/lib/emulator/downloadableEmulators.js +7 -7
- package/lib/emulator/functionsEmulator.js +18 -4
- package/lib/emulator/functionsEmulatorRuntime.js +29 -7
- package/lib/emulator/storage/cloudFunctions.js +37 -7
- package/lib/extensions/askUserForConsent.js +14 -1
- package/lib/extensions/askUserForParam.js +81 -4
- package/lib/extensions/checkProjectBilling.js +7 -7
- package/lib/extensions/export.js +107 -0
- package/lib/extensions/extensionsApi.js +104 -21
- package/lib/extensions/extensionsHelper.js +6 -2
- package/lib/extensions/listExtensions.js +16 -11
- package/lib/extensions/paramHelper.js +9 -6
- package/lib/extensions/provisioningHelper.js +16 -3
- package/lib/extensions/refs.js +9 -1
- package/lib/extensions/secretsUtils.js +59 -0
- package/lib/extensions/updateHelper.js +12 -2
- package/lib/extensions/versionHelper.js +14 -0
- package/lib/extensions/warnings.js +33 -1
- package/lib/functional.js +8 -1
- package/lib/functions/env.js +10 -4
- package/lib/functions/runtimeConfigExport.js +137 -0
- package/lib/gcp/artifactregistry.js +16 -0
- package/lib/gcp/cloudfunctions.js +20 -74
- package/lib/gcp/cloudfunctionsv2.js +12 -90
- package/lib/gcp/cloudscheduler.js +22 -16
- package/lib/gcp/cloudtasks.js +143 -0
- package/lib/gcp/docker.js +7 -1
- package/lib/gcp/proto.js +2 -2
- package/lib/gcp/pubsub.js +1 -9
- package/lib/gcp/secretManager.js +132 -0
- package/lib/gcp/storage.js +16 -0
- package/lib/projectUtils.js +10 -1
- package/lib/requireInteractive.js +12 -0
- package/lib/utils.js +30 -1
- package/package.json +5 -4
- package/schema/firebase-config.json +9 -0
- package/lib/deploy/functions/deploymentPlanner.js +0 -113
- package/lib/deploy/functions/deploymentTimer.js +0 -23
- package/lib/deploy/functions/errorHandler.js +0 -75
- package/lib/deploy/functions/release.js +0 -116
- package/lib/deploy/functions/tasks.js +0 -324
- package/lib/functions/listFunctions.js +0 -10
- package/lib/functionsDelete.js +0 -60
|
@@ -283,7 +283,13 @@ class ProjectState {
|
|
|
283
283
|
createRefreshTokenFor(userInfo, provider, { extraClaims = {}, secondFactor, } = {}) {
|
|
284
284
|
const localId = userInfo.localId;
|
|
285
285
|
const refreshToken = utils_1.randomBase64UrlStr(204);
|
|
286
|
-
this.refreshTokens.set(refreshToken, {
|
|
286
|
+
this.refreshTokens.set(refreshToken, {
|
|
287
|
+
localId,
|
|
288
|
+
provider,
|
|
289
|
+
extraClaims,
|
|
290
|
+
secondFactor,
|
|
291
|
+
tenantId: userInfo.tenantId,
|
|
292
|
+
});
|
|
287
293
|
let refreshTokens = this.refreshTokensForLocalId.get(localId);
|
|
288
294
|
if (!refreshTokens) {
|
|
289
295
|
refreshTokens = new Set();
|
|
@@ -416,9 +422,9 @@ exports.ProjectState = ProjectState;
|
|
|
416
422
|
class AgentProjectState extends ProjectState {
|
|
417
423
|
constructor(projectId) {
|
|
418
424
|
super(projectId);
|
|
419
|
-
this.tenantForTenantId = new Map();
|
|
420
425
|
this._oneAccountPerEmail = true;
|
|
421
426
|
this._usageMode = UsageMode.DEFAULT;
|
|
427
|
+
this.tenantProjectForTenantId = new Map();
|
|
422
428
|
this._authCloudFunction = new cloudFunctions_1.AuthCloudFunction(this.projectId);
|
|
423
429
|
}
|
|
424
430
|
get authCloudFunction() {
|
|
@@ -436,27 +442,84 @@ class AgentProjectState extends ProjectState {
|
|
|
436
442
|
set usageMode(usageMode) {
|
|
437
443
|
this._usageMode = usageMode;
|
|
438
444
|
}
|
|
439
|
-
|
|
440
|
-
|
|
445
|
+
get allowPasswordSignup() {
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
get disableAuth() {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
get mfaConfig() {
|
|
452
|
+
return { state: "ENABLED", enabledProviders: ["PHONE_SMS"] };
|
|
453
|
+
}
|
|
454
|
+
get enableAnonymousUser() {
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
get enableEmailLinkSignin() {
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
getTenantProject(tenantId) {
|
|
461
|
+
if (!this.tenantProjectForTenantId.has(tenantId)) {
|
|
462
|
+
this.createTenantWithTenantId(tenantId, {
|
|
463
|
+
tenantId,
|
|
464
|
+
allowPasswordSignup: true,
|
|
465
|
+
disableAuth: false,
|
|
466
|
+
mfaConfig: {
|
|
467
|
+
state: "ENABLED",
|
|
468
|
+
enabledProviders: ["PHONE_SMS"],
|
|
469
|
+
},
|
|
470
|
+
enableAnonymousUser: true,
|
|
471
|
+
enableEmailLinkSignin: true,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
return this.tenantProjectForTenantId.get(tenantId);
|
|
441
475
|
}
|
|
442
|
-
listTenants() {
|
|
443
|
-
|
|
476
|
+
listTenants(startToken) {
|
|
477
|
+
const tenantProjects = [];
|
|
478
|
+
for (const tenantProject of this.tenantProjectForTenantId.values()) {
|
|
479
|
+
if (!startToken || tenantProject.tenantId > startToken) {
|
|
480
|
+
tenantProjects.push(tenantProject);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
tenantProjects.sort((a, b) => {
|
|
484
|
+
if (a.tenantId < b.tenantId) {
|
|
485
|
+
return -1;
|
|
486
|
+
}
|
|
487
|
+
else if (a.tenantId > b.tenantId) {
|
|
488
|
+
return 1;
|
|
489
|
+
}
|
|
490
|
+
return 0;
|
|
491
|
+
});
|
|
492
|
+
return tenantProjects.map((tenantProject) => tenantProject.tenantConfig);
|
|
444
493
|
}
|
|
445
|
-
createTenant() {
|
|
446
|
-
|
|
494
|
+
createTenant(tenant) {
|
|
495
|
+
for (let i = 0; i < 10; i++) {
|
|
496
|
+
const tenantId = utils_1.randomId(28);
|
|
497
|
+
const createdTenant = this.createTenantWithTenantId(tenantId, tenant);
|
|
498
|
+
if (createdTenant) {
|
|
499
|
+
return createdTenant;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
throw new Error("Could not generate a random unique tenantId after 10 tries");
|
|
447
503
|
}
|
|
448
|
-
|
|
449
|
-
|
|
504
|
+
createTenantWithTenantId(tenantId, tenant) {
|
|
505
|
+
if (this.tenantProjectForTenantId.has(tenantId)) {
|
|
506
|
+
return undefined;
|
|
507
|
+
}
|
|
508
|
+
tenant.name = `projects/${this.projectId}/tenants/${tenantId}`;
|
|
509
|
+
tenant.tenantId = tenantId;
|
|
510
|
+
this.tenantProjectForTenantId.set(tenantId, new TenantProjectState(this.projectId, tenantId, tenant, this));
|
|
511
|
+
return tenant;
|
|
450
512
|
}
|
|
451
|
-
deleteTenant() {
|
|
452
|
-
|
|
513
|
+
deleteTenant(tenantId) {
|
|
514
|
+
this.tenantProjectForTenantId.delete(tenantId);
|
|
453
515
|
}
|
|
454
516
|
}
|
|
455
517
|
exports.AgentProjectState = AgentProjectState;
|
|
456
518
|
class TenantProjectState extends ProjectState {
|
|
457
|
-
constructor(projectId, tenantId, parentProject) {
|
|
519
|
+
constructor(projectId, tenantId, _tenantConfig, parentProject) {
|
|
458
520
|
super(projectId);
|
|
459
521
|
this.tenantId = tenantId;
|
|
522
|
+
this._tenantConfig = _tenantConfig;
|
|
460
523
|
this.parentProject = parentProject;
|
|
461
524
|
}
|
|
462
525
|
get oneAccountPerEmail() {
|
|
@@ -468,6 +531,81 @@ class TenantProjectState extends ProjectState {
|
|
|
468
531
|
get usageMode() {
|
|
469
532
|
return this.parentProject.usageMode;
|
|
470
533
|
}
|
|
534
|
+
get tenantConfig() {
|
|
535
|
+
return this._tenantConfig;
|
|
536
|
+
}
|
|
537
|
+
get allowPasswordSignup() {
|
|
538
|
+
return this._tenantConfig.allowPasswordSignup;
|
|
539
|
+
}
|
|
540
|
+
get disableAuth() {
|
|
541
|
+
return this._tenantConfig.disableAuth;
|
|
542
|
+
}
|
|
543
|
+
get mfaConfig() {
|
|
544
|
+
return this._tenantConfig.mfaConfig;
|
|
545
|
+
}
|
|
546
|
+
get enableAnonymousUser() {
|
|
547
|
+
return this._tenantConfig.enableAnonymousUser;
|
|
548
|
+
}
|
|
549
|
+
get enableEmailLinkSignin() {
|
|
550
|
+
return this._tenantConfig.enableEmailLinkSignin;
|
|
551
|
+
}
|
|
552
|
+
delete() {
|
|
553
|
+
this.parentProject.deleteTenant(this.tenantId);
|
|
554
|
+
}
|
|
555
|
+
updateTenant(update, updateMask) {
|
|
556
|
+
var _a, _b, _c, _d, _e;
|
|
557
|
+
if (!updateMask) {
|
|
558
|
+
const mfaConfig = (_a = update.mfaConfig) !== null && _a !== void 0 ? _a : {};
|
|
559
|
+
if (!("state" in mfaConfig)) {
|
|
560
|
+
mfaConfig.state = "DISABLED";
|
|
561
|
+
}
|
|
562
|
+
if (!("enabledProviders" in mfaConfig)) {
|
|
563
|
+
mfaConfig.enabledProviders = [];
|
|
564
|
+
}
|
|
565
|
+
this._tenantConfig = {
|
|
566
|
+
tenantId: this.tenantId,
|
|
567
|
+
name: this.tenantConfig.name,
|
|
568
|
+
allowPasswordSignup: (_b = update.allowPasswordSignup) !== null && _b !== void 0 ? _b : false,
|
|
569
|
+
disableAuth: (_c = update.disableAuth) !== null && _c !== void 0 ? _c : false,
|
|
570
|
+
mfaConfig: mfaConfig,
|
|
571
|
+
enableAnonymousUser: (_d = update.enableAnonymousUser) !== null && _d !== void 0 ? _d : false,
|
|
572
|
+
enableEmailLinkSignin: (_e = update.enableEmailLinkSignin) !== null && _e !== void 0 ? _e : false,
|
|
573
|
+
displayName: update.displayName,
|
|
574
|
+
};
|
|
575
|
+
return this.tenantConfig;
|
|
576
|
+
}
|
|
577
|
+
const paths = updateMask.split(",");
|
|
578
|
+
for (const path of paths) {
|
|
579
|
+
const fields = path.split(".");
|
|
580
|
+
let updateField = update;
|
|
581
|
+
let existingField = this._tenantConfig;
|
|
582
|
+
let field;
|
|
583
|
+
for (let i = 0; i < fields.length - 1; i++) {
|
|
584
|
+
field = fields[i];
|
|
585
|
+
if (updateField[field] == null) {
|
|
586
|
+
console.warn(`Unable to find field '${field}' in update '${updateField}`);
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
if (Array.isArray(updateField[field]) ||
|
|
590
|
+
Object(updateField[field]) !== updateField[field]) {
|
|
591
|
+
console.warn(`Field '${field}' is singular and cannot have sub-fields`);
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
if (!existingField[field]) {
|
|
595
|
+
existingField[field] = {};
|
|
596
|
+
}
|
|
597
|
+
updateField = updateField[field];
|
|
598
|
+
existingField = existingField[field];
|
|
599
|
+
}
|
|
600
|
+
field = fields[fields.length - 1];
|
|
601
|
+
if (updateField[field] == null) {
|
|
602
|
+
console.warn(`Unable to find field '${field}' in update '${JSON.stringify(updateField)}`);
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
existingField[field] = updateField[field];
|
|
606
|
+
}
|
|
607
|
+
return this.tenantConfig;
|
|
608
|
+
}
|
|
471
609
|
}
|
|
472
610
|
exports.TenantProjectState = TenantProjectState;
|
|
473
611
|
function getProviderEmailsForUser(user) {
|
package/lib/emulator/download.js
CHANGED
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.downloadEmulator = void 0;
|
|
4
|
-
const url_1 = require("url");
|
|
5
4
|
const crypto = require("crypto");
|
|
6
5
|
const fs = require("fs-extra");
|
|
7
6
|
const path = require("path");
|
|
8
|
-
const ProgressBar = require("progress");
|
|
9
7
|
const tmp = require("tmp");
|
|
10
8
|
const unzipper = require("unzipper");
|
|
11
|
-
const apiv2_1 = require("../apiv2");
|
|
12
9
|
const emulatorLogger_1 = require("./emulatorLogger");
|
|
13
10
|
const error_1 = require("../error");
|
|
14
11
|
const downloadableEmulators = require("./downloadableEmulators");
|
|
12
|
+
const downloadUtils = require("../downloadUtils");
|
|
15
13
|
tmp.setGracefulCleanup();
|
|
16
14
|
async function downloadEmulator(name) {
|
|
17
15
|
const emulator = downloadableEmulators.getDownloadDetails(name);
|
|
18
16
|
emulatorLogger_1.EmulatorLogger.forEmulator(name).logLabeled("BULLET", name, `downloading ${path.basename(emulator.downloadPath)}...`);
|
|
19
17
|
fs.ensureDirSync(emulator.opts.cacheDir);
|
|
20
|
-
const tmpfile = await downloadToTmp(emulator.opts.remoteUrl);
|
|
18
|
+
const tmpfile = await downloadUtils.downloadToTmp(emulator.opts.remoteUrl);
|
|
21
19
|
if (!emulator.opts.skipChecksumAndSize) {
|
|
22
20
|
await validateSize(tmpfile, emulator.opts.expectedSize);
|
|
23
21
|
await validateChecksum(tmpfile, emulator.opts.expectedChecksum);
|
|
@@ -58,33 +56,6 @@ function removeOldFiles(name, emulator, removeAllVersions = false) {
|
|
|
58
56
|
}
|
|
59
57
|
}
|
|
60
58
|
}
|
|
61
|
-
async function downloadToTmp(remoteUrl) {
|
|
62
|
-
const u = new url_1.URL(remoteUrl);
|
|
63
|
-
const c = new apiv2_1.Client({ urlPrefix: u.origin, auth: false });
|
|
64
|
-
const tmpfile = tmp.fileSync();
|
|
65
|
-
const writeStream = fs.createWriteStream(tmpfile.name);
|
|
66
|
-
const res = await c.request({
|
|
67
|
-
method: "GET",
|
|
68
|
-
path: u.pathname,
|
|
69
|
-
queryParams: u.searchParams,
|
|
70
|
-
responseType: "stream",
|
|
71
|
-
resolveOnHTTPError: true,
|
|
72
|
-
});
|
|
73
|
-
if (res.status !== 200) {
|
|
74
|
-
throw new error_1.FirebaseError(`download failed, status ${res.status}`, { exit: 1 });
|
|
75
|
-
}
|
|
76
|
-
const total = parseInt(res.response.headers.get("content-length") || "0", 10);
|
|
77
|
-
const totalMb = Math.ceil(total / 1000000);
|
|
78
|
-
const bar = new ProgressBar(`Progress: :bar (:percent of ${totalMb}MB)`, { total, head: ">" });
|
|
79
|
-
res.body.on("data", (chunk) => {
|
|
80
|
-
bar.tick(chunk.length);
|
|
81
|
-
});
|
|
82
|
-
await new Promise((resolve) => {
|
|
83
|
-
writeStream.on("finish", resolve);
|
|
84
|
-
res.body.pipe(writeStream);
|
|
85
|
-
});
|
|
86
|
-
return tmpfile.name;
|
|
87
|
-
}
|
|
88
59
|
function validateSize(filepath, expectedSize) {
|
|
89
60
|
return new Promise((resolve, reject) => {
|
|
90
61
|
const stat = fs.statSync(filepath);
|
|
@@ -50,15 +50,15 @@ exports.DownloadDetails = {
|
|
|
50
50
|
},
|
|
51
51
|
},
|
|
52
52
|
ui: {
|
|
53
|
-
version: "1.6.
|
|
54
|
-
downloadPath: path.join(CACHE_DIR, "ui-v1.6.
|
|
55
|
-
unzipDir: path.join(CACHE_DIR, "ui-v1.6.
|
|
56
|
-
binaryPath: path.join(CACHE_DIR, "ui-v1.6.
|
|
53
|
+
version: "1.6.4",
|
|
54
|
+
downloadPath: path.join(CACHE_DIR, "ui-v1.6.4.zip"),
|
|
55
|
+
unzipDir: path.join(CACHE_DIR, "ui-v1.6.4"),
|
|
56
|
+
binaryPath: path.join(CACHE_DIR, "ui-v1.6.4", "server.bundle.js"),
|
|
57
57
|
opts: {
|
|
58
58
|
cacheDir: CACHE_DIR,
|
|
59
|
-
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.
|
|
60
|
-
expectedSize:
|
|
61
|
-
expectedChecksum: "
|
|
59
|
+
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.4.zip",
|
|
60
|
+
expectedSize: 3757300,
|
|
61
|
+
expectedChecksum: "20d4ee71e4ff7527b1843b6a8636142e",
|
|
62
62
|
namePrefix: "ui",
|
|
63
63
|
},
|
|
64
64
|
},
|
|
@@ -123,10 +123,22 @@ class FunctionsEmulator {
|
|
|
123
123
|
});
|
|
124
124
|
};
|
|
125
125
|
const multicastHandler = (req, res) => {
|
|
126
|
-
|
|
127
|
-
const proto = JSON.parse(reqBody.toString());
|
|
128
|
-
const triggers = this.multicastTriggers[`${this.args.projectId}:${proto.eventType}`] || [];
|
|
126
|
+
var _a;
|
|
129
127
|
const projectId = req.params.project_id;
|
|
128
|
+
const reqBody = req.rawBody;
|
|
129
|
+
let proto = JSON.parse(reqBody.toString());
|
|
130
|
+
let triggerKey;
|
|
131
|
+
if ((_a = req.headers["content-type"]) === null || _a === void 0 ? void 0 : _a.includes("cloudevent")) {
|
|
132
|
+
triggerKey = `${this.args.projectId}:${proto.type}`;
|
|
133
|
+
if (types_2.EventUtils.isBinaryCloudEvent(req)) {
|
|
134
|
+
proto = types_2.EventUtils.extractBinaryCloudEventContext(req);
|
|
135
|
+
proto.data = req.body;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
triggerKey = `${this.args.projectId}:${proto.eventType}`;
|
|
140
|
+
}
|
|
141
|
+
const triggers = this.multicastTriggers[triggerKey] || [];
|
|
130
142
|
triggers.forEach((triggerId) => {
|
|
131
143
|
this.workQueue.submit(() => {
|
|
132
144
|
this.logger.log("DEBUG", `Accepted multicast request ${req.method} ${req.url} --> ${triggerId}`);
|
|
@@ -514,6 +526,8 @@ class FunctionsEmulator {
|
|
|
514
526
|
const envs = {};
|
|
515
527
|
envs.FUNCTIONS_EMULATOR = "true";
|
|
516
528
|
envs.TZ = "UTC";
|
|
529
|
+
envs.FIREBASE_DEBUG_MODE = "true";
|
|
530
|
+
envs.FIREBASE_DEBUG_FEATURES = JSON.stringify({ skipTokenVerification: true });
|
|
517
531
|
const firestoreEmulator = this.getEmulatorInfo(types_1.Emulators.FIRESTORE);
|
|
518
532
|
if (firestoreEmulator != null) {
|
|
519
533
|
envs[constants_1.Constants.FIRESTORE_EMULATOR_HOST] = functionsEmulatorShared_1.formatHost(firestoreEmulator);
|
|
@@ -718,7 +732,7 @@ class FunctionsEmulator {
|
|
|
718
732
|
const reqBody = req.rawBody;
|
|
719
733
|
const isCallable = trigger.labels && trigger.labels["deployment-callable"] === "true";
|
|
720
734
|
const authHeader = req.header("Authorization");
|
|
721
|
-
if (authHeader && isCallable) {
|
|
735
|
+
if (authHeader && isCallable && trigger.platform !== "gcfv2") {
|
|
722
736
|
const token = this.tokenFromAuthHeader(authHeader);
|
|
723
737
|
if (token) {
|
|
724
738
|
const contextAuth = {
|
|
@@ -259,13 +259,35 @@ async function initializeFirebaseFunctionsStubs(frb) {
|
|
|
259
259
|
};
|
|
260
260
|
const onCallInnerMethodName = "_onCallWithOptions";
|
|
261
261
|
const onCallMethodOriginal = httpsProvider[onCallInnerMethodName];
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
262
|
+
if (onCallMethodOriginal.length === 3) {
|
|
263
|
+
httpsProvider[onCallInnerMethodName] = (opts, handler, deployOpts) => {
|
|
264
|
+
const wrapped = wrapCallableHandler(handler);
|
|
265
|
+
const cf = onCallMethodOriginal(opts, wrapped, deployOpts);
|
|
266
|
+
return cf;
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
httpsProvider[onCallInnerMethodName] = (handler, opts) => {
|
|
271
|
+
const wrapped = wrapCallableHandler(handler);
|
|
272
|
+
const cf = onCallMethodOriginal(wrapped, opts);
|
|
273
|
+
return cf;
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
httpsProvider.onCall = function (optsOrHandler, handler) {
|
|
277
|
+
if (onCallMethodOriginal.length === 3) {
|
|
278
|
+
let opts;
|
|
279
|
+
if (arguments.length === 1) {
|
|
280
|
+
opts = {};
|
|
281
|
+
handler = optsOrHandler;
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
opts = optsOrHandler;
|
|
285
|
+
}
|
|
286
|
+
return httpsProvider[onCallInnerMethodName](opts, handler, {});
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
return httpsProvider[onCallInnerMethodName](optsOrHandler, {});
|
|
290
|
+
}
|
|
269
291
|
};
|
|
270
292
|
}
|
|
271
293
|
function wrapCallableHandler(handler) {
|
|
@@ -6,6 +6,12 @@ const types_1 = require("../types");
|
|
|
6
6
|
const emulatorLogger_1 = require("../emulatorLogger");
|
|
7
7
|
const metadata_1 = require("./metadata");
|
|
8
8
|
const apiv2_1 = require("../../apiv2");
|
|
9
|
+
const STORAGE_V2_ACTION_MAP = {
|
|
10
|
+
finalize: "finalized",
|
|
11
|
+
metadataUpdate: "metadataUpdated",
|
|
12
|
+
delete: "deleted",
|
|
13
|
+
archive: "archived",
|
|
14
|
+
};
|
|
9
15
|
class StorageCloudFunctions {
|
|
10
16
|
constructor(projectId) {
|
|
11
17
|
this.projectId = projectId;
|
|
@@ -19,26 +25,37 @@ class StorageCloudFunctions {
|
|
|
19
25
|
this.functionsEmulatorInfo = functionsEmulator.getInfo();
|
|
20
26
|
this.multicastOrigin = `http://${registry_1.EmulatorRegistry.getInfoHostString(this.functionsEmulatorInfo)}`;
|
|
21
27
|
this.multicastPath = `/functions/projects/${projectId}/trigger_multicast`;
|
|
28
|
+
this.client = new apiv2_1.Client({ urlPrefix: this.multicastOrigin, auth: false });
|
|
22
29
|
}
|
|
23
30
|
}
|
|
24
31
|
async dispatch(action, object) {
|
|
25
|
-
if (!this.enabled)
|
|
32
|
+
if (!this.enabled) {
|
|
26
33
|
return;
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
let res;
|
|
34
|
+
}
|
|
35
|
+
const errStatus = [];
|
|
30
36
|
let err;
|
|
31
37
|
try {
|
|
32
|
-
|
|
38
|
+
const eventBody = this.createLegacyEventRequestBody(action, object);
|
|
39
|
+
const eventRes = await this.client.post(this.multicastPath, eventBody);
|
|
40
|
+
if (eventRes.status !== 200) {
|
|
41
|
+
errStatus.push(eventRes.status);
|
|
42
|
+
}
|
|
43
|
+
const cloudEventBody = this.createCloudEventRequestBody(action, object);
|
|
44
|
+
const cloudEventRes = await this.client.post(this.multicastPath, cloudEventBody, {
|
|
45
|
+
headers: { "Content-Type": "application/cloudevents+json; charset=UTF-8" },
|
|
46
|
+
});
|
|
47
|
+
if (cloudEventRes.status !== 200) {
|
|
48
|
+
errStatus.push(cloudEventRes.status);
|
|
49
|
+
}
|
|
33
50
|
}
|
|
34
51
|
catch (e) {
|
|
35
52
|
err = e;
|
|
36
53
|
}
|
|
37
|
-
if (err ||
|
|
54
|
+
if (err || errStatus.length > 0) {
|
|
38
55
|
this.logger.logLabeled("WARN", "functions", `Firebase Storage function was not triggered due to emulation error. Please file a bug.`);
|
|
39
56
|
}
|
|
40
57
|
}
|
|
41
|
-
|
|
58
|
+
createLegacyEventRequestBody(action, objectMetadataPayload) {
|
|
42
59
|
const timestamp = new Date();
|
|
43
60
|
return JSON.stringify({
|
|
44
61
|
eventId: `${timestamp.getTime()}`,
|
|
@@ -52,5 +69,18 @@ class StorageCloudFunctions {
|
|
|
52
69
|
data: objectMetadataPayload,
|
|
53
70
|
});
|
|
54
71
|
}
|
|
72
|
+
createCloudEventRequestBody(action, objectMetadataPayload) {
|
|
73
|
+
const ceAction = STORAGE_V2_ACTION_MAP[action];
|
|
74
|
+
if (!ceAction) {
|
|
75
|
+
throw new Error("Action is not definied as a CloudEvents action");
|
|
76
|
+
}
|
|
77
|
+
const data = objectMetadataPayload;
|
|
78
|
+
return JSON.stringify({
|
|
79
|
+
specVersion: 1,
|
|
80
|
+
type: `google.cloud.storage.object.v1.${ceAction}`,
|
|
81
|
+
source: `//storage.googleapis.com/projects/_/buckets/${objectMetadataPayload.bucket}/objects/${objectMetadataPayload.name}`,
|
|
82
|
+
data,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
55
85
|
}
|
|
56
86
|
exports.StorageCloudFunctions = StorageCloudFunctions;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.promptForPublisherTOS = exports.displayRoles = exports.retrieveRoleInfo = exports.formatDescription = void 0;
|
|
3
|
+
exports.promptForPublisherTOS = exports.displayApis = exports.displayRoles = exports.retrieveRoleInfo = exports.formatDescription = void 0;
|
|
4
4
|
const _ = require("lodash");
|
|
5
5
|
const clc = require("cli-color");
|
|
6
6
|
const marked = require("marked");
|
|
@@ -35,6 +35,19 @@ async function displayRoles(extensionName, projectId, roles) {
|
|
|
35
35
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, message);
|
|
36
36
|
}
|
|
37
37
|
exports.displayRoles = displayRoles;
|
|
38
|
+
function displayApis(extensionName, projectId, apis) {
|
|
39
|
+
if (!apis.length) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const question = `${clc.bold(extensionName)} will enable the following APIs for project ${clc.bold(projectId)}`;
|
|
43
|
+
const results = apis.map((api) => {
|
|
44
|
+
return `- ${api.apiName}: ${api.reason}`;
|
|
45
|
+
});
|
|
46
|
+
results.unshift(question);
|
|
47
|
+
const message = results.join("\n");
|
|
48
|
+
utils.logLabeledBullet(extensionsHelper_1.logPrefix, message);
|
|
49
|
+
}
|
|
50
|
+
exports.displayApis = displayApis;
|
|
38
51
|
async function promptForPublisherTOS() {
|
|
39
52
|
const termsOfServiceMsg = "By registering as a publisher, you confirm that you have read the Firebase Extensions Publisher Terms and Conditions (linked below) and you, on behalf of yourself and the organization you represent, agree to comply with it. Here is a brief summary of the highlights of our terms and conditions:\n" +
|
|
40
53
|
" - You ensure extensions you publish comply with all laws and regulations; do not include any viruses, spyware, Trojan horses, or other malicious code; and do not violate any person’s rights, including intellectual property, privacy, and security rights.\n" +
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ask = exports.getInquirerDefault = exports.askForParam = exports.checkResponse = void 0;
|
|
3
|
+
exports.ask = exports.getInquirerDefault = exports.promptCreateSecret = exports.askForParam = exports.checkResponse = void 0;
|
|
4
4
|
const _ = require("lodash");
|
|
5
5
|
const clc = require("cli-color");
|
|
6
6
|
const marked = require("marked");
|
|
7
7
|
const extensionsApi_1 = require("./extensionsApi");
|
|
8
|
+
const secretManagerApi = require("../gcp/secretManager");
|
|
9
|
+
const secretsUtils = require("./secretsUtils");
|
|
8
10
|
const extensionsHelper_1 = require("./extensionsHelper");
|
|
9
11
|
const utils_1 = require("./utils");
|
|
10
12
|
const logger_1 = require("../logger");
|
|
11
13
|
const prompt_1 = require("../prompt");
|
|
12
14
|
const utils = require("../utils");
|
|
15
|
+
var SecretUpdateAction;
|
|
16
|
+
(function (SecretUpdateAction) {
|
|
17
|
+
SecretUpdateAction[SecretUpdateAction["LEAVE"] = 0] = "LEAVE";
|
|
18
|
+
SecretUpdateAction[SecretUpdateAction["SET_NEW"] = 1] = "SET_NEW";
|
|
19
|
+
})(SecretUpdateAction || (SecretUpdateAction = {}));
|
|
13
20
|
function checkResponse(response, spec) {
|
|
14
21
|
let valid = true;
|
|
15
22
|
let responses;
|
|
@@ -48,7 +55,7 @@ function checkResponse(response, spec) {
|
|
|
48
55
|
return valid;
|
|
49
56
|
}
|
|
50
57
|
exports.checkResponse = checkResponse;
|
|
51
|
-
async function askForParam(paramSpec) {
|
|
58
|
+
async function askForParam(projectId, instanceId, paramSpec, reconfiguring) {
|
|
52
59
|
let valid = false;
|
|
53
60
|
let response = "";
|
|
54
61
|
const description = paramSpec.description || "";
|
|
@@ -89,6 +96,11 @@ async function askForParam(paramSpec) {
|
|
|
89
96
|
choices: utils_1.convertExtensionOptionToLabeledList(paramSpec.options),
|
|
90
97
|
});
|
|
91
98
|
break;
|
|
99
|
+
case extensionsApi_1.ParamType.SECRET:
|
|
100
|
+
response = reconfiguring
|
|
101
|
+
? await promptReconfigureSecret(projectId, instanceId, paramSpec)
|
|
102
|
+
: await promptCreateSecret(projectId, instanceId, paramSpec);
|
|
103
|
+
break;
|
|
92
104
|
default:
|
|
93
105
|
response = await prompt_1.promptOnce({
|
|
94
106
|
name: paramSpec.param,
|
|
@@ -102,6 +114,71 @@ async function askForParam(paramSpec) {
|
|
|
102
114
|
return response;
|
|
103
115
|
}
|
|
104
116
|
exports.askForParam = askForParam;
|
|
117
|
+
async function promptReconfigureSecret(projectId, instanceId, paramSpec) {
|
|
118
|
+
const action = await prompt_1.promptOnce({
|
|
119
|
+
type: "list",
|
|
120
|
+
message: `Choose what you would like to do with this secret:`,
|
|
121
|
+
choices: [
|
|
122
|
+
{ name: "Leave unchanged", value: SecretUpdateAction.LEAVE },
|
|
123
|
+
{ name: "Set new value", value: SecretUpdateAction.SET_NEW },
|
|
124
|
+
],
|
|
125
|
+
});
|
|
126
|
+
switch (action) {
|
|
127
|
+
case SecretUpdateAction.SET_NEW:
|
|
128
|
+
let secret;
|
|
129
|
+
let secretName;
|
|
130
|
+
if (paramSpec.default) {
|
|
131
|
+
secret = secretManagerApi.parseSecretResourceName(paramSpec.default);
|
|
132
|
+
secretName = secret.name;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
secretName = await generateSecretName(projectId, instanceId, paramSpec.param);
|
|
136
|
+
}
|
|
137
|
+
const secretValue = await prompt_1.promptOnce({
|
|
138
|
+
name: paramSpec.param,
|
|
139
|
+
type: "password",
|
|
140
|
+
message: `This secret will be stored in Cloud Secret Manager as ${secretName}.\nEnter new value for ${paramSpec.label.trim()}:`,
|
|
141
|
+
});
|
|
142
|
+
if (!secret) {
|
|
143
|
+
secret = await secretManagerApi.createSecret(projectId, secretName, secretsUtils.getSecretLabels(instanceId));
|
|
144
|
+
}
|
|
145
|
+
return addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue);
|
|
146
|
+
case SecretUpdateAction.LEAVE:
|
|
147
|
+
default:
|
|
148
|
+
return paramSpec.default || "";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async function promptCreateSecret(projectId, instanceId, paramSpec, secretName) {
|
|
152
|
+
const name = secretName !== null && secretName !== void 0 ? secretName : (await generateSecretName(projectId, instanceId, paramSpec.param));
|
|
153
|
+
const secretValue = await prompt_1.promptOnce({
|
|
154
|
+
name: paramSpec.param,
|
|
155
|
+
type: "password",
|
|
156
|
+
default: paramSpec.default,
|
|
157
|
+
message: `This secret will be stored in Cloud Secret Manager (https://cloud.google.com/secret-manager/pricing) as ${name} and managed by Firebase Extensions (Firebase Extensions Service Agent will be granted Secret Admin role on this secret).\nEnter a value for ${paramSpec.label.trim()}:`,
|
|
158
|
+
});
|
|
159
|
+
if (secretValue === "" && paramSpec.required) {
|
|
160
|
+
logger_1.logger.info(`Secret value cannot be empty for required param ${paramSpec.param}`);
|
|
161
|
+
return await promptCreateSecret(projectId, instanceId, paramSpec, name);
|
|
162
|
+
}
|
|
163
|
+
else if (secretValue !== "") {
|
|
164
|
+
const secret = await secretManagerApi.createSecret(projectId, name, secretsUtils.getSecretLabels(instanceId));
|
|
165
|
+
return addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue);
|
|
166
|
+
}
|
|
167
|
+
return secretValue;
|
|
168
|
+
}
|
|
169
|
+
exports.promptCreateSecret = promptCreateSecret;
|
|
170
|
+
async function generateSecretName(projectId, instanceId, paramName) {
|
|
171
|
+
let secretName = `ext-${instanceId}-${paramName}`;
|
|
172
|
+
while (await secretManagerApi.secretExists(projectId, secretName)) {
|
|
173
|
+
secretName += `-${utils_1.getRandomString(3)}`;
|
|
174
|
+
}
|
|
175
|
+
return secretName;
|
|
176
|
+
}
|
|
177
|
+
async function addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue) {
|
|
178
|
+
const version = await secretManagerApi.addVersion(secret, secretValue);
|
|
179
|
+
await secretsUtils.grantFirexServiceAgentSecretAdminRole(secret);
|
|
180
|
+
return `projects/${version.secret.projectId}/secrets/${version.secret.name}/versions/${version.versionId}`;
|
|
181
|
+
}
|
|
105
182
|
function getInquirerDefault(options, def) {
|
|
106
183
|
const defaultOption = _.find(options, (option) => {
|
|
107
184
|
return option.value === def;
|
|
@@ -109,7 +186,7 @@ function getInquirerDefault(options, def) {
|
|
|
109
186
|
return defaultOption ? defaultOption.label || defaultOption.value : "";
|
|
110
187
|
}
|
|
111
188
|
exports.getInquirerDefault = getInquirerDefault;
|
|
112
|
-
async function ask(paramSpecs, firebaseProjectParams) {
|
|
189
|
+
async function ask(projectId, instanceId, paramSpecs, firebaseProjectParams, reconfiguring) {
|
|
113
190
|
if (_.isEmpty(paramSpecs)) {
|
|
114
191
|
logger_1.logger.debug("No params were specified for this extension.");
|
|
115
192
|
return {};
|
|
@@ -119,7 +196,7 @@ async function ask(paramSpecs, firebaseProjectParams) {
|
|
|
119
196
|
const result = {};
|
|
120
197
|
const promises = _.map(substituted, (paramSpec) => {
|
|
121
198
|
return async () => {
|
|
122
|
-
result[paramSpec.param] = await askForParam(paramSpec);
|
|
199
|
+
result[paramSpec.param] = await askForParam(projectId, instanceId, paramSpec, reconfiguring);
|
|
123
200
|
};
|
|
124
201
|
});
|
|
125
202
|
await promises.reduce((prev, cur) => prev.then(cur), Promise.resolve());
|
|
@@ -33,13 +33,13 @@ async function openBillingAccount(projectId, url, open) {
|
|
|
33
33
|
});
|
|
34
34
|
return cloudbilling.checkBillingEnabled(projectId);
|
|
35
35
|
}
|
|
36
|
-
async function chooseBillingAccount(projectId,
|
|
36
|
+
async function chooseBillingAccount(projectId, accounts) {
|
|
37
37
|
const choices = accounts.map((m) => m.displayName);
|
|
38
38
|
choices.push(ADD_BILLING_ACCOUNT);
|
|
39
39
|
const answer = await prompt.promptOnce({
|
|
40
40
|
name: "billing",
|
|
41
41
|
type: "list",
|
|
42
|
-
message: `
|
|
42
|
+
message: `Extensions require your project to be upgraded to the Blaze plan. You have access to the following billing accounts.
|
|
43
43
|
Please select the one that you would like to associate with this project:`,
|
|
44
44
|
choices: choices,
|
|
45
45
|
});
|
|
@@ -54,10 +54,10 @@ Please select the one that you would like to associate with this project:`,
|
|
|
54
54
|
}
|
|
55
55
|
return logBillingStatus(billingEnabled, projectId);
|
|
56
56
|
}
|
|
57
|
-
async function setUpBillingAccount(projectId
|
|
57
|
+
async function setUpBillingAccount(projectId) {
|
|
58
58
|
const billingURL = `https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
|
|
59
59
|
logger_1.logger.info();
|
|
60
|
-
logger_1.logger.info(`
|
|
60
|
+
logger_1.logger.info(`Extension require your project to be upgraded to the Blaze plan. Please visit the following link to add a billing account:`);
|
|
61
61
|
logger_1.logger.info();
|
|
62
62
|
logger_1.logger.info(clc.bold.underline(billingURL));
|
|
63
63
|
logger_1.logger.info();
|
|
@@ -70,13 +70,13 @@ async function setUpBillingAccount(projectId, extensionName) {
|
|
|
70
70
|
const billingEnabled = await openBillingAccount(projectId, billingURL, open);
|
|
71
71
|
return logBillingStatus(billingEnabled, projectId);
|
|
72
72
|
}
|
|
73
|
-
async function enableBilling(projectId
|
|
73
|
+
async function enableBilling(projectId) {
|
|
74
74
|
const billingAccounts = await cloudbilling.listBillingAccounts();
|
|
75
75
|
if (billingAccounts) {
|
|
76
76
|
const accounts = billingAccounts.filter((account) => account.open);
|
|
77
77
|
return accounts.length > 0
|
|
78
|
-
? chooseBillingAccount(projectId,
|
|
79
|
-
: setUpBillingAccount(projectId
|
|
78
|
+
? chooseBillingAccount(projectId, accounts)
|
|
79
|
+
: setUpBillingAccount(projectId);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
exports.enableBilling = enableBilling;
|