firebase-tools 9.22.0 → 9.23.3
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/commands/functions-delete.js +7 -1
- package/lib/commands/remoteconfig-get.js +6 -5
- package/lib/deploy/extensions/params.js +39 -0
- package/lib/deploy/extensions/planner.js +11 -12
- package/lib/deploy/extensions/prepare.js +9 -1
- package/lib/deploy/functions/checkIam.js +65 -4
- package/lib/deploy/functions/containerCleaner.js +68 -77
- package/lib/deploy/functions/eventTypes.js +10 -0
- package/lib/deploy/functions/prepare.js +12 -1
- package/lib/deploy/functions/release/fabricator.js +6 -1
- package/lib/deploy/functions/release/index.js +5 -1
- package/lib/deploy/functions/release/reporter.js +4 -0
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +3 -9
- package/lib/deploy/functions/services/index.js +38 -0
- package/lib/deploy/functions/services/storage.js +43 -0
- package/lib/deploy/functions/triggerRegionHelper.js +6 -30
- package/lib/deploy/index.js +9 -1
- package/lib/emulator/auth/handlers.js +1 -1
- package/lib/emulator/auth/operations.js +27 -9
- package/lib/emulator/auth/widget_ui.js +17 -3
- package/lib/emulator/downloadableEmulators.js +5 -5
- package/lib/emulator/functionsEmulator.js +15 -1
- package/lib/emulator/functionsEmulatorShared.js +1 -0
- package/lib/emulator/pubsubEmulator.js +58 -45
- package/lib/emulator/storage/cloudFunctions.js +13 -6
- package/lib/ensureApiEnabled.js +11 -14
- package/lib/extensions/askUserForParam.js +32 -8
- package/lib/extensions/emulator/triggerHelper.js +1 -0
- package/lib/functions/env.js +2 -2
- package/lib/gcp/cloudfunctions.js +8 -6
- package/lib/gcp/cloudfunctionsv2.js +35 -3
- package/lib/gcp/docker.js +29 -1
- package/lib/gcp/location.js +44 -0
- package/lib/gcp/storage.js +48 -32
- package/lib/init/features/functions/index.js +3 -3
- package/lib/init/features/hosting/github.js +3 -0
- package/lib/init/features/project.js +2 -1
- package/lib/projectUtils.js +10 -1
- package/package.json +4 -4
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serviceForEndpoint = exports.EVENT_SERVICE_MAPPING = exports.StorageService = exports.PubSubService = exports.NoOpService = void 0;
|
|
4
|
+
const backend = require("../backend");
|
|
5
|
+
const storage_1 = require("./storage");
|
|
6
|
+
const noop = () => Promise.resolve();
|
|
7
|
+
exports.NoOpService = {
|
|
8
|
+
name: "noop",
|
|
9
|
+
api: "",
|
|
10
|
+
requiredProjectBindings: undefined,
|
|
11
|
+
ensureTriggerRegion: noop,
|
|
12
|
+
};
|
|
13
|
+
exports.PubSubService = {
|
|
14
|
+
name: "pubsub",
|
|
15
|
+
api: "pubsub.googleapis.com",
|
|
16
|
+
requiredProjectBindings: undefined,
|
|
17
|
+
ensureTriggerRegion: noop,
|
|
18
|
+
};
|
|
19
|
+
exports.StorageService = {
|
|
20
|
+
name: "storage",
|
|
21
|
+
api: "storage.googleapis.com",
|
|
22
|
+
requiredProjectBindings: storage_1.obtainStorageBindings,
|
|
23
|
+
ensureTriggerRegion: storage_1.ensureStorageTriggerRegion,
|
|
24
|
+
};
|
|
25
|
+
exports.EVENT_SERVICE_MAPPING = {
|
|
26
|
+
"google.cloud.pubsub.topic.v1.messagePublished": exports.PubSubService,
|
|
27
|
+
"google.cloud.storage.object.v1.finalized": exports.StorageService,
|
|
28
|
+
"google.cloud.storage.object.v1.archived": exports.StorageService,
|
|
29
|
+
"google.cloud.storage.object.v1.deleted": exports.StorageService,
|
|
30
|
+
"google.cloud.storage.object.v1.metadataUpdated": exports.StorageService,
|
|
31
|
+
};
|
|
32
|
+
function serviceForEndpoint(endpoint) {
|
|
33
|
+
if (!backend.isEventTriggered(endpoint)) {
|
|
34
|
+
return exports.NoOpService;
|
|
35
|
+
}
|
|
36
|
+
return exports.EVENT_SERVICE_MAPPING[endpoint.eventTrigger.eventType] || exports.NoOpService;
|
|
37
|
+
}
|
|
38
|
+
exports.serviceForEndpoint = serviceForEndpoint;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureStorageTriggerRegion = exports.obtainStorageBindings = void 0;
|
|
4
|
+
const storage = require("../../../gcp/storage");
|
|
5
|
+
const logger_1 = require("../../../logger");
|
|
6
|
+
const error_1 = require("../../../error");
|
|
7
|
+
const location_1 = require("../../../gcp/location");
|
|
8
|
+
const PUBSUB_PUBLISHER_ROLE = "roles/pubsub.publisher";
|
|
9
|
+
async function obtainStorageBindings(projectId, existingPolicy) {
|
|
10
|
+
const storageResponse = await storage.getServiceAccount(projectId);
|
|
11
|
+
const storageServiceAgent = `serviceAccount:${storageResponse.email_address}`;
|
|
12
|
+
let pubsubBinding = existingPolicy.bindings.find((b) => b.role === PUBSUB_PUBLISHER_ROLE);
|
|
13
|
+
if (!pubsubBinding) {
|
|
14
|
+
pubsubBinding = {
|
|
15
|
+
role: PUBSUB_PUBLISHER_ROLE,
|
|
16
|
+
members: [],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (!pubsubBinding.members.find((m) => m === storageServiceAgent)) {
|
|
20
|
+
pubsubBinding.members.push(storageServiceAgent);
|
|
21
|
+
}
|
|
22
|
+
return [pubsubBinding];
|
|
23
|
+
}
|
|
24
|
+
exports.obtainStorageBindings = obtainStorageBindings;
|
|
25
|
+
async function ensureStorageTriggerRegion(endpoint, eventTrigger) {
|
|
26
|
+
if (!eventTrigger.region) {
|
|
27
|
+
logger_1.logger.debug("Looking up bucket region for the storage event trigger");
|
|
28
|
+
try {
|
|
29
|
+
const bucket = await storage.getBucket(eventTrigger.eventFilters.bucket);
|
|
30
|
+
eventTrigger.region = bucket.location.toLowerCase();
|
|
31
|
+
logger_1.logger.debug("Setting the event trigger region to", eventTrigger.region, ".");
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
throw new error_1.FirebaseError("Can't find the storage bucket region", { original: err });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (endpoint.region !== eventTrigger.region &&
|
|
38
|
+
eventTrigger.region !== "us-central1" &&
|
|
39
|
+
!location_1.regionInLocation(endpoint.region, eventTrigger.region)) {
|
|
40
|
+
throw new error_1.FirebaseError(`A function in region ${endpoint.region} cannot listen to a bucket in region ${eventTrigger.region}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.ensureStorageTriggerRegion = ensureStorageTriggerRegion;
|
|
@@ -1,40 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.ensureTriggerRegions = void 0;
|
|
4
4
|
const backend = require("./backend");
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
const logger_1 = require("../../logger");
|
|
8
|
-
const noop = () => Promise.resolve();
|
|
9
|
-
const LOOKUP_BY_EVENT_TYPE = {
|
|
10
|
-
"google.cloud.pubsub.topic.v1.messagePublished": noop,
|
|
11
|
-
"google.cloud.storage.object.v1.finalized": lookupBucketRegion,
|
|
12
|
-
"google.cloud.storage.object.v1.archived": lookupBucketRegion,
|
|
13
|
-
"google.cloud.storage.object.v1.deleted": lookupBucketRegion,
|
|
14
|
-
"google.cloud.storage.object.v1.metadataUpdated": lookupBucketRegion,
|
|
15
|
-
};
|
|
16
|
-
async function lookupMissingTriggerRegions(want) {
|
|
5
|
+
const services_1 = require("./services");
|
|
6
|
+
async function ensureTriggerRegions(want) {
|
|
17
7
|
const regionLookups = [];
|
|
18
8
|
for (const ep of backend.allEndpoints(want)) {
|
|
19
|
-
if (ep.platform === "gcfv1" || !backend.isEventTriggered(ep)
|
|
9
|
+
if (ep.platform === "gcfv1" || !backend.isEventTriggered(ep)) {
|
|
20
10
|
continue;
|
|
21
11
|
}
|
|
22
|
-
|
|
23
|
-
if (!lookup) {
|
|
24
|
-
logger_1.logger.debug("Don't know how to look up trigger region for event type", ep.eventTrigger.eventType, ". Deploy will fail unless this event type is global");
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
regionLookups.push(lookup(ep));
|
|
12
|
+
regionLookups.push(services_1.serviceForEndpoint(ep).ensureTriggerRegion(ep, ep.eventTrigger));
|
|
28
13
|
}
|
|
29
14
|
await Promise.all(regionLookups);
|
|
30
15
|
}
|
|
31
|
-
exports.
|
|
32
|
-
async function lookupBucketRegion(endpoint) {
|
|
33
|
-
try {
|
|
34
|
-
const bucket = await storage.getBucket(endpoint.eventTrigger.eventFilters.bucket);
|
|
35
|
-
endpoint.eventTrigger.region = bucket.location.toLowerCase();
|
|
36
|
-
}
|
|
37
|
-
catch (err) {
|
|
38
|
-
throw new error_1.FirebaseError("Can't find the storage bucket region", { original: err });
|
|
39
|
-
}
|
|
40
|
-
}
|
|
16
|
+
exports.ensureTriggerRegions = ensureTriggerRegions;
|
package/lib/deploy/index.js
CHANGED
|
@@ -38,6 +38,7 @@ var deploy = function (targetNames, options, customContext = {}) {
|
|
|
38
38
|
var deploys = [];
|
|
39
39
|
var releases = [];
|
|
40
40
|
var postdeploys = [];
|
|
41
|
+
var startTime = Date.now();
|
|
41
42
|
for (var i = 0; i < targetNames.length; i++) {
|
|
42
43
|
var targetName = targetNames[i];
|
|
43
44
|
var target = TARGETS[targetName];
|
|
@@ -75,8 +76,15 @@ var deploy = function (targetNames, options, customContext = {}) {
|
|
|
75
76
|
})
|
|
76
77
|
.then(function () {
|
|
77
78
|
if (_.has(options, "config.notes.databaseRules")) {
|
|
78
|
-
track("Rules Deploy", options.config.notes.databaseRules);
|
|
79
|
+
return track("Rules Deploy", options.config.notes.databaseRules);
|
|
79
80
|
}
|
|
81
|
+
return;
|
|
82
|
+
})
|
|
83
|
+
.then(function () {
|
|
84
|
+
const duration = Date.now() - startTime;
|
|
85
|
+
return track("Product Deploy", [...targetNames].sort().join(","), duration);
|
|
86
|
+
})
|
|
87
|
+
.then(function () {
|
|
80
88
|
logger.info();
|
|
81
89
|
utils.logSuccess(clc.underline.bold("Deploy complete!"));
|
|
82
90
|
logger.info();
|
|
@@ -178,7 +178,7 @@ function registerHandlers(app, getProjectStateByApiKey) {
|
|
|
178
178
|
: `
|
|
179
179
|
<span class="mdc-list-item__graphic material-icons" aria-hidden=true>person</span>`}
|
|
180
180
|
<span class="mdc-list-item__text"><span class="mdc-list-item__primary-text">${info.displayName || "(No display name)"}</span>
|
|
181
|
-
<span class="mdc-list-item__secondary-text fallback-secondary-text">${info.email || ""}</span>
|
|
181
|
+
<span class="mdc-list-item__secondary-text fallback-secondary-text" id="reuse-email">${info.email || ""}</span>
|
|
182
182
|
</li>`)
|
|
183
183
|
.join("\n");
|
|
184
184
|
res.end(widget_ui_1.WIDGET_UI.replace(widget_ui_1.PROVIDERS_LIST_PLACEHOLDER, options));
|
|
@@ -1010,7 +1010,7 @@ function signInWithEmailLink(state, reqBody) {
|
|
|
1010
1010
|
}
|
|
1011
1011
|
}
|
|
1012
1012
|
function signInWithIdp(state, reqBody) {
|
|
1013
|
-
var _a, _b;
|
|
1013
|
+
var _a, _b, _c;
|
|
1014
1014
|
errors_1.assert(!state.disableAuth, "PROJECT_DISABLED");
|
|
1015
1015
|
errors_1.assert(state.usageMode !== state_1.UsageMode.PASSTHROUGH, "UNSUPPORTED_PASSTHROUGH_OPERATION");
|
|
1016
1016
|
if (reqBody.returnRefreshToken) {
|
|
@@ -1041,7 +1041,16 @@ function signInWithIdp(state, reqBody) {
|
|
|
1041
1041
|
throw new errors_1.NotImplementedError("The Auth Emulator only supports sign-in with credentials (id_token required).");
|
|
1042
1042
|
}
|
|
1043
1043
|
}
|
|
1044
|
-
let
|
|
1044
|
+
let samlResponse;
|
|
1045
|
+
let signInAttributes = undefined;
|
|
1046
|
+
if (normalizedUri.searchParams.get("SAMLResponse")) {
|
|
1047
|
+
samlResponse = JSON.parse(normalizedUri.searchParams.get("SAMLResponse"));
|
|
1048
|
+
signInAttributes = (_b = samlResponse.assertion) === null || _b === void 0 ? void 0 : _b.attributeStatements;
|
|
1049
|
+
errors_1.assert(samlResponse.assertion, "INVALID_IDP_RESPONSE ((Missing assertion in SAMLResponse.))");
|
|
1050
|
+
errors_1.assert(samlResponse.assertion.subject, "INVALID_IDP_RESPONSE ((Missing assertion.subject in SAMLResponse.))");
|
|
1051
|
+
errors_1.assert(samlResponse.assertion.subject.nameId, "INVALID_IDP_RESPONSE ((Missing assertion.subject.nameId in SAMLResponse.))");
|
|
1052
|
+
}
|
|
1053
|
+
let { response, rawId } = fakeFetchUserInfoFromIdp(providerId, claims, samlResponse);
|
|
1045
1054
|
response.oauthAccessToken =
|
|
1046
1055
|
oauthAccessToken || `FirebaseAuthEmulatorFakeAccessToken_${providerId}`;
|
|
1047
1056
|
response.oauthIdToken = oauthIdToken;
|
|
@@ -1101,12 +1110,12 @@ function signInWithIdp(state, reqBody) {
|
|
|
1101
1110
|
if (state instanceof state_1.TenantProjectState) {
|
|
1102
1111
|
response.tenantId = state.tenantId;
|
|
1103
1112
|
}
|
|
1104
|
-
if ((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") && ((
|
|
1113
|
+
if ((state.mfaConfig.state === "ENABLED" || state.mfaConfig.state === "MANDATORY") && ((_c = user.mfaInfo) === null || _c === void 0 ? void 0 : _c.length)) {
|
|
1105
1114
|
return Object.assign(Object.assign({}, response), mfaPending(state, user, providerId));
|
|
1106
1115
|
}
|
|
1107
1116
|
else {
|
|
1108
1117
|
user = state.updateUserByLocalId(user.localId, { lastLoginAt: Date.now().toString() });
|
|
1109
|
-
return Object.assign(Object.assign({}, response), issueTokens(state, user, providerId));
|
|
1118
|
+
return Object.assign(Object.assign({}, response), issueTokens(state, user, providerId, { signInAttributes }));
|
|
1110
1119
|
}
|
|
1111
1120
|
}
|
|
1112
1121
|
function signInWithPassword(state, reqBody) {
|
|
@@ -1391,7 +1400,7 @@ function redactPasswordHash(user) {
|
|
|
1391
1400
|
function hashPassword(password, salt) {
|
|
1392
1401
|
return `fakeHash:salt=${salt}:password=${password}`;
|
|
1393
1402
|
}
|
|
1394
|
-
function issueTokens(state, user, signInProvider, { extraClaims, secondFactor, } = {}) {
|
|
1403
|
+
function issueTokens(state, user, signInProvider, { extraClaims, secondFactor, signInAttributes, } = {}) {
|
|
1395
1404
|
user = state.updateUserByLocalId(user.localId, { lastRefreshAt: new Date().toISOString() });
|
|
1396
1405
|
const usageMode = state.usageMode === state_1.UsageMode.PASSTHROUGH ? "passthrough" : undefined;
|
|
1397
1406
|
const tenantId = state instanceof state_1.TenantProjectState ? state.tenantId : undefined;
|
|
@@ -1404,6 +1413,7 @@ function issueTokens(state, user, signInProvider, { extraClaims, secondFactor, }
|
|
|
1404
1413
|
secondFactor,
|
|
1405
1414
|
usageMode,
|
|
1406
1415
|
tenantId,
|
|
1416
|
+
signInAttributes,
|
|
1407
1417
|
});
|
|
1408
1418
|
const refreshToken = state.usageMode === state_1.UsageMode.DEFAULT
|
|
1409
1419
|
? state.createRefreshTokenFor(user, signInProvider, {
|
|
@@ -1435,7 +1445,7 @@ function parseIdToken(state, idToken) {
|
|
|
1435
1445
|
const signInProvider = decoded.payload.firebase.sign_in_provider;
|
|
1436
1446
|
return { user, signInProvider, payload: decoded.payload };
|
|
1437
1447
|
}
|
|
1438
|
-
function generateJwt(user, { projectId, signInProvider, expiresInSeconds, extraClaims = {}, secondFactor, usageMode, tenantId, }) {
|
|
1448
|
+
function generateJwt(user, { projectId, signInProvider, expiresInSeconds, extraClaims = {}, secondFactor, usageMode, tenantId, signInAttributes, }) {
|
|
1439
1449
|
const identities = {};
|
|
1440
1450
|
if (user.email) {
|
|
1441
1451
|
identities["email"] = [user.email];
|
|
@@ -1459,6 +1469,7 @@ function generateJwt(user, { projectId, signInProvider, expiresInSeconds, extraC
|
|
|
1459
1469
|
sign_in_second_factor: secondFactor === null || secondFactor === void 0 ? void 0 : secondFactor.provider,
|
|
1460
1470
|
usage_mode: usageMode,
|
|
1461
1471
|
tenant: tenantId,
|
|
1472
|
+
sign_in_attributes: signInAttributes,
|
|
1462
1473
|
} });
|
|
1463
1474
|
const jwtStr = jsonwebtoken_1.sign(customPayloadFields, "", {
|
|
1464
1475
|
algorithm: "none",
|
|
@@ -1603,7 +1614,8 @@ function parseClaims(idTokenOrJsonClaims) {
|
|
|
1603
1614
|
errors_1.assert(typeof claims.sub === "string", 'INVALID_IDP_RESPONSE : ((The "sub" field must be a string.))');
|
|
1604
1615
|
return claims;
|
|
1605
1616
|
}
|
|
1606
|
-
function fakeFetchUserInfoFromIdp(providerId, claims) {
|
|
1617
|
+
function fakeFetchUserInfoFromIdp(providerId, claims, samlResponse) {
|
|
1618
|
+
var _a, _b, _c, _d, _e;
|
|
1607
1619
|
const rawId = claims.sub;
|
|
1608
1620
|
const email = claims.email ? utils_1.canonicalizeEmailAddress(claims.email) : undefined;
|
|
1609
1621
|
const emailVerified = !!claims.email_verified;
|
|
@@ -1620,7 +1632,7 @@ function fakeFetchUserInfoFromIdp(providerId, claims) {
|
|
|
1620
1632
|
emailVerified,
|
|
1621
1633
|
photoUrl,
|
|
1622
1634
|
};
|
|
1623
|
-
let federatedId;
|
|
1635
|
+
let federatedId = rawId;
|
|
1624
1636
|
switch (providerId) {
|
|
1625
1637
|
case "google.com": {
|
|
1626
1638
|
federatedId = `https://accounts.google.com/${rawId}`;
|
|
@@ -1643,8 +1655,14 @@ function fakeFetchUserInfoFromIdp(providerId, claims) {
|
|
|
1643
1655
|
});
|
|
1644
1656
|
break;
|
|
1645
1657
|
}
|
|
1658
|
+
case (_a = providerId.match(/^saml\./)) === null || _a === void 0 ? void 0 : _a.input:
|
|
1659
|
+
const nameId = (_c = (_b = samlResponse === null || samlResponse === void 0 ? void 0 : samlResponse.assertion) === null || _b === void 0 ? void 0 : _b.subject) === null || _c === void 0 ? void 0 : _c.nameId;
|
|
1660
|
+
response.email = nameId && utils_1.isValidEmailAddress(nameId) ? nameId : response.email;
|
|
1661
|
+
response.emailVerified = true;
|
|
1662
|
+
response.rawUserInfo = JSON.stringify((_d = samlResponse === null || samlResponse === void 0 ? void 0 : samlResponse.assertion) === null || _d === void 0 ? void 0 : _d.attributeStatements);
|
|
1663
|
+
break;
|
|
1664
|
+
case (_e = providerId.match(/^oidc\./)) === null || _e === void 0 ? void 0 : _e.input:
|
|
1646
1665
|
default:
|
|
1647
|
-
federatedId = rawId;
|
|
1648
1666
|
response.rawUserInfo = JSON.stringify(claims);
|
|
1649
1667
|
break;
|
|
1650
1668
|
}
|
|
@@ -33,6 +33,7 @@ var firebaseAppId = query.get('appId');
|
|
|
33
33
|
var apn = query.get('apn');
|
|
34
34
|
var ibi = query.get('ibi');
|
|
35
35
|
var appIdentifier = apn || ibi;
|
|
36
|
+
var isSamlProvider = !!providerId.match(/^saml\./);
|
|
36
37
|
assert(
|
|
37
38
|
appName || clientId || firebaseAppId || appIdentifier,
|
|
38
39
|
'Missing one of appName / clientId / appId / apn / ibi query params.'
|
|
@@ -157,21 +158,34 @@ var reuseAccountEls = document.querySelectorAll('.js-reuse-account');
|
|
|
157
158
|
if (reuseAccountEls.length) {
|
|
158
159
|
[].forEach.call(reuseAccountEls, function (el) {
|
|
159
160
|
var urlEncodedIdToken = el.dataset.idToken;
|
|
161
|
+
const decoded = JSON.parse(decodeURIComponent(urlEncodedIdToken));
|
|
160
162
|
el.addEventListener('click', function (e) {
|
|
161
163
|
e.preventDefault();
|
|
162
|
-
finishWithUser(urlEncodedIdToken);
|
|
164
|
+
finishWithUser(urlEncodedIdToken, decoded.email);
|
|
163
165
|
});
|
|
164
166
|
});
|
|
165
167
|
} else {
|
|
166
168
|
document.querySelector('.js-accounts-help-text').textContent = "No " + formattedProviderId + " accounts exist in the Auth Emulator.";
|
|
167
169
|
}
|
|
168
170
|
|
|
169
|
-
function finishWithUser(urlEncodedIdToken) {
|
|
171
|
+
function finishWithUser(urlEncodedIdToken, email) {
|
|
170
172
|
// Use widget URL, but replace all query parameters (no apiKey etc.).
|
|
171
173
|
var url = window.location.href.split('?')[0];
|
|
172
174
|
// Avoid URLSearchParams for browser compatibility.
|
|
173
175
|
url += '?providerId=' + encodeURIComponent(providerId);
|
|
174
176
|
url += '&id_token=' + urlEncodedIdToken;
|
|
177
|
+
|
|
178
|
+
// Save reasonable defaults for SAML providers
|
|
179
|
+
if (isSamlProvider) {
|
|
180
|
+
url += '&SAMLResponse=' + encodeURIComponent(JSON.stringify({
|
|
181
|
+
assertion: {
|
|
182
|
+
subject: {
|
|
183
|
+
nameId: email,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
|
|
175
189
|
saveAuthEvent({
|
|
176
190
|
type: authType,
|
|
177
191
|
eventId: eventId,
|
|
@@ -220,7 +234,7 @@ document.getElementById('main-form').addEventListener('submit', function(e) {
|
|
|
220
234
|
if (screenName) claims.screenName = screenName;
|
|
221
235
|
if (photoUrl) claims.photoUrl = photoUrl;
|
|
222
236
|
|
|
223
|
-
finishWithUser(createFakeClaims(claims));
|
|
237
|
+
finishWithUser(createFakeClaims(claims), claims.email);
|
|
224
238
|
}
|
|
225
239
|
});
|
|
226
240
|
|
|
@@ -17,13 +17,13 @@ const EMULATOR_INSTANCE_KILL_TIMEOUT = 4000;
|
|
|
17
17
|
const CACHE_DIR = process.env.FIREBASE_EMULATORS_PATH || path.join(os.homedir(), ".cache", "firebase", "emulators");
|
|
18
18
|
exports.DownloadDetails = {
|
|
19
19
|
database: {
|
|
20
|
-
downloadPath: path.join(CACHE_DIR, "firebase-database-emulator-v4.7.
|
|
21
|
-
version: "4.7.
|
|
20
|
+
downloadPath: path.join(CACHE_DIR, "firebase-database-emulator-v4.7.3.jar"),
|
|
21
|
+
version: "4.7.3",
|
|
22
22
|
opts: {
|
|
23
23
|
cacheDir: CACHE_DIR,
|
|
24
|
-
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v4.7.
|
|
25
|
-
expectedSize:
|
|
26
|
-
expectedChecksum: "
|
|
24
|
+
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v4.7.3.jar",
|
|
25
|
+
expectedSize: 28862098,
|
|
26
|
+
expectedChecksum: "8f696f24ee89c937a789498a0c0e4899",
|
|
27
27
|
namePrefix: "firebase-database-emulator",
|
|
28
28
|
},
|
|
29
29
|
},
|
|
@@ -29,6 +29,7 @@ const defaultCredentials_1 = require("../defaultCredentials");
|
|
|
29
29
|
const adminSdkConfig_1 = require("./adminSdkConfig");
|
|
30
30
|
const functionsEnv = require("../functions/env");
|
|
31
31
|
const types_2 = require("./events/types");
|
|
32
|
+
const validate_1 = require("../deploy/functions/validate");
|
|
32
33
|
const EVENT_INVOKE = "functions:invoke";
|
|
33
34
|
const DATABASE_PATH_PATTERN = new RegExp("^projects/[^/]+/instances/([^/]+)/refs(/.*)$");
|
|
34
35
|
class FunctionsEmulator {
|
|
@@ -138,6 +139,9 @@ class FunctionsEmulator {
|
|
|
138
139
|
else {
|
|
139
140
|
triggerKey = `${this.args.projectId}:${proto.eventType}`;
|
|
140
141
|
}
|
|
142
|
+
if (proto.data.bucket) {
|
|
143
|
+
triggerKey += `:${proto.data.bucket}`;
|
|
144
|
+
}
|
|
141
145
|
const triggers = this.multicastTriggers[triggerKey] || [];
|
|
142
146
|
triggers.forEach((triggerId) => {
|
|
143
147
|
this.workQueue.submit(() => {
|
|
@@ -247,6 +251,13 @@ class FunctionsEmulator {
|
|
|
247
251
|
return !anyEnabledMatch;
|
|
248
252
|
});
|
|
249
253
|
for (const definition of toSetup) {
|
|
254
|
+
try {
|
|
255
|
+
validate_1.functionIdsAreValid([definition]);
|
|
256
|
+
}
|
|
257
|
+
catch (e) {
|
|
258
|
+
this.logger.logLabeled("WARN", `functions[${definition.id}]`, `Invalid function id: ${e.message}`);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
250
261
|
let added = false;
|
|
251
262
|
let url = undefined;
|
|
252
263
|
if (definition.httpsTrigger) {
|
|
@@ -396,7 +407,10 @@ class FunctionsEmulator {
|
|
|
396
407
|
}
|
|
397
408
|
addStorageTrigger(projectId, key, eventTrigger) {
|
|
398
409
|
logger_1.logger.debug(`addStorageTrigger`, JSON.stringify({ eventTrigger }));
|
|
399
|
-
const
|
|
410
|
+
const bucket = eventTrigger.resource.startsWith("projects/_/buckets/")
|
|
411
|
+
? eventTrigger.resource.split("/")[3]
|
|
412
|
+
: eventTrigger.resource;
|
|
413
|
+
const eventTriggerId = `${projectId}:${eventTrigger.eventType}:${bucket}`;
|
|
400
414
|
const triggers = this.multicastTriggers[eventTriggerId] || [];
|
|
401
415
|
triggers.push(key);
|
|
402
416
|
this.multicastTriggers[eventTriggerId] = triggers;
|
|
@@ -55,6 +55,7 @@ function emulatedFunctionsByRegion(definitions) {
|
|
|
55
55
|
defDeepCopy.regions = [region];
|
|
56
56
|
defDeepCopy.region = region;
|
|
57
57
|
defDeepCopy.id = `${region}-${defDeepCopy.name}`;
|
|
58
|
+
defDeepCopy.platform = defDeepCopy.platform || "gcfv1";
|
|
58
59
|
regionDefinitions.push(defDeepCopy);
|
|
59
60
|
}
|
|
60
61
|
}
|
|
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PubsubEmulator = void 0;
|
|
4
4
|
const uuid = require("uuid");
|
|
5
5
|
const pubsub_1 = require("@google-cloud/pubsub");
|
|
6
|
-
const api = require("../api");
|
|
7
6
|
const downloadableEmulators = require("./downloadableEmulators");
|
|
7
|
+
const apiv2_1 = require("../apiv2");
|
|
8
8
|
const emulatorLogger_1 = require("./emulatorLogger");
|
|
9
9
|
const types_1 = require("../emulator/types");
|
|
10
10
|
const constants_1 = require("./constants");
|
|
@@ -87,47 +87,54 @@ class PubsubEmulator {
|
|
|
87
87
|
this.triggersForTopic.set(topicName, triggers);
|
|
88
88
|
this.subscriptionForTopic.set(topicName, sub);
|
|
89
89
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
context: {
|
|
97
|
-
eventId: uuid.v4(),
|
|
98
|
-
resource: {
|
|
99
|
-
service: "pubsub.googleapis.com",
|
|
100
|
-
name: `projects/${this.args.projectId}/topics/${topic}`,
|
|
101
|
-
},
|
|
102
|
-
eventType: "google.pubsub.topic.publish",
|
|
103
|
-
timestamp: message.publishTime.toISOString(),
|
|
104
|
-
},
|
|
105
|
-
data: {
|
|
106
|
-
data: message.data,
|
|
107
|
-
attributes: message.attributes,
|
|
108
|
-
},
|
|
109
|
-
} });
|
|
90
|
+
ensureFunctionsClient() {
|
|
91
|
+
if (this.client != undefined)
|
|
92
|
+
return;
|
|
93
|
+
const funcEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.FUNCTIONS);
|
|
94
|
+
if (!funcEmulator) {
|
|
95
|
+
throw new error_1.FirebaseError(`Attempted to execute pubsub trigger but could not find the Functions emulator`);
|
|
110
96
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
97
|
+
this.client = new apiv2_1.Client({
|
|
98
|
+
urlPrefix: `http://${registry_1.EmulatorRegistry.getInfoHostString(funcEmulator.getInfo())}`,
|
|
99
|
+
auth: false,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
createLegacyEventRequestBody(topic, message) {
|
|
103
|
+
return {
|
|
104
|
+
context: {
|
|
105
|
+
eventId: uuid.v4(),
|
|
106
|
+
resource: {
|
|
107
|
+
service: "pubsub.googleapis.com",
|
|
108
|
+
name: `projects/${this.args.projectId}/topics/${topic}`,
|
|
119
109
|
},
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
110
|
+
eventType: "google.pubsub.topic.publish",
|
|
111
|
+
timestamp: message.publishTime.toISOString(),
|
|
112
|
+
},
|
|
113
|
+
data: {
|
|
114
|
+
data: message.data,
|
|
115
|
+
attributes: message.attributes,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
createCloudEventRequestBody(topic, message) {
|
|
120
|
+
const data = {
|
|
121
|
+
message: {
|
|
122
|
+
messageId: message.id,
|
|
123
|
+
publishTime: message.publishTime,
|
|
124
|
+
attributes: message.attributes,
|
|
125
|
+
orderingKey: message.orderingKey,
|
|
126
|
+
data: message.data.toString("base64"),
|
|
127
|
+
},
|
|
128
|
+
subscription: this.subscriptionForTopic.get(topic).name,
|
|
129
|
+
};
|
|
130
|
+
return {
|
|
131
|
+
specversion: "1",
|
|
132
|
+
id: uuid.v4(),
|
|
133
|
+
time: message.publishTime.toISOString(),
|
|
134
|
+
type: "google.cloud.pubsub.topic.v1.messagePublished",
|
|
135
|
+
source: `//pubsub.googleapis.com/projects/${this.args.projectId}/topics/${topic}`,
|
|
136
|
+
data,
|
|
137
|
+
};
|
|
131
138
|
}
|
|
132
139
|
async onMessage(topicName, message) {
|
|
133
140
|
this.logger.logLabeled("DEBUG", "pubsub", `onMessage(${topicName}, ${message.id})`);
|
|
@@ -135,14 +142,20 @@ class PubsubEmulator {
|
|
|
135
142
|
if (!triggers || triggers.length === 0) {
|
|
136
143
|
throw new error_1.FirebaseError(`No trigger for topic: ${topicName}`);
|
|
137
144
|
}
|
|
138
|
-
if (!registry_1.EmulatorRegistry.get(types_1.Emulators.FUNCTIONS)) {
|
|
139
|
-
throw new error_1.FirebaseError(`Attempted to execute pubsub trigger for topic ${topicName} but could not find Functions emulator`);
|
|
140
|
-
}
|
|
141
145
|
this.logger.logLabeled("DEBUG", "pubsub", `Executing ${triggers.length} matching triggers (${JSON.stringify(triggers.map((t) => t.triggerKey))})`);
|
|
146
|
+
this.ensureFunctionsClient();
|
|
142
147
|
for (const { triggerKey, signatureType } of triggers) {
|
|
143
|
-
const reqOpts = this.getRequestOptions(topicName, message, signatureType);
|
|
144
148
|
try {
|
|
145
|
-
|
|
149
|
+
const path = `/functions/projects/${this.args.projectId}/triggers/${triggerKey}`;
|
|
150
|
+
if (signatureType === "event") {
|
|
151
|
+
await this.client.post(path, this.createLegacyEventRequestBody(topicName, message));
|
|
152
|
+
}
|
|
153
|
+
else if (signatureType === "cloudevent") {
|
|
154
|
+
await this.client.post(path, this.createCloudEventRequestBody(topicName, message), { headers: { "Content-Type": "application/cloudevents+json; charset=UTF-8" } });
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
throw new error_1.FirebaseError(`Unsupported trigger signature: ${signatureType}`);
|
|
158
|
+
}
|
|
146
159
|
}
|
|
147
160
|
catch (e) {
|
|
148
161
|
this.logger.logLabeled("DEBUG", "pubsub", e);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.StorageCloudFunctions = void 0;
|
|
4
|
+
const uuid = require("uuid");
|
|
4
5
|
const registry_1 = require("../registry");
|
|
5
6
|
const types_1 = require("../types");
|
|
6
7
|
const emulatorLogger_1 = require("../emulatorLogger");
|
|
@@ -57,7 +58,7 @@ class StorageCloudFunctions {
|
|
|
57
58
|
}
|
|
58
59
|
createLegacyEventRequestBody(action, objectMetadataPayload) {
|
|
59
60
|
const timestamp = new Date();
|
|
60
|
-
return
|
|
61
|
+
return {
|
|
61
62
|
eventId: `${timestamp.getTime()}`,
|
|
62
63
|
timestamp: metadata_1.toSerializedDate(timestamp),
|
|
63
64
|
eventType: `google.storage.object.${action}`,
|
|
@@ -67,20 +68,26 @@ class StorageCloudFunctions {
|
|
|
67
68
|
type: "storage#object",
|
|
68
69
|
},
|
|
69
70
|
data: objectMetadataPayload,
|
|
70
|
-
}
|
|
71
|
+
};
|
|
71
72
|
}
|
|
72
73
|
createCloudEventRequestBody(action, objectMetadataPayload) {
|
|
73
74
|
const ceAction = STORAGE_V2_ACTION_MAP[action];
|
|
74
75
|
if (!ceAction) {
|
|
75
|
-
throw new Error("Action is not
|
|
76
|
+
throw new Error("Action is not defined as a CloudEvents action");
|
|
76
77
|
}
|
|
77
78
|
const data = objectMetadataPayload;
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
let time = new Date().toISOString();
|
|
80
|
+
if (data.updated) {
|
|
81
|
+
time = typeof data.updated === "string" ? data.updated : data.updated.toISOString();
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
specversion: "1",
|
|
85
|
+
id: uuid.v4(),
|
|
80
86
|
type: `google.cloud.storage.object.v1.${ceAction}`,
|
|
81
87
|
source: `//storage.googleapis.com/projects/_/buckets/${objectMetadataPayload.bucket}/objects/${objectMetadataPayload.name}`,
|
|
88
|
+
time,
|
|
82
89
|
data,
|
|
83
|
-
}
|
|
90
|
+
};
|
|
84
91
|
}
|
|
85
92
|
}
|
|
86
93
|
exports.StorageCloudFunctions = StorageCloudFunctions;
|
package/lib/ensureApiEnabled.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ensure = exports.
|
|
4
|
-
const _ = require("lodash");
|
|
3
|
+
exports.ensure = exports.check = exports.POLL_SETTINGS = void 0;
|
|
5
4
|
const cli_color_1 = require("cli-color");
|
|
6
5
|
const track = require("./track");
|
|
7
|
-
const
|
|
6
|
+
const api_1 = require("./api");
|
|
7
|
+
const apiv2_1 = require("./apiv2");
|
|
8
8
|
const utils = require("./utils");
|
|
9
9
|
const error_1 = require("./error");
|
|
10
10
|
exports.POLL_SETTINGS = {
|
|
11
11
|
pollInterval: 10000,
|
|
12
12
|
pollsBeforeRetry: 12,
|
|
13
13
|
};
|
|
14
|
+
const apiClient = new apiv2_1.Client({
|
|
15
|
+
urlPrefix: api_1.serviceUsageOrigin,
|
|
16
|
+
apiVersion: "v1",
|
|
17
|
+
});
|
|
14
18
|
async function check(projectId, apiName, prefix, silent = false) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
origin: api.serviceUsageOrigin,
|
|
18
|
-
});
|
|
19
|
-
const isEnabled = _.get(response.body, "state") === "ENABLED";
|
|
19
|
+
const res = await apiClient.get(`/projects/${projectId}/services/${apiName}`);
|
|
20
|
+
const isEnabled = res.body.state === "ENABLED";
|
|
20
21
|
if (isEnabled && !silent) {
|
|
21
22
|
utils.logLabeledSuccess(prefix, `required API ${cli_color_1.bold(apiName)} is enabled`);
|
|
22
23
|
}
|
|
@@ -25,10 +26,7 @@ async function check(projectId, apiName, prefix, silent = false) {
|
|
|
25
26
|
exports.check = check;
|
|
26
27
|
async function enable(projectId, apiName) {
|
|
27
28
|
try {
|
|
28
|
-
await
|
|
29
|
-
auth: true,
|
|
30
|
-
origin: api.serviceUsageOrigin,
|
|
31
|
-
});
|
|
29
|
+
await apiClient.post(`/projects/${projectId}/services/${apiName}:enable`);
|
|
32
30
|
}
|
|
33
31
|
catch (err) {
|
|
34
32
|
if (error_1.isBillingError(err)) {
|
|
@@ -39,7 +37,6 @@ https://console.firebase.google.com/project/${projectId}/usage/details`);
|
|
|
39
37
|
throw err;
|
|
40
38
|
}
|
|
41
39
|
}
|
|
42
|
-
exports.enable = enable;
|
|
43
40
|
async function pollCheckEnabled(projectId, apiName, prefix, silent, enablementRetries, pollRetries = 0) {
|
|
44
41
|
if (pollRetries > exports.POLL_SETTINGS.pollsBeforeRetry) {
|
|
45
42
|
return enableApiWithRetries(projectId, apiName, prefix, silent, enablementRetries + 1);
|
|
@@ -49,7 +46,7 @@ async function pollCheckEnabled(projectId, apiName, prefix, silent, enablementRe
|
|
|
49
46
|
});
|
|
50
47
|
const isEnabled = await check(projectId, apiName, prefix, silent);
|
|
51
48
|
if (isEnabled) {
|
|
52
|
-
track("api_enabled", apiName);
|
|
49
|
+
void track("api_enabled", apiName);
|
|
53
50
|
return;
|
|
54
51
|
}
|
|
55
52
|
if (!silent) {
|