backend-manager 4.0.15 → 4.0.17

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.0.15",
3
+ "version": "4.0.17",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -355,6 +355,11 @@ Module.prototype.uploadPost = function (content) {
355
355
  return reject(existing);
356
356
  }
357
357
 
358
+ // We have to arbitrarily wait for a bit to ensure the images have started the GitHub build action
359
+ // Otherwise, the GH action might say: "Canceling since a higher priority waiting request for 'refs/heads/master' exists"
360
+ // The result of this is that the file will be comitted in the repo but not included in the public build
361
+ await powertools.wait(30000);
362
+
358
363
  // Upload post
359
364
  await self.octokit.rest.repos.createOrUpdateFileContents({
360
365
  owner: owner,
@@ -562,15 +562,22 @@ function _attachHeaderProperties(self, options, error) {
562
562
  BackendAssistant.prototype.authenticate = async function (options) {
563
563
  const self = this;
564
564
 
565
+ // Shortcuts
565
566
  let admin = self.ref.admin;
566
567
  let functions = self.ref.functions;
567
568
  let req = self.ref.req;
568
569
  let res = self.ref.res;
569
570
  let data = self.request.data;
571
+
572
+ // Build the ID token from the request
570
573
  let idToken;
574
+ let backendManagerKey;
575
+ // let user;
571
576
 
577
+ // Set options
572
578
  options = options || {};
573
579
  options.resolve = typeof options.resolve === 'undefined' ? true : options.resolve;
580
+ options.debug = typeof options.debug === 'undefined' ? false : options.debug;
574
581
 
575
582
  function _resolve(user) {
576
583
  user = user || {};
@@ -586,51 +593,89 @@ BackendAssistant.prototype.authenticate = async function (options) {
586
593
  }
587
594
  }
588
595
 
596
+ // Extract the BEM token
597
+ // Having this is separate from the ID token allows for the user to be authenticated as an ADMIN
598
+ if (options.backendManagerKey || data.backendManagerKey) {
599
+ // Read token from backendManagerKey or authenticationToken or apiKey
600
+ backendManagerKey = options.backendManagerKey || data.backendManagerKey;
601
+
602
+ // Log the token
603
+ self.log('Found "backendManagerKey" parameter', backendManagerKey);
604
+ }
605
+
606
+ // Extract the token / API key
607
+ // This is the main token that will be used to authenticate the user (it can be a JWT or a user's API key)
589
608
  if (req?.headers?.authorization && req?.headers?.authorization?.startsWith('Bearer ')) {
590
609
  // Read the ID Token from the Authorization header.
591
610
  idToken = req.headers.authorization.split('Bearer ')[1];
611
+
612
+ // Log the token
592
613
  self.log('Found "Authorization" header', idToken);
593
614
  } else if (req?.cookies?.__session) {
594
615
  // Read the ID Token from cookie.
595
616
  idToken = req.cookies.__session;
617
+
618
+ // Log the token
596
619
  self.log('Found "__session" cookie', idToken);
597
- } else if (data.backendManagerKey || data.authenticationToken) {
598
- // Check with custom BEM Token
599
- let storedApiKey;
620
+ } else if (
621
+ options.authenticationToken || data.authenticationToken
622
+ || options.apiKey || data.apiKey
623
+ ) {
624
+ // Read token OR API Key from options or data
625
+ idToken = options.authenticationToken || data.authenticationToken
626
+ || options.apiKey || data.apiKey;
627
+
628
+ // Log the token
629
+ self.log('Found "authenticationToken" parameter', idToken);
630
+ } else {
631
+ // No token found
632
+ return _resolve(self.request.user);
633
+ }
634
+
635
+ // Check if the token is a JWT
636
+ if (isJWT(idToken)) {
637
+ // Check with firebase
600
638
  try {
601
- // Disabled this 5/11/24 because i dont know why we would need to do functions.config() if we already have the Manager
602
- // const workingConfig = self.Manager?.config || functions.config();
603
- storedApiKey = self.Manager?.config?.backend_manager?.key || '';
604
- } catch (e) {
605
- // Do nothing
606
- }
639
+ const decodedIdToken = await admin.auth().verifyIdToken(idToken);
607
640
 
608
- // Set idToken as working token of either backendManagerKey or authenticationToken
609
- idToken = data.backendManagerKey || data.authenticationToken;
641
+ // Log the token
642
+ if (options.debug) {
643
+ self.log('JWT token decoded', decodedIdToken.email, decodedIdToken.user_id);
644
+ }
610
645
 
611
- // Log the token
612
- self.log('Found "backendManagerKey" or "authenticationToken" parameter', {storedApiKey: storedApiKey, idToken: idToken});
646
+ // Get the user
647
+ await admin.firestore().doc(`users/${decodedIdToken.user_id}`)
648
+ .get()
649
+ .then((doc) => {
650
+ // Set the user
651
+ if (doc.exists) {
652
+ self.request.user = Object.assign({}, self.request.user, doc.data());
653
+ self.request.user.authenticated = true;
654
+ self.request.user.auth.uid = decodedIdToken.user_id;
655
+ self.request.user.auth.email = decodedIdToken.email;
656
+ }
657
+
658
+ // Log the user
659
+ if (options.debug) {
660
+ self.log('Found user doc', self.request.user)
661
+ }
662
+ })
613
663
 
614
- // Check if the token is correct
615
- if (storedApiKey && (storedApiKey === data.backendManagerKey || storedApiKey === data.authenticationToken)) {
616
- self.request.user.authenticated = true;
617
- self.request.user.roles.admin = true;
664
+ // Return the user
618
665
  return _resolve(self.request.user);
619
- }
620
- } else if (options.apiKey || data.apiKey) {
621
- const apiKey = options.apiKey || data.apiKey;
622
- self.log('Found "options.apiKey"', apiKey);
623
-
624
- if (apiKey.includes('test')) {
666
+ } catch (error) {
667
+ self.error('Error while verifying JWT:', error);
625
668
  return _resolve(self.request.user);
626
669
  }
627
-
670
+ } else {
671
+ // Query by API key
628
672
  await admin.firestore().collection(`users`)
629
673
  .where('api.privateKey', '==', apiKey)
630
674
  .get()
631
675
  .then(function(querySnapshot) {
632
676
  querySnapshot.forEach(function(doc) {
633
677
  self.request.user = doc.data();
678
+ self.request.user = Object.assign({}, self.request.user, doc.data());
634
679
  self.request.user.authenticated = true;
635
680
  });
636
681
  })
@@ -639,39 +684,6 @@ BackendAssistant.prototype.authenticate = async function (options) {
639
684
  });
640
685
 
641
686
  return _resolve(self.request.user);
642
- } else {
643
- // self.log('No Firebase ID token was able to be extracted.',
644
- // 'Make sure you authenticate your request by providing either the following HTTP header:',
645
- // 'Authorization: Bearer <Firebase ID Token>',
646
- // 'or by passing a "__session" cookie',
647
- // 'or by passing backendManagerKey or authenticationToken in the body or query');
648
-
649
- return _resolve(self.request.user);
650
- }
651
-
652
- // Check with firebase
653
- try {
654
- const decodedIdToken = await admin.auth().verifyIdToken(idToken);
655
- if (options.debug) {
656
- self.log('Token correctly decoded', decodedIdToken.email, decodedIdToken.user_id);
657
- }
658
- await admin.firestore().doc(`users/${decodedIdToken.user_id}`)
659
- .get()
660
- .then(async function (doc) {
661
- if (doc.exists) {
662
- self.request.user = Object.assign({}, self.request.user, doc.data());
663
- }
664
- self.request.user.authenticated = true;
665
- self.request.user.auth.uid = decodedIdToken.user_id;
666
- self.request.user.auth.email = decodedIdToken.email;
667
- if (options.debug) {
668
- self.log('Found user doc', self.request.user)
669
- }
670
- })
671
- return _resolve(self.request.user);
672
- } catch (error) {
673
- self.error('Error while verifying Firebase ID token:', error);
674
- return _resolve(self.request.user);
675
687
  }
676
688
  };
677
689
 
@@ -992,6 +1004,21 @@ BackendAssistant.prototype.parseMultipartFormData = function (options) {
992
1004
  });
993
1005
  }
994
1006
 
1007
+ const isJWT = (token) => {
1008
+ // Ensure the token has three parts separated by dots
1009
+ const parts = token.split('.');
1010
+
1011
+ try {
1012
+ // Decode the header (first part) to verify it is JSON
1013
+ const header = JSON.parse(Buffer.from(parts[0], 'base64').toString('utf8'));
1014
+ // Check for expected JWT keys in the header
1015
+ return header.alg && header.typ === 'JWT';
1016
+ } catch (err) {
1017
+ // If parsing fails, it's not a valid JWT
1018
+ return false;
1019
+ }
1020
+ };
1021
+
995
1022
  // Not sure what this is for? But it has a good serializer code
996
1023
  // Disabled 2024-03-21 because there was another stringify() function that i was intending to use but it was actually using this
997
1024
  // It was adding escaped quotes to strings
@@ -562,22 +562,15 @@ function _attachHeaderProperties(self, options, error) {
562
562
  BackendAssistant.prototype.authenticate = async function (options) {
563
563
  const self = this;
564
564
 
565
- // Shortcuts
566
565
  let admin = self.ref.admin;
567
566
  let functions = self.ref.functions;
568
567
  let req = self.ref.req;
569
568
  let res = self.ref.res;
570
569
  let data = self.request.data;
571
-
572
- // Build the ID token from the request
573
570
  let idToken;
574
- let backendManagerKey;
575
- // let user;
576
571
 
577
- // Set options
578
572
  options = options || {};
579
573
  options.resolve = typeof options.resolve === 'undefined' ? true : options.resolve;
580
- options.debug = typeof options.debug === 'undefined' ? false : options.debug;
581
574
 
582
575
  function _resolve(user) {
583
576
  user = user || {};
@@ -593,89 +586,51 @@ BackendAssistant.prototype.authenticate = async function (options) {
593
586
  }
594
587
  }
595
588
 
596
- // Extract the BEM token
597
- // Having this is separate from the ID token allows for the user to be authenticated as an ADMIN
598
- if (options.backendManagerKey || data.backendManagerKey) {
599
- // Read token from backendManagerKey or authenticationToken or apiKey
600
- backendManagerKey = options.backendManagerKey || data.backendManagerKey;
601
-
602
- // Log the token
603
- self.log('Found "backendManagerKey" parameter', backendManagerKey);
604
- }
605
-
606
- // Extract the token / API key
607
- // This is the main token that will be used to authenticate the user (it can be a JWT or a user's API key)
608
589
  if (req?.headers?.authorization && req?.headers?.authorization?.startsWith('Bearer ')) {
609
590
  // Read the ID Token from the Authorization header.
610
591
  idToken = req.headers.authorization.split('Bearer ')[1];
611
-
612
- // Log the token
613
592
  self.log('Found "Authorization" header', idToken);
614
593
  } else if (req?.cookies?.__session) {
615
594
  // Read the ID Token from cookie.
616
595
  idToken = req.cookies.__session;
617
-
618
- // Log the token
619
596
  self.log('Found "__session" cookie', idToken);
620
- } else if (
621
- options.authenticationToken || data.authenticationToken
622
- || options.apiKey || data.apiKey
623
- ) {
624
- // Read token OR API Key from options or data
625
- idToken = options.authenticationToken || data.authenticationToken
626
- || options.apiKey || data.apiKey;
627
-
628
- // Log the token
629
- self.log('Found "authenticationToken" parameter', idToken);
630
- } else {
631
- // No token found
632
- return _resolve(self.request.user);
633
- }
634
-
635
- // Check if the token is a JWT
636
- if (isJWT(idToken)) {
637
- // Check with firebase
597
+ } else if (data.backendManagerKey || data.authenticationToken) {
598
+ // Check with custom BEM Token
599
+ let storedApiKey;
638
600
  try {
639
- const decodedIdToken = await admin.auth().verifyIdToken(idToken);
601
+ // Disabled this 5/11/24 because i dont know why we would need to do functions.config() if we already have the Manager
602
+ // const workingConfig = self.Manager?.config || functions.config();
603
+ storedApiKey = self.Manager?.config?.backend_manager?.key || '';
604
+ } catch (e) {
605
+ // Do nothing
606
+ }
640
607
 
641
- // Log the token
642
- if (options.debug) {
643
- self.log('JWT token decoded', decodedIdToken.email, decodedIdToken.user_id);
644
- }
608
+ // Set idToken as working token of either backendManagerKey or authenticationToken
609
+ idToken = data.backendManagerKey || data.authenticationToken;
645
610
 
646
- // Get the user
647
- await admin.firestore().doc(`users/${decodedIdToken.user_id}`)
648
- .get()
649
- .then((doc) => {
650
- // Set the user
651
- if (doc.exists) {
652
- self.request.user = Object.assign({}, self.request.user, doc.data());
653
- self.request.user.authenticated = true;
654
- self.request.user.auth.uid = decodedIdToken.user_id;
655
- self.request.user.auth.email = decodedIdToken.email;
656
- }
657
-
658
- // Log the user
659
- if (options.debug) {
660
- self.log('Found user doc', self.request.user)
661
- }
662
- })
611
+ // Log the token
612
+ self.log('Found "backendManagerKey" or "authenticationToken" parameter', {storedApiKey: storedApiKey, idToken: idToken});
663
613
 
664
- // Return the user
614
+ // Check if the token is correct
615
+ if (storedApiKey && (storedApiKey === data.backendManagerKey || storedApiKey === data.authenticationToken)) {
616
+ self.request.user.authenticated = true;
617
+ self.request.user.roles.admin = true;
665
618
  return _resolve(self.request.user);
666
- } catch (error) {
667
- self.error('Error while verifying JWT:', error);
619
+ }
620
+ } else if (options.apiKey || data.apiKey) {
621
+ const apiKey = options.apiKey || data.apiKey;
622
+ self.log('Found "options.apiKey"', apiKey);
623
+
624
+ if (apiKey.includes('test')) {
668
625
  return _resolve(self.request.user);
669
626
  }
670
- } else {
671
- // Query by API key
627
+
672
628
  await admin.firestore().collection(`users`)
673
629
  .where('api.privateKey', '==', apiKey)
674
630
  .get()
675
631
  .then(function(querySnapshot) {
676
632
  querySnapshot.forEach(function(doc) {
677
633
  self.request.user = doc.data();
678
- self.request.user = Object.assign({}, self.request.user, doc.data());
679
634
  self.request.user.authenticated = true;
680
635
  });
681
636
  })
@@ -684,6 +639,39 @@ BackendAssistant.prototype.authenticate = async function (options) {
684
639
  });
685
640
 
686
641
  return _resolve(self.request.user);
642
+ } else {
643
+ // self.log('No Firebase ID token was able to be extracted.',
644
+ // 'Make sure you authenticate your request by providing either the following HTTP header:',
645
+ // 'Authorization: Bearer <Firebase ID Token>',
646
+ // 'or by passing a "__session" cookie',
647
+ // 'or by passing backendManagerKey or authenticationToken in the body or query');
648
+
649
+ return _resolve(self.request.user);
650
+ }
651
+
652
+ // Check with firebase
653
+ try {
654
+ const decodedIdToken = await admin.auth().verifyIdToken(idToken);
655
+ if (options.debug) {
656
+ self.log('Token correctly decoded', decodedIdToken.email, decodedIdToken.user_id);
657
+ }
658
+ await admin.firestore().doc(`users/${decodedIdToken.user_id}`)
659
+ .get()
660
+ .then(async function (doc) {
661
+ if (doc.exists) {
662
+ self.request.user = Object.assign({}, self.request.user, doc.data());
663
+ }
664
+ self.request.user.authenticated = true;
665
+ self.request.user.auth.uid = decodedIdToken.user_id;
666
+ self.request.user.auth.email = decodedIdToken.email;
667
+ if (options.debug) {
668
+ self.log('Found user doc', self.request.user)
669
+ }
670
+ })
671
+ return _resolve(self.request.user);
672
+ } catch (error) {
673
+ self.error('Error while verifying Firebase ID token:', error);
674
+ return _resolve(self.request.user);
687
675
  }
688
676
  };
689
677
 
@@ -1004,21 +992,6 @@ BackendAssistant.prototype.parseMultipartFormData = function (options) {
1004
992
  });
1005
993
  }
1006
994
 
1007
- const isJWT = (token) => {
1008
- // Ensure the token has three parts separated by dots
1009
- const parts = token.split('.');
1010
-
1011
- try {
1012
- // Decode the header (first part) to verify it is JSON
1013
- const header = JSON.parse(Buffer.from(parts[0], 'base64').toString('utf8'));
1014
- // Check for expected JWT keys in the header
1015
- return header.alg && header.typ === 'JWT';
1016
- } catch (err) {
1017
- // If parsing fails, it's not a valid JWT
1018
- return false;
1019
- }
1020
- };
1021
-
1022
995
  // Not sure what this is for? But it has a good serializer code
1023
996
  // Disabled 2024-03-21 because there was another stringify() function that i was intending to use but it was actually using this
1024
997
  // It was adding escaped quotes to strings
@@ -27,6 +27,7 @@ Utilities.prototype.iterateCollection = function (callback, options) {
27
27
  // Set counters
28
28
  let batch = -1;
29
29
  let collectionCount = 0;
30
+ let callbackResults = [];
30
31
 
31
32
  // Set defaults
32
33
  options = options || {};
@@ -116,7 +117,7 @@ Utilities.prototype.iterateCollection = function (callback, options) {
116
117
 
117
118
  // If no documents, resolve
118
119
  if (snap.docs.length === 0) {
119
- return resolve();
120
+ return resolve(callbackResults);
120
121
  }
121
122
 
122
123
  // Log
@@ -134,11 +135,14 @@ Utilities.prototype.iterateCollection = function (callback, options) {
134
135
  collectionCount,
135
136
  )
136
137
  .then((r) => {
138
+ // Append to result
139
+ callbackResults.push(r);
140
+
137
141
  // Construct a new query starting at this document (unless we've reached the end)
138
142
  if (lastVisible && batch + 1 < options.maxBatches) {
139
143
  iterate(lastVisible)
140
144
  } else {
141
- return resolve();
145
+ return resolve(callbackResults);
142
146
  }
143
147
  })
144
148
  .catch((e) => {
@@ -180,6 +184,7 @@ Utilities.prototype.iterateUsers = function (callback, options) {
180
184
 
181
185
  // Set counters
182
186
  let batch = -1;
187
+ let callbackResults = [];
183
188
 
184
189
  // Set defaults
185
190
  options = options || {};
@@ -199,7 +204,7 @@ Utilities.prototype.iterateUsers = function (callback, options) {
199
204
 
200
205
  // If no users, resolve
201
206
  if (listUsersResult.users.length === 0) {
202
- return resolve();
207
+ return resolve(callbackResults);
203
208
  }
204
209
 
205
210
  // Log
@@ -213,12 +218,15 @@ Utilities.prototype.iterateUsers = function (callback, options) {
213
218
  users: listUsersResult.users,
214
219
  pageToken: listUsersResult.pageToken,
215
220
  }, batch)
216
- .then(r => {
221
+ .then((r) => {
222
+ // Append to result
223
+ callbackResults.push(r);
224
+
217
225
  // Construct a new query starting at this document (unless we've reached the end)
218
226
  if (listUsersResult.pageToken && batch + 1 < options.maxBatches) {
219
227
  iterate(listUsersResult.pageToken);
220
228
  } else {
221
- return resolve();
229
+ return resolve(callbackResults);
222
230
  }
223
231
  })
224
232
  .catch((e) => {
@@ -279,7 +287,7 @@ Utilities.prototype.getDocumentWithOwnerUser = function (path, options) {
279
287
  // Get document
280
288
  const document = await admin.firestore().doc(path)
281
289
  .get()
282
- .then(doc => {
290
+ .then((doc) => {
283
291
  const data = doc.data();
284
292
 
285
293
  // If the document doesn't exist, throw an error
@@ -308,7 +316,7 @@ Utilities.prototype.getDocumentWithOwnerUser = function (path, options) {
308
316
  // Get the owner user
309
317
  const user = admin.firestore().doc(`users/${ownerUID}`)
310
318
  .get()
311
- .then(doc => {
319
+ .then((doc) => {
312
320
  const data = doc.data();
313
321
 
314
322
  // If the user doesn't exist, throw an error