firebase-tools 10.4.2 → 10.7.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.
Files changed (108) hide show
  1. package/lib/bin/firebase.js +1 -1
  2. package/lib/command.js +4 -4
  3. package/lib/commands/deploy.js +1 -1
  4. package/lib/commands/emulators-start.js +13 -3
  5. package/lib/commands/ext-configure.js +15 -5
  6. package/lib/commands/ext-dev-emulators-start.js +5 -1
  7. package/lib/commands/ext-export.js +6 -5
  8. package/lib/commands/ext-install.js +28 -44
  9. package/lib/commands/ext-update.js +9 -1
  10. package/lib/commands/functions-delete.js +2 -5
  11. package/lib/commands/functions-secrets-destroy.js +23 -3
  12. package/lib/commands/functions-secrets-prune.js +15 -12
  13. package/lib/commands/functions-secrets-set.js +51 -4
  14. package/lib/commands/hosting-channel-deploy.js +2 -2
  15. package/lib/deploy/database/deploy.js +4 -0
  16. package/lib/deploy/database/index.js +1 -0
  17. package/lib/deploy/extensions/deploy.js +4 -4
  18. package/lib/deploy/extensions/deploymentSummary.js +8 -5
  19. package/lib/deploy/extensions/planner.js +36 -9
  20. package/lib/deploy/extensions/prepare.js +1 -1
  21. package/lib/deploy/extensions/secrets.js +2 -2
  22. package/lib/deploy/extensions/tasks.js +60 -21
  23. package/lib/deploy/functions/backend.js +17 -6
  24. package/lib/deploy/functions/build.js +162 -0
  25. package/lib/deploy/functions/checkIam.js +6 -5
  26. package/lib/deploy/functions/deploy.js +14 -15
  27. package/lib/deploy/functions/ensure.js +4 -4
  28. package/lib/deploy/functions/functionsDeployHelper.js +54 -23
  29. package/lib/deploy/functions/prepare.js +92 -39
  30. package/lib/deploy/functions/prepareFunctionsUpload.js +16 -21
  31. package/lib/deploy/functions/pricing.js +6 -3
  32. package/lib/deploy/functions/prompts.js +1 -7
  33. package/lib/deploy/functions/release/fabricator.js +44 -5
  34. package/lib/deploy/functions/release/index.js +31 -6
  35. package/lib/deploy/functions/release/planner.js +10 -8
  36. package/lib/deploy/functions/release/reporter.js +14 -11
  37. package/lib/deploy/functions/runtimes/discovery/parsing.js +12 -6
  38. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +61 -13
  39. package/lib/deploy/functions/runtimes/node/index.js +1 -1
  40. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +3 -3
  41. package/lib/deploy/functions/runtimes/node/parseTriggers.js +29 -24
  42. package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
  43. package/lib/deploy/functions/services/auth.js +95 -0
  44. package/lib/deploy/functions/services/index.js +41 -21
  45. package/lib/deploy/functions/services/storage.js +1 -6
  46. package/lib/deploy/functions/validate.js +8 -5
  47. package/lib/deploy/hosting/args.js +2 -0
  48. package/lib/deploy/hosting/convertConfig.js +37 -8
  49. package/lib/deploy/hosting/deploy.js +3 -3
  50. package/lib/deploy/hosting/prepare.js +2 -2
  51. package/lib/deploy/hosting/release.js +6 -2
  52. package/lib/deploy/index.js +82 -93
  53. package/lib/deploy/remoteconfig/deploy.js +4 -0
  54. package/lib/deploy/remoteconfig/index.js +3 -1
  55. package/lib/emulator/auth/operations.js +26 -20
  56. package/lib/emulator/auth/state.js +79 -43
  57. package/lib/emulator/auth/utils.js +3 -25
  58. package/lib/emulator/commandUtils.js +72 -2
  59. package/lib/emulator/controller.js +14 -5
  60. package/lib/emulator/downloadableEmulators.js +47 -24
  61. package/lib/emulator/extensions/postinstall.js +41 -0
  62. package/lib/emulator/extensions/validation.js +2 -2
  63. package/lib/emulator/extensionsEmulator.js +85 -21
  64. package/lib/emulator/functionsEmulator.js +79 -7
  65. package/lib/emulator/functionsEmulatorShared.js +36 -21
  66. package/lib/emulator/registry.js +34 -12
  67. package/lib/emulator/shared/request.js +19 -0
  68. package/lib/emulator/storage/apis/firebase.js +32 -35
  69. package/lib/emulator/storage/apis/gcloud.js +84 -66
  70. package/lib/emulator/storage/files.js +56 -52
  71. package/lib/emulator/storage/index.js +23 -3
  72. package/lib/emulator/storage/metadata.js +18 -8
  73. package/lib/emulator/storage/rules/manager.js +7 -17
  74. package/lib/emulator/storage/rules/utils.js +11 -3
  75. package/lib/emulator/storage/server.js +38 -12
  76. package/lib/ensureApiEnabled.js +8 -4
  77. package/lib/extensions/askUserForParam.js +14 -11
  78. package/lib/extensions/changelog.js +1 -1
  79. package/lib/extensions/emulator/optionsHelper.js +9 -10
  80. package/lib/extensions/emulator/specHelper.js +7 -1
  81. package/lib/extensions/emulator/triggerHelper.js +11 -14
  82. package/lib/extensions/extensionsApi.js +2 -1
  83. package/lib/extensions/extensionsHelper.js +30 -24
  84. package/lib/extensions/manifest.js +28 -8
  85. package/lib/extensions/paramHelper.js +19 -13
  86. package/lib/extensions/provisioningHelper.js +2 -2
  87. package/lib/extensions/warnings.js +3 -3
  88. package/lib/functions/env.js +10 -2
  89. package/lib/functions/events/index.js +7 -0
  90. package/lib/functions/events/v1.js +6 -0
  91. package/lib/functions/projectConfig.js +24 -3
  92. package/lib/functions/runtimeConfigExport.js +10 -6
  93. package/lib/functions/secrets.js +99 -6
  94. package/lib/gcp/cloudfunctions.js +37 -18
  95. package/lib/gcp/cloudfunctionsv2.js +41 -25
  96. package/lib/gcp/cloudtasks.js +5 -3
  97. package/lib/gcp/identityPlatform.js +44 -0
  98. package/lib/gcp/secretManager.js +2 -2
  99. package/lib/metaprogramming.js +2 -0
  100. package/lib/previews.js +1 -1
  101. package/lib/serve/hosting.js +25 -12
  102. package/lib/serve/index.js +6 -0
  103. package/lib/track.js +15 -21
  104. package/lib/utils.js +30 -1
  105. package/npm-shrinkwrap.json +44 -2
  106. package/package.json +4 -1
  107. package/schema/firebase-config.json +6 -0
  108. package/lib/emulator/storage/list.js +0 -18
