firebase-tools 10.2.2 → 10.4.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 (76) hide show
  1. package/lib/commands/deploy.js +1 -1
  2. package/lib/commands/experimental-functions-shell.js +1 -1
  3. package/lib/commands/ext-configure.js +68 -7
  4. package/lib/commands/ext-export.js +10 -9
  5. package/lib/commands/ext-install.js +73 -9
  6. package/lib/commands/ext-uninstall.js +9 -0
  7. package/lib/commands/ext-update.js +58 -3
  8. package/lib/commands/functions-config-export.js +2 -2
  9. package/lib/commands/functions-shell.js +1 -1
  10. package/lib/commands/hosting-channel-create.js +2 -2
  11. package/lib/commands/hosting-channel-delete.js +2 -2
  12. package/lib/commands/hosting-channel-deploy.js +2 -2
  13. package/lib/commands/hosting-channel-list.js +2 -2
  14. package/lib/commands/hosting-channel-open.js +2 -2
  15. package/lib/commands/hosting-sites-delete.js +2 -2
  16. package/lib/commands/serve.js +1 -1
  17. package/lib/commands/target-apply.js +2 -2
  18. package/lib/commands/target-clear.js +2 -2
  19. package/lib/commands/target-remove.js +2 -2
  20. package/lib/commands/target.js +2 -2
  21. package/lib/config.js +9 -3
  22. package/lib/deploy/extensions/planner.js +15 -9
  23. package/lib/deploy/functions/backend.js +10 -1
  24. package/lib/deploy/functions/checkIam.js +4 -4
  25. package/lib/deploy/functions/prepare.js +2 -1
  26. package/lib/deploy/functions/release/fabricator.js +4 -4
  27. package/lib/deploy/functions/release/planner.js +34 -20
  28. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +6 -1
  29. package/lib/deploy/functions/runtimes/node/index.js +27 -0
  30. package/lib/deploy/functions/runtimes/node/parseTriggers.js +36 -13
  31. package/lib/deploy/functions/services/firebaseAlerts.js +30 -0
  32. package/lib/deploy/functions/services/index.js +9 -1
  33. package/lib/deploy/functions/services/storage.js +10 -4
  34. package/lib/deploy/functions/triggerRegionHelper.js +1 -1
  35. package/lib/emulator/auth/apiSpec.js +37 -0
  36. package/lib/emulator/commandUtils.js +2 -2
  37. package/lib/emulator/constants.js +1 -0
  38. package/lib/emulator/controller.js +9 -7
  39. package/lib/emulator/extensions/validation.js +37 -2
  40. package/lib/emulator/extensionsEmulator.js +47 -9
  41. package/lib/emulator/functionsEmulator.js +17 -12
  42. package/lib/emulator/functionsEmulatorShared.js +34 -11
  43. package/lib/emulator/storage/apis/firebase.js +316 -341
  44. package/lib/emulator/storage/apis/gcloud.js +238 -113
  45. package/lib/emulator/storage/crc.js +5 -1
  46. package/lib/emulator/storage/errors.js +9 -0
  47. package/lib/emulator/storage/files.js +161 -304
  48. package/lib/emulator/storage/index.js +25 -74
  49. package/lib/emulator/storage/metadata.js +63 -49
  50. package/lib/emulator/storage/multipart.js +62 -0
  51. package/lib/emulator/storage/persistence.js +78 -0
  52. package/lib/emulator/storage/rules/config.js +34 -0
  53. package/lib/emulator/storage/rules/manager.js +98 -0
  54. package/lib/emulator/storage/rules/runtime.js +4 -0
  55. package/lib/emulator/storage/rules/utils.js +48 -0
  56. package/lib/emulator/storage/server.js +2 -2
  57. package/lib/emulator/storage/upload.js +106 -0
  58. package/lib/extensions/askUserForParam.js +77 -28
  59. package/lib/extensions/emulator/optionsHelper.js +35 -3
  60. package/lib/extensions/extensionsHelper.js +19 -10
  61. package/lib/extensions/manifest.js +142 -14
  62. package/lib/extensions/paramHelper.js +32 -9
  63. package/lib/fsutils.js +14 -1
  64. package/lib/functions/env.js +4 -6
  65. package/lib/functions/events/v2.js +11 -0
  66. package/lib/gcp/cloudfunctions.js +20 -7
  67. package/lib/gcp/cloudfunctionsv2.js +30 -12
  68. package/lib/gcp/resourceManager.js +4 -4
  69. package/lib/requireConfig.js +11 -9
  70. package/lib/serve/functions.js +2 -1
  71. package/lib/utils.js +14 -1
  72. package/npm-shrinkwrap.json +2 -2
  73. package/package.json +1 -1
  74. package/lib/deploy/extensions/params.js +0 -42
  75. package/lib/deploy/functions/eventTypes.js +0 -10
  76. package/lib/prepareUpload.js +0 -44
