backend-manager 4.1.2 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "4.1.2",
3
+ "version": "4.2.0",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -205,7 +205,7 @@ Module.prototype.getAllUsers = function () {
205
205
  Module.prototype.getAllNotifications = function () {
206
206
  const self = this;
207
207
  return new Promise(async function(resolve, reject) {
208
- await self.libraries.admin.firestore().collection('notifications/subscriptions/all')
208
+ await self.libraries.admin.firestore().collection('notifications')
209
209
  .get()
210
210
  .then(function(querySnapshot) {
211
211
  return resolve(querySnapshot.size)
@@ -1,20 +1,26 @@
1
- const path_processing = 'notifications/processing/all/{notificationId}';
2
- const path_subscriptions = 'notifications/subscriptions/all';
3
- const badTokenReasons = ['messaging/invalid-registration-token', 'messaging/registration-token-not-registered']
4
-
1
+ // Constants
2
+ const PATH_NOTIFICATIONS = 'notifications';
3
+ const BAD_TOKEN_REASONS = [
4
+ 'messaging/invalid-registration-token',
5
+ 'messaging/registration-token-not-registered',
6
+ ];
7
+
8
+ // Module
5
9
  function Module() {
6
10
 
7
11
  }
8
12
 
13
+ // Main
9
14
  Module.prototype.main = function () {
10
15
  const self = this;
16
+
17
+ // Shortcuts
11
18
  const Manager = self.Manager;
12
19
  const Api = self.Api;
13
20
  const assistant = self.assistant;
14
21
  const payload = self.payload;
15
22
 
16
23
  return new Promise(async function(resolve, reject) {
17
-
18
24
  // Set up response obj
19
25
  payload.response.data = {
20
26
  subscribers: 0,
@@ -24,33 +30,39 @@ Module.prototype.main = function () {
24
30
  }
25
31
 
26
32
  // Fix notification payload
27
- self._notificationPayload = {
28
- notification: self.payload.data.payload.notification || {},
29
- };
30
- self._notificationPayload.notification.title = self.payload.data.payload.title || self.payload.data.payload.notification.title || 'Notification';
31
- self._notificationPayload.notification.click_action = self.payload.data.payload.click_action || self.payload.data.payload.notification.click_action || 'https://itwcreativeworks.com';
32
- self._notificationPayload.notification.body = self.payload.data.payload.body || self.payload.data.payload.notification.body || 'Check this out';
33
- self._notificationPayload.notification.icon = self.payload.data.payload.icon || self.payload.data.payload.notification.icon || self.Manager.config.brand.brandmark || 'https://cdn.itwcreativeworks.com/assets/itw-creative-works/images/socials/itw-creative-works-brandmark-square-black-1024x1024.png';
33
+ // Yes, it needs to be NESTED!!! DO NOT REMOVE THE NEST!
34
+ const note = {
35
+ notification: {
36
+ title: payload.data.payload.title || 'Notification',
37
+ body: payload.data.payload.body || 'Check this out',
38
+ icon: payload.data.payload.icon || 'https://cdn.itwcreativeworks.com/assets/itw-creative-works/images/socials/itw-creative-works-brandmark-square-black-1024x1024.png',
39
+ click_action: payload.data.payload.clickAction || 'https://itwcreativeworks.com',
40
+ }
41
+ }
34
42
 
43
+ // Set notification payload
35
44
  try {
36
- self._notificationPayload.notification.click_action = new URL(self._notificationPayload.notification.click_action);
37
- self._notificationPayload.notification.click_action.searchParams.set('cb', new Date().getTime())
38
- self._notificationPayload.notification.click_action = self._notificationPayload.notification.click_action.toString()
45
+ const url = new URL(note.notification.click_action);
46
+ url.searchParams.set('cb', new Date().getTime());
47
+ note.notification.click_action = url.toString();
39
48
  } catch (e) {
40
- assistant.errorify(`Failed to add cb to URL: ${e}`, {code: 500, log: true});
49
+ reject(assistant.errorify(`Failed to add cb to URL: ${e}`, {code: 400, log: true}));
41
50
  }
42
51
 
43
- assistant.log('Resolved notification payload', self._notificationPayload)
52
+ // Log
53
+ assistant.log('Resolved notification payload', note)
44
54
 
55
+ // Check if user is admin
45
56
  if (!payload.user.roles.admin) {
46
57
  return reject(assistant.errorify(`Admin required.`, {code: 401}));
47
58
  }
48
59
 
49
- if (!payload.data.payload.title || !payload.data.payload.body) {
60
+ // Check if title and body are set
61
+ if (!note.notification.title || !note.notification.body) {
50
62
  return reject(assistant.errorify(`Parameters <title> and <body> required`, {code: 400, sentry: true}));
51
63
  }
52
64
 
53
- await self.getTokens({tags: false})
65
+ await self.processTokens(note, {tags: false})
54
66
  .then(r => {
55
67
  return resolve({data: payload.response.data})
56
68
  })
@@ -62,136 +74,158 @@ Module.prototype.main = function () {
62
74
  };
63
75
 
64
76
  // HELPERS //
65
- Module.prototype.getTokens = function (options) {
77
+ Module.prototype.processTokens = async function (note, options) {
66
78
  const self = this;
79
+
80
+ // Shortcuts
81
+ const Manager = self.Manager;
82
+ const Api = self.Api;
83
+ const assistant = self.assistant;
84
+ const payload = self.payload;
85
+
86
+ // Set options
67
87
  options = options || {};
68
88
  options.tags = options.tags || false;
69
89
 
70
- return new Promise(async function(resolve, reject) {
71
- let subs = self.libraries.admin.firestore().collection(path_subscriptions);
72
- let batchPromises = [];
90
+ // Define collection path
91
+ const queryConditions = options.tags
92
+ ? [{ field: 'tags', operator: 'array-contains-any', value: options.tags }]
93
+ : [];
73
94
 
74
- if (options.tags) {
75
- subs.where('tags', 'array-contains-any', options.tags)
76
- }
77
- await subs
78
- .get()
79
- .then(function(querySnapshot) {
80
- self.assistant.log(`Queried ${querySnapshot.size} tokens.`);
81
- self.payload.response.data.subscribers = querySnapshot.size;
82
- // self.result.subscriptionsStart = querySnapshot.size;
83
- let batchCurrentSize = 0;
84
- let batchSizeMax = 1000;
85
-
86
- let batchCurrent = [];
87
- let batchLoops = 1;
88
-
89
- querySnapshot.forEach(function(doc) {
90
- // log(self, 'loading... ', batchLoops+'/'+querySnapshot.size);
91
- if ((batchCurrentSize < batchSizeMax - 1) && (batchLoops < querySnapshot.size)) {
92
- batchCurrent.push(doc.data().token);
93
- batchCurrentSize++;
94
- } else {
95
- let batchId = batchPromises.length + 1;
96
- batchCurrent.push(doc.data().token);
97
- batchCurrentSize++;
98
- console.log(`Got batch ID: ${batchId} with ${batchCurrentSize} tokens.`);
99
- batchPromises.push(self.sendBatch(batchCurrent, batchId));
100
- batchCurrent = [];
101
- batchCurrentSize = 0;
102
- self.payload.response.data.batches++;
103
- }
104
- batchLoops++;
105
- });
106
- })
107
- .catch(function(e) {
108
- self.assistant.error('Error querying tokens: ', e)
109
- reject(error);
110
- });
111
-
112
- await Promise.all(batchPromises)
113
- .then(function(values) {
114
- self.assistant.log('Finished all batches.');
115
- })
116
- .catch(function(e) {
117
- self.assistant.error('Error sending batches: ', e)
118
- });
119
- resolve();
95
+ // Batch processing logic
96
+ await Manager.Utilities().iterateCollection(
97
+ async (batch, index) => {
98
+ let batchTokens = [];
99
+
100
+ // Collect tokens from the current batch
101
+ for (const doc of batch.docs) {
102
+ const data = doc.data();
103
+ batchTokens.push(data.token);
104
+ }
120
105
 
106
+ // Send the batch
107
+ try {
108
+ assistant.log(`Sending batch ${index} with ${batchTokens.length} tokens.`);
109
+ await self.sendBatch(batchTokens, note, index);
110
+ } catch (e) {
111
+ assistant.error(`Error sending batch ${index}`, e);
112
+ }
113
+ },
114
+ {
115
+ collection: PATH_NOTIFICATIONS,
116
+ where: queryConditions,
117
+ batchSize: 1000,
118
+ log: true,
119
+ }
120
+ )
121
+ .then(() => {
122
+ assistant.log('All batches processed successfully.');
123
+ })
124
+ .catch(e => {
125
+ assistant.errorify(`Error during token processing: ${e}`, { code: 500, log: true });
121
126
  });
122
- }
127
+ };
123
128
 
124
- Module.prototype.sendBatch = function (batch, id) {
129
+ Module.prototype.sendBatch = async function (batch, note, id) {
125
130
  const self = this;
126
- return new Promise(async function(resolve, reject) {
127
- // self.assistant.log(`Sending batch ID: ${id}`, batch);
128
- self.assistant.log(`Sending batch ID: ${id}`);
129
-
130
- // self.assistant.log('payload', payload);
131
-
132
- await self.libraries.admin.messaging().sendToDevice(batch, self._notificationPayload)
133
- .then(async function (response) {
134
- // self.result.batches.list.push('#' + id + ' | ' + '✅ ' + response.successCount + ' | ' + '❌ ' + response.failureCount);
135
- self.assistant.log('Sent batch #' + id);
136
- // self.result.successes += response.successCount;
137
- // self.result.failures += response.failureCount;
138
- // console.log('RESP', response);
139
- if (response.failureCount > 0) {
140
- await self.cleanTokens(batch, response.results, id);
141
- }
142
- self.payload.response.data.sent += (batch.length - response.failureCount);
143
- resolve();
144
- })
145
- .catch(function (e) {
146
- self.assistant.error('Error sending batch #' + id, e);
147
- // self.result.status = 'fail';
148
- reject(e);
149
- })
150
- });
151
- }
152
131
 
153
- Module.prototype.cleanTokens = function (batch, results, id) {
132
+ // Shortcuts
133
+ const Manager = self.Manager;
134
+ const Api = self.Api;
135
+ const assistant = self.assistant;
136
+ const payload = self.payload;
137
+
138
+ // Libraries
139
+ const { admin } = self.libraries;
140
+
141
+ try {
142
+ // Log
143
+ assistant.log(`Sending batch ID: ${id}`);
144
+
145
+ // Send the batch
146
+ const response = await admin.messaging().sendToDevice(batch, note);
147
+
148
+ // Log
149
+ assistant.log(`Sent batch ID: ${id}, Success: ${response.successCount}, Failures: ${response.failureCount}`);
150
+
151
+ // Clean bad tokens
152
+ if (response.failureCount > 0) {
153
+ await self.cleanTokens(batch, response.results, id);
154
+ }
155
+
156
+ // Update response
157
+ payload.response.data.sent += (batch.length - response.failureCount);
158
+ } catch (e) {
159
+ throw assistant.errorify(`Error sending batch ${id}: ${e}`, { code: 500, log: true });
160
+ }
161
+ };
162
+
163
+ Module.prototype.cleanTokens = async function (batch, results, id) {
154
164
  const self = this;
155
165
 
156
- return new Promise(async function(resolve, reject) {
157
- let cleanPromises = [];
158
- // self.assistant.log(`Cleaning tokens of batch ID: ${id}`, results);
159
- self.assistant.log(`Cleaning tokens of batch ID: ${id}`);
166
+ // Shortcuts
167
+ const Manager = self.Manager;
168
+ const Api = self.Api;
169
+ const assistant = self.assistant;
170
+ const payload = self.payload;
160
171
 
161
- results.forEach(function (item, index) {
162
- if (!item.error) {
163
- return false;
164
- }
165
- let curCode = item.error.code;
166
- let token = batch[index];
167
- self.assistant.log(`Found bad token: ${index} = ${curCode}`);
168
- if (badTokenReasons.includes(curCode)) {
169
- cleanPromises.push(self.deleteToken(token, curCode));
172
+ // Log
173
+ assistant.log(`Cleaning tokens of batch ID: ${id}`);
174
+
175
+ // Filter out bad tokens
176
+ const cleanPromises = results
177
+ .map((item, index) => {
178
+ // Check if the token is bad
179
+ if (item.error && BAD_TOKEN_REASONS.includes(item.error.code)) {
180
+ const token = batch[index];
181
+
182
+ // Log
183
+ assistant.log(`Found bad token: ${token} (Reason: ${item.error.code})`);
184
+
185
+ // Delete the token
186
+ return self.deleteToken(token, item.error.code);
170
187
  }
188
+
189
+ // Return null for valid tokens
190
+ return null;
171
191
  })
172
- await Promise.all(cleanPromises)
173
- .catch(function(e) {
174
- self.assistant.log('error', "Error cleaning failed tokens: ", e);
175
- });
176
- resolve();
177
- });
178
- }
192
+ // Filter out nulls for valid tokens
193
+ .filter(Boolean);
194
+
195
+ // Clean bad tokens
196
+ try {
197
+ await Promise.all(cleanPromises);
198
+ assistant.log(`Completed cleaning tokens for batch ID: ${id}`);
199
+ } catch (e) {
200
+ assistant.error(`Error cleaning tokens for batch ID: ${id}`, e);
201
+ }
202
+ };
179
203
 
180
- Module.prototype.deleteToken = function (token, errorCode) {
204
+ Module.prototype.deleteToken = async function (token, errorCode) {
181
205
  const self = this;
182
- return new Promise(function(resolve, reject) {
183
- self.libraries.admin.firestore().doc(`${path_subscriptions}/${token}`)
184
- .delete()
185
- .then(function() {
186
- self.assistant.log(`Deleting bad token: ${token} for reason ${errorCode}`);
187
- self.payload.response.data.deleted++;
188
- resolve();
189
- })
190
- .catch(function(error) {
191
- self.assistant.log('error', `Error deleting bad token: ${token} for reason ${errorCode} because of error ${error}`);
192
- resolve();
193
- })
194
- });
195
- }
206
+
207
+ // Shortcuts
208
+ const Manager = self.Manager;
209
+ const Api = self.Api;
210
+ const assistant = self.assistant;
211
+ const payload = self.payload;
212
+
213
+ // Libraries
214
+ const { admin } = self.libraries;
215
+
216
+ // Delete the token
217
+ try {
218
+ // Delete the token
219
+ await admin.firestore().doc(`${PATH_NOTIFICATIONS}/${token}`).delete();
220
+
221
+ // Log
222
+ assistant.log(`Deleted bad token: ${token} (Reason: ${errorCode})`);
223
+
224
+ // Update response
225
+ payload.response.data.deleted++;
226
+ } catch (error) {
227
+ assistant.error(`Failed to delete bad token: ${token} (Reason: ${errorCode}). Error: ${error}`);
228
+ }
229
+ };
196
230
 
197
231
  module.exports = Module;
@@ -9,6 +9,8 @@ function Module() {
9
9
 
10
10
  Module.prototype.init = function (Manager, data) {
11
11
  const self = this;
12
+
13
+ // Set properties
12
14
  self.Manager = Manager;
13
15
  self.libraries = Manager.libraries;
14
16
  self.assistant = Manager.Assistant({req: data.req, res: data.res})
@@ -30,15 +32,14 @@ Module.prototype.init = function (Manager, data) {
30
32
  self.assistant.request.data.payload = self.assistant.request.data.payload || {};
31
33
  self.assistant.request.data.options = self.assistant.request.data.options || {};
32
34
 
33
- if (Manager.options.log) {
34
- self.assistant.log(`Executing (log): ${resolved.command}`, self.assistant.request, JSON.stringify(self.assistant.request))
35
- }
36
-
35
+ // Return
37
36
  return self;
38
37
  }
39
38
 
40
39
  Module.prototype.main = function() {
41
40
  const self = this;
41
+
42
+ // Shortcuts
42
43
  const Manager = self.Manager;
43
44
  const libraries = self.libraries;
44
45
  const assistant = self.assistant;
@@ -47,19 +48,34 @@ Module.prototype.main = function() {
47
48
 
48
49
  return new Promise(async function(resolve, reject) {
49
50
  return libraries.cors(req, res, async () => {
51
+ // Set properties
50
52
  self.payload.data = assistant.request.data;
51
53
  self.payload.user = await assistant.authenticate();
52
54
 
55
+ // Set properties
56
+ const headers = assistant.request.headers;
57
+ const method = assistant.request.method;
58
+ const url = assistant.request.url;
59
+ const geolocation = assistant.request.geolocation;
60
+ const client = assistant.request.client;
61
+
62
+ // Strip URL
63
+ const strippedUrl = stripUrl(url);
64
+
53
65
  // Quit if OPTIONS request
54
- if (self.assistant.request.method === 'OPTIONS') {
66
+ if (method === 'OPTIONS') {
55
67
  return resolve();
56
68
  }
57
69
 
58
70
  // Resolve command
59
71
  const resolved = self.resolveCommand(self.payload.data.command);
60
72
 
61
- self.assistant.log(`Executing: ${resolved.command}`, self.payload, JSON.stringify(self.payload))
62
- self.assistant.log(`Resolved URL: ${Manager.project.functionsUrl}?command=${encodeURIComponent(resolved.command)}&payload=${encodeURIComponent(JSON.stringify(self.assistant.request.data.payload))}`)
73
+ // Log
74
+ // assistant.log(`Executing: ${resolved.command}`, self.payload, JSON.stringify(self.payload))
75
+ // assistant.log(`Resolved URL: ${Manager.project.functionsUrl}?command=${encodeURIComponent(resolved.command)}&payload=${encodeURIComponent(JSON.stringify(self.assistant.request.data.payload))}`)
76
+ assistant.log(`bm_api(${resolved.command}): Request (${geolocation.ip} @ ${geolocation.country}, ${geolocation.region}, ${geolocation.city}) [${method} > ${strippedUrl}]`, JSON.stringify(assistant.request.data));
77
+ assistant.log(`bm_api(${resolved.command}): Headers`, JSON.stringify(headers));
78
+
63
79
 
64
80
  // Set up options
65
81
  self.payload.data.options = self.payload.data.options || {};
@@ -70,7 +86,7 @@ Module.prototype.main = function() {
70
86
  let delay = Math.floor(self.payload.data.options.delay / 1000);
71
87
 
72
88
  await powertools.poll(() => {
73
- self.assistant.log(`Delaying for ${delay--} seconds...`);
89
+ assistant.log(`Delaying for ${delay--} seconds...`);
74
90
  }, {interval: 1000, timeout: self.payload.data.options.delay})
75
91
  .catch(e => e);
76
92
  }
@@ -116,7 +132,7 @@ Module.prototype.main = function() {
116
132
 
117
133
  // Send response
118
134
  if (self.payload.response.status >= 200 && self.payload.response.status < 399) {
119
- self.assistant.log(`Finished ${resolved.command} (status=${self.payload.response.status})`, self.payload, JSON.stringify(self.payload))
135
+ assistant.log(`Finished ${resolved.command} (status=${self.payload.response.status})`, self.payload, JSON.stringify(self.payload))
120
136
 
121
137
  if (self.payload.response.redirect) {
122
138
  res.redirect(self.payload.response.redirect);
@@ -126,7 +142,7 @@ Module.prototype.main = function() {
126
142
  return resolve();
127
143
  }
128
144
  } else {
129
- self.assistant.error(`Error executing ${resolved.command} @ ${resolved.path} (status=${self.payload.response.status}):`, self.payload.response.error)
145
+ assistant.error(`Error executing ${resolved.command} @ ${resolved.path} (status=${self.payload.response.status}):`, self.payload.response.error)
130
146
  res.send(`${self.payload.response.error}`)
131
147
  return reject(self.payload.response.error);
132
148
  }
@@ -188,8 +204,17 @@ Module.prototype.import = function (command, payload, user, response) {
188
204
 
189
205
  Module.prototype.resolveCommand = function (command) {
190
206
  const self = this;
207
+
208
+ // Shortcuts
209
+ const assistant = self.assistant;
210
+
211
+ // Set original command
191
212
  const originalCommand = command;
192
213
 
214
+ // Set properties
215
+ const method = assistant.request.method;
216
+
217
+ // Set command
193
218
  command = command || '';
194
219
 
195
220
  // Start
@@ -251,9 +276,9 @@ Module.prototype.resolveCommand = function (command) {
251
276
  // Check local path
252
277
  const resolvedPath = self.resolveApiPath(command);
253
278
 
254
- // if (!command || command === 'error:error') {
255
- if (!resolvedPath) {
256
- self.assistant.log(`This command does not exist: ${originalCommand} => ${command} @ ${resolvedPath}`)
279
+ // Log if command does not exist
280
+ if (method !== 'OPTIONS' && !resolvedPath) {
281
+ assistant.error(`This command does not exist: ${originalCommand} => ${command} @ ${resolvedPath}`)
257
282
  }
258
283
 
259
284
  return {
@@ -354,4 +379,10 @@ Module.prototype.resolveApiPath = function (command) {
354
379
  }
355
380
  };
356
381
 
382
+ function stripUrl(url) {
383
+ const newUrl = new URL(url);
384
+
385
+ return `${newUrl.hostname}${newUrl.pathname}`.replace(/\/$/, '');
386
+ }
387
+
357
388
  module.exports = Module;
@@ -179,7 +179,7 @@ let Module = {
179
179
  getAllSubscriptions: function () {
180
180
  let self = this;
181
181
  return new Promise(async function(resolve, reject) {
182
- await self.libraries.admin.firestore().collection('notifications/subscriptions/all')
182
+ await self.libraries.admin.firestore().collection('notifications')
183
183
  .get()
184
184
  .then(function(querySnapshot) {
185
185
  return resolve(querySnapshot.size)
@@ -49,12 +49,12 @@ Module.prototype.main = function () {
49
49
  if (eventType === 'delete') {
50
50
  await libraries.admin.firestore().doc(`meta/stats`)
51
51
  .update({
52
- 'subscriptions.total': libraries.admin.firestore.FieldValue.increment(-1),
52
+ 'notifications.total': libraries.admin.firestore.FieldValue.increment(-1),
53
53
  })
54
54
  .then(r => {
55
55
  analytics = self.Manager.Analytics({
56
56
  assistant: assistant,
57
- uuid: _.get(dataBefore, 'link.user.data.uid', undefined),
57
+ uuid: dataBefore?.owner?.uid,
58
58
  })
59
59
  .event({
60
60
  name: 'notification-unsubscribe',
@@ -78,12 +78,12 @@ Module.prototype.main = function () {
78
78
  } else if (eventType === 'create') {
79
79
  await libraries.admin.firestore().doc(`meta/stats`)
80
80
  .update({
81
- 'subscriptions.total': libraries.admin.firestore.FieldValue.increment(1),
81
+ 'notifications.total': libraries.admin.firestore.FieldValue.increment(1),
82
82
  })
83
83
  .then(r => {
84
84
  analytics = self.Manager.Analytics({
85
85
  assistant: assistant,
86
- uuid: _.get(dataAfter, 'link.user.data.uid', undefined),
86
+ uuid: dataAfter?.owner?.uid,
87
87
  })
88
88
  .event({
89
89
  name: 'notification-subscribe',
@@ -0,0 +1,116 @@
1
+ BackendAssistant.prototype.authenticate = async function (options) {
2
+ const self = this;
3
+
4
+ // Shortcuts
5
+ let admin = self.ref.admin;
6
+ let functions = self.ref.functions;
7
+ let req = self.ref.req;
8
+ let res = self.ref.res;
9
+ let data = self.request.data;
10
+ let idToken;
11
+
12
+ options = options || {};
13
+ options.resolve = typeof options.resolve === 'undefined' ? true : options.resolve;
14
+
15
+ function _resolve(user) {
16
+ user = user || {};
17
+ user.authenticated = typeof user.authenticated === 'undefined'
18
+ ? false
19
+ : user.authenticated;
20
+
21
+ if (options.resolve) {
22
+ self.request.user = self.resolveAccount(user);
23
+ return self.request.user;
24
+ } else {
25
+ return user;
26
+ }
27
+ }
28
+
29
+ if (req?.headers?.authorization && req?.headers?.authorization?.startsWith('Bearer ')) {
30
+ // Read the ID Token from the Authorization header.
31
+ idToken = req.headers.authorization.split('Bearer ')[1];
32
+ self.log('Found "Authorization" header', idToken);
33
+ } else if (req?.cookies?.__session) {
34
+ // Read the ID Token from cookie.
35
+ idToken = req.cookies.__session;
36
+ self.log('Found "__session" cookie', idToken);
37
+ } else if (data.backendManagerKey || data.authenticationToken) {
38
+ // Check with custom BEM Token
39
+ let storedApiKey;
40
+ try {
41
+ // Disabled this 5/11/24 because i dont know why we would need to do functions.config() if we already have the Manager
42
+ // const workingConfig = self.Manager?.config || functions.config();
43
+ storedApiKey = self.Manager?.config?.backend_manager?.key || '';
44
+ } catch (e) {
45
+ // Do nothing
46
+ }
47
+
48
+ // Set idToken as working token of either backendManagerKey or authenticationToken
49
+ idToken = data.backendManagerKey || data.authenticationToken;
50
+
51
+ // Log the token
52
+ self.log('Found "backendManagerKey" or "authenticationToken" parameter', {storedApiKey: storedApiKey, idToken: idToken});
53
+
54
+ // Check if the token is correct
55
+ if (storedApiKey && (storedApiKey === data.backendManagerKey || storedApiKey === data.authenticationToken)) {
56
+ self.request.user.authenticated = true;
57
+ self.request.user.roles.admin = true;
58
+ return _resolve(self.request.user);
59
+ }
60
+ } else if (options.apiKey || data.apiKey) {
61
+ const apiKey = options.apiKey || data.apiKey;
62
+ self.log('Found "options.apiKey"', apiKey);
63
+
64
+ if (apiKey.includes('test')) {
65
+ return _resolve(self.request.user);
66
+ }
67
+
68
+ await admin.firestore().collection(`users`)
69
+ .where('api.privateKey', '==', apiKey)
70
+ .get()
71
+ .then(function(querySnapshot) {
72
+ querySnapshot.forEach(function(doc) {
73
+ self.request.user = doc.data();
74
+ self.request.user.authenticated = true;
75
+ });
76
+ })
77
+ .catch(function(error) {
78
+ console.error('Error getting documents: ', error);
79
+ });
80
+
81
+ return _resolve(self.request.user);
82
+ } else {
83
+ // self.log('No Firebase ID token was able to be extracted.',
84
+ // 'Make sure you authenticate your request by providing either the following HTTP header:',
85
+ // 'Authorization: Bearer <Firebase ID Token>',
86
+ // 'or by passing a "__session" cookie',
87
+ // 'or by passing backendManagerKey or authenticationToken in the body or query');
88
+
89
+ return _resolve(self.request.user);
90
+ }
91
+
92
+ // Check with firebase
93
+ try {
94
+ const decodedIdToken = await admin.auth().verifyIdToken(idToken);
95
+ if (options.debug) {
96
+ self.log('Token correctly decoded', decodedIdToken.email, decodedIdToken.user_id);
97
+ }
98
+ await admin.firestore().doc(`users/${decodedIdToken.user_id}`)
99
+ .get()
100
+ .then(async function (doc) {
101
+ if (doc.exists) {
102
+ self.request.user = Object.assign({}, self.request.user, doc.data());
103
+ }
104
+ self.request.user.authenticated = true;
105
+ self.request.user.auth.uid = decodedIdToken.user_id;
106
+ self.request.user.auth.email = decodedIdToken.email;
107
+ if (options.debug) {
108
+ self.log('Found user doc', self.request.user)
109
+ }
110
+ })
111
+ return _resolve(self.request.user);
112
+ } catch (error) {
113
+ self.error('Error while verifying Firebase ID token:', error);
114
+ return _resolve(self.request.user);
115
+ }
116
+ };