firebase-tools 13.16.0 → 13.17.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/lib/api.js +3 -1
- package/lib/apphosting/index.js +2 -1
- package/lib/commands/dataconnect-sql-diff.js +2 -1
- package/lib/commands/dataconnect-sql-migrate.js +0 -1
- package/lib/commands/firestore-databases-restore.js +39 -1
- package/lib/dataconnect/schemaMigration.js +134 -48
- package/lib/dataconnect/types.js +1 -0
- package/lib/deploy/functions/release/planner.js +2 -1
- package/lib/emulator/apphosting/index.js +38 -0
- package/lib/emulator/apphosting/serve.js +26 -0
- package/lib/emulator/auth/handlers.js +27 -0
- package/lib/emulator/auth/operations.js +41 -3
- package/lib/emulator/auth/state.js +2 -1
- package/lib/emulator/constants.js +3 -0
- package/lib/emulator/controller.js +12 -0
- package/lib/emulator/downloadableEmulators.js +9 -9
- package/lib/emulator/portUtils.js +1 -0
- package/lib/emulator/registry.js +1 -0
- package/lib/emulator/types.js +3 -0
- package/lib/experiments.js +4 -0
- package/lib/firestore/api.js +2 -1
- package/lib/firestore/options.js +7 -0
- package/lib/gcp/cloudsql/connect.js +49 -37
- package/lib/gcp/cloudsql/permissions.js +160 -0
- package/lib/init/features/dataconnect/index.js +1 -1
- package/lib/utils.js +9 -1
- package/package.json +1 -1
- package/schema/firebase-config.json +12 -0
package/lib/api.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.cloudRunApiOrigin = exports.hostingApiOrigin = exports.firebaseStorageOrigin = exports.storageOrigin = exports.runtimeconfigOrigin = exports.rulesOrigin = exports.resourceManagerOrigin = exports.remoteConfigApiOrigin = exports.rtdbMetadataOrigin = exports.rtdbManagementOrigin = exports.realtimeOrigin = exports.extensionsTOSOrigin = exports.extensionsPublisherOrigin = exports.extensionsOrigin = exports.iamOrigin = exports.identityOrigin = exports.hostingOrigin = exports.googleOrigin = exports.pubsubOrigin = exports.cloudTasksOrigin = exports.cloudschedulerOrigin = exports.cloudbuildOrigin = exports.functionsDefaultRegion = exports.runOrigin = exports.functionsV2Origin = exports.functionsOrigin = exports.firestoreOrigin = exports.firestoreOriginOrEmulator = exports.firedataOrigin = exports.firebaseExtensionsRegistryOrigin = exports.firebaseApiOrigin = exports.eventarcOrigin = exports.dynamicLinksKey = exports.dynamicLinksOrigin = exports.consoleOrigin = exports.authOrigin = exports.apphostingGitHubAppInstallationURL = exports.apphostingP4SADomain = exports.apphostingOrigin = exports.appDistributionOrigin = exports.artifactRegistryDomain = exports.developerConnectP4SADomain = exports.developerConnectOrigin = exports.containerRegistryDomain = exports.cloudMonitoringOrigin = exports.cloudloggingOrigin = exports.cloudbillingOrigin = exports.clientSecret = exports.clientId = exports.authProxyOrigin = void 0;
|
|
4
|
-
exports.setScopes = exports.getScopes = exports.vertexAIOrigin = exports.cloudSQLAdminOrigin = exports.dataConnectLocalConnString = exports.dataconnectOrigin = exports.githubClientSecret = exports.githubClientId = exports.computeOrigin = exports.secretManagerOrigin = exports.githubApiOrigin = exports.githubOrigin = exports.serviceUsageOrigin = void 0;
|
|
4
|
+
exports.setScopes = exports.getScopes = exports.vertexAIOrigin = exports.cloudSQLAdminOrigin = exports.dataConnectLocalConnString = exports.dataconnectP4SADomain = exports.dataconnectOrigin = exports.githubClientSecret = exports.githubClientId = exports.computeOrigin = exports.secretManagerOrigin = exports.githubApiOrigin = exports.githubOrigin = exports.serviceUsageOrigin = void 0;
|
|
5
5
|
const constants_1 = require("./emulator/constants");
|
|
6
6
|
const logger_1 = require("./logger");
|
|
7
7
|
const scopes = require("./scopes");
|
|
@@ -128,6 +128,8 @@ const githubClientSecret = () => utils.envOverride("GITHUB_CLIENT_SECRET", "3330
|
|
|
128
128
|
exports.githubClientSecret = githubClientSecret;
|
|
129
129
|
const dataconnectOrigin = () => utils.envOverride("FIREBASE_DATACONNECT_URL", "https://firebasedataconnect.googleapis.com");
|
|
130
130
|
exports.dataconnectOrigin = dataconnectOrigin;
|
|
131
|
+
const dataconnectP4SADomain = () => utils.envOverride("FIREBASE_DATACONNECT_P4SA_DOMAIN", "gcp-sa-firebasedataconnect.iam.gserviceaccount.com");
|
|
132
|
+
exports.dataconnectP4SADomain = dataconnectP4SADomain;
|
|
131
133
|
const dataConnectLocalConnString = () => utils.envOverride("FIREBASE_DATACONNECT_POSTGRESQL_STRING", "");
|
|
132
134
|
exports.dataConnectLocalConnString = dataConnectLocalConnString;
|
|
133
135
|
const cloudSQLAdminOrigin = () => utils.envOverride("CLOUD_SQL_URL", "https://sqladmin.googleapis.com");
|
package/lib/apphosting/index.js
CHANGED
|
@@ -33,7 +33,8 @@ async function tlsReady(url) {
|
|
|
33
33
|
}
|
|
34
34
|
catch (err) {
|
|
35
35
|
const maybeNodeError = err;
|
|
36
|
-
if (/HANDSHAKE_FAILURE/.test((_a = maybeNodeError === null || maybeNodeError === void 0 ? void 0 : maybeNodeError.cause) === null || _a === void 0 ? void 0 : _a.code)
|
|
36
|
+
if (/HANDSHAKE_FAILURE/.test((_a = maybeNodeError === null || maybeNodeError === void 0 ? void 0 : maybeNodeError.cause) === null || _a === void 0 ? void 0 : _a.code) ||
|
|
37
|
+
"EPROTO" === (maybeNodeError === null || maybeNodeError === void 0 ? void 0 : maybeNodeError.code)) {
|
|
37
38
|
return false;
|
|
38
39
|
}
|
|
39
40
|
return true;
|
|
@@ -17,9 +17,10 @@ exports.command = new command_1.Command("dataconnect:sql:diff [serviceId]")
|
|
|
17
17
|
])
|
|
18
18
|
.before(requireAuth_1.requireAuth)
|
|
19
19
|
.action(async (serviceId, options) => {
|
|
20
|
+
var _a;
|
|
20
21
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
21
22
|
await (0, ensureApis_1.ensureApis)(projectId);
|
|
22
23
|
const serviceInfo = await (0, fileUtils_1.pickService)(projectId, options.config, serviceId);
|
|
23
|
-
const diffs = await (0, schemaMigration_1.diffSchema)(serviceInfo.schema);
|
|
24
|
+
const diffs = await (0, schemaMigration_1.diffSchema)(serviceInfo.schema, (_a = serviceInfo.dataConnectYaml.schema.datasource.postgresql) === null || _a === void 0 ? void 0 : _a.schemaValidation);
|
|
24
25
|
return { projectId, serviceId, diffs };
|
|
25
26
|
});
|
|
@@ -17,7 +17,6 @@ exports.command = new command_1.Command("dataconnect:sql:migrate [serviceId]")
|
|
|
17
17
|
"firebasedataconnect.schemas.list",
|
|
18
18
|
"firebasedataconnect.schemas.update",
|
|
19
19
|
"cloudsql.instances.connect",
|
|
20
|
-
"cloudsql.users.create",
|
|
21
20
|
])
|
|
22
21
|
.before(requireAuth_1.requireAuth)
|
|
23
22
|
.withForce("Execute any required database changes without prompting")
|
|
@@ -8,12 +8,17 @@ const logger_1 = require("../logger");
|
|
|
8
8
|
const requirePermissions_1 = require("../requirePermissions");
|
|
9
9
|
const types_1 = require("../emulator/types");
|
|
10
10
|
const commandUtils_1 = require("../emulator/commandUtils");
|
|
11
|
+
const options_1 = require("../firestore/options");
|
|
11
12
|
const pretty_print_1 = require("../firestore/pretty-print");
|
|
12
13
|
const error_1 = require("../error");
|
|
13
14
|
exports.command = new command_1.Command("firestore:databases:restore")
|
|
14
15
|
.description("Restore a Firestore database in your Firebase project.")
|
|
15
16
|
.option("-d, --database <databaseID>", "ID of the database to restore into")
|
|
16
17
|
.option("-b, --backup <backup>", "Backup from which to restore")
|
|
18
|
+
.option("-e, --encryption-type <encryptionType>", `Encryption method of the restored database; one of ${options_1.EncryptionType.USE_SOURCE_ENCRYPTION} (default), ` +
|
|
19
|
+
`${options_1.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION}, ${options_1.EncryptionType.GOOGLE_DEFAULT_ENCRYPTION}`)
|
|
20
|
+
.option("-k, --kms-key-name <kmsKeyName>", "Resource ID of the Cloud KMS key to encrypt the restored database. This " +
|
|
21
|
+
"feature is allowlist only in initial launch.")
|
|
17
22
|
.before(requirePermissions_1.requirePermissions, ["datastore.backups.restoreDatabase"])
|
|
18
23
|
.before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.FIRESTORE)
|
|
19
24
|
.action(async (options) => {
|
|
@@ -28,7 +33,28 @@ exports.command = new command_1.Command("firestore:databases:restore")
|
|
|
28
33
|
throw new error_1.FirebaseError(`Missing required flag --backup. ${helpCommandText}`);
|
|
29
34
|
}
|
|
30
35
|
const backupName = options.backup;
|
|
31
|
-
|
|
36
|
+
let encryptionConfig = undefined;
|
|
37
|
+
switch (options.encryptionType) {
|
|
38
|
+
case options_1.EncryptionType.GOOGLE_DEFAULT_ENCRYPTION:
|
|
39
|
+
throwIfKmsKeyNameIsSet(options.kmsKeyName);
|
|
40
|
+
encryptionConfig = { googleDefaultEncryption: {} };
|
|
41
|
+
break;
|
|
42
|
+
case options_1.EncryptionType.USE_SOURCE_ENCRYPTION:
|
|
43
|
+
throwIfKmsKeyNameIsSet(options.kmsKeyName);
|
|
44
|
+
encryptionConfig = { useSourceEncryption: {} };
|
|
45
|
+
break;
|
|
46
|
+
case options_1.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION:
|
|
47
|
+
encryptionConfig = {
|
|
48
|
+
customerManagedEncryption: { kmsKeyName: getKmsKeyOrThrow(options.kmsKeyName) },
|
|
49
|
+
};
|
|
50
|
+
break;
|
|
51
|
+
case undefined:
|
|
52
|
+
throwIfKmsKeyNameIsSet(options.kmsKeyName);
|
|
53
|
+
break;
|
|
54
|
+
default:
|
|
55
|
+
throw new error_1.FirebaseError(`Invalid value for flag --encryption-type. ${helpCommandText}`);
|
|
56
|
+
}
|
|
57
|
+
const databaseResp = await api.restoreDatabase(options.project, databaseId, backupName, encryptionConfig);
|
|
32
58
|
if (options.json) {
|
|
33
59
|
logger_1.logger.info(JSON.stringify(databaseResp, undefined, 2));
|
|
34
60
|
}
|
|
@@ -40,4 +66,16 @@ exports.command = new command_1.Command("firestore:databases:restore")
|
|
|
40
66
|
logger_1.logger.info(`Once the restore is complete, your database may be viewed at ${printer.firebaseConsoleDatabaseUrl(options.project, databaseId)}`);
|
|
41
67
|
}
|
|
42
68
|
return databaseResp;
|
|
69
|
+
function throwIfKmsKeyNameIsSet(kmsKeyName) {
|
|
70
|
+
if (kmsKeyName) {
|
|
71
|
+
throw new error_1.FirebaseError("--kms-key-name can only be set when specifying an --encryption-type " +
|
|
72
|
+
`of ${options_1.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION}.`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function getKmsKeyOrThrow(kmsKeyName) {
|
|
76
|
+
if (kmsKeyName)
|
|
77
|
+
return kmsKeyName;
|
|
78
|
+
throw new error_1.FirebaseError("--kms-key-name must be provided when specifying an --encryption-type " +
|
|
79
|
+
`of ${options_1.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION}.`);
|
|
80
|
+
}
|
|
43
81
|
});
|
|
@@ -6,23 +6,41 @@ const sql_formatter_1 = require("sql-formatter");
|
|
|
6
6
|
const types_1 = require("./types");
|
|
7
7
|
const client_1 = require("./client");
|
|
8
8
|
const connect_1 = require("../gcp/cloudsql/connect");
|
|
9
|
+
const permissions_1 = require("../gcp/cloudsql/permissions");
|
|
9
10
|
const prompt_1 = require("../prompt");
|
|
10
11
|
const logger_1 = require("../logger");
|
|
11
12
|
const error_1 = require("../error");
|
|
12
|
-
const projectUtils_1 = require("../projectUtils");
|
|
13
13
|
const utils_1 = require("../utils");
|
|
14
14
|
const experiments = require("../experiments");
|
|
15
15
|
const errors = require("./errors");
|
|
16
|
-
async function diffSchema(schema) {
|
|
16
|
+
async function diffSchema(schema, schemaValidation) {
|
|
17
17
|
const { serviceName, instanceName, databaseId } = getIdentifiers(schema);
|
|
18
18
|
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, false);
|
|
19
|
-
|
|
19
|
+
let diffs = [];
|
|
20
|
+
let validationMode = "STRICT";
|
|
21
|
+
if (experiments.isEnabled("fdccompatiblemode")) {
|
|
22
|
+
if (!schemaValidation) {
|
|
23
|
+
validationMode = "COMPATIBLE";
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
validationMode = schemaValidation;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
setSchemaValidationMode(schema, validationMode);
|
|
20
30
|
try {
|
|
31
|
+
if (!schemaValidation && experiments.isEnabled("fdccompatiblemode")) {
|
|
32
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `generating required schema changes...`);
|
|
33
|
+
}
|
|
21
34
|
await (0, client_1.upsertSchema)(schema, true);
|
|
22
|
-
(
|
|
35
|
+
if (validationMode === "STRICT") {
|
|
36
|
+
(0, utils_1.logLabeledSuccess)("dataconnect", `Database schema is up to date.`);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
(0, utils_1.logLabeledSuccess)("dataconnect", `Database schema is compatible.`);
|
|
40
|
+
}
|
|
23
41
|
}
|
|
24
42
|
catch (err) {
|
|
25
|
-
if (err.status !== 400) {
|
|
43
|
+
if ((err === null || err === void 0 ? void 0 : err.status) !== 400) {
|
|
26
44
|
throw err;
|
|
27
45
|
}
|
|
28
46
|
const invalidConnectors = errors.getInvalidConnectors(err);
|
|
@@ -34,24 +52,56 @@ async function diffSchema(schema) {
|
|
|
34
52
|
displayInvalidConnectors(invalidConnectors);
|
|
35
53
|
}
|
|
36
54
|
if (incompatible) {
|
|
37
|
-
displaySchemaChanges(incompatible);
|
|
38
|
-
|
|
55
|
+
displaySchemaChanges(incompatible, validationMode, instanceName, databaseId);
|
|
56
|
+
diffs = incompatible.diffs;
|
|
39
57
|
}
|
|
40
58
|
}
|
|
41
|
-
|
|
59
|
+
if (experiments.isEnabled("fdccompatiblemode")) {
|
|
60
|
+
if (!schemaValidation) {
|
|
61
|
+
validationMode = "STRICT";
|
|
62
|
+
setSchemaValidationMode(schema, validationMode);
|
|
63
|
+
try {
|
|
64
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `generating schema changes, including optional changes...`);
|
|
65
|
+
await (0, client_1.upsertSchema)(schema, true);
|
|
66
|
+
(0, utils_1.logLabeledSuccess)("dataconnect", `no additional optional changes`);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
if ((err === null || err === void 0 ? void 0 : err.status) !== 400) {
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
const incompatible = errors.getIncompatibleSchemaError(err);
|
|
73
|
+
if (incompatible) {
|
|
74
|
+
if (!diffsEqual(diffs, incompatible.diffs)) {
|
|
75
|
+
if (diffs.length === 0) {
|
|
76
|
+
displaySchemaChanges(incompatible, "STRICT_AFTER_COMPATIBLE", instanceName, databaseId);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
displaySchemaChanges(incompatible, validationMode, instanceName, databaseId);
|
|
80
|
+
}
|
|
81
|
+
diffs = incompatible.diffs;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
(0, utils_1.logLabeledSuccess)("dataconnect", `no additional optional changes`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return diffs;
|
|
42
91
|
}
|
|
43
92
|
exports.diffSchema = diffSchema;
|
|
44
93
|
async function migrateSchema(args) {
|
|
45
94
|
const { options, schema, validateOnly } = args;
|
|
46
95
|
const { serviceName, instanceId, instanceName, databaseId } = getIdentifiers(schema);
|
|
47
96
|
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, true);
|
|
48
|
-
|
|
97
|
+
const validationMode = experiments.isEnabled("fdccompatiblemode") ? "COMPATIBLE" : "STRICT";
|
|
98
|
+
setSchemaValidationMode(schema, validationMode);
|
|
49
99
|
try {
|
|
50
100
|
await (0, client_1.upsertSchema)(schema, validateOnly);
|
|
51
101
|
logger_1.logger.debug(`Database schema was up to date for ${instanceId}:${databaseId}`);
|
|
52
102
|
}
|
|
53
103
|
catch (err) {
|
|
54
|
-
if (err.status !== 400) {
|
|
104
|
+
if ((err === null || err === void 0 ? void 0 : err.status) !== 400) {
|
|
55
105
|
throw err;
|
|
56
106
|
}
|
|
57
107
|
const incompatible = errors.getIncompatibleSchemaError(err);
|
|
@@ -59,7 +109,7 @@ async function migrateSchema(args) {
|
|
|
59
109
|
if (!incompatible && !invalidConnectors.length) {
|
|
60
110
|
throw err;
|
|
61
111
|
}
|
|
62
|
-
const migrationMode = await promptForSchemaMigration(options, databaseId, incompatible, validateOnly);
|
|
112
|
+
const migrationMode = await promptForSchemaMigration(options, instanceName, databaseId, incompatible, validateOnly, validationMode);
|
|
63
113
|
const shouldDeleteInvalidConnectors = await promptForInvalidConnectorError(options, serviceName, invalidConnectors, validateOnly);
|
|
64
114
|
let diffs = [];
|
|
65
115
|
if (incompatible) {
|
|
@@ -82,24 +132,23 @@ async function migrateSchema(args) {
|
|
|
82
132
|
return [];
|
|
83
133
|
}
|
|
84
134
|
exports.migrateSchema = migrateSchema;
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
database: databaseId,
|
|
95
|
-
cloudSql: {
|
|
96
|
-
instance: instanceName,
|
|
97
|
-
},
|
|
98
|
-
schemaValidation: "COMPATIBLE",
|
|
99
|
-
},
|
|
100
|
-
};
|
|
135
|
+
function diffsEqual(x, y) {
|
|
136
|
+
if (x.length !== y.length) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
for (let i = 0; i < x.length; i++) {
|
|
140
|
+
if (x[i].description !== y[i].description ||
|
|
141
|
+
x[i].destructive !== y[i].destructive ||
|
|
142
|
+
x[i].sql !== y[i].sql) {
|
|
143
|
+
return false;
|
|
101
144
|
}
|
|
102
145
|
}
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
function setSchemaValidationMode(schema, schemaValidation) {
|
|
149
|
+
if (experiments.isEnabled("fdccompatiblemode") && schema.primaryDatasource.postgresql) {
|
|
150
|
+
schema.primaryDatasource.postgresql.schemaValidation = schemaValidation;
|
|
151
|
+
}
|
|
103
152
|
}
|
|
104
153
|
function getIdentifiers(schema) {
|
|
105
154
|
var _a, _b;
|
|
@@ -131,8 +180,6 @@ async function handleIncompatibleSchemaError(args) {
|
|
|
131
180
|
if (incompatibleSchemaError.destructive && choice === "safe") {
|
|
132
181
|
throw new error_1.FirebaseError("This schema migration includes potentially destructive changes. If you'd like to execute it anyway, rerun this command with --force");
|
|
133
182
|
}
|
|
134
|
-
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
135
|
-
const iamUser = await (0, connect_1.setupIAMUser)(instanceId, databaseId, options);
|
|
136
183
|
const commandsToExecute = incompatibleSchemaError.diffs
|
|
137
184
|
.filter((d) => {
|
|
138
185
|
switch (choice) {
|
|
@@ -146,25 +193,36 @@ async function handleIncompatibleSchemaError(args) {
|
|
|
146
193
|
})
|
|
147
194
|
.map((d) => d.sql);
|
|
148
195
|
if (commandsToExecute.length) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
196
|
+
const commandsToExecuteBySuperUser = commandsToExecute.filter((sql) => sql.startsWith("CREATE EXTENSION") || sql.startsWith("CREATE SCHEMA"));
|
|
197
|
+
const commandsToExecuteByOwner = commandsToExecute.filter((sql) => !commandsToExecuteBySuperUser.includes(sql));
|
|
198
|
+
const userIsCSQLAdmin = await (0, permissions_1.iamUserIsCSQLAdmin)(options);
|
|
199
|
+
if (!userIsCSQLAdmin && commandsToExecuteBySuperUser.length) {
|
|
200
|
+
throw new error_1.FirebaseError(`Some SQL commands required for this migration require Admin permissions.\n
|
|
201
|
+
Please ask a user with 'roles/cloudsql.admin' to apply the following commands.\n
|
|
202
|
+
${commandsToExecuteBySuperUser.join("\n")}`);
|
|
203
|
+
}
|
|
204
|
+
if (userIsCSQLAdmin) {
|
|
205
|
+
await (0, connect_1.setupIAMUsers)(instanceId, databaseId, options);
|
|
206
|
+
}
|
|
207
|
+
if (!(await (0, permissions_1.checkSQLRoleIsGranted)(options, instanceId, databaseId, (0, permissions_1.firebaseowner)(databaseId), (await (0, connect_1.getIAMUser)(options)).user))) {
|
|
208
|
+
throw new error_1.FirebaseError(`Command aborted. Only users granted firebaseowner SQL role can run migrations.`);
|
|
209
|
+
}
|
|
210
|
+
if (commandsToExecuteBySuperUser.length) {
|
|
211
|
+
logger_1.logger.info(`The diffs require CloudSQL superuser permissions, attempting to apply changes as superuser.`);
|
|
212
|
+
await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, commandsToExecuteBySuperUser, false);
|
|
213
|
+
}
|
|
214
|
+
if (commandsToExecuteByOwner.length) {
|
|
215
|
+
await (0, connect_1.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, [`SET ROLE "${(0, permissions_1.firebaseowner)(databaseId)}"`, ...commandsToExecuteByOwner], false);
|
|
216
|
+
return incompatibleSchemaError.diffs;
|
|
217
|
+
}
|
|
160
218
|
}
|
|
161
219
|
return [];
|
|
162
220
|
}
|
|
163
|
-
async function promptForSchemaMigration(options,
|
|
221
|
+
async function promptForSchemaMigration(options, instanceName, databaseId, err, validateOnly, schemaValidation) {
|
|
164
222
|
if (!err) {
|
|
165
223
|
return "none";
|
|
166
224
|
}
|
|
167
|
-
displaySchemaChanges(err);
|
|
225
|
+
displaySchemaChanges(err, schemaValidation, instanceName, databaseId);
|
|
168
226
|
if (!options.nonInteractive) {
|
|
169
227
|
if (validateOnly && options.force) {
|
|
170
228
|
return "all";
|
|
@@ -179,7 +237,7 @@ async function promptForSchemaMigration(options, databaseName, err, validateOnly
|
|
|
179
237
|
{ name: "Abort changes", value: "none" },
|
|
180
238
|
];
|
|
181
239
|
return await (0, prompt_1.promptOnce)({
|
|
182
|
-
message: `Would you like to execute these changes against ${
|
|
240
|
+
message: `Would you like to execute these changes against ${databaseId}?`,
|
|
183
241
|
type: "list",
|
|
184
242
|
choices,
|
|
185
243
|
});
|
|
@@ -264,19 +322,47 @@ async function ensureServiceIsConnectedToCloudSql(serviceName, instanceId, datab
|
|
|
264
322
|
await (0, client_1.upsertSchema)(currentSchema, false);
|
|
265
323
|
}
|
|
266
324
|
catch (err) {
|
|
267
|
-
if (err.status >= 500) {
|
|
325
|
+
if ((err === null || err === void 0 ? void 0 : err.status) >= 500) {
|
|
268
326
|
throw err;
|
|
269
327
|
}
|
|
270
328
|
logger_1.logger.debug(err);
|
|
271
329
|
}
|
|
272
330
|
}
|
|
273
|
-
function displaySchemaChanges(error) {
|
|
331
|
+
function displaySchemaChanges(error, schemaValidation, instanceName, databaseId) {
|
|
274
332
|
switch (error.violationType) {
|
|
275
333
|
case "INCOMPATIBLE_SCHEMA":
|
|
276
334
|
{
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
335
|
+
let message;
|
|
336
|
+
if (schemaValidation === "COMPATIBLE") {
|
|
337
|
+
message =
|
|
338
|
+
"Your new application schema is incompatible with the schema of your PostgreSQL database " +
|
|
339
|
+
databaseId +
|
|
340
|
+
" in your CloudSQL instance " +
|
|
341
|
+
instanceName +
|
|
342
|
+
". " +
|
|
343
|
+
"The following SQL statements will migrate your database schema to be compatible with your new Data Connect schema.\n" +
|
|
344
|
+
error.diffs.map(toString).join("\n");
|
|
345
|
+
}
|
|
346
|
+
else if (schemaValidation === "STRICT_AFTER_COMPATIBLE") {
|
|
347
|
+
message =
|
|
348
|
+
"Your new application schema is compatible with the schema of your PostgreSQL database " +
|
|
349
|
+
databaseId +
|
|
350
|
+
" in your CloudSQL instance " +
|
|
351
|
+
instanceName +
|
|
352
|
+
", but contains unused tables or columns. " +
|
|
353
|
+
"The following optional SQL statements will migrate your database schema to match your new Data Connect schema.\n" +
|
|
354
|
+
error.diffs.map(toString).join("\n");
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
message =
|
|
358
|
+
"Your new application schema does not match the schema of your PostgreSQL database " +
|
|
359
|
+
databaseId +
|
|
360
|
+
" in your CloudSQL instance " +
|
|
361
|
+
instanceName +
|
|
362
|
+
". " +
|
|
363
|
+
"The following SQL statements will migrate your database schema to match your new Data Connect schema.\n" +
|
|
364
|
+
error.diffs.map(toString).join("\n");
|
|
365
|
+
}
|
|
280
366
|
(0, utils_1.logLabeledWarning)("dataconnect", message);
|
|
281
367
|
}
|
|
282
368
|
break;
|
package/lib/dataconnect/types.js
CHANGED
|
@@ -207,7 +207,8 @@ function checkForIllegalUpdate(want, have) {
|
|
|
207
207
|
exports.checkForIllegalUpdate = checkForIllegalUpdate;
|
|
208
208
|
function checkForV2Upgrade(want, have) {
|
|
209
209
|
if (want.platform === "gcfv2" && have.platform === "gcfv1") {
|
|
210
|
-
throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(have)}] Upgrading from
|
|
210
|
+
throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(have)}] Upgrading from 1st Gen to 2nd Gen is not yet supported. ` +
|
|
211
|
+
"See https://firebase.google.com/docs/functions/2nd-gen-upgrade before migrating to 2nd Gen.");
|
|
211
212
|
}
|
|
212
213
|
}
|
|
213
214
|
exports.checkForV2Upgrade = checkForV2Upgrade;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AppHostingEmulator = void 0;
|
|
4
|
+
const emulatorLogger_1 = require("../emulatorLogger");
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
const serve_1 = require("./serve");
|
|
7
|
+
class AppHostingEmulator {
|
|
8
|
+
constructor(args) {
|
|
9
|
+
this.args = args;
|
|
10
|
+
this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.APPHOSTING);
|
|
11
|
+
}
|
|
12
|
+
async start() {
|
|
13
|
+
this.args.options.host = this.args.host;
|
|
14
|
+
this.args.options.port = this.args.port;
|
|
15
|
+
this.logger.logLabeled("INFO", types_1.Emulators.APPHOSTING, "starting apphosting emulator");
|
|
16
|
+
const { port } = await (0, serve_1.start)(this.args.options);
|
|
17
|
+
this.logger.logLabeled("INFO", types_1.Emulators.APPHOSTING, `serving on port ${port}`);
|
|
18
|
+
}
|
|
19
|
+
connect() {
|
|
20
|
+
this.logger.logLabeled("INFO", types_1.Emulators.APPHOSTING, "connecting apphosting emulator");
|
|
21
|
+
return Promise.resolve();
|
|
22
|
+
}
|
|
23
|
+
stop() {
|
|
24
|
+
this.logger.logLabeled("INFO", types_1.Emulators.APPHOSTING, "stopping apphosting emulator");
|
|
25
|
+
return Promise.resolve();
|
|
26
|
+
}
|
|
27
|
+
getInfo() {
|
|
28
|
+
return {
|
|
29
|
+
name: types_1.Emulators.APPHOSTING,
|
|
30
|
+
host: this.args.host,
|
|
31
|
+
port: this.args.port,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
getName() {
|
|
35
|
+
return types_1.Emulators.APPHOSTING;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.AppHostingEmulator = AppHostingEmulator;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serve = exports.start = void 0;
|
|
4
|
+
const net_1 = require("net");
|
|
5
|
+
const portUtils_1 = require("../portUtils");
|
|
6
|
+
const spawn_1 = require("../../init/spawn");
|
|
7
|
+
async function start(options) {
|
|
8
|
+
let port = options.port;
|
|
9
|
+
while (!(await availablePort(options.host, port))) {
|
|
10
|
+
port += 1;
|
|
11
|
+
}
|
|
12
|
+
serve(options, port);
|
|
13
|
+
return { port };
|
|
14
|
+
}
|
|
15
|
+
exports.start = start;
|
|
16
|
+
function availablePort(host, port) {
|
|
17
|
+
return (0, portUtils_1.checkListenable)({
|
|
18
|
+
address: host,
|
|
19
|
+
port,
|
|
20
|
+
family: (0, net_1.isIPv4)(host) ? "IPv4" : "IPv6",
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async function serve(options, port) {
|
|
24
|
+
await (0, spawn_1.wrapSpawn)("npm", ["run", "dev", "--", "-H", options.host, "-p", port], process.cwd());
|
|
25
|
+
}
|
|
26
|
+
exports.serve = serve;
|
|
@@ -131,6 +131,33 @@ function registerHandlers(app, getProjectStateByApiKey) {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
+
case "verifyAndChangeEmail": {
|
|
135
|
+
try {
|
|
136
|
+
const { newEmail } = (0, operations_1.setAccountInfoImpl)(state, { oobCode });
|
|
137
|
+
if (continueUrl) {
|
|
138
|
+
return res.redirect(303, continueUrl);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
return res.status(200).json({
|
|
142
|
+
authEmulator: { success: `The email has been successfully changed.`, newEmail },
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
if (e instanceof errors_1.NotImplementedError ||
|
|
148
|
+
(e instanceof errors_1.BadRequestError && e.message === "INVALID_OOB_CODE")) {
|
|
149
|
+
return res.status(400).json({
|
|
150
|
+
authEmulator: {
|
|
151
|
+
error: `Your request to change your email has expired or the link has already been used.`,
|
|
152
|
+
instructions: `Try changing your email again.`,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
throw e;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
134
161
|
case "signIn": {
|
|
135
162
|
if (!continueUrl) {
|
|
136
163
|
return res.status(400).json({
|
|
@@ -616,6 +616,7 @@ function resetPassword(state, reqBody) {
|
|
|
616
616
|
kind: "identitytoolkit#ResetPasswordResponse",
|
|
617
617
|
requestType: oob.requestType,
|
|
618
618
|
email: oob.requestType === "EMAIL_SIGNIN" ? undefined : oob.email,
|
|
619
|
+
newEmail: oob.newEmail,
|
|
619
620
|
};
|
|
620
621
|
}
|
|
621
622
|
exports.resetPassword = resetPassword;
|
|
@@ -630,6 +631,7 @@ function sendOobCode(state, reqBody, ctx) {
|
|
|
630
631
|
(0, errors_1.assert)((0, utils_1.parseAbsoluteUri)(reqBody.continueUrl), "INVALID_CONTINUE_URI : ((expected an absolute URI with valid scheme and host))");
|
|
631
632
|
}
|
|
632
633
|
let email;
|
|
634
|
+
let newEmail;
|
|
633
635
|
let mode;
|
|
634
636
|
switch (reqBody.requestType) {
|
|
635
637
|
case "EMAIL_SIGNIN":
|
|
@@ -665,6 +667,24 @@ function sendOobCode(state, reqBody, ctx) {
|
|
|
665
667
|
email = user.email;
|
|
666
668
|
}
|
|
667
669
|
break;
|
|
670
|
+
case "VERIFY_AND_CHANGE_EMAIL":
|
|
671
|
+
mode = "verifyAndChangeEmail";
|
|
672
|
+
(0, errors_1.assert)(reqBody.newEmail, "MISSING_NEW_EMAIL");
|
|
673
|
+
newEmail = (0, utils_1.canonicalizeEmailAddress)(reqBody.newEmail);
|
|
674
|
+
if (reqBody.returnOobLink && !reqBody.idToken) {
|
|
675
|
+
(0, errors_1.assert)(reqBody.email, "MISSING_EMAIL");
|
|
676
|
+
email = (0, utils_1.canonicalizeEmailAddress)(reqBody.email);
|
|
677
|
+
const maybeUser = state.getUserByEmail(email);
|
|
678
|
+
(0, errors_1.assert)(maybeUser, "USER_NOT_FOUND");
|
|
679
|
+
}
|
|
680
|
+
else {
|
|
681
|
+
(0, errors_1.assert)(reqBody.idToken, "MISSING_ID_TOKEN");
|
|
682
|
+
const user = parseIdToken(state, reqBody.idToken).user;
|
|
683
|
+
(0, errors_1.assert)(user.email, "MISSING_EMAIL");
|
|
684
|
+
email = user.email;
|
|
685
|
+
}
|
|
686
|
+
(0, errors_1.assert)(!state.getUserByEmail(newEmail), "EMAIL_EXISTS");
|
|
687
|
+
break;
|
|
668
688
|
default:
|
|
669
689
|
throw new errors_1.NotImplementedError(reqBody.requestType);
|
|
670
690
|
}
|
|
@@ -676,6 +696,7 @@ function sendOobCode(state, reqBody, ctx) {
|
|
|
676
696
|
requestType: reqBody.requestType,
|
|
677
697
|
mode,
|
|
678
698
|
continueUrl: reqBody.continueUrl,
|
|
699
|
+
newEmail,
|
|
679
700
|
});
|
|
680
701
|
if (reqBody.returnOobLink) {
|
|
681
702
|
return {
|
|
@@ -747,6 +768,7 @@ function setAccountInfoImpl(state, reqBody, { privileged = false, emulatorUrl =
|
|
|
747
768
|
let user;
|
|
748
769
|
let signInProvider;
|
|
749
770
|
let isEmailUpdate = false;
|
|
771
|
+
let newEmail;
|
|
750
772
|
if (reqBody.oobCode) {
|
|
751
773
|
const oob = state.validateOobCode(reqBody.oobCode);
|
|
752
774
|
(0, errors_1.assert)(oob, "INVALID_OOB_CODE");
|
|
@@ -763,6 +785,19 @@ function setAccountInfoImpl(state, reqBody, { privileged = false, emulatorUrl =
|
|
|
763
785
|
}
|
|
764
786
|
break;
|
|
765
787
|
}
|
|
788
|
+
case "VERIFY_AND_CHANGE_EMAIL":
|
|
789
|
+
state.deleteOobCode(reqBody.oobCode);
|
|
790
|
+
const maybeUser = state.getUserByEmail(oob.email);
|
|
791
|
+
(0, errors_1.assert)(maybeUser, "INVALID_OOB_CODE");
|
|
792
|
+
(0, errors_1.assert)(oob.newEmail, "INVALID_OOB_CODE");
|
|
793
|
+
(0, errors_1.assert)(!state.getUserByEmail(oob.newEmail), "EMAIL_EXISTS");
|
|
794
|
+
user = maybeUser;
|
|
795
|
+
if (oob.newEmail !== user.email) {
|
|
796
|
+
updates.email = oob.newEmail;
|
|
797
|
+
updates.emailVerified = true;
|
|
798
|
+
newEmail = oob.newEmail;
|
|
799
|
+
}
|
|
800
|
+
break;
|
|
766
801
|
case "RECOVER_EMAIL": {
|
|
767
802
|
state.deleteOobCode(reqBody.oobCode);
|
|
768
803
|
const maybeUser = state.getUserByInitialEmail(oob.email);
|
|
@@ -792,7 +827,7 @@ function setAccountInfoImpl(state, reqBody, { privileged = false, emulatorUrl =
|
|
|
792
827
|
}
|
|
793
828
|
if (reqBody.email) {
|
|
794
829
|
(0, errors_1.assert)((0, utils_1.isValidEmailAddress)(reqBody.email), "INVALID_EMAIL");
|
|
795
|
-
|
|
830
|
+
newEmail = (0, utils_1.canonicalizeEmailAddress)(reqBody.email);
|
|
796
831
|
if (newEmail !== user.email) {
|
|
797
832
|
(0, errors_1.assert)(!state.getUserByEmail(newEmail), "EMAIL_EXISTS");
|
|
798
833
|
updates.email = newEmail;
|
|
@@ -882,7 +917,7 @@ function setAccountInfoImpl(state, reqBody, { privileged = false, emulatorUrl =
|
|
|
882
917
|
}
|
|
883
918
|
sendOobForEmailReset(state, user.initialEmail, emulatorUrl);
|
|
884
919
|
}
|
|
885
|
-
return redactPasswordHash(Object.assign({ kind: "identitytoolkit#SetAccountInfoResponse", localId: user.localId, emailVerified: user.emailVerified, providerUserInfo: user.providerUserInfo, email: user.email, displayName: user.displayName, photoUrl: user.photoUrl, passwordHash: user.passwordHash }, (updates.validSince && signInProvider ? issueTokens(state, user, signInProvider) : {})));
|
|
920
|
+
return redactPasswordHash(Object.assign({ kind: "identitytoolkit#SetAccountInfoResponse", localId: user.localId, emailVerified: user.emailVerified, providerUserInfo: user.providerUserInfo, email: user.email, displayName: user.displayName, photoUrl: user.photoUrl, newEmail, passwordHash: user.passwordHash }, (updates.validSince && signInProvider ? issueTokens(state, user, signInProvider) : {})));
|
|
886
921
|
}
|
|
887
922
|
exports.setAccountInfoImpl = setAccountInfoImpl;
|
|
888
923
|
function sendOobForEmailReset(state, initialEmail, url) {
|
|
@@ -893,7 +928,7 @@ function sendOobForEmailReset(state, initialEmail, url) {
|
|
|
893
928
|
logOobMessage(oobRecord);
|
|
894
929
|
}
|
|
895
930
|
function createOobRecord(state, email, url, params) {
|
|
896
|
-
const oobRecord = state.createOob(email, params.requestType, (oobCode) => {
|
|
931
|
+
const oobRecord = state.createOob(email, params.newEmail, params.requestType, (oobCode) => {
|
|
897
932
|
url.pathname = "/emulator/action";
|
|
898
933
|
url.searchParams.set("mode", params.mode);
|
|
899
934
|
url.searchParams.set("lang", "en");
|
|
@@ -923,6 +958,9 @@ function logOobMessage(oobRecord) {
|
|
|
923
958
|
case "VERIFY_EMAIL":
|
|
924
959
|
maybeMessage = `To verify the email address ${email}, follow this link: ${oobLink}`;
|
|
925
960
|
break;
|
|
961
|
+
case "VERIFY_AND_CHANGE_EMAIL":
|
|
962
|
+
maybeMessage = `To verify and change the email address from ${email} to ${oobRecord.newEmail}, follow this link: ${oobLink}`;
|
|
963
|
+
break;
|
|
926
964
|
case "RECOVER_EMAIL":
|
|
927
965
|
maybeMessage = `To reset your email address to ${email}, follow this link: ${oobLink}`;
|
|
928
966
|
break;
|
|
@@ -299,11 +299,12 @@ class ProjectState {
|
|
|
299
299
|
secondFactor: record.secondFactor,
|
|
300
300
|
};
|
|
301
301
|
}
|
|
302
|
-
createOob(email, requestType, generateLink) {
|
|
302
|
+
createOob(email, newEmail, requestType, generateLink) {
|
|
303
303
|
const oobCode = (0, utils_1.randomBase64UrlStr)(54);
|
|
304
304
|
const oobLink = generateLink(oobCode);
|
|
305
305
|
const oob = {
|
|
306
306
|
email,
|
|
307
|
+
newEmail,
|
|
307
308
|
requestType,
|
|
308
309
|
oobCode,
|
|
309
310
|
oobLink,
|
|
@@ -8,6 +8,7 @@ exports.DEFAULT_PORTS = {
|
|
|
8
8
|
hosting: 5000,
|
|
9
9
|
functions: 5001,
|
|
10
10
|
extensions: 5001,
|
|
11
|
+
apphosting: 5002,
|
|
11
12
|
firestore: 8080,
|
|
12
13
|
pubsub: 8085,
|
|
13
14
|
database: 9000,
|
|
@@ -22,6 +23,7 @@ exports.FIND_AVAILBLE_PORT_BY_DEFAULT = {
|
|
|
22
23
|
hub: true,
|
|
23
24
|
logging: true,
|
|
24
25
|
hosting: true,
|
|
26
|
+
apphosting: true,
|
|
25
27
|
functions: false,
|
|
26
28
|
firestore: false,
|
|
27
29
|
database: false,
|
|
@@ -38,6 +40,7 @@ exports.EMULATOR_DESCRIPTION = {
|
|
|
38
40
|
hub: "emulator hub",
|
|
39
41
|
logging: "Logging Emulator",
|
|
40
42
|
hosting: "Hosting Emulator",
|
|
43
|
+
apphosting: "App Hosting Emulator",
|
|
41
44
|
functions: "Functions Emulator",
|
|
42
45
|
firestore: "Firestore Emulator",
|
|
43
46
|
database: "Database Emulator",
|
|
@@ -45,6 +45,7 @@ const pubsubEmulator_1 = require("./pubsubEmulator");
|
|
|
45
45
|
const storage_1 = require("./storage");
|
|
46
46
|
const fileUtils_1 = require("../dataconnect/fileUtils");
|
|
47
47
|
const tasksEmulator_1 = require("./tasksEmulator");
|
|
48
|
+
const apphosting_1 = require("./apphosting");
|
|
48
49
|
const START_LOGGING_EMULATOR = utils.envOverride("START_LOGGING_EMULATOR", "false", (val) => val === "true");
|
|
49
50
|
async function exportOnExit(options) {
|
|
50
51
|
const exportOnExitDir = options.exportOnExit;
|
|
@@ -571,6 +572,17 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
571
572
|
});
|
|
572
573
|
await startEmulator(hostingEmulator);
|
|
573
574
|
}
|
|
575
|
+
if (experiments.isEnabled("emulatorapphosting")) {
|
|
576
|
+
if (listenForEmulator.apphosting) {
|
|
577
|
+
const apphostingAddr = legacyGetFirstAddr(types_1.Emulators.APPHOSTING);
|
|
578
|
+
const apphostingEmulator = new apphosting_1.AppHostingEmulator({
|
|
579
|
+
host: apphostingAddr.host,
|
|
580
|
+
port: apphostingAddr.port,
|
|
581
|
+
options,
|
|
582
|
+
});
|
|
583
|
+
await startEmulator(apphostingEmulator);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
574
586
|
if (listenForEmulator.logging) {
|
|
575
587
|
const loggingAddr = legacyGetFirstAddr(types_1.Emulators.LOGGING);
|
|
576
588
|
const loggingEmulator = new loggingEmulator_1.LoggingEmulator({
|
|
@@ -46,20 +46,20 @@ const EMULATOR_UPDATE_DETAILS = {
|
|
|
46
46
|
},
|
|
47
47
|
dataconnect: process.platform === "darwin"
|
|
48
48
|
? {
|
|
49
|
-
version: "1.3.
|
|
50
|
-
expectedSize:
|
|
51
|
-
expectedChecksum: "
|
|
49
|
+
version: "1.3.6",
|
|
50
|
+
expectedSize: 24867584,
|
|
51
|
+
expectedChecksum: "b924d31e3620d7ed4486a95e22629fc8",
|
|
52
52
|
}
|
|
53
53
|
: process.platform === "win32"
|
|
54
54
|
? {
|
|
55
|
-
version: "1.3.
|
|
56
|
-
expectedSize:
|
|
57
|
-
expectedChecksum: "
|
|
55
|
+
version: "1.3.6",
|
|
56
|
+
expectedSize: 25292288,
|
|
57
|
+
expectedChecksum: "45025491b43b55a94f4e4db8df903250",
|
|
58
58
|
}
|
|
59
59
|
: {
|
|
60
|
-
version: "1.3.
|
|
61
|
-
expectedSize:
|
|
62
|
-
expectedChecksum: "
|
|
60
|
+
version: "1.3.6",
|
|
61
|
+
expectedSize: 24785048,
|
|
62
|
+
expectedChecksum: "6ae5820c0470c5a954540ad97838ec01",
|
|
63
63
|
},
|
|
64
64
|
};
|
|
65
65
|
exports.DownloadDetails = {
|
package/lib/emulator/registry.js
CHANGED
package/lib/emulator/types.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Severity = exports.EmulatorLog = exports.FunctionsExecutionMode = exports.isEmulator = exports.isDownloadableEmulator = exports.ALL_EMULATORS = exports.EMULATORS_SUPPORTED_BY_USE_EMULATOR = exports.EMULATORS_SUPPORTED_BY_UI = exports.EMULATORS_SUPPORTED_BY_FUNCTIONS = exports.ALL_SERVICE_EMULATORS = exports.IMPORT_EXPORT_EMULATORS = exports.DOWNLOADABLE_EMULATORS = exports.Emulators = void 0;
|
|
4
|
+
const experiments = require("../experiments");
|
|
4
5
|
var Emulators;
|
|
5
6
|
(function (Emulators) {
|
|
6
7
|
Emulators["AUTH"] = "auth";
|
|
@@ -9,6 +10,7 @@ var Emulators;
|
|
|
9
10
|
Emulators["FIRESTORE"] = "firestore";
|
|
10
11
|
Emulators["DATABASE"] = "database";
|
|
11
12
|
Emulators["HOSTING"] = "hosting";
|
|
13
|
+
Emulators["APPHOSTING"] = "apphosting";
|
|
12
14
|
Emulators["PUBSUB"] = "pubsub";
|
|
13
15
|
Emulators["UI"] = "ui";
|
|
14
16
|
Emulators["LOGGING"] = "logging";
|
|
@@ -38,6 +40,7 @@ exports.ALL_SERVICE_EMULATORS = [
|
|
|
38
40
|
Emulators.FIRESTORE,
|
|
39
41
|
Emulators.DATABASE,
|
|
40
42
|
Emulators.HOSTING,
|
|
43
|
+
...(experiments.isEnabled("emulatorapphosting") ? [Emulators.APPHOSTING] : []),
|
|
41
44
|
Emulators.PUBSUB,
|
|
42
45
|
Emulators.STORAGE,
|
|
43
46
|
Emulators.EVENTARC,
|
package/lib/experiments.js
CHANGED
|
@@ -56,6 +56,10 @@ exports.ALL_EXPERIMENTS = experiments({
|
|
|
56
56
|
emulatoruisnapshot: {
|
|
57
57
|
shortDescription: "Load pre-release versions of the emulator UI",
|
|
58
58
|
},
|
|
59
|
+
emulatorapphosting: {
|
|
60
|
+
shortDescription: "App Hosting emulator",
|
|
61
|
+
public: false,
|
|
62
|
+
},
|
|
59
63
|
webframeworks: {
|
|
60
64
|
shortDescription: "Native support for popular web frameworks",
|
|
61
65
|
fullDescription: "Adds support for popular web frameworks such as Next.js " +
|
package/lib/firestore/api.js
CHANGED
|
@@ -459,11 +459,12 @@ class FirestoreApi {
|
|
|
459
459
|
}
|
|
460
460
|
return database;
|
|
461
461
|
}
|
|
462
|
-
async restoreDatabase(project, databaseId, backupName) {
|
|
462
|
+
async restoreDatabase(project, databaseId, backupName, encryptionConfig) {
|
|
463
463
|
const url = `/projects/${project}/databases:restore`;
|
|
464
464
|
const payload = {
|
|
465
465
|
databaseId,
|
|
466
466
|
backup: backupName,
|
|
467
|
+
encryptionConfig: encryptionConfig,
|
|
467
468
|
};
|
|
468
469
|
const options = { queryParams: { databaseId: databaseId } };
|
|
469
470
|
const res = await this.apiClient.post(url, payload, options);
|
package/lib/firestore/options.js
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EncryptionType = void 0;
|
|
4
|
+
var EncryptionType;
|
|
5
|
+
(function (EncryptionType) {
|
|
6
|
+
EncryptionType["CUSTOMER_MANAGED_ENCRYPTION"] = "CUSTOMER_MANAGED_ENCRYPTION";
|
|
7
|
+
EncryptionType["USE_SOURCE_ENCRYPTION"] = "USE_SOURCE_ENCRYPTION";
|
|
8
|
+
EncryptionType["GOOGLE_DEFAULT_ENCRYPTION"] = "GOOGLE_DEFAULT_ENCRYPTION";
|
|
9
|
+
})(EncryptionType = exports.EncryptionType || (exports.EncryptionType = {}));
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.toDatabaseUser = exports.setupIAMUsers = exports.getIAMUser = exports.getDataConnectP4SA = exports.executeSqlCmdsAsSuperUser = exports.executeSqlCmdsAsIamUser = exports.execute = void 0;
|
|
4
4
|
const pg = require("pg");
|
|
5
5
|
const cloud_sql_connector_1 = require("@google-cloud/cloud-sql-connector");
|
|
6
6
|
const requireAuth_1 = require("../../requireAuth");
|
|
7
7
|
const projectUtils_1 = require("../../projectUtils");
|
|
8
|
+
const api_1 = require("../../api");
|
|
8
9
|
const cloudSqlAdminClient = require("./cloudsqladmin");
|
|
9
10
|
const utils = require("../../utils");
|
|
10
11
|
const logger_1 = require("../../logger");
|
|
11
12
|
const error_1 = require("../../error");
|
|
12
13
|
const fbToolsAuthClient_1 = require("./fbToolsAuthClient");
|
|
14
|
+
const permissions_1 = require("./permissions");
|
|
13
15
|
async function execute(sqlStatements, opts) {
|
|
14
16
|
const logFn = opts.silent ? logger_1.logger.debug : logger_1.logger.info;
|
|
15
17
|
const instance = await cloudSqlAdminClient.getInstance(opts.projectId, opts.instanceId);
|
|
@@ -74,52 +76,61 @@ async function execute(sqlStatements, opts) {
|
|
|
74
76
|
connector.close();
|
|
75
77
|
}
|
|
76
78
|
exports.execute = execute;
|
|
77
|
-
async function
|
|
79
|
+
async function executeSqlCmdsAsIamUser(options, instanceId, databaseId, cmds, silent = false) {
|
|
78
80
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
81
|
+
const { user: iamUser } = await getIAMUser(options);
|
|
82
|
+
return await execute(cmds, {
|
|
83
|
+
projectId,
|
|
84
|
+
instanceId,
|
|
85
|
+
databaseId,
|
|
86
|
+
username: iamUser,
|
|
87
|
+
silent: silent,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
exports.executeSqlCmdsAsIamUser = executeSqlCmdsAsIamUser;
|
|
91
|
+
async function executeSqlCmdsAsSuperUser(options, instanceId, databaseId, cmds, silent = false) {
|
|
92
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
93
|
+
const superuser = "firebasesuperuser";
|
|
94
|
+
const temporaryPassword = utils.generateId(20);
|
|
95
|
+
await cloudSqlAdminClient.createUser(projectId, instanceId, "BUILT_IN", superuser, temporaryPassword);
|
|
96
|
+
return await execute([`SET ROLE = cloudsqlsuperuser`, ...cmds], {
|
|
97
|
+
projectId,
|
|
98
|
+
instanceId,
|
|
99
|
+
databaseId,
|
|
100
|
+
username: superuser,
|
|
101
|
+
password: temporaryPassword,
|
|
102
|
+
silent: silent,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
exports.executeSqlCmdsAsSuperUser = executeSqlCmdsAsSuperUser;
|
|
106
|
+
function getDataConnectP4SA(projectNumber) {
|
|
107
|
+
return `service-${projectNumber}@${(0, api_1.dataconnectP4SADomain)()}`;
|
|
108
|
+
}
|
|
109
|
+
exports.getDataConnectP4SA = getDataConnectP4SA;
|
|
110
|
+
async function getIAMUser(options) {
|
|
79
111
|
const account = await (0, requireAuth_1.requireAuth)(options);
|
|
80
112
|
if (!account) {
|
|
81
113
|
throw new error_1.FirebaseError("No account to set up! Run `firebase login` or set Application Default Credentials");
|
|
82
114
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
115
|
+
return toDatabaseUser(account);
|
|
116
|
+
}
|
|
117
|
+
exports.getIAMUser = getIAMUser;
|
|
118
|
+
async function setupIAMUsers(instanceId, databaseId, options) {
|
|
119
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
120
|
+
const { user, mode } = await getIAMUser(options);
|
|
87
121
|
await cloudSqlAdminClient.createUser(projectId, instanceId, mode, user);
|
|
122
|
+
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
123
|
+
const { user: fdcP4SAUser, mode: fdcP4SAmode } = toDatabaseUser(getDataConnectP4SA(projectNumber));
|
|
124
|
+
await cloudSqlAdminClient.createUser(projectId, instanceId, fdcP4SAmode, fdcP4SAUser);
|
|
125
|
+
await (0, permissions_1.setupSQLPermissions)(instanceId, databaseId, options, true);
|
|
88
126
|
const grants = [
|
|
89
|
-
`
|
|
90
|
-
|
|
91
|
-
begin
|
|
92
|
-
if not exists (select FROM pg_catalog.pg_roles
|
|
93
|
-
WHERE rolname = '${firebaseowner(databaseId)}') then
|
|
94
|
-
CREATE ROLE "${firebaseowner(databaseId)}" WITH ADMIN "${setupUser}";
|
|
95
|
-
end if;
|
|
96
|
-
end
|
|
97
|
-
$$
|
|
98
|
-
;`,
|
|
99
|
-
`GRANT ALL PRIVILEGES ON DATABASE "${databaseId}" TO "${firebaseowner(databaseId)}"`,
|
|
100
|
-
`GRANT cloudsqlsuperuser TO "${firebaseowner(databaseId)}"`,
|
|
101
|
-
`GRANT "${firebaseowner(databaseId)}" TO "${setupUser}"`,
|
|
102
|
-
`GRANT "${firebaseowner(databaseId)}" TO "${user}"`,
|
|
103
|
-
`ALTER SCHEMA public OWNER TO "${firebaseowner(databaseId)}"`,
|
|
104
|
-
`GRANT USAGE ON SCHEMA "public" TO PUBLIC`,
|
|
105
|
-
`GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA "public" TO PUBLIC`,
|
|
106
|
-
`GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA "public" TO PUBLIC`,
|
|
127
|
+
`GRANT "${(0, permissions_1.firebaseowner)(databaseId)}" TO "${user}"`,
|
|
128
|
+
`GRANT "${(0, permissions_1.firebasewriter)(databaseId)}" TO "${fdcP4SAUser}"`,
|
|
107
129
|
];
|
|
108
|
-
await
|
|
109
|
-
projectId,
|
|
110
|
-
instanceId,
|
|
111
|
-
databaseId,
|
|
112
|
-
username: setupUser,
|
|
113
|
-
password: temporaryPassword,
|
|
114
|
-
silent: true,
|
|
115
|
-
});
|
|
130
|
+
await executeSqlCmdsAsSuperUser(options, instanceId, databaseId, grants, true);
|
|
116
131
|
return user;
|
|
117
132
|
}
|
|
118
|
-
exports.
|
|
119
|
-
function firebaseowner(databaseId) {
|
|
120
|
-
return `firebaseowner_${databaseId}_public`;
|
|
121
|
-
}
|
|
122
|
-
exports.firebaseowner = firebaseowner;
|
|
133
|
+
exports.setupIAMUsers = setupIAMUsers;
|
|
123
134
|
function toDatabaseUser(account) {
|
|
124
135
|
let mode = "CLOUD_IAM_USER";
|
|
125
136
|
let user = account;
|
|
@@ -129,3 +140,4 @@ function toDatabaseUser(account) {
|
|
|
129
140
|
}
|
|
130
141
|
return { user, mode };
|
|
131
142
|
}
|
|
143
|
+
exports.toDatabaseUser = toDatabaseUser;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupSQLPermissions = exports.iamUserIsCSQLAdmin = exports.checkSQLRoleIsGranted = exports.firebasewriter = exports.firebasereader = exports.firebaseowner = void 0;
|
|
4
|
+
const projectUtils_1 = require("../../projectUtils");
|
|
5
|
+
const connect_1 = require("./connect");
|
|
6
|
+
const iam_1 = require("../iam");
|
|
7
|
+
const logger_1 = require("../../logger");
|
|
8
|
+
const lodash_1 = require("lodash");
|
|
9
|
+
const error_1 = require("../../error");
|
|
10
|
+
function firebaseowner(databaseId) {
|
|
11
|
+
return `firebaseowner_${databaseId}_public`;
|
|
12
|
+
}
|
|
13
|
+
exports.firebaseowner = firebaseowner;
|
|
14
|
+
function firebasereader(databaseId) {
|
|
15
|
+
return `firebasereader_${databaseId}_public`;
|
|
16
|
+
}
|
|
17
|
+
exports.firebasereader = firebasereader;
|
|
18
|
+
function firebasewriter(databaseId) {
|
|
19
|
+
return `firebasewriter_${databaseId}_public`;
|
|
20
|
+
}
|
|
21
|
+
exports.firebasewriter = firebasewriter;
|
|
22
|
+
async function checkSQLRoleIsGranted(options, instanceId, databaseId, grantedRole, granteeRole) {
|
|
23
|
+
const checkCmd = `
|
|
24
|
+
DO $$
|
|
25
|
+
DECLARE
|
|
26
|
+
role_count INTEGER;
|
|
27
|
+
BEGIN
|
|
28
|
+
-- Count the number of rows matching the criteria
|
|
29
|
+
SELECT COUNT(*)
|
|
30
|
+
INTO role_count
|
|
31
|
+
FROM
|
|
32
|
+
pg_auth_members m
|
|
33
|
+
JOIN
|
|
34
|
+
pg_roles grantee ON grantee.oid = m.member
|
|
35
|
+
JOIN
|
|
36
|
+
pg_roles granted ON granted.oid = m.roleid
|
|
37
|
+
JOIN
|
|
38
|
+
pg_roles grantor ON grantor.oid = m.grantor
|
|
39
|
+
WHERE
|
|
40
|
+
granted.rolname = '${grantedRole}'
|
|
41
|
+
AND grantee.rolname = '${granteeRole}';
|
|
42
|
+
|
|
43
|
+
-- If no rows were found, raise an exception
|
|
44
|
+
IF role_count = 0 THEN
|
|
45
|
+
RAISE EXCEPTION 'Role "%", is not granted to role "%".', '${grantedRole}', '${granteeRole}';
|
|
46
|
+
END IF;
|
|
47
|
+
END $$;
|
|
48
|
+
`;
|
|
49
|
+
try {
|
|
50
|
+
await (0, connect_1.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, [checkCmd], true);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
if (e instanceof error_1.FirebaseError && e.message.includes("not granted to role")) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
logger_1.logger.error(`Role Check Failed: ${e}`);
|
|
58
|
+
throw e;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.checkSQLRoleIsGranted = checkSQLRoleIsGranted;
|
|
62
|
+
async function iamUserIsCSQLAdmin(options) {
|
|
63
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
64
|
+
const requiredPermissions = [
|
|
65
|
+
"cloudsql.instances.connect",
|
|
66
|
+
"cloudsql.instances.get",
|
|
67
|
+
"cloudsql.users.create",
|
|
68
|
+
"cloudsql.users.update",
|
|
69
|
+
];
|
|
70
|
+
try {
|
|
71
|
+
const iamResult = await (0, iam_1.testIamPermissions)(projectId, requiredPermissions);
|
|
72
|
+
return iamResult.passed;
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
logger_1.logger.debug(`[iam] error while checking permissions, command may fail: ${err}`);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.iamUserIsCSQLAdmin = iamUserIsCSQLAdmin;
|
|
80
|
+
function ownerRolePermissions(databaseId, superuser, schema) {
|
|
81
|
+
const firebaseOwnerRole = firebaseowner(databaseId);
|
|
82
|
+
return [
|
|
83
|
+
`do
|
|
84
|
+
$$
|
|
85
|
+
begin
|
|
86
|
+
if not exists (select FROM pg_catalog.pg_roles
|
|
87
|
+
WHERE rolname = '${firebaseOwnerRole}') then
|
|
88
|
+
CREATE ROLE "${firebaseOwnerRole}" WITH ADMIN "${superuser}";
|
|
89
|
+
end if;
|
|
90
|
+
end
|
|
91
|
+
$$
|
|
92
|
+
;`,
|
|
93
|
+
`GRANT "${firebaseOwnerRole}" TO "cloudsqlsuperuser"`,
|
|
94
|
+
`ALTER SCHEMA "${schema}" OWNER TO "${firebaseOwnerRole}"`,
|
|
95
|
+
`GRANT USAGE ON SCHEMA "${schema}" TO "${firebaseOwnerRole}"`,
|
|
96
|
+
`GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA "${schema}" TO "${firebaseOwnerRole}"`,
|
|
97
|
+
`GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA "${schema}" TO "${firebaseOwnerRole}"`,
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
function writerRolePermissions(databaseId, superuser, schema) {
|
|
101
|
+
const firebaseWriterRole = firebasewriter(databaseId);
|
|
102
|
+
return [
|
|
103
|
+
`do
|
|
104
|
+
$$
|
|
105
|
+
begin
|
|
106
|
+
if not exists (select FROM pg_catalog.pg_roles
|
|
107
|
+
WHERE rolname = '${firebaseWriterRole}') then
|
|
108
|
+
CREATE ROLE "${firebaseWriterRole}" WITH ADMIN "${superuser}";
|
|
109
|
+
end if;
|
|
110
|
+
end
|
|
111
|
+
$$
|
|
112
|
+
;`,
|
|
113
|
+
`GRANT "${firebaseWriterRole}" TO "cloudsqlsuperuser"`,
|
|
114
|
+
`GRANT USAGE ON SCHEMA "${schema}" TO "${firebaseWriterRole}"`,
|
|
115
|
+
`GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE ON ALL TABLES IN SCHEMA "${schema}" TO "${firebaseWriterRole}"`,
|
|
116
|
+
`GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA "${schema}" TO "${firebaseWriterRole}"`,
|
|
117
|
+
`GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA "${schema}" TO "${firebaseWriterRole}"`,
|
|
118
|
+
`SET ROLE = '${firebaseowner(databaseId)}';`,
|
|
119
|
+
`ALTER DEFAULT PRIVILEGES IN SCHEMA "${schema}" GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE ON TABLES TO "${firebaseWriterRole}";`,
|
|
120
|
+
`ALTER DEFAULT PRIVILEGES IN SCHEMA "${schema}" GRANT USAGE ON SEQUENCES TO "${firebaseWriterRole}";`,
|
|
121
|
+
`ALTER DEFAULT PRIVILEGES IN SCHEMA "${schema}" GRANT EXECUTE ON FUNCTIONS TO "${firebaseWriterRole}"`,
|
|
122
|
+
`SET ROLE = cloudsqlsuperuser`,
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
function readerRolePermissions(databaseId, superuser, schema) {
|
|
126
|
+
const firebaseReaderRole = firebasereader(databaseId);
|
|
127
|
+
return [
|
|
128
|
+
`do
|
|
129
|
+
$$
|
|
130
|
+
begin
|
|
131
|
+
if not exists (select FROM pg_catalog.pg_roles
|
|
132
|
+
WHERE rolname = '${firebaseReaderRole}') then
|
|
133
|
+
CREATE ROLE "${firebaseReaderRole}" WITH ADMIN "${superuser}";
|
|
134
|
+
end if;
|
|
135
|
+
end
|
|
136
|
+
$$
|
|
137
|
+
;`,
|
|
138
|
+
`GRANT "${firebaseReaderRole}" TO "cloudsqlsuperuser"`,
|
|
139
|
+
`GRANT USAGE ON SCHEMA "${schema}" TO "${firebaseReaderRole}"`,
|
|
140
|
+
`GRANT SELECT ON ALL TABLES IN SCHEMA "${schema}" TO "${firebaseReaderRole}"`,
|
|
141
|
+
`GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA "${schema}" TO "${firebaseReaderRole}"`,
|
|
142
|
+
`GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA "${schema}" TO "${firebaseReaderRole}"`,
|
|
143
|
+
`SET ROLE = '${firebaseowner(databaseId)}';`,
|
|
144
|
+
`ALTER DEFAULT PRIVILEGES IN SCHEMA "${schema}" GRANT SELECT ON TABLES TO "${firebaseReaderRole}";`,
|
|
145
|
+
`ALTER DEFAULT PRIVILEGES IN SCHEMA "${schema}" GRANT SELECT, USAGE ON SEQUENCES TO "${firebaseReaderRole}";`,
|
|
146
|
+
`ALTER DEFAULT PRIVILEGES IN SCHEMA "${schema}" GRANT EXECUTE ON FUNCTIONS TO "${firebaseReaderRole}"`,
|
|
147
|
+
`SET ROLE = cloudsqlsuperuser`,
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
async function setupSQLPermissions(instanceId, databaseId, options, silent = false) {
|
|
151
|
+
const superuser = "firebasesuperuser";
|
|
152
|
+
const revokes = [];
|
|
153
|
+
if (await checkSQLRoleIsGranted(options, instanceId, databaseId, "cloudsqlsuperuser", firebaseowner(databaseId))) {
|
|
154
|
+
logger_1.logger.warn("Detected cloudsqlsuperuser was previously given to firebase owner, revoking to improve database security.");
|
|
155
|
+
revokes.push(`REVOKE "cloudsqlsuperuser" FROM "${firebaseowner(databaseId)}"`);
|
|
156
|
+
}
|
|
157
|
+
const sqlRoleSetupCmds = (0, lodash_1.concat)(revokes, [`CREATE SCHEMA IF NOT EXISTS "public"`], ownerRolePermissions(databaseId, superuser, "public"), writerRolePermissions(databaseId, superuser, "public"), readerRolePermissions(databaseId, superuser, "public"));
|
|
158
|
+
return (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, sqlRoleSetupCmds, silent);
|
|
159
|
+
}
|
|
160
|
+
exports.setupSQLPermissions = setupSQLPermissions;
|
package/lib/utils.js
CHANGED
|
@@ -589,6 +589,14 @@ function readSecretValue(prompt, dataFile) {
|
|
|
589
589
|
if (dataFile && dataFile !== "-") {
|
|
590
590
|
input = dataFile;
|
|
591
591
|
}
|
|
592
|
-
|
|
592
|
+
try {
|
|
593
|
+
return Promise.resolve(fs.readFileSync(input, "utf-8"));
|
|
594
|
+
}
|
|
595
|
+
catch (e) {
|
|
596
|
+
if (e.code === "ENOENT") {
|
|
597
|
+
throw new error_1.FirebaseError(`File not found: ${input}`, { original: e });
|
|
598
|
+
}
|
|
599
|
+
throw e;
|
|
600
|
+
}
|
|
593
601
|
}
|
|
594
602
|
exports.readSecretValue = readSecretValue;
|
package/package.json
CHANGED
|
@@ -357,6 +357,18 @@
|
|
|
357
357
|
"emulators": {
|
|
358
358
|
"additionalProperties": false,
|
|
359
359
|
"properties": {
|
|
360
|
+
"apphosting": {
|
|
361
|
+
"additionalProperties": false,
|
|
362
|
+
"properties": {
|
|
363
|
+
"host": {
|
|
364
|
+
"type": "string"
|
|
365
|
+
},
|
|
366
|
+
"port": {
|
|
367
|
+
"type": "number"
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
"type": "object"
|
|
371
|
+
},
|
|
360
372
|
"auth": {
|
|
361
373
|
"additionalProperties": false,
|
|
362
374
|
"properties": {
|