@@ -11,33 +11,34 @@ const crc_1 = require("../crc");
11
11
  const multipart_1 = require("../multipart");
12
12
  const upload_1 = require("../upload");
13
13
  const errors_1 = require("../errors");
14
+ const request_1 = require("../../shared/request");
14
15
  function createCloudEndpoints(emulator) {
15
16
  const gcloudStorageAPI = (0, express_1.Router)();
16
- const { storageLayer, uploadService } = emulator;
17
+ const { adminStorageLayer, uploadService } = emulator;
17
18
  gcloudStorageAPI.use(/.*\/b\/(.+?)\/.*/, (req, res, next) => {
18
- storageLayer.createBucket(req.params[0]);
19
+ adminStorageLayer.createBucket(req.params[0]);
19
20
  next();
20
21
  });
21
22
  gcloudStorageAPI.get("/b", async (req, res) => {
22
23
  res.json({
23
24
  kind: "storage#buckets",
24
- items: await storageLayer.listBuckets(),
25
+ items: await adminStorageLayer.listBuckets(),
25
26
  });
26
27
  });
27
28
  gcloudStorageAPI.get(["/b/:bucketId/o/:objectId", "/download/storage/v1/b/:bucketId/o/:objectId"], async (req, res) => {
28
29
  let getObjectResponse;
29
30
  try {
30
- getObjectResponse = await storageLayer.handleGetObject({
31
+ getObjectResponse = await adminStorageLayer.getObject({
31
32
  bucketId: req.params.bucketId,
32
33
  decodedObjectId: req.params.objectId,
33
- }, true);
34
+ });
34
35
  }
35
36
  catch (err) {
36
37
  if (err instanceof errors_1.NotFoundError) {
37
38
  return sendObjectNotFound(req, res);
38
39
  }
39
40
  if (err instanceof errors_1.ForbiddenError) {
40
- throw new Error("Request failed unexpectedly due to Firebase Rules.");
41
+ return res.sendStatus(403);
41
42
  }
42
43
  throw err;
43
44
  }
@@ -49,67 +50,67 @@ function createCloudEndpoints(emulator) {
49
50
  gcloudStorageAPI.patch("/b/:bucketId/o/:objectId", async (req, res) => {
50
51
  let updatedMetadata;
51
52
  try {
52
- updatedMetadata = await storageLayer.handleUpdateObjectMetadata({
53
+ updatedMetadata = await adminStorageLayer.updateObjectMetadata({
53
54
  bucketId: req.params.bucketId,
54
55
  decodedObjectId: req.params.objectId,
55
56
  metadata: req.body,
56
- }, true);
57
+ });
57
58
  }
58
59
  catch (err) {
59
60
  if (err instanceof errors_1.NotFoundError) {
60
61
  return sendObjectNotFound(req, res);
61
62
  }
62
63
  if (err instanceof errors_1.ForbiddenError) {
63
- throw new Error("Request failed unexpectedly due to Firebase Rules.");
64
+ return res.sendStatus(403);
64
65
  }
65
66
  throw err;
66
67
  }
67
68
  return res.json(new metadata_1.CloudStorageObjectMetadata(updatedMetadata));
68
69
  });
69
- gcloudStorageAPI.get("/b/:bucketId/o", (req, res) => {
70
- let maxRes = undefined;
71
- if (req.query.maxResults) {
72
- maxRes = +req.query.maxResults.toString();
73
- }
74
- const delimiter = req.query.delimiter ? req.query.delimiter.toString() : "";
75
- const pageToken = req.query.pageToken ? req.query.pageToken.toString() : undefined;
76
- const prefix = req.query.prefix ? req.query.prefix.toString() : "";
77
- const listResult = storageLayer.listItems(req.params.bucketId, prefix, delimiter, pageToken, maxRes);
78
- res.json(Object.assign(Object.assign({}, listResult), { kind: "#storage/objects" }));
70
+ gcloudStorageAPI.get("/b/:bucketId/o", async (req, res) => {
71
+ var _a;
72
+ let listResponse;
73
+ try {
74
+ listResponse = await adminStorageLayer.listObjects({
75
+ bucketId: req.params.bucketId,
76
+ prefix: req.query.prefix ? req.query.prefix.toString() : "",
77
+ delimiter: req.query.delimiter ? req.query.delimiter.toString() : "",
78
+ pageToken: req.query.pageToken ? req.query.pageToken.toString() : undefined,
79
+ maxResults: req.query.maxResults ? +req.query.maxResults.toString() : undefined,
80
+ authorization: req.header("authorization"),
81
+ });
82
+ }
83
+ catch (err) {
84
+ if (err instanceof errors_1.ForbiddenError) {
85
+ return res.sendStatus(403);
86
+ }
87
+ throw err;
88
+ }
89
+ return res.status(200).json({
90
+ kind: "#storage/objects",
91
+ nextPageToken: listResponse.nextPageToken,
92
+ prefixes: listResponse.prefixes,
93
+ items: (_a = listResponse.items) === null || _a === void 0 ? void 0 : _a.map((item) => new metadata_1.CloudStorageObjectMetadata(item)),
94
+ });
79
95
  });
80
96
  gcloudStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => {
81
97
  try {
82
- await storageLayer.handleDeleteObject({
98
+ await adminStorageLayer.deleteObject({
83
99
  bucketId: req.params.bucketId,
84
100
  decodedObjectId: req.params.objectId,
85
- }, true);
101
+ });
86
102
  }
87
103
  catch (err) {
88
104
  if (err instanceof errors_1.NotFoundError) {
89
105
  return sendObjectNotFound(req, res);
90
106
  }
91
107
  if (err instanceof errors_1.ForbiddenError) {
92
- throw new Error("Request failed unexpectedly due to Firebase Rules.");
108
+ return res.sendStatus(403);
93
109
  }
94
110
  throw err;
95
111
  }
96
112
  return res.sendStatus(204);
97
113
  });
98
- const reqBodyToBuffer = async (req) => {
99
- if (req.body instanceof Buffer) {
100
- return Buffer.from(req.body);
101
- }
102
- const bufs = [];
103
- req.on("data", (data) => {
104
- bufs.push(data);
105
- });
106
- await new Promise((resolve) => {
107
- req.on("end", () => {
108
- resolve();
109
- });
110
- });
111
- return Buffer.concat(bufs);
112
- };
113
114
  gcloudStorageAPI.put("/upload/storage/v1/b/:bucketId/o", async (req, res) => {
114
115
  if (!req.query.upload_id) {
115
116
  res.sendStatus(400);
@@ -118,7 +119,7 @@ function createCloudEndpoints(emulator) {
118
119
  const uploadId = req.query.upload_id.toString();
119
120
  let upload;
120
121
  try {
121
- uploadService.continueResumableUpload(uploadId, await reqBodyToBuffer(req));
122
+ uploadService.continueResumableUpload(uploadId, await (0, request_1.reqBodyToBuffer)(req));
122
123
  upload = uploadService.finalizeResumableUpload(uploadId);
123
124
  }
124
125
  catch (err) {
@@ -132,11 +133,11 @@ function createCloudEndpoints(emulator) {
132
133
  }
133
134
  let metadata;
134
135
  try {
135
- metadata = await storageLayer.handleUploadObject(upload, true);
136
+ metadata = await adminStorageLayer.uploadObject(upload);
136
137
  }
137
138
  catch (err) {
138
139
  if (err instanceof errors_1.ForbiddenError) {
139
- throw new Error("Request failed unexpectedly due to Firebase Rules.");
140
+ return res.sendStatus(403);
140
141
  }
141
142
  throw err;
142
143
  }
@@ -147,17 +148,17 @@ function createCloudEndpoints(emulator) {
147
148
  emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("WARN_ONCE", "Cloud Storage ACLs are not supported in the Storage Emulator. All related methods will succeed, but have no effect.");
148
149
  let getObjectResponse;
149
150
  try {
150
- getObjectResponse = await storageLayer.handleGetObject({
151
+ getObjectResponse = await adminStorageLayer.getObject({
151
152
  bucketId: req.params.bucketId,
152
153
  decodedObjectId: req.params.objectId,
153
- }, true);
154
+ });
154
155
  }
155
156
  catch (err) {
156
157
  if (err instanceof errors_1.NotFoundError) {
157
158
  return sendObjectNotFound(req, res);
158
159
  }
159
160
  if (err instanceof errors_1.ForbiddenError) {
160
- throw new Error("Request failed unexpectedly due to Firebase Rules.");
161
+ return res.sendStatus(403);
161
162
  }
162
163
  throw err;
163
164
  }
@@ -199,22 +200,28 @@ function createCloudEndpoints(emulator) {
199
200
  metadataRaw: JSON.stringify(req.body),
200
201
  authorization: req.header("authorization"),
201
202
  });
202
- const { host, port } = emulatorInfo;
203
- const uploadUrl = `http://${host}:${port}/upload/storage/v1/b/${req.params.bucketId}/o?name=${name}&uploadType=resumable&upload_id=${upload.id}`;
204
- return res.header("location", uploadUrl).sendStatus(200);
203
+ const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
204
+ uploadUrl.pathname = `/upload/storage/v1/b/${req.params.bucketId}/o`;
205
+ uploadUrl.searchParams.set("name", name);
206
+ uploadUrl.searchParams.set("uploadType", "resumable");
207
+ uploadUrl.searchParams.set("upload_id", upload.id);
208
+ return res.header("location", uploadUrl.toString()).sendStatus(200);
205
209
  }
206
210
  let metadataRaw;
207
211
  let dataRaw;
208
212
  try {
209
- ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await reqBodyToBuffer(req)));
213
+ ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
210
214
  }
211
215
  catch (err) {
212
- return res.status(400).json({
213
- error: {
214
- code: 400,
215
- message: err,
216
- },
217
- });
216
+ if (err instanceof Error) {
217
+ return res.status(400).json({
218
+ error: {
219
+ code: 400,
220
+ message: err.message,
221
+ },
222
+ });
223
+ }
224
+ throw err;
218
225
  }
219
226
  const upload = uploadService.multipartUpload({
220
227
  bucketId: req.params.bucketId,
@@ -225,11 +232,11 @@ function createCloudEndpoints(emulator) {
225
232
  });
226
233
  let metadata;
227
234
  try {
228
- metadata = await storageLayer.handleUploadObject(upload, true);
235
+ metadata = await adminStorageLayer.uploadObject(upload);
229
236
  }
230
237
  catch (err) {
231
238
  if (err instanceof errors_1.ForbiddenError) {
232
- throw new Error("Request failed unexpectedly due to Firebase Rules.");
239
+ return res.sendStatus(403);
233
240
  }
234
241
  throw err;
235
242
  }
@@ -238,34 +245,45 @@ function createCloudEndpoints(emulator) {
238
245
  gcloudStorageAPI.get("/:bucketId/:objectId(**)", async (req, res) => {
239
246
  let getObjectResponse;
240
247
  try {
241
- getObjectResponse = await storageLayer.handleGetObject({
248
+ getObjectResponse = await adminStorageLayer.getObject({
242
249
  bucketId: req.params.bucketId,
243
250
  decodedObjectId: req.params.objectId,
244
- }, true);
251
+ });
245
252
  }
246
253
  catch (err) {
247
254
  if (err instanceof errors_1.NotFoundError) {
248
255
  return sendObjectNotFound(req, res);
249
256
  }
250
257
  if (err instanceof errors_1.ForbiddenError) {
251
- throw new Error("Request failed unexpectedly due to Firebase Rules.");
258
+ return res.sendStatus(403);
252
259
  }
253
260
  throw err;
254
261
  }
255
262
  return sendFileBytes(getObjectResponse.metadata, getObjectResponse.data, req, res);
256
263
  });
257
264
  gcloudStorageAPI.post("/b/:bucketId/o/:objectId/:method(rewriteTo|copyTo)/b/:destBucketId/o/:destObjectId", (req, res, next) => {
258
- const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId);
259
- if (!md) {
260
- return sendObjectNotFound(req, res);
261
- }
262
265
  if (req.params.method === "rewriteTo" && req.query.rewriteToken) {
263
266
  return next();
264
267
  }
265
- const metadata = storageLayer.copyFile(md, req.params.destBucketId, req.params.destObjectId, req.body);
266
- if (!metadata) {
267
- res.sendStatus(400);
268
- return;
268
+ let metadata;
269
+ try {
270
+ metadata = adminStorageLayer.copyObject({
271
+ sourceBucket: req.params.bucketId,
272
+ sourceObject: req.params.objectId,
273
+ destinationBucket: req.params.destBucketId,
274
+ destinationObject: req.params.destObjectId,
275
+ incomingMetadata: req.body,
276
+ authorization: "Bearer owner",
277
+ });
278
+ }
279
+ catch (err) {
280
+ if (err instanceof errors_1.NotFoundError) {
281
+ return sendObjectNotFound(req, res);
282
+ }
283
+ if (err instanceof errors_1.ForbiddenError) {
284
+ return res.sendStatus(403);
285
+ }
286
+ throw err;
269
287
  }
270
288
  const resource = new metadata_1.CloudStorageObjectMetadata(metadata);
271
289
  res.status(200);
@@ -9,12 +9,10 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.StorageLayer = exports.StoredFile = void 0;
11
11
  const fs_1 = require("fs");
12
- const list_1 = require("./list");
13
12
  const metadata_1 = require("./metadata");
14
13
  const errors_1 = require("./errors");
15
14
  const path = require("path");
16
15
  const fse = require("fs-extra");
17
- const cloudFunctions_1 = require("./cloudFunctions");
18
16
  const logger_1 = require("../../logger");
19
17
  const adminSdkConfig_1 = require("../adminSdkConfig");
20
18
  const types_1 = require("./rules/types");
@@ -39,17 +37,14 @@ class StoredFile {
39
37
  }
40
38
  exports.StoredFile = StoredFile;
41
39
  class StorageLayer {
42
- constructor(_projectId, _rulesValidator, _adminCredsValidator, _persistence) {
40
+ constructor(_projectId, _files, _buckets, _rulesValidator, _adminCredsValidator, _persistence, _cloudFunctions) {
43
41
  this._projectId = _projectId;
42
+ this._files = _files;
43
+ this._buckets = _buckets;
44
44
  this._rulesValidator = _rulesValidator;
45
45
  this._adminCredsValidator = _adminCredsValidator;
46
46
  this._persistence = _persistence;
47
- this.reset();
48
- this._cloudFunctions = new cloudFunctions_1.StorageCloudFunctions(this._projectId);
49
- }
50
- reset() {
51
- this._files = new Map();
52
- this._buckets = new Map();
47
+ this._cloudFunctions = _cloudFunctions;
53
48
  }
54
49
  createBucket(id) {
55
50
  if (!this._buckets.has(id)) {
@@ -66,11 +61,11 @@ class StorageLayer {
66
61
  }
67
62
  return [...this._buckets.values()];
68
63
  }
69
- async handleGetObject(request, skipAuth = false) {
64
+ async getObject(request) {
70
65
  var _a;
71
66
  const metadata = this.getMetadata(request.bucketId, request.decodedObjectId);
72
67
  const hasValidDownloadToken = ((metadata === null || metadata === void 0 ? void 0 : metadata.downloadTokens) || []).includes((_a = request.downloadToken) !== null && _a !== void 0 ? _a : "");
73
- let authorized = skipAuth || hasValidDownloadToken;
68
+ let authorized = hasValidDownloadToken;
74
69
  if (!authorized) {
75
70
  authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.GET, { before: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, request.authorization);
76
71
  }
@@ -99,10 +94,9 @@ class StorageLayer {
99
94
  }
100
95
  return undefined;
101
96
  }
102
- async handleDeleteObject(request, skipAuth = false) {
97
+ async deleteObject(request) {
103
98
  const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId);
104
- const authorized = skipAuth ||
105
- (await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.DELETE, { before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource() }, request.authorization));
99
+ const authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.DELETE, { before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource() }, request.authorization);
106
100
  if (!authorized) {
107
101
  throw new errors_1.ForbiddenError();
108
102
  }
@@ -131,13 +125,12 @@ class StorageLayer {
131
125
  return true;
132
126
  }
133
127
  }
134
- async handleUpdateObjectMetadata(request, skipAuth = false) {
128
+ async updateObjectMetadata(request) {
135
129
  const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId);
136
- const authorized = skipAuth ||
137
- (await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.UPDATE, {
138
- before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(),
139
- after: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(request.metadata),
140
- }, request.authorization));
130
+ const authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.UPDATE, {
131
+ before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(),
132
+ after: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(request.metadata),
133
+ }, request.authorization);
141
134
  if (!authorized) {
142
135
  throw new errors_1.ForbiddenError();
143
136
  }
@@ -147,7 +140,7 @@ class StorageLayer {
147
140
  storedMetadata.update(request.metadata);
148
141
  return storedMetadata;
149
142
  }
150
- async handleUploadObject(upload, skipAuth = false) {
143
+ async uploadObject(upload) {
151
144
  if (upload.status !== upload_1.UploadStatus.FINISHED) {
152
145
  throw new Error(`Unexpected upload status encountered: ${upload.status}.`);
153
146
  }
@@ -162,8 +155,8 @@ class StorageLayer {
162
155
  cacheControl: upload.metadata.cacheControl,
163
156
  customMetadata: upload.metadata.metadata,
164
157
  }, this._cloudFunctions, this._persistence.readBytes(upload.path, upload.size));
165
- const authorized = skipAuth ||
166
- (await this._rulesValidator.validate(["b", upload.bucketId, "o", upload.objectId].join("/"), upload.bucketId, types_1.RulesetOperationMethod.CREATE, { after: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, upload.authorization));
158
+ metadata.update(upload.metadata, false);
159
+ const authorized = await this._rulesValidator.validate(["b", upload.bucketId, "o", upload.objectId].join("/"), upload.bucketId, types_1.RulesetOperationMethod.CREATE, { after: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, upload.authorization);
167
160
  if (!authorized) {
168
161
  this._persistence.deleteFile(upload.path);
169
162
  throw new errors_1.ForbiddenError();
@@ -174,17 +167,24 @@ class StorageLayer {
174
167
  this._cloudFunctions.dispatch("finalize", new metadata_1.CloudStorageObjectMetadata(metadata));
175
168
  return metadata;
176
169
  }
177
- copyFile(sourceFile, destinationBucket, destinationObject, incomingMetadata) {
178
- const filePath = this.path(destinationBucket, destinationObject);
179
- this._persistence.deleteFile(filePath, true);
180
- const bytes = this.getBytes(sourceFile.bucket, sourceFile.name);
181
- this._persistence.appendBytes(filePath, bytes);
182
- const newMetadata = Object.assign(Object.assign(Object.assign({}, sourceFile), { metadata: sourceFile.customMetadata }), incomingMetadata);
183
- if (sourceFile.downloadTokens.length &&
170
+ copyObject({ sourceBucket, sourceObject, destinationBucket, destinationObject, incomingMetadata, authorization, }) {
171
+ if (!this._adminCredsValidator.validate(authorization)) {
172
+ throw new errors_1.ForbiddenError();
173
+ }
174
+ const sourceMetadata = this.getMetadata(sourceBucket, sourceObject);
175
+ if (!sourceMetadata) {
176
+ throw new errors_1.NotFoundError();
177
+ }
178
+ const sourceBytes = this.getBytes(sourceBucket, sourceObject);
179
+ const destinationFilePath = this.path(destinationBucket, destinationObject);
180
+ this._persistence.deleteFile(destinationFilePath, true);
181
+ this._persistence.appendBytes(destinationFilePath, sourceBytes);
182
+ const newMetadata = Object.assign(Object.assign(Object.assign({}, sourceMetadata), { metadata: sourceMetadata.customMetadata }), incomingMetadata);
183
+ if (sourceMetadata.downloadTokens.length &&
184
184
  !((incomingMetadata === null || incomingMetadata === void 0 ? void 0 : incomingMetadata.metadata) && Object.keys(incomingMetadata === null || incomingMetadata === void 0 ? void 0 : incomingMetadata.metadata).length)) {
185
185
  if (!newMetadata.metadata)
186
186
  newMetadata.metadata = {};
187
- newMetadata.metadata.firebaseStorageDownloadTokens = sourceFile.downloadTokens.join(",");
187
+ newMetadata.metadata.firebaseStorageDownloadTokens = sourceMetadata.downloadTokens.join(",");
188
188
  }
189
189
  if (newMetadata.metadata) {
190
190
  for (const [k, v] of Object.entries(newMetadata.metadata)) {
@@ -201,27 +201,23 @@ class StorageLayer {
201
201
  contentLanguage: newMetadata.contentLanguage,
202
202
  cacheControl: newMetadata.cacheControl,
203
203
  customMetadata: newMetadata.metadata,
204
- }, this._cloudFunctions, bytes, incomingMetadata);
205
- const file = new StoredFile(copiedFileMetadata, this._persistence.getDiskPath(filePath));
206
- this._files.set(filePath, file);
204
+ }, this._cloudFunctions, sourceBytes, incomingMetadata);
205
+ const file = new StoredFile(copiedFileMetadata, this._persistence.getDiskPath(destinationFilePath));
206
+ this._files.set(destinationFilePath, file);
207
207
  this._cloudFunctions.dispatch("finalize", new metadata_1.CloudStorageObjectMetadata(file.metadata));
208
208
  return file.metadata;
209
209
  }
210
- async handleListObjects(request, skipAuth = false) {
211
- var _a, _b, _c;
212
- const authorized = skipAuth ||
213
- (await this._rulesValidator.validate(["b", request.bucketId, "o", request.prefix].join("/"), request.bucketId, types_1.RulesetOperationMethod.LIST, {}, request.authorization));
210
+ async listObjects(request) {
211
+ var _a;
212
+ const { bucketId, prefix, delimiter, pageToken, authorization } = request;
213
+ const authorized = await this._rulesValidator.validate(["b", bucketId, "o", prefix].join("/"), bucketId, types_1.RulesetOperationMethod.LIST, {}, authorization);
214
214
  if (!authorized) {
215
215
  throw new errors_1.ForbiddenError();
216
216
  }
217
- const itemsResults = this.listItems(request.bucketId, request.prefix, request.delimiter, request.pageToken, request.maxResults);
218
- return new list_1.ListResponse((_a = itemsResults.prefixes) !== null && _a !== void 0 ? _a : [], (_c = (_b = itemsResults.items) === null || _b === void 0 ? void 0 : _b.map((i) => new list_1.ListItem(i.name, i.bucket))) !== null && _c !== void 0 ? _c : [], itemsResults.nextPageToken);
219
- }
220
- listItems(bucket, prefix, delimiter, pageToken, maxResults) {
221
217
  let items = [];
222
218
  const prefixes = new Set();
223
219
  for (const [, file] of this._files) {
224
- if (file.metadata.bucket !== bucket) {
220
+ if (file.metadata.bucket !== bucketId) {
225
221
  continue;
226
222
  }
227
223
  const name = file.metadata.name;
@@ -258,9 +254,7 @@ class StorageLayer {
258
254
  items = items.slice(idx);
259
255
  }
260
256
  }
261
- if (!maxResults) {
262
- maxResults = 1000;
263
- }
257
+ const maxResults = (_a = request.maxResults) !== null && _a !== void 0 ? _a : 1000;
264
258
  let nextPageToken = undefined;
265
259
  if (items.length > maxResults) {
266
260
  nextPageToken = items[maxResults].name;
@@ -269,10 +263,10 @@ class StorageLayer {
269
263
  return {
270
264
  nextPageToken,
271
265
  prefixes: prefixes.size > 0 ? [...prefixes].sort() : undefined,
272
- items: items.length > 0 ? items.map((item) => new metadata_1.CloudStorageObjectMetadata(item)) : undefined,
266
+ items: items.length > 0 ? items : undefined,
273
267
  };
274
268
  }
275
- handleCreateDownloadToken(request) {
269
+ createDownloadToken(request) {
276
270
  if (!this._adminCredsValidator.validate(request.authorization)) {
277
271
  throw new errors_1.ForbiddenError();
278
272
  }
@@ -283,7 +277,7 @@ class StorageLayer {
283
277
  metadata.addDownloadToken();
284
278
  return metadata;
285
279
  }
286
- handleDeleteDownloadToken(request) {
280
+ deleteDownloadToken(request) {
287
281
  if (!this._adminCredsValidator.validate(request.authorization)) {
288
282
  throw new errors_1.ForbiddenError();
289
283
  }
@@ -354,10 +348,16 @@ class StorageLayer {
354
348
  logger_1.logger.warn(`Could not find file "${blobPath}" in storage export.`);
355
349
  continue;
356
350
  }
357
- const file = new StoredFile(metadata, blobPath);
358
- this._files.set(blobPath, file);
351
+ let decodedBlobPath = decodeURIComponent(blobPath);
352
+ const decodedBlobPathSep = getPathSep(decodedBlobPath);
353
+ if (decodedBlobPathSep !== path.sep) {
354
+ decodedBlobPath = decodedBlobPath.split(decodedBlobPathSep).join(path.sep);
355
+ }
356
+ const blobDiskPath = this._persistence.getDiskPath(decodedBlobPath);
357
+ const file = new StoredFile(metadata, blobDiskPath);
358
+ this._files.set(decodedBlobPath, file);
359
+ fse.copyFileSync(blobAbsPath, blobDiskPath);
359
360
  }
360
- fse.copySync(blobsDir, this.dirPath);
361
361
  }
362
362
  *walkDirSync(dir) {
363
363
  const files = (0, fs_1.readdirSync)(dir);
@@ -373,3 +373,7 @@ class StorageLayer {
373
373
  }
374
374
  }
375
375
  exports.StorageLayer = StorageLayer;
376
+ function getPathSep(decodedPath) {
377
+ const firstSepIndex = decodedPath.search(/[^a-z0-9-_.]/g);
378
+ return decodedPath[firstSepIndex];
379
+ }
@@ -13,19 +13,30 @@ const runtime_1 = require("./rules/runtime");
13
13
  const utils_1 = require("./rules/utils");
14
14
  const persistence_1 = require("./persistence");
15
15
  const upload_1 = require("./upload");
16
+ const cloudFunctions_1 = require("./cloudFunctions");
16
17
  class StorageEmulator {
17
18
  constructor(args) {
18
19
  this.args = args;
19
20
  this._logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE);
21
+ this._files = new Map();
22
+ this._buckets = new Map();
20
23
  this._rulesRuntime = new runtime_1.StorageRulesRuntime();
21
- this._rulesManager = (0, manager_1.createStorageRulesManager)(this.args.rules, this._rulesRuntime);
24
+ this._rulesManager = this.createRulesManager(this.args.rules);
25
+ this._cloudFunctions = new cloudFunctions_1.StorageCloudFunctions(args.projectId);
22
26
  this._persistence = new persistence_1.Persistence(this.getPersistenceTmpDir());
23
- this._storageLayer = new files_1.StorageLayer(args.projectId, (0, utils_1.getRulesValidator)((resource) => this._rulesManager.getRuleset(resource)), (0, utils_1.getAdminCredentialValidator)(), this._persistence);
24
27
  this._uploadService = new upload_1.UploadService(this._persistence);
28
+ const createStorageLayer = (rulesValidator) => {
29
+ return new files_1.StorageLayer(args.projectId, this._files, this._buckets, rulesValidator, (0, utils_1.getAdminCredentialValidator)(), this._persistence, this._cloudFunctions);
30
+ };
31
+ this._storageLayer = createStorageLayer((0, utils_1.getFirebaseRulesValidator)((resource) => this._rulesManager.getRuleset(resource)));
32
+ this._adminStorageLayer = createStorageLayer((0, utils_1.getAdminOnlyFirebaseRulesValidator)());
25
33
  }
26
34
  get storageLayer() {
27
35
  return this._storageLayer;
28
36
  }
37
+ get adminStorageLayer() {
38
+ return this._adminStorageLayer;
39
+ }
29
40
  get uploadService() {
30
41
  return this._uploadService;
31
42
  }
@@ -36,7 +47,8 @@ class StorageEmulator {
36
47
  return this._logger;
37
48
  }
38
49
  reset() {
39
- this._storageLayer.reset();
50
+ this._files.clear();
51
+ this._buckets.clear();
40
52
  this._persistence.reset(this.getPersistenceTmpDir());
41
53
  this._uploadService.reset();
42
54
  }
@@ -70,6 +82,14 @@ class StorageEmulator {
70
82
  getApp() {
71
83
  return this._app;
72
84
  }
85
+ async replaceRules(rules) {
86
+ await this._rulesManager.stop();
87
+ this._rulesManager = this.createRulesManager(rules);
88
+ return this._rulesManager.start();
89
+ }
90
+ createRulesManager(rules) {
91
+ return (0, manager_1.createStorageRulesManager)(rules, this._rulesRuntime);
92
+ }
73
93
  getPersistenceTmpDir() {
74
94
  return `${(0, os_1.tmpdir)()}/firebase/storage/blobs`;
75
95
  }
@@ -44,7 +44,7 @@ class StoredFileMetadata {
44
44
  throw new Error("Must pass bytes array or opts object with size, md5hash, and crc32c");
45
45
  }
46
46
  if (incomingMetadata) {
47
- this.update(incomingMetadata);
47
+ this.update(incomingMetadata, false);
48
48
  }
49
49
  this.deleteFieldsSetAsNull();
50
50
  this.setDownloadTokensFromCustomMetadata();
@@ -86,8 +86,10 @@ class StoredFileMetadata {
86
86
  }
87
87
  if (this.customMetadata.firebaseStorageDownloadTokens) {
88
88
  this.downloadTokens = [
89
- ...this.downloadTokens,
90
- ...this.customMetadata.firebaseStorageDownloadTokens.split(","),
89
+ ...new Set([
90
+ ...this.downloadTokens,
91
+ ...this.customMetadata.firebaseStorageDownloadTokens.split(","),
92
+ ]),
91
93
  ];
92
94
  delete this.customMetadata.firebaseStorageDownloadTokens;
93
95
  }
@@ -115,7 +117,7 @@ class StoredFileMetadata {
115
117
  });
116
118
  }
117
119
  }
118
- update(incoming) {
120
+ update(incoming, shouldTrigger = true) {
119
121
  if (incoming.contentDisposition) {
120
122
  this.contentDisposition = incoming.contentDisposition;
121
123
  }
@@ -143,15 +145,17 @@ class StoredFileMetadata {
143
145
  }
144
146
  this.setDownloadTokensFromCustomMetadata();
145
147
  this.deleteFieldsSetAsNull();
146
- this._cloudFunctions.dispatch("metadataUpdate", new CloudStorageObjectMetadata(this));
148
+ if (shouldTrigger) {
149
+ this._cloudFunctions.dispatch("metadataUpdate", new CloudStorageObjectMetadata(this));
150
+ }
147
151
  }
148
- addDownloadToken() {
152
+ addDownloadToken(shouldTrigger = true) {
149
153
  if (!this.downloadTokens.length) {
150
154
  this.downloadTokens.push(uuid.v4());
151
155
  return;
152
156
  }
153
157
  this.downloadTokens = [...this.downloadTokens, uuid.v4()];
154
- this.update({});
158
+ this.update({}, shouldTrigger);
155
159
  }
156
160
  deleteDownloadToken(token) {
157
161
  if (!this.downloadTokens.length) {
@@ -160,7 +164,7 @@ class StoredFileMetadata {
160
164
  const remainingTokens = this.downloadTokens.filter((t) => t !== token);
161
165
  this.downloadTokens = remainingTokens;
162
166
  if (remainingTokens.length === 0) {
163
- this.addDownloadToken();
167
+ this.addDownloadToken(false);
164
168
  }
165
169
  this.update({});
166
170
  }
@@ -265,6 +269,12 @@ class CloudStorageObjectMetadata {
265
269
  if (metadata.cacheControl) {
266
270
  this.cacheControl = metadata.cacheControl;
267
271
  }
272
+ if (metadata.contentDisposition) {
273
+ this.contentDisposition = metadata.contentDisposition;
274
+ }
275
+ if (metadata.contentEncoding) {
276
+ this.contentEncoding = metadata.contentEncoding;
277
+ }
268
278
  if (metadata.customTime) {
269
279
  this.customTime = toSerializedDate(metadata.customTime);
270
280
  }