@@ -1,110 +1,58 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StorageEmulator = void 0;
4
+ const os_1 = require("os");
4
5
  const utils = require("../../utils");
5
6
  const constants_1 = require("../constants");
6
7
  const types_1 = require("../types");
7
8
  const server_1 = require("./server");
8
9
  const files_1 = require("./files");
9
- const chokidar = require("chokidar");
10
10
  const emulatorLogger_1 = require("../emulatorLogger");
11
- const fs = require("fs");
11
+ const manager_1 = require("./rules/manager");
12
12
  const runtime_1 = require("./rules/runtime");
13
- const error_1 = require("../../error");
14
- const downloadableEmulators_1 = require("../downloadableEmulators");
13
+ const utils_1 = require("./rules/utils");
14
+ const persistence_1 = require("./persistence");
15
+ const upload_1 = require("./upload");
15
16
  class StorageEmulator {
16
17
  constructor(args) {
17
18
  this.args = args;
18
19
  this._logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE);
19
- const downloadDetails = (0, downloadableEmulators_1.getDownloadDetails)(types_1.Emulators.STORAGE);
20
20
  this._rulesRuntime = new runtime_1.StorageRulesRuntime();
21
- this._storageLayer = new files_1.StorageLayer(args.projectId);
21
+ this._rulesManager = (0, manager_1.createStorageRulesManager)(this.args.rules, this._rulesRuntime);
22
+ 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
+ this._uploadService = new upload_1.UploadService(this._persistence);
22
25
  }
23
26
  get storageLayer() {
24
27
  return this._storageLayer;
25
28
  }
26
- get rules() {
27
- return this._rules;
29
+ get uploadService() {
30
+ return this._uploadService;
31
+ }
32
+ get rulesManager() {
33
+ return this._rulesManager;
28
34
  }
29
35
  get logger() {
30
36
  return this._logger;
31
37
  }
38
+ reset() {
39
+ this._storageLayer.reset();
40
+ this._persistence.reset(this.getPersistenceTmpDir());
41
+ this._uploadService.reset();
42
+ }
32
43
  async start() {
33
44
  const { host, port } = this.getInfo();
34
45
  await this._rulesRuntime.start(this.args.auto_download);
46
+ await this._rulesManager.start();
35
47
  this._app = await (0, server_1.createApp)(this.args.projectId, this);
36
- if (typeof this.args.rules === "string") {
37
- const rulesFile = this.args.rules;
38
- this.updateRulesSource(rulesFile);
39
- }
40
- else {
41
- this._rulesetSource = this.args.rules;
42
- }
43
- if (!this._rulesetSource || this._rulesetSource.files.length === 0) {
44
- throw new error_1.FirebaseError("Can not initialize Storage emulator without a rules source / file.");
45
- }
46
- else if (this._rulesetSource.files.length > 1) {
47
- throw new error_1.FirebaseError("Can not initialize Storage emulator with more than one rules source / file.");
48
- }
49
- await this.loadRuleset();
50
- const rulesPath = this._rulesetSource.files[0].name;
51
- this._rulesWatcher = chokidar.watch(rulesPath, { persistent: true, ignoreInitial: true });
52
- this._rulesWatcher.on("change", async () => {
53
- await new Promise((res) => setTimeout(res, 5));
54
- this._logger.logLabeled("BULLET", "storage", `Change detected, updating rules for Cloud Storage...`);
55
- this.updateRulesSource(rulesPath);
56
- await this.loadRuleset();
57
- });
58
48
  const server = this._app.listen(port, host);
59
49
  this.destroyServer = utils.createDestroyer(server);
60
50
  }
61
- updateRulesSource(rulesFile) {
62
- this._rulesetSource = {
63
- files: [
64
- {
65
- name: rulesFile,
66
- content: fs.readFileSync(rulesFile).toString(),
67
- },
68
- ],
69
- };
70
- }
71
- async loadRuleset(source) {
72
- if (source) {
73
- this._rulesetSource = source;
74
- }
75
- if (!this._rulesetSource) {
76
- const msg = "Attempting to update ruleset without a source.";
77
- this._logger.log("WARN", msg);
78
- const error = JSON.stringify({ error: msg });
79
- return new runtime_1.StorageRulesIssues([error], []);
80
- }
81
- const { ruleset, issues } = await this._rulesRuntime.loadRuleset(this._rulesetSource);
82
- if (!ruleset) {
83
- issues.all.forEach((issue) => {
84
- let parsedIssue;
85
- try {
86
- parsedIssue = JSON.parse(issue);
87
- }
88
- catch (_a) {
89
- }
90
- if (parsedIssue) {
91
- this._logger.log("WARN", `${parsedIssue.description_.replace(/\.$/, "")} in ${parsedIssue.sourcePosition_.fileName_}:${parsedIssue.sourcePosition_.line_}`);
92
- }
93
- else {
94
- this._logger.log("WARN", issue);
95
- }
96
- });
97
- delete this._rules;
98
- }
99
- else {
100
- this._rules = ruleset;
101
- }
102
- return issues;
103
- }
104
51
  async connect() {
105
52
  }
106
53
  async stop() {
107
- await this.storageLayer.deleteAll();
54
+ await this._persistence.deleteAll();
55
+ await this._rulesManager.stop();
108
56
  return this.destroyServer ? this.destroyServer() : Promise.resolve();
109
57
  }
110
58
  getInfo() {
@@ -122,5 +70,8 @@ class StorageEmulator {
122
70
  getApp() {
123
71
  return this._app;
124
72
  }
73
+ getPersistenceTmpDir() {
74
+ return `${(0, os_1.tmpdir)()}/firebase/storage/blobs`;
75
+ }
125
76
  }
126
77
  exports.StorageEmulator = StorageEmulator;
@@ -15,14 +15,19 @@ class StoredFileMetadata {
15
15
  this.metageneration = opts.metageneration || 1;
16
16
  this.generation = opts.generation || Date.now();
17
17
  this.storageClass = opts.storageClass || "STANDARD";
18
- this.etag = opts.etag || "someETag";
19
18
  this.contentDisposition = opts.contentDisposition || "inline";
20
- this.cacheControl = opts.cacheControl;
19
+ this.cacheControl = opts.cacheControl || "public, max-age=3600";
21
20
  this.contentLanguage = opts.contentLanguage;
22
21
  this.customTime = opts.customTime;
23
22
  this.contentEncoding = opts.contentEncoding || "identity";
24
23
  this.customMetadata = opts.customMetadata;
25
24
  this.downloadTokens = opts.downloadTokens || [];
25
+ if (opts.etag) {
26
+ this.etag = opts.etag;
27
+ }
28
+ else {
29
+ this.etag = generateETag(this.generation, this.metageneration);
30
+ }
26
31
  this.timeCreated = opts.timeCreated ? new Date(opts.timeCreated) : new Date();
27
32
  this.updated = opts.updated ? new Date(opts.updated) : this.timeCreated;
28
33
  if (bytes) {
@@ -118,7 +123,10 @@ class StoredFileMetadata {
118
123
  this.contentType = incoming.contentType;
119
124
  }
120
125
  if (incoming.metadata) {
121
- this.customMetadata = incoming.metadata;
126
+ this.customMetadata = this.customMetadata ? Object.assign({}, this.customMetadata) : {};
127
+ for (const [k, v] of Object.entries(incoming.metadata)) {
128
+ this.customMetadata[k] = v === null ? null : String(v);
129
+ }
122
130
  }
123
131
  if (incoming.contentLanguage) {
124
132
  this.contentLanguage = incoming.contentLanguage;
@@ -171,25 +179,25 @@ class StoredFileMetadata {
171
179
  }
172
180
  exports.StoredFileMetadata = StoredFileMetadata;
173
181
  class OutgoingFirebaseMetadata {
174
- constructor(md) {
175
- this.name = md.name;
176
- this.bucket = md.bucket;
177
- this.generation = md.generation.toString();
178
- this.metageneration = md.metageneration.toString();
179
- this.contentType = md.contentType;
180
- this.timeCreated = toSerializedDate(md.timeCreated);
181
- this.updated = toSerializedDate(md.updated);
182
- this.storageClass = md.storageClass;
183
- this.size = md.size.toString();
184
- this.md5Hash = md.md5Hash;
185
- this.crc32c = md.crc32c;
186
- this.etag = md.etag;
187
- this.downloadTokens = md.downloadTokens.join(",");
188
- this.contentEncoding = md.contentEncoding;
189
- this.contentDisposition = md.contentDisposition;
190
- this.metadata = md.customMetadata;
191
- this.contentLanguage = md.contentLanguage;
192
- this.cacheControl = md.cacheControl;
182
+ constructor(metadata) {
183
+ this.name = metadata.name;
184
+ this.bucket = metadata.bucket;
185
+ this.generation = metadata.generation.toString();
186
+ this.metageneration = metadata.metageneration.toString();
187
+ this.contentType = metadata.contentType;
188
+ this.timeCreated = toSerializedDate(metadata.timeCreated);
189
+ this.updated = toSerializedDate(metadata.updated);
190
+ this.storageClass = metadata.storageClass;
191
+ this.size = metadata.size.toString();
192
+ this.md5Hash = metadata.md5Hash;
193
+ this.crc32c = metadata.crc32c;
194
+ this.etag = metadata.etag;
195
+ this.downloadTokens = metadata.downloadTokens.join(",");
196
+ this.contentEncoding = metadata.contentEncoding;
197
+ this.contentDisposition = metadata.contentDisposition;
198
+ this.metadata = metadata.customMetadata;
199
+ this.contentLanguage = metadata.contentLanguage;
200
+ this.cacheControl = metadata.cacheControl;
193
201
  }
194
202
  }
195
203
  exports.OutgoingFirebaseMetadata = OutgoingFirebaseMetadata;
@@ -226,44 +234,45 @@ class CloudStorageObjectAccessControlMetadata {
226
234
  }
227
235
  exports.CloudStorageObjectAccessControlMetadata = CloudStorageObjectAccessControlMetadata;
228
236
  class CloudStorageObjectMetadata {
229
- constructor(md) {
237
+ constructor(metadata) {
230
238
  var _a, _b, _c, _d;
231
239
  this.kind = "#storage#object";
232
- this.name = md.name;
233
- this.bucket = md.bucket;
234
- this.generation = md.generation.toString();
235
- this.metageneration = md.metageneration.toString();
236
- this.contentType = md.contentType;
237
- this.timeCreated = toSerializedDate(md.timeCreated);
238
- this.updated = toSerializedDate(md.updated);
239
- this.storageClass = md.storageClass;
240
- this.size = md.size.toString();
241
- this.md5Hash = md.md5Hash;
242
- this.etag = md.etag;
240
+ this.name = metadata.name;
241
+ this.bucket = metadata.bucket;
242
+ this.generation = metadata.generation.toString();
243
+ this.metageneration = metadata.metageneration.toString();
244
+ this.contentType = metadata.contentType;
245
+ this.contentDisposition = metadata.contentDisposition;
246
+ this.timeCreated = toSerializedDate(metadata.timeCreated);
247
+ this.updated = toSerializedDate(metadata.updated);
248
+ this.storageClass = metadata.storageClass;
249
+ this.size = metadata.size.toString();
250
+ this.md5Hash = metadata.md5Hash;
251
+ this.etag = metadata.etag;
243
252
  this.metadata = {};
244
- if (Object.keys(md.customMetadata || {})) {
245
- this.metadata = Object.assign(Object.assign({}, this.metadata), md.customMetadata);
253
+ if (Object.keys(metadata.customMetadata || {})) {
254
+ this.metadata = Object.assign(Object.assign({}, this.metadata), metadata.customMetadata);
246
255
  }
247
- if (md.downloadTokens.length) {
248
- this.metadata = Object.assign(Object.assign({}, this.metadata), { firebaseStorageDownloadTokens: md.downloadTokens.join(",") });
256
+ if (metadata.downloadTokens.length) {
257
+ this.metadata = Object.assign(Object.assign({}, this.metadata), { firebaseStorageDownloadTokens: metadata.downloadTokens.join(",") });
249
258
  }
250
259
  if (!Object.keys(this.metadata).length) {
251
260
  delete this.metadata;
252
261
  }
253
- if (md.contentLanguage) {
254
- this.contentLanguage = md.contentLanguage;
262
+ if (metadata.contentLanguage) {
263
+ this.contentLanguage = metadata.contentLanguage;
255
264
  }
256
- if (md.cacheControl) {
257
- this.cacheControl = md.cacheControl;
265
+ if (metadata.cacheControl) {
266
+ this.cacheControl = metadata.cacheControl;
258
267
  }
259
- if (md.customTime) {
260
- this.customTime = toSerializedDate(md.customTime);
268
+ if (metadata.customTime) {
269
+ this.customTime = toSerializedDate(metadata.customTime);
261
270
  }
262
- this.crc32c = "----" + Buffer.from([md.crc32c]).toString("base64");
263
- this.timeStorageClassUpdated = toSerializedDate(md.timeCreated);
264
- this.id = `${md.bucket}/${md.name}/${md.generation}`;
265
- this.selfLink = `http://${(_a = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _a === void 0 ? void 0 : _a.host}:${(_b = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _b === void 0 ? void 0 : _b.port}/storage/v1/b/${md.bucket}/o/${encodeURIComponent(md.name)}`;
266
- this.mediaLink = `http://${(_c = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _c === void 0 ? void 0 : _c.host}:${(_d = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _d === void 0 ? void 0 : _d.port}/download/storage/v1/b/${md.bucket}/o/${encodeURIComponent(md.name)}?generation=${md.generation}&alt=media`;
271
+ this.crc32c = (0, crc_1.crc32cToString)(metadata.crc32c);
272
+ this.timeStorageClassUpdated = toSerializedDate(metadata.timeCreated);
273
+ this.id = `${metadata.bucket}/${metadata.name}/${metadata.generation}`;
274
+ this.selfLink = `http://${(_a = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _a === void 0 ? void 0 : _a.host}:${(_b = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _b === void 0 ? void 0 : _b.port}/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}`;
275
+ this.mediaLink = `http://${(_c = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _c === void 0 ? void 0 : _c.host}:${(_d = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _d === void 0 ? void 0 : _d.port}/download/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}?generation=${metadata.generation}&alt=media`;
267
276
  }
268
277
  }
269
278
  exports.CloudStorageObjectMetadata = CloudStorageObjectMetadata;
@@ -287,3 +296,8 @@ function generateMd5Hash(bytes) {
287
296
  hash.update(bytes);
288
297
  return hash.digest("base64");
289
298
  }
299
+ function generateETag(generation, metadatageneration) {
300
+ const hash = crypto.createHash("sha1");
301
+ hash.update(`${generation}/${metadatageneration}`);
302
+ return hash.digest("base64").slice(0, -1);
303
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseObjectUploadMultipartRequest = void 0;
4
+ const LINE_SEPARATOR = `\r\n`;
5
+ function splitBufferByDelimiter(buffer, delimiter, maxResults = -1) {
6
+ let offset = 0;
7
+ let nextDelimiterIndex = buffer.indexOf(delimiter, offset);
8
+ const bufferParts = [];
9
+ while (nextDelimiterIndex !== -1) {
10
+ if (maxResults === 0) {
11
+ return bufferParts;
12
+ }
13
+ else if (maxResults === 1) {
14
+ bufferParts.push(Buffer.from(buffer.slice(offset)));
15
+ return bufferParts;
16
+ }
17
+ bufferParts.push(Buffer.from(buffer.slice(offset, nextDelimiterIndex)));
18
+ offset = nextDelimiterIndex + delimiter.length;
19
+ nextDelimiterIndex = buffer.indexOf(delimiter, offset);
20
+ maxResults -= 1;
21
+ }
22
+ bufferParts.push(Buffer.from(buffer.slice(offset)));
23
+ return bufferParts;
24
+ }
25
+ function parseMultipartRequestBody(boundaryId, body) {
26
+ const boundaryString = `--${boundaryId}`;
27
+ const bodyParts = splitBufferByDelimiter(body, boundaryString).map((buf) => {
28
+ return Buffer.from(buf.slice(2));
29
+ });
30
+ const parsedParts = [];
31
+ for (const bodyPart of bodyParts.slice(1, bodyParts.length - 1)) {
32
+ parsedParts.push(parseMultipartRequestBodyPart(bodyPart));
33
+ }
34
+ return parsedParts;
35
+ }
36
+ function parseMultipartRequestBodyPart(bodyPart) {
37
+ const sections = splitBufferByDelimiter(bodyPart, LINE_SEPARATOR, 3);
38
+ const contentTypeRaw = sections[0].toString();
39
+ if (!contentTypeRaw.startsWith("Content-Type: ")) {
40
+ throw new Error(`Failed to parse multipart request body part. Missing content type.`);
41
+ }
42
+ const dataRaw = Buffer.from(sections[2]).slice(0, sections[2].byteLength - LINE_SEPARATOR.length);
43
+ return { contentTypeRaw, dataRaw };
44
+ }
45
+ function parseObjectUploadMultipartRequest(contentTypeHeader, body) {
46
+ if (!contentTypeHeader.startsWith("multipart/related")) {
47
+ throw new Error(`Invalid Content-Type: ${contentTypeHeader}`);
48
+ }
49
+ const boundaryId = contentTypeHeader.split("boundary=")[1];
50
+ if (!boundaryId) {
51
+ throw new Error(`Invalid Content-Type header: ${contentTypeHeader}`);
52
+ }
53
+ const parsedBody = parseMultipartRequestBody(boundaryId, body);
54
+ if (parsedBody.length !== 2) {
55
+ throw new Error(`Unexpected number of parts in request body`);
56
+ }
57
+ return {
58
+ metadataRaw: parsedBody[0].dataRaw.toString(),
59
+ dataRaw: Buffer.from(parsedBody[1].dataRaw),
60
+ };
61
+ }
62
+ exports.parseObjectUploadMultipartRequest = parseObjectUploadMultipartRequest;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Persistence = void 0;
4
+ const fs_1 = require("fs");
5
+ const rimraf = require("rimraf");
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ class Persistence {
9
+ constructor(dirPath) {
10
+ this.reset(dirPath);
11
+ }
12
+ reset(dirPath) {
13
+ this._dirPath = dirPath;
14
+ (0, fs_1.mkdirSync)(dirPath, {
15
+ recursive: true,
16
+ });
17
+ }
18
+ get dirPath() {
19
+ return this._dirPath;
20
+ }
21
+ appendBytes(fileName, bytes) {
22
+ const filepath = this.getDiskPath(fileName);
23
+ let fd;
24
+ try {
25
+ fs.appendFileSync(filepath, bytes);
26
+ return filepath;
27
+ }
28
+ finally {
29
+ if (fd) {
30
+ (0, fs_1.closeSync)(fd);
31
+ }
32
+ }
33
+ }
34
+ readBytes(fileName, size, fileOffset) {
35
+ let fd;
36
+ try {
37
+ fd = (0, fs_1.openSync)(this.getDiskPath(fileName), "r");
38
+ const buf = Buffer.alloc(size);
39
+ const offset = fileOffset && fileOffset > 0 ? fileOffset : 0;
40
+ (0, fs_1.readSync)(fd, buf, 0, size, offset);
41
+ return buf;
42
+ }
43
+ finally {
44
+ if (fd) {
45
+ (0, fs_1.closeSync)(fd);
46
+ }
47
+ }
48
+ }
49
+ deleteFile(fileName, failSilently = false) {
50
+ try {
51
+ (0, fs_1.unlinkSync)(this.getDiskPath(fileName));
52
+ }
53
+ catch (err) {
54
+ if (!failSilently) {
55
+ throw err;
56
+ }
57
+ }
58
+ }
59
+ deleteAll() {
60
+ return new Promise((resolve, reject) => {
61
+ rimraf(this._dirPath, (err) => {
62
+ if (err) {
63
+ reject(err);
64
+ }
65
+ else {
66
+ resolve();
67
+ }
68
+ });
69
+ });
70
+ }
71
+ renameFile(oldName, newName) {
72
+ (0, fs_1.renameSync)(this.getDiskPath(oldName), this.getDiskPath(newName));
73
+ }
74
+ getDiskPath(fileName) {
75
+ return path.join(this._dirPath, encodeURIComponent(fileName));
76
+ }
77
+ }
78
+ exports.Persistence = Persistence;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getStorageRulesConfig = void 0;
4
+ const error_1 = require("../../../error");
5
+ const fsutils_1 = require("../../../fsutils");
6
+ function getSourceFile(rules, options) {
7
+ const path = options.config.path(rules);
8
+ return { name: path, content: (0, fsutils_1.readFile)(path) };
9
+ }
10
+ function getStorageRulesConfig(projectId, options) {
11
+ const storageConfig = options.config.data.storage;
12
+ if (!storageConfig) {
13
+ throw new error_1.FirebaseError("Cannot start the Storage emulator without rules file specified in firebase.json: run 'firebase init' and set up your Storage configuration");
14
+ }
15
+ if (!Array.isArray(storageConfig)) {
16
+ if (!storageConfig.rules) {
17
+ throw new error_1.FirebaseError("Cannot start the Storage emulator without rules file specified in firebase.json: run 'firebase init' and set up your Storage configuration");
18
+ }
19
+ return getSourceFile(storageConfig.rules, options);
20
+ }
21
+ const results = [];
22
+ const { rc } = options;
23
+ for (const targetConfig of storageConfig) {
24
+ if (!targetConfig.target) {
25
+ throw new error_1.FirebaseError("Must supply 'target' in Storage configuration");
26
+ }
27
+ rc.requireTarget(projectId, "storage", targetConfig.target);
28
+ rc.target(projectId, "storage", targetConfig.target).forEach((resource) => {
29
+ results.push({ resource, rules: getSourceFile(targetConfig.rules, options) });
30
+ });
31
+ }
32
+ return results;
33
+ }
34
+ exports.getStorageRulesConfig = getStorageRulesConfig;
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createStorageRulesManager = void 0;
4
+ const chokidar = require("chokidar");
5
+ const emulatorLogger_1 = require("../../emulatorLogger");
6
+ const types_1 = require("../../types");
7
+ const runtime_1 = require("./runtime");
8
+ function createStorageRulesManager(rules, runtime) {
9
+ return Array.isArray(rules)
10
+ ? new ResourceBasedStorageRulesManager(rules, runtime)
11
+ : new DefaultStorageRulesManager(rules, runtime);
12
+ }
13
+ exports.createStorageRulesManager = createStorageRulesManager;
14
+ class DefaultStorageRulesManager {
15
+ constructor(_rules, _runtime) {
16
+ this._runtime = _runtime;
17
+ this._watcher = new chokidar.FSWatcher();
18
+ this._logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE);
19
+ this._rules = _rules;
20
+ }
21
+ start() {
22
+ return this.updateSourceFile(this._rules);
23
+ }
24
+ getRuleset() {
25
+ return this._ruleset;
26
+ }
27
+ async updateSourceFile(rules) {
28
+ const prevRulesFile = this._rules.name;
29
+ this._rules = rules;
30
+ const issues = await this.loadRuleset();
31
+ this.updateWatcher(rules.name, prevRulesFile);
32
+ return issues;
33
+ }
34
+ async stop() {
35
+ await this._watcher.close();
36
+ }
37
+ updateWatcher(rulesFile, prevRulesFile) {
38
+ if (prevRulesFile) {
39
+ this._watcher.unwatch(prevRulesFile);
40
+ }
41
+ this._watcher = chokidar
42
+ .watch(rulesFile, { persistent: true, ignoreInitial: true })
43
+ .on("change", async () => {
44
+ await new Promise((res) => setTimeout(res, 5));
45
+ this._logger.logLabeled("BULLET", "storage", "Change detected, updating rules for Cloud Storage...");
46
+ await this.loadRuleset();
47
+ });
48
+ }
49
+ async loadRuleset() {
50
+ const { ruleset, issues } = await this._runtime.loadRuleset({ files: [this._rules] });
51
+ if (ruleset) {
52
+ this._ruleset = ruleset;
53
+ return issues;
54
+ }
55
+ issues.all.forEach((issue) => {
56
+ try {
57
+ const parsedIssue = JSON.parse(issue);
58
+ this._logger.log("WARN", `${parsedIssue.description_.replace(/\.$/, "")} in ${parsedIssue.sourcePosition_.fileName_}:${parsedIssue.sourcePosition_.line_}`);
59
+ }
60
+ catch (_a) {
61
+ this._logger.log("WARN", issue);
62
+ }
63
+ });
64
+ return issues;
65
+ }
66
+ }
67
+ class ResourceBasedStorageRulesManager {
68
+ constructor(_rulesConfig, _runtime) {
69
+ this._runtime = _runtime;
70
+ this._rulesManagers = new Map();
71
+ for (const { resource, rules } of _rulesConfig) {
72
+ this.createRulesManager(resource, rules);
73
+ }
74
+ }
75
+ async start() {
76
+ const allIssues = new runtime_1.StorageRulesIssues();
77
+ for (const rulesManager of this._rulesManagers.values()) {
78
+ allIssues.extend(await rulesManager.start());
79
+ }
80
+ return allIssues;
81
+ }
82
+ getRuleset(resource) {
83
+ var _a;
84
+ return (_a = this._rulesManagers.get(resource)) === null || _a === void 0 ? void 0 : _a.getRuleset();
85
+ }
86
+ updateSourceFile(rules, resource) {
87
+ const rulesManager = this._rulesManagers.get(resource) || this.createRulesManager(resource, rules);
88
+ return rulesManager.updateSourceFile(rules);
89
+ }
90
+ async stop() {
91
+ await Promise.all(Array.from(this._rulesManagers.values(), async (rulesManager) => await rulesManager.stop()));
92
+ }
93
+ createRulesManager(resource, rules) {
94
+ const rulesManager = new DefaultStorageRulesManager(rules, this._runtime);
95
+ this._rulesManagers.set(resource, rulesManager);
96
+ return rulesManager;
97
+ }
98
+ }
@@ -49,6 +49,10 @@ class StorageRulesIssues {
49
49
  exist() {
50
50
  return !!(this.errors.length || this.warnings.length);
51
51
  }
52
+ extend(other) {
53
+ this.errors.push(...other.errors);
54
+ this.warnings.push(...other.warnings);
55
+ }
52
56
  }
53
57
  exports.StorageRulesIssues = StorageRulesIssues;
54
58
  class StorageRulesRuntime {
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isPermitted = exports.getAdminCredentialValidator = exports.getRulesValidator = void 0;
4
+ const emulatorLogger_1 = require("../../emulatorLogger");
5
+ const types_1 = require("../../types");
6
+ function getRulesValidator(rulesetProvider) {
7
+ return {
8
+ validate: async (path, bucketId, method, variableOverrides, authorization) => {
9
+ return await isPermitted({
10
+ ruleset: rulesetProvider(bucketId),
11
+ file: variableOverrides,
12
+ path,
13
+ method,
14
+ authorization,
15
+ });
16
+ },
17
+ };
18
+ }
19
+ exports.getRulesValidator = getRulesValidator;
20
+ function getAdminCredentialValidator() {
21
+ return { validate: isValidAdminCredentials };
22
+ }
23
+ exports.getAdminCredentialValidator = getAdminCredentialValidator;
24
+ async function isPermitted(opts) {
25
+ if (!opts.ruleset) {
26
+ emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("WARN", `Can not process SDK request with no loaded ruleset`);
27
+ return false;
28
+ }
29
+ if (isValidAdminCredentials(opts.authorization)) {
30
+ return true;
31
+ }
32
+ const { permitted, issues } = await opts.ruleset.verify({
33
+ method: opts.method,
34
+ path: opts.path,
35
+ file: opts.file,
36
+ token: opts.authorization ? opts.authorization.split(" ")[1] : undefined,
37
+ });
38
+ if (issues.exist()) {
39
+ issues.all.forEach((warningOrError) => {
40
+ emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("WARN", warningOrError);
41
+ });
42
+ }
43
+ return !!permitted;
44
+ }
45
+ exports.isPermitted = isPermitted;
46
+ function isValidAdminCredentials(authorization) {
47
+ return ["Bearer owner", "Firebase owner"].includes(authorization !== null && authorization !== void 0 ? authorization : "");
48
+ }
@@ -58,7 +58,7 @@ function createApp(defaultProjectId, emulator) {
58
58
  }
59
59
  const name = file.name;
60
60
  const content = file.content;
61
- const issues = await emulator.loadRuleset({ files: [{ name, content }] });
61
+ const issues = await emulator.rulesManager.updateSourceFile({ name, content }, req.params.bucketId);
62
62
  if (issues.errors.length > 0) {
63
63
  res.status(400).json({
64
64
  message: "There was an error updating rules, see logs for more details",
@@ -70,7 +70,7 @@ function createApp(defaultProjectId, emulator) {
70
70
  });
71
71
  });
72
72
  app.post("/internal/reset", (req, res) => {
73
- storageLayer.reset();
73
+ emulator.reset();
74
74
  res.sendStatus(200);
75
75
  });
76
76
  app.use("/v0", (0, firebase_1.createFirebaseEndpoints)(emulator));