backend-manager 3.2.169 → 3.2.171

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 (88) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +1 -1
  3. package/dist/cli/cli.js +1534 -0
  4. package/dist/manager/functions/core/actions/api/admin/backup.js +338 -0
  5. package/dist/manager/functions/core/actions/api/admin/create-post.js +388 -0
  6. package/dist/manager/functions/core/actions/api/admin/cron.js +37 -0
  7. package/dist/manager/functions/core/actions/api/admin/database-read.js +35 -0
  8. package/dist/manager/functions/core/actions/api/admin/database-write.js +39 -0
  9. package/dist/manager/functions/core/actions/api/admin/edit-post.js +158 -0
  10. package/dist/manager/functions/core/actions/api/admin/firestore-query.js +165 -0
  11. package/dist/manager/functions/core/actions/api/admin/firestore-read.js +38 -0
  12. package/dist/manager/functions/core/actions/api/admin/firestore-write.js +54 -0
  13. package/dist/manager/functions/core/actions/api/admin/get-stats.js +269 -0
  14. package/dist/manager/functions/core/actions/api/admin/payment-processor.js +57 -0
  15. package/dist/manager/functions/core/actions/api/admin/run-hook.js +95 -0
  16. package/dist/manager/functions/core/actions/api/admin/send-notification.js +197 -0
  17. package/dist/manager/functions/core/actions/api/admin/sync-users.js +125 -0
  18. package/dist/manager/functions/core/actions/api/admin/templates/post.html +16 -0
  19. package/dist/manager/functions/core/actions/api/firebase/get-providers.js +102 -0
  20. package/dist/manager/functions/core/actions/api/general/emails/general:download-app-link.js +21 -0
  21. package/dist/manager/functions/core/actions/api/general/fetch-post.js +99 -0
  22. package/dist/manager/functions/core/actions/api/general/generate-uuid.js +41 -0
  23. package/dist/manager/functions/core/actions/api/general/send-email.js +112 -0
  24. package/dist/manager/functions/core/actions/api/handler/create-post.js +146 -0
  25. package/dist/manager/functions/core/actions/api/special/setup-electron-manager-client.js +103 -0
  26. package/dist/manager/functions/core/actions/api/template.js +33 -0
  27. package/dist/manager/functions/core/actions/api/test/authenticate.js +22 -0
  28. package/dist/manager/functions/core/actions/api/test/create-test-accounts.js +27 -0
  29. package/dist/manager/functions/core/actions/api/test/lab.js +55 -0
  30. package/dist/manager/functions/core/actions/api/test/redirect.js +26 -0
  31. package/dist/manager/functions/core/actions/api/test/webhook.js +30 -0
  32. package/dist/manager/functions/core/actions/api/user/create-custom-token.js +32 -0
  33. package/dist/manager/functions/core/actions/api/user/delete.js +68 -0
  34. package/dist/manager/functions/core/actions/api/user/get-active-sessions.js +45 -0
  35. package/dist/manager/functions/core/actions/api/user/get-subscription-info.js +49 -0
  36. package/dist/manager/functions/core/actions/api/user/oauth2/discord.js +114 -0
  37. package/dist/manager/functions/core/actions/api/user/oauth2/google.js +99 -0
  38. package/dist/manager/functions/core/actions/api/user/oauth2.js +476 -0
  39. package/dist/manager/functions/core/actions/api/user/regenerate-api-keys.js +54 -0
  40. package/dist/manager/functions/core/actions/api/user/resolve.js +32 -0
  41. package/dist/manager/functions/core/actions/api/user/sign-out-all-sessions.js +118 -0
  42. package/dist/manager/functions/core/actions/api/user/sign-up copy.js +544 -0
  43. package/dist/manager/functions/core/actions/api/user/sign-up.js +99 -0
  44. package/dist/manager/functions/core/actions/api/user/submit-feedback.js +96 -0
  45. package/dist/manager/functions/core/actions/api/user/validate-settings.js +86 -0
  46. package/dist/manager/functions/core/actions/api.js +354 -0
  47. package/dist/manager/functions/core/actions/create-post-handler.js +184 -0
  48. package/dist/manager/functions/core/actions/generate-uuid.js +62 -0
  49. package/dist/manager/functions/core/actions/sign-up-handler.js +205 -0
  50. package/dist/manager/functions/core/admin/create-post.js +206 -0
  51. package/dist/manager/functions/core/admin/firestore-write.js +72 -0
  52. package/dist/manager/functions/core/admin/get-stats.js +218 -0
  53. package/dist/manager/functions/core/admin/query.js +198 -0
  54. package/dist/manager/functions/core/admin/send-notification.js +206 -0
  55. package/dist/manager/functions/core/cron/daily/ghostii-auto-publisher.js +377 -0
  56. package/dist/manager/functions/core/cron/daily/reset-usage.js +197 -0
  57. package/dist/manager/functions/core/cron/daily.js +114 -0
  58. package/dist/manager/functions/core/events/auth/before-create.js +124 -0
  59. package/dist/manager/functions/core/events/auth/before-signin.js +62 -0
  60. package/dist/manager/functions/core/events/auth/on-create copy.js +121 -0
  61. package/dist/manager/functions/core/events/auth/on-create.js +564 -0
  62. package/dist/manager/functions/core/events/auth/on-delete.js +72 -0
  63. package/dist/manager/functions/core/events/firestore/on-subscription.js +107 -0
  64. package/dist/manager/functions/test/authenticate.js +38 -0
  65. package/dist/manager/functions/test/create-test-accounts.js +144 -0
  66. package/dist/manager/functions/test/webhook.js +37 -0
  67. package/dist/manager/functions/wrappers/mailchimp/addToList.js +25 -0
  68. package/dist/manager/helpers/analytics copy.js +217 -0
  69. package/dist/manager/helpers/analytics.js +467 -0
  70. package/dist/manager/helpers/api-manager.js +324 -0
  71. package/dist/manager/helpers/assistant.js +1043 -0
  72. package/dist/manager/helpers/metadata.js +32 -0
  73. package/dist/manager/helpers/middleware.js +154 -0
  74. package/dist/manager/helpers/roles.js +69 -0
  75. package/dist/manager/helpers/settings.js +158 -0
  76. package/dist/manager/helpers/subscription-resolver-new.js +828 -0
  77. package/dist/manager/helpers/subscription-resolver.js +842 -0
  78. package/dist/manager/helpers/usage.js +381 -0
  79. package/dist/manager/helpers/user.js +198 -0
  80. package/dist/manager/helpers/utilities.js +292 -0
  81. package/dist/manager/index.js +1076 -0
  82. package/dist/manager/libraries/openai.js +460 -0
  83. package/dist/manager/routes/restart/index.js +52 -0
  84. package/dist/manager/routes/test/index.js +43 -0
  85. package/dist/manager/schemas/restart.js +13 -0
  86. package/dist/manager/schemas/test.js +13 -0
  87. package/dist/require.js +3 -0
  88. package/package.json +19 -9
@@ -0,0 +1,1043 @@
1
+ const os = require('os');
2
+ const path = require('path');
3
+ const _ = require('lodash');
4
+ const uuid = require('uuid');
5
+ let JSON5;
6
+
7
+ function BackendAssistant() {
8
+ const self = this;
9
+
10
+ // Set ref
11
+ self.meta = {};
12
+ self.initialized = false;
13
+
14
+ // Add log methods
15
+ addLogMethods();
16
+
17
+ return self;
18
+ }
19
+
20
+ function tryParse(input) {
21
+ var ret;
22
+
23
+ JSON5 = JSON5 || require('json5');
24
+
25
+ try {
26
+ ret = JSON5.parse(input);
27
+ } catch (e) {
28
+ ret = input
29
+ }
30
+ return ret;
31
+ }
32
+
33
+ BackendAssistant.prototype.init = function (ref, options) {
34
+ const self = this;
35
+
36
+ options = options || {};
37
+ options.accept = options.accept || 'json';
38
+ options.showOptionsLog = typeof options.showOptionsLog !== 'undefined' ? options.showOptionsLog : false;
39
+ options.optionsLogString = typeof options.optionsLogString !== 'undefined' ? options.optionsLogString : '\n\n\n\n\n';
40
+ options.fileSavePath = options.fileSavePath || process.env.npm_package_name || '';
41
+
42
+ const now = new Date();
43
+
44
+ // Attached libraries - used in .errorify()
45
+ self.analytics = null;
46
+ self.usage = null;
47
+ self.settings = null;
48
+
49
+ // Set meta
50
+ self.meta = {};
51
+
52
+ self.meta.startTime = {};
53
+ self.meta.startTime.timestamp = now.toISOString();
54
+ self.meta.startTime.timestampUNIX = Math.round((now.getTime()) / 1000);
55
+
56
+ self.meta.name = options.functionName || process.env.FUNCTION_TARGET || 'unnamed';
57
+ self.meta.environment = options.environment || self.getEnvironment();
58
+ self.meta.type = options.functionType || process.env.FUNCTION_SIGNATURE_TYPE || 'unknown';
59
+
60
+ // Set ref
61
+ self.ref = {};
62
+ ref = ref || {};
63
+ self.ref.res = ref.res || {};
64
+ self.ref.req = ref.req || {};
65
+ self.ref.admin = ref.admin || {};
66
+ self.ref.functions = ref.functions || {};
67
+ self.ref.Manager = ref.Manager || {};
68
+ self.Manager = self.ref.Manager;
69
+
70
+ // Set ID
71
+ try {
72
+ const headers = self?.ref?.req.headers || {};
73
+
74
+ self.id = headers['function-execution-id']
75
+ || headers['X-Cloud-Trace-Context']
76
+ || self.Manager.Utilities().randomId();
77
+ } catch {
78
+ self.id = now.getTime();
79
+ }
80
+
81
+ // Set tag
82
+ self.tag = `${self.meta.name}/${self.id}`;
83
+
84
+ // Set logger prefix
85
+ self.logPrefix = '';
86
+
87
+ // Set stuff about request
88
+ self.request = {};
89
+ self.request.referrer = (self.ref.req.headers || {}).referrer || (self.ref.req.headers || {}).referer || '';
90
+ self.request.method = (self.ref.req.method || undefined);
91
+
92
+ // Set geolocation data
93
+ self.request.geolocation = {
94
+ ip: self.getHeaderIp(self.ref.req.headers),
95
+ continent: self.getHeaderContinent(self.ref.req.headers),
96
+ country: self.getHeaderCountry(self.ref.req.headers),
97
+ region: self.getHeaderRegion(self.ref.req.headers),
98
+ city: self.getHeaderCity(self.ref.req.headers),
99
+ latitude: self.getHeaderLatitude(self.ref.req.headers),
100
+ longitude: self.getHeaderLongitude(self.ref.req.headers),
101
+ };
102
+
103
+ // Set client data
104
+ self.request.client = {
105
+ userAgent: self.getHeaderUserAgent(self.ref.req.headers),
106
+ language: self.getHeaderLanguage(self.ref.req.headers),
107
+ platform: self.getHeaderPlatform(self.ref.req.headers),
108
+ mobile: self.getHeaderMobile(self.ref.req.headers),
109
+ };
110
+
111
+ // Deprecated notice for old properties
112
+ Object.defineProperty(self.request, 'ip', {
113
+ get: function() {
114
+ console.error('⛔️ [Deprecation]: request.ip is deprecated, use request.geolocation.ip instead');
115
+ return self.request.geolocation.ip;
116
+ }
117
+ });
118
+ Object.defineProperty(self.request, 'country', {
119
+ get: function() {
120
+ console.error('⛔️ [Deprecation]: request.country is deprecated, use request.geolocation.country instead');
121
+ return self.request.geolocation.country;
122
+ }
123
+ });
124
+ Object.defineProperty(self.request, 'userAgent', {
125
+ get: function() {
126
+ console.error('⛔️ [Deprecation]: request.userAgent is deprecated, use request.client.userAgent instead');
127
+ return self.request.client.userAgent;
128
+ }
129
+ });
130
+
131
+ /*
132
+ MORE HEADERS TO GET
133
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Platform-Version
134
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Model
135
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Mobile
136
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Full-Version-List
137
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Full-Version
138
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Arch
139
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA
140
+ */
141
+
142
+ // Set request type
143
+ if (
144
+ self.ref.req.xhr || (self.ref.req?.headers?.accept || '').includes('json')
145
+ || (self.ref.req?.headers?.['content-type'] || '').includes('json')
146
+ ) {
147
+ self.request.type = 'ajax';
148
+ } else {
149
+ self.request.type = 'form';
150
+ }
151
+ self.request.path = self.ref.req.path || '';
152
+ self.request.user = self.resolveAccount({authenticated: false});
153
+
154
+ // Set body and query
155
+ if (options.accept === 'json') {
156
+ self.request.body = tryParse(self.ref.req.body || '{}');
157
+ self.request.query = tryParse(self.ref.req.query || '{}');
158
+ }
159
+
160
+ // Set headers
161
+ self.request.headers = self.ref.req.headers || {};
162
+
163
+ // Merge data
164
+ self.request.data = _.merge({}, self.request.body, self.request.query);
165
+
166
+ // Set multipart data
167
+ self.request.multipartData = {
168
+ fields: {},
169
+ files: {},
170
+ };
171
+
172
+ // Log the request
173
+ // if (Object.keys(self.request.data).length > 0) {
174
+ // self.log('Request:', self.request.data, {
175
+ // ip: self.request.ip,
176
+ // });
177
+ // }
178
+
179
+ // Constants
180
+ self.constant = {};
181
+ self.constant.pastTime = {};
182
+ self.constant.pastTime.timestamp = '1999-01-01T00:00:00Z';
183
+ self.constant.pastTime.timestampUNIX = 915148800;
184
+
185
+ // Log options
186
+ if (
187
+ (self.isDevelopment())
188
+ && ((self.request.method !== 'OPTIONS') || (self.request.method === 'OPTIONS' && options.showOptionsLog))
189
+ && (self.request.method !== 'undefined')
190
+ // && (self.request.method !== 'undefined' && typeof self.request.method !== 'undefined')
191
+ ) {
192
+ console.log(options.optionsLogString);
193
+ }
194
+
195
+ // Set tmpdir
196
+ self.tmpdir = path.resolve(os.tmpdir(), options.fileSavePath, uuid.v4());
197
+
198
+ // Set initialized
199
+ self.initialized = true;
200
+
201
+ return self;
202
+ };
203
+
204
+ BackendAssistant.prototype.getEnvironment = function () {
205
+ // return (process.env.FUNCTIONS_EMULATOR === true || process.env.FUNCTIONS_EMULATOR === 'true' || process.env.ENVIRONMENT !== 'production' ? 'development' : 'production')
206
+ if (process.env.ENVIRONMENT === 'production') {
207
+ return 'production';
208
+ } else if (
209
+ process.env.ENVIRONMENT === 'development'
210
+ || process.env.FUNCTIONS_EMULATOR === true
211
+ || process.env.FUNCTIONS_EMULATOR === 'true'
212
+ || process.env.TERM_PROGRAM === 'Apple_Terminal'
213
+ || process.env.TERM_PROGRAM === 'vscode'
214
+ ) {
215
+ return 'development';
216
+ } else {
217
+ return 'production'
218
+ }
219
+ };
220
+
221
+ BackendAssistant.prototype.isDevelopment = function () {
222
+ const self = this;
223
+
224
+ return self.meta.environment === 'development';
225
+ }
226
+
227
+ BackendAssistant.prototype.isProduction = function () {
228
+ const self = this;
229
+
230
+ return self.meta.environment === 'production';
231
+ }
232
+
233
+ BackendAssistant.prototype.logProd = function () {
234
+ const self = this;
235
+
236
+ self._log.apply(self, args);
237
+ };
238
+
239
+ BackendAssistant.prototype.log = function () {
240
+ const self = this;
241
+
242
+ const args = Array.prototype.slice.call(arguments);
243
+
244
+ self._log.apply(self, args);
245
+ };
246
+
247
+ function addLogMethods() {
248
+ const levels = ['error', 'warn', 'info', 'debug', 'notice', 'critical', 'emergency'];
249
+
250
+ // Add log methods
251
+ levels.forEach((level) => {
252
+ BackendAssistant.prototype[level] = function() {
253
+ const self = this;
254
+ const args = Array.prototype.slice.call(arguments);
255
+
256
+ // Prepend level to args
257
+ args.unshift(level);
258
+ self.log.apply(this, args);
259
+ };
260
+ });
261
+ }
262
+
263
+ BackendAssistant.prototype._log = function () {
264
+ const self = this;
265
+
266
+ // 1. Convert args to a normal array
267
+ const logs = [...Array.prototype.slice.call(arguments)];
268
+
269
+ // Add log prefix
270
+ const prefix = self.logPrefix
271
+ ? ` ${self.logPrefix}:`
272
+ : ':';
273
+
274
+ // 2. Prepend log prefix log string
275
+ logs.unshift(
276
+ `[${self.tag} @ ${new Date().toISOString()}]${prefix}`
277
+ );
278
+
279
+ // 3. Pass along arguments to console.log
280
+ if (logs[1] === 'error') {
281
+ logs.splice(1,1)
282
+ console.error.apply(console, logs);
283
+ } else if (logs[1] === 'warn') {
284
+ logs.splice(1,1)
285
+ console.warn.apply(console, logs);
286
+ } else if (logs[1] === 'info') {
287
+ logs.splice(1,1)
288
+ console.info.apply(console, logs);
289
+ } else if (logs[1] === 'debug') {
290
+ logs.splice(1,1)
291
+ console.debug.apply(console, logs);
292
+ } else if (logs[1] === 'notice') {
293
+ logs.splice(1,1)
294
+ if (self.isDevelopment()) {
295
+ console.log.apply(console, logs);
296
+ } else {
297
+ self.ref.functions.logger.write({
298
+ severity: 'NOTICE',
299
+ message: logs,
300
+ });
301
+ }
302
+ } else if (logs[1] === 'critical') {
303
+ logs.splice(1,1)
304
+ if (isDevelopment) {
305
+ console.log.apply(console, logs);
306
+ } else {
307
+ self.ref.functions.logger.write({
308
+ severity: 'CRITICAL',
309
+ message: logs,
310
+ });
311
+ }
312
+ } else if (logs[1] === 'emergency') {
313
+ logs.splice(1,1)
314
+ if (isDevelopment) {
315
+ console.log.apply(console, logs);
316
+ } else {
317
+ self.ref.functions.logger.write({
318
+ severity: 'EMERGENCY',
319
+ message: logs,
320
+ });
321
+ }
322
+ } else if (logs[1] === 'log') {
323
+ logs.splice(1,1)
324
+ console.log.apply(console, logs);
325
+ } else {
326
+ console.log.apply(console, logs);
327
+ }
328
+ }
329
+
330
+ BackendAssistant.prototype.setLogPrefix = function (s) {
331
+ const self = this;
332
+
333
+ // Set logger prefix
334
+ self.logPrefix = s
335
+
336
+ return self;
337
+ };
338
+
339
+ BackendAssistant.prototype.clearLogPrefix = function () {
340
+ const self = this;
341
+
342
+ // Set logger prefix
343
+ self.logPrefix = '';
344
+
345
+ return self;
346
+ };
347
+
348
+ BackendAssistant.prototype.getLogPrefix = function () {
349
+ const self = this;
350
+
351
+ return self.logPrefix;
352
+ };
353
+
354
+ BackendAssistant.prototype.getUser = function () {
355
+ const self = this;
356
+
357
+ return self?.usage?.user || self.request.user;
358
+ }
359
+
360
+ BackendAssistant.prototype.errorify = function (e, options) {
361
+ const self = this;
362
+ const res = self.ref.res;
363
+
364
+ // Set options
365
+ options = options || {};
366
+
367
+ // Code: default to 500, or else use the user's option
368
+ const isCodeSet = typeof options.code !== 'undefined';
369
+ options.code = !isCodeSet
370
+ ? 500
371
+ : options.code;
372
+
373
+ // Sentry: default to false, or else use the user's option
374
+ options.sentry = typeof options.sentry === 'undefined'
375
+ ? false
376
+ : options.sentry;
377
+
378
+ // Log: default to sentry, or else use the user's option
379
+ options.log = typeof options.log === 'undefined'
380
+ ? options.sentry
381
+ : options.log;
382
+
383
+ // Send: default to false, or else use the user's option
384
+ options.send = typeof options.send === 'undefined'
385
+ ? false
386
+ : options.send;
387
+
388
+ // Stack: default to false, or else use the user's option
389
+ options.stack = typeof options.stack === 'undefined'
390
+ ? false
391
+ : options.stack;
392
+
393
+ // Construct error
394
+ const newError = e instanceof Error
395
+ ? e
396
+ : new Error(stringifyNonStrings(e));
397
+
398
+ // Fix code
399
+ // options.code = newError.code || options.code;
400
+ options.code = isCodeSet ? options.code : newError.code || options.code;
401
+ options.code = parseInt(options.code);
402
+ options.code = isBetween(options.code, 400, 599) ? options.code : 500;
403
+
404
+ // Attach properties
405
+ _attachHeaderProperties(self, options, newError);
406
+
407
+ // Log the error
408
+ if (options.log) {
409
+ self.error(newError);
410
+ }
411
+
412
+ // Send error to Sentry
413
+ if (options.sentry) {
414
+ self.Manager.libraries.sentry.captureException(newError);
415
+ }
416
+
417
+ // Quit and respond to the request only if the assistant has a res (it sometimes does not, like in auth().onCreate() triggers)
418
+ if (options.send && res?.status) {
419
+ let sendable = newError?.stack && options.stack
420
+ ? newError?.stack
421
+ : newError?.message;
422
+
423
+ // Set error
424
+ sendable = `${sendable || newError || 'Unknown error'}`;
425
+
426
+ // Attach tag
427
+ if (newError.tag) {
428
+ // sendable = `(${newError.tag}) ${sendable}`;
429
+ sendable = `${sendable} (${newError.tag})`;
430
+ }
431
+
432
+ // Clear log prefix before sending
433
+ self.clearLogPrefix();
434
+
435
+ // Log
436
+ if (options.log) {
437
+ self.log(`Sending response (${options.code}):`, JSON.stringify(sendable));
438
+ }
439
+
440
+ // Send response
441
+ res
442
+ .status(options.code)
443
+ .send(sendable);
444
+ }
445
+
446
+ return newError;
447
+ }
448
+
449
+ BackendAssistant.prototype.errorManager = BackendAssistant.prototype.errorify;
450
+
451
+ BackendAssistant.prototype.redirect = function(response, options) {
452
+ const self = this;
453
+ const res = self.ref.res;
454
+
455
+ // Set options
456
+ options = options || {};
457
+ options.code = typeof options.code === 'undefined'
458
+ ? 302
459
+ : options.code;
460
+
461
+ return self.respond(response, options);
462
+ }
463
+
464
+ BackendAssistant.prototype.respond = function(response, options) {
465
+ const self = this;
466
+ const res = self.ref.res;
467
+
468
+ // Set options
469
+ options = options || {};
470
+ options.code = typeof options.code === 'undefined'
471
+ ? 200
472
+ : options.code;
473
+ options.log = typeof options.log === 'undefined'
474
+ ? true
475
+ : options.log;
476
+
477
+ // Fix code
478
+ options.code = parseInt(options.code);
479
+
480
+ // Handle error
481
+ const isErrorCode = isBetween(options.code, 400, 599);
482
+ if (
483
+ response instanceof Error
484
+ || isErrorCode
485
+ ) {
486
+ options.code = isErrorCode ? options.code : undefined;
487
+ options.send = true;
488
+
489
+ return self.errorify(response, options);
490
+ }
491
+
492
+ // Attach properties
493
+ _attachHeaderProperties(self, options);
494
+
495
+ // Send response
496
+ res.status(options.code);
497
+
498
+ // Log function
499
+ function _log(text) {
500
+ if (options.log) {
501
+ self.log(`${text} (${options.code}):`, JSON.stringify(response));
502
+ }
503
+ }
504
+
505
+ // Clear log prefix before sending
506
+ self.clearLogPrefix();
507
+
508
+ // Redirect
509
+ const isRedirect = isBetween(options.code, 300, 399);
510
+ if (isRedirect) {
511
+ // Log
512
+ _log(`Redirecting`);
513
+
514
+ // Send
515
+ return res.redirect(response);
516
+ }
517
+
518
+ // Log
519
+ _log(`Sending response`);
520
+
521
+ // If it is an object, send as json
522
+ if (
523
+ response
524
+ && typeof response === 'object'
525
+ && typeof res.json === 'function'
526
+ ) {
527
+ return res.json(response);
528
+ } else {
529
+ return res.send(response);
530
+ }
531
+ }
532
+
533
+ function isBetween(value, min, max) {
534
+ return value >= min && value <= max;
535
+ }
536
+
537
+ function stringifyNonStrings(e) {
538
+ if (typeof e === 'string') {
539
+ return e;
540
+ } else {
541
+ return JSON.stringify(e);
542
+ }
543
+ }
544
+
545
+ function _attachHeaderProperties(self, options, error) {
546
+ // Create headers
547
+ const headers = {
548
+ code: options.code,
549
+ tag: self.tag,
550
+ usage: {
551
+ current: self?.usage?.getUsage() || {},
552
+ limits: self?.usage?.getLimit() || {},
553
+ },
554
+ additional: options.additional || {},
555
+ }
556
+ const req = self.ref.req;
557
+ const res = self.ref.res;
558
+
559
+ // Attach properties if this assistant has a res (it sometimes does not, like in auth().onCreate() triggers)
560
+ if (res?.header && res?.get) {
561
+ res.header('bm-properties', JSON.stringify(headers));
562
+
563
+ // Add bm-properties to Access-Control-Expose-Headers
564
+ const existingExposed = res.get('Access-Control-Expose-Headers') || '';
565
+ const newExposed = `${existingExposed}, bm-properties`.replace(/^, /, '');
566
+
567
+ if (!existingExposed.match(/bm-properties/i)) {
568
+ res.header('Access-Control-Expose-Headers', newExposed);
569
+ }
570
+ }
571
+
572
+ // Attach properties
573
+ if (error) {
574
+ Object.keys(headers)
575
+ .forEach((item, i) => {
576
+ error[item] = headers[item];
577
+ });
578
+ }
579
+ }
580
+
581
+ BackendAssistant.prototype.authenticate = async function (options) {
582
+ const self = this;
583
+
584
+ let admin = self.ref.admin;
585
+ let functions = self.ref.functions;
586
+ let req = self.ref.req;
587
+ let res = self.ref.res;
588
+ let data = self.request.data;
589
+ let idToken;
590
+
591
+ options = options || {};
592
+ options.resolve = typeof options.resolve === 'undefined' ? true : options.resolve;
593
+
594
+ function _resolve(user) {
595
+ user = user || {};
596
+ user.authenticated = typeof user.authenticated === 'undefined'
597
+ ? false
598
+ : user.authenticated;
599
+
600
+ if (options.resolve) {
601
+ self.request.user = self.resolveAccount(user);
602
+ return self.request.user;
603
+ } else {
604
+ return user;
605
+ }
606
+ }
607
+
608
+ if (req?.headers?.authorization && req?.headers?.authorization?.startsWith('Bearer ')) {
609
+ // Read the ID Token from the Authorization header.
610
+ idToken = req.headers.authorization.split('Bearer ')[1];
611
+ self.log('Found "Authorization" header', idToken);
612
+ } else if (req?.cookies?.__session) {
613
+ // Read the ID Token from cookie.
614
+ idToken = req.cookies.__session;
615
+ self.log('Found "__session" cookie', idToken);
616
+ } else if (data.backendManagerKey || data.authenticationToken) {
617
+ // Check with custom BEM Token
618
+ let storedApiKey;
619
+ try {
620
+ // Disabled this 5/11/24 because i dont know why we would need to do functions.config() if we already have the Manager
621
+ // const workingConfig = self.Manager?.config || functions.config();
622
+ storedApiKey = self.Manager?.config?.backend_manager?.key || '';
623
+ } catch (e) {
624
+ // Do nothing
625
+ }
626
+
627
+ // Set idToken as working token of either backendManagerKey or authenticationToken
628
+ idToken = data.backendManagerKey || data.authenticationToken;
629
+
630
+ // Log the token
631
+ self.log('Found "backendManagerKey" or "authenticationToken" parameter', {storedApiKey: storedApiKey, idToken: idToken});
632
+
633
+ // Check if the token is correct
634
+ if (storedApiKey && (storedApiKey === data.backendManagerKey || storedApiKey === data.authenticationToken)) {
635
+ self.request.user.authenticated = true;
636
+ self.request.user.roles.admin = true;
637
+ return _resolve(self.request.user);
638
+ }
639
+ } else if (options.apiKey || data.apiKey) {
640
+ const apiKey = options.apiKey || data.apiKey;
641
+ self.log('Found "options.apiKey"', apiKey);
642
+
643
+ if (apiKey.includes('test')) {
644
+ return _resolve(self.request.user);
645
+ }
646
+
647
+ await admin.firestore().collection(`users`)
648
+ .where('api.privateKey', '==', apiKey)
649
+ .get()
650
+ .then(function(querySnapshot) {
651
+ querySnapshot.forEach(function(doc) {
652
+ self.request.user = doc.data();
653
+ self.request.user.authenticated = true;
654
+ });
655
+ })
656
+ .catch(function(error) {
657
+ console.error('Error getting documents: ', error);
658
+ });
659
+
660
+ return _resolve(self.request.user);
661
+ } else {
662
+ // self.log('No Firebase ID token was able to be extracted.',
663
+ // 'Make sure you authenticate your request by providing either the following HTTP header:',
664
+ // 'Authorization: Bearer <Firebase ID Token>',
665
+ // 'or by passing a "__session" cookie',
666
+ // 'or by passing backendManagerKey or authenticationToken in the body or query');
667
+
668
+ return _resolve(self.request.user);
669
+ }
670
+
671
+ // Check with firebase
672
+ try {
673
+ const decodedIdToken = await admin.auth().verifyIdToken(idToken);
674
+ if (options.debug) {
675
+ self.log('Token correctly decoded', decodedIdToken.email, decodedIdToken.user_id);
676
+ }
677
+ await admin.firestore().doc(`users/${decodedIdToken.user_id}`)
678
+ .get()
679
+ .then(async function (doc) {
680
+ if (doc.exists) {
681
+ self.request.user = Object.assign({}, self.request.user, doc.data());
682
+ }
683
+ self.request.user.authenticated = true;
684
+ self.request.user.auth.uid = decodedIdToken.user_id;
685
+ self.request.user.auth.email = decodedIdToken.email;
686
+ if (options.debug) {
687
+ self.log('Found user doc', self.request.user)
688
+ }
689
+ })
690
+ return _resolve(self.request.user);
691
+ } catch (error) {
692
+ self.error('Error while verifying Firebase ID token:', error);
693
+ return _resolve(self.request.user);
694
+ }
695
+ };
696
+
697
+ BackendAssistant.prototype.resolveAccount = function (user) {
698
+ const ResolveAccount = new (require('resolve-account'))();
699
+
700
+ return ResolveAccount.resolve(undefined, user)
701
+ }
702
+
703
+ BackendAssistant.prototype.parseRepo = function (repo) {
704
+ let repoSplit = repo.split('/');
705
+ for (var i = 0; i < repoSplit.length; i++) {
706
+ repoSplit[i] = repoSplit[i].replace('.git', '');
707
+ }
708
+ repoSplit = repoSplit.filter(function(value, index, arr){
709
+ return value !== 'http:' &&
710
+ value !== 'https:' &&
711
+ value !== '' &&
712
+ value !== 'github.com';
713
+ });
714
+ return {
715
+ user: repoSplit[0],
716
+ name: repoSplit[1],
717
+ }
718
+ };
719
+
720
+ BackendAssistant.prototype.getHeaderIp = function (headers) {
721
+ headers = headers || {};
722
+
723
+ return (
724
+ // these are present for cloudflare requests (11/21/2020)
725
+ headers['cf-connecting-ip']
726
+ || headers['fastly-temp-xff']
727
+
728
+ // these are present for non-cloudflare requests (11/21/2020)
729
+ || headers['x-appengine-user-ip']
730
+ || headers['x-forwarded-for']
731
+
732
+ // Not sure about these
733
+ // || headers['fastly-client-ip']
734
+
735
+ // If unsure, return local IP
736
+ || '127.0.0.1'
737
+ )
738
+ .split(',')[0]
739
+ .trim();
740
+ }
741
+
742
+ BackendAssistant.prototype.getHeaderContinent = function (headers) {
743
+ headers = headers || {};
744
+
745
+ return (
746
+ // these are present for cloudflare requests (11/21/2020)
747
+ headers['cf-ipcontinent']
748
+
749
+ // If unsure, return ZZ
750
+ || 'ZZ'
751
+ )
752
+ .split(',')[0]
753
+ .trim();
754
+ }
755
+
756
+ BackendAssistant.prototype.getHeaderCountry = function (headers) {
757
+ headers = headers || {};
758
+
759
+ return (
760
+ // these are present for cloudflare requests (11/21/2020)
761
+ headers['cf-ipcountry']
762
+
763
+ //
764
+ || headers['x-country-code']
765
+
766
+ // these are present for non-cloudflare requests (11/21/2020)
767
+ || headers['x-appengine-country']
768
+
769
+ // If unsure, return ZZ
770
+ || 'ZZ'
771
+ )
772
+ .split(',')[0]
773
+ .trim();
774
+ }
775
+
776
+ BackendAssistant.prototype.getHeaderRegion = function (headers) {
777
+ headers = headers || {};
778
+
779
+ return (
780
+ // these are present for cloudflare requests (11/21/2020)
781
+ headers['cf-region']
782
+
783
+ // these are present for non-cloudflare requests (11/21/2020)
784
+ || headers['x-appengine-region']
785
+
786
+ // If unsure, return unknown
787
+ || 'Unknown'
788
+ )
789
+ .split(',')[0]
790
+ .trim();
791
+ }
792
+
793
+ BackendAssistant.prototype.getHeaderCity = function (headers) {
794
+ headers = headers || {};
795
+
796
+ return (
797
+ // these are present for cloudflare requests (11/21/2020)
798
+ headers['cf-ipcity']
799
+
800
+ || headers['x-appengine-city']
801
+
802
+ // If unsure, return unknown
803
+ || 'Unknown'
804
+ )
805
+ .split(',')[0]
806
+ .trim();
807
+ }
808
+
809
+ BackendAssistant.prototype.getHeaderLatitude = function (headers) {
810
+ headers = headers || {};
811
+
812
+ return parseFloat((
813
+ // these are present for cloudflare requests (11/21/2020)
814
+ headers['cf-iplatitude']
815
+
816
+ || (headers['x-appengine-citylatlong'] || '').split(',')[0]
817
+
818
+ // If unsure, return unknown
819
+ || '0'
820
+ )
821
+ .split(',')[0]
822
+ .trim());
823
+ }
824
+
825
+ BackendAssistant.prototype.getHeaderLongitude = function (headers) {
826
+ headers = headers || {};
827
+
828
+ return parseFloat((
829
+ // Cloudflare requests
830
+ headers['cf-iplongitude']
831
+
832
+ || (headers['x-appengine-citylatlong'] || '').split(',')[1]
833
+
834
+ // If unsure, return unknown
835
+ || '0'
836
+ )
837
+ .split(',')[0]
838
+ .trim());
839
+ }
840
+
841
+
842
+ BackendAssistant.prototype.getHeaderUserAgent = function (headers) {
843
+ headers = headers || {};
844
+
845
+ return (
846
+ headers['user-agent']
847
+ || ''
848
+ )
849
+ .trim();
850
+ }
851
+
852
+ BackendAssistant.prototype.getHeaderLanguage = function (headers) {
853
+ headers = headers || {};
854
+
855
+ return (
856
+ headers['accept-language']
857
+ || headers['x-orig-accept-language']
858
+ || ''
859
+ )
860
+ .trim();
861
+ }
862
+
863
+ BackendAssistant.prototype.getHeaderPlatform = function (headers) {
864
+ headers = headers || {};
865
+
866
+ return (
867
+ headers['sec-ch-ua-platform']
868
+ || ''
869
+ )
870
+ .replace(/"/ig, '')
871
+ .trim();
872
+ }
873
+
874
+ BackendAssistant.prototype.getHeaderMobile = function (headers) {
875
+ headers = headers || {};
876
+
877
+ // Will be ?0 if fale or ?1 if true
878
+ const mobile = (headers['sec-ch-ua-mobile'] || '').replace(/\?/ig, '');
879
+
880
+ return mobile === '1' || mobile === true || mobile === 'true';
881
+ }
882
+
883
+ /**
884
+ * Parses a 'multipart/form-data' upload request
885
+ *
886
+ * @param {Object} req Cloud Function request context.
887
+ * @param {Object} res Cloud Function response context.
888
+ */
889
+ // https://cloud.google.com/functions/docs/writing/http#multipart_data
890
+ BackendAssistant.prototype.parseMultipartFormData = function (options) {
891
+ const self = this;
892
+ return new Promise(function(resolve, reject) {
893
+ if (!self.initialized) {
894
+ return reject(new Error('Cannot run .parseMultipartForm() until .init() has been called'));
895
+ }
896
+ const existingData = self.request.multipartData;
897
+ const getFields = existingData?.fields || {};
898
+ const getFiles = existingData?.files || {};
899
+
900
+ // If there are already fields or files, return them
901
+ if (Object.keys(getFields).length + Object.keys(getFiles).length > 0) {
902
+ return resolve(existingData);
903
+ }
904
+
905
+ // Set options
906
+ options = options || {};
907
+
908
+ // Set headers
909
+ const fs = require('fs');
910
+ const req = self.ref.req;
911
+ const res = self.ref.res;
912
+
913
+ // Node.js doesn't have a built-in multipart/form-data parsing library.
914
+ // Instead, we can use the 'busboy' library from NPM to parse these requests.
915
+ const busboy = require('busboy');
916
+ const jetpack = require('fs-jetpack');
917
+
918
+ // if (req.method !== 'POST') {
919
+ // // Return a "method not allowed" error
920
+ // return res.status(405).end();
921
+ // }
922
+ options.headers = options.headers || req.headers;
923
+ options.limits = options.limits || {};
924
+
925
+ // console.log('++++++++options.headers', options.headers);
926
+ // console.log('++++++++req.rawBody', req.rawBody);
927
+ // console.log('++++++++options.limits', options.limits);
928
+ // console.log('----req.rawBody', req.rawBody);
929
+
930
+ // https://github.com/mscdex/busboy
931
+ // https://github.com/mscdex/busboy/issues/266
932
+ const bb = busboy({
933
+ headers: options.headers,
934
+ limits: options.limits,
935
+ });
936
+
937
+ // This object will accumulate all the fields, keyed by their name
938
+ const fields = {};
939
+
940
+ // This object will accumulate all the uploaded files, keyed by their name.
941
+ const uploads = {};
942
+
943
+ // This code will process each non-file field in the form.
944
+ bb.on('field', (fieldname, val, info) => {
945
+ // console.log(`Processed field ${fieldname}: ${val}.`);
946
+ fields[fieldname] = val;
947
+ });
948
+
949
+ const fileWrites = [];
950
+
951
+ // This code will process each file uploaded.
952
+ bb.on('file', (fieldname, file, info) => {
953
+ // file.on('error', (e) => {
954
+ // console.error('File error', e);
955
+ // });
956
+ // Note: os.tmpdir() points to an in-memory file system on GCF
957
+ // Thus, any files in it must fit in the instance's memory.
958
+ jetpack.dir(self.tmpdir)
959
+
960
+ const filename = info.filename;
961
+ const filepath = path.join(self.tmpdir, filename);
962
+ uploads[fieldname] = filepath;
963
+ const writeStream = fs.createWriteStream(filepath);
964
+ file.pipe(writeStream);
965
+
966
+
967
+ // File was processed by Busboy; wait for it to be written.
968
+ // Note: GCF may not persist saved files across invocations.
969
+ // Persistent files must be kept in other locations
970
+ // (such as Cloud Storage buckets).
971
+ const promise = new Promise((resolve, reject) => {
972
+ file.on('end', () => {
973
+ writeStream.end();
974
+ });
975
+ writeStream.on('finish', resolve);
976
+ writeStream.on('error', reject);
977
+ });
978
+ fileWrites.push(promise);
979
+ });
980
+
981
+ // bb.on('error', async (e) => {
982
+ // console.error('Busboy error', e);
983
+ // })
984
+
985
+ // Triggered once all uploaded files are processed by Busboy.
986
+ // We still need to wait for the disk writes (saves) to complete.
987
+ bb.on('finish', async () => {
988
+ await Promise.all(fileWrites);
989
+
990
+ /**
991
+ * TODO(developer): Process saved files here
992
+ */
993
+ // for (const file in uploads) {
994
+ // fs.unlinkSync(uploads[file]);
995
+ // }
996
+ // res.send();
997
+ self.request.multipartData = {
998
+ fields: fields,
999
+ files: uploads,
1000
+ }
1001
+
1002
+ return resolve(self.request.multipartData);
1003
+ });
1004
+
1005
+ // Because of an error when using in both Optiic glitch server and ITWCW firebase functions
1006
+ if (req.rawBody) {
1007
+ return bb.end(req.rawBody);
1008
+ } else {
1009
+ return req.pipe(bb);
1010
+ }
1011
+ });
1012
+ }
1013
+
1014
+ // Not sure what this is for? But it has a good serializer code
1015
+ // Disabled 2024-03-21 because there was another stringify() function that i was intending to use but it was actually using this
1016
+ // It was adding escaped quotes to strings
1017
+ // function stringify(obj, replacer, spaces, cycleReplacer) {
1018
+ // return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces)
1019
+ // }
1020
+
1021
+ // // https://github.com/moll/json-stringify-safe/blob/master/stringify.js
1022
+ // function serializer(replacer, cycleReplacer) {
1023
+ // var stack = [], keys = []
1024
+
1025
+ // if (cycleReplacer == null) cycleReplacer = function(key, value) {
1026
+ // if (stack[0] === value) return '[Circular ~]'
1027
+ // return `[Circular ~.${keys.slice(0, stack.indexOf(value)).join('.')}]`;
1028
+ // }
1029
+
1030
+ // return function(key, value) {
1031
+ // if (stack.length > 0) {
1032
+ // var thisPos = stack.indexOf(this)
1033
+ // ~thisPos ? stack.splice(thisPos + 1) : stack.push(this)
1034
+ // ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key)
1035
+ // if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value)
1036
+ // }
1037
+ // else stack.push(value)
1038
+
1039
+ // return replacer == null ? value : replacer.call(this, key, value)
1040
+ // }
1041
+ // }
1042
+
1043
+ module.exports = BackendAssistant;