backend-manager 4.1.3 → 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.3",
3
+ "version": "4.2.0",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -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
+ };
@@ -69,7 +69,7 @@ function tryUrl(self) {
69
69
  : `${protocol}://${host}/${self.meta.name}`;
70
70
  }
71
71
  } else if (projectType === 'custom') {
72
- return `@@@TODO`;
72
+ return `@TODO`;
73
73
  }
74
74
 
75
75
  return '';
@@ -598,22 +598,44 @@ function _attachHeaderProperties(self, options, error) {
598
598
  BackendAssistant.prototype.authenticate = async function (options) {
599
599
  const self = this;
600
600
 
601
- let admin = self.ref.admin;
602
- let functions = self.ref.functions;
603
- let req = self.ref.req;
604
- let res = self.ref.res;
605
- let data = self.request.data;
601
+ // Shortcuts
602
+ const admin = self.ref.admin;
603
+ const functions = self.ref.functions;
604
+ const req = self.ref.req;
605
+ const res = self.ref.res;
606
+ const data = self.request.data;
607
+
608
+ // Get stored backendManagerKey
609
+ const BACKEND_MANAGER_KEY = self.Manager?.config?.backend_manager?.key || '';
610
+
611
+ // Build the ID token from the request
606
612
  let idToken;
613
+ let backendManagerKey;
614
+ // let user;
607
615
 
616
+ // Set options
608
617
  options = options || {};
609
618
  options.resolve = typeof options.resolve === 'undefined' ? true : options.resolve;
619
+ options.debug = typeof options.debug === 'undefined' ? false : options.debug;
610
620
 
611
621
  function _resolve(user) {
622
+ // Resolve the properties
612
623
  user = user || {};
613
624
  user.authenticated = typeof user.authenticated === 'undefined'
614
625
  ? false
615
626
  : user.authenticated;
616
627
 
628
+ // Validate BACKEND_MANAGER_KEY
629
+ if (backendManagerKey && backendManagerKey === BACKEND_MANAGER_KEY) {
630
+ // Update roles
631
+ user.roles = user.roles || {};
632
+ user.roles.admin = true;
633
+
634
+ // Set authenticated
635
+ user.authenticated = true;
636
+ }
637
+
638
+ // Resolve the user
617
639
  if (options.resolve) {
618
640
  self.request.user = self.resolveAccount(user);
619
641
  return self.request.user;
@@ -622,91 +644,102 @@ BackendAssistant.prototype.authenticate = async function (options) {
622
644
  }
623
645
  }
624
646
 
625
- if (req?.headers?.authorization && req?.headers?.authorization?.startsWith('Bearer ')) {
647
+ // Get shortcuts
648
+ const authHeader = req?.headers?.authorization || '';
649
+
650
+ // Extract the BEM token
651
+ // Having this is separate from the ID token allows for the user to be authenticated as an ADMIN
652
+ if (options.backendManagerKey || data.backendManagerKey) {
653
+ // Read token from backendManagerKey or authenticationToken or apiKey
654
+ backendManagerKey = options.backendManagerKey || data.backendManagerKey;
655
+
656
+ // Log the token
657
+ self.log('Found "backendManagerKey" parameter', backendManagerKey);
658
+ }
659
+
660
+ // Extract the token / API key
661
+ // This is the main token that will be used to authenticate the user (it can be a JWT or a user's API key)
662
+ if (authHeader.startsWith('Bearer ')) {
626
663
  // Read the ID Token from the Authorization header.
627
- idToken = req.headers.authorization.split('Bearer ')[1];
664
+ idToken = authHeader.split('Bearer ')[1];
665
+
666
+ // Log the token
628
667
  self.log('Found "Authorization" header', idToken);
629
668
  } else if (req?.cookies?.__session) {
630
669
  // Read the ID Token from cookie.
631
670
  idToken = req.cookies.__session;
671
+
672
+ // Log the token
632
673
  self.log('Found "__session" cookie', idToken);
633
- } else if (data.backendManagerKey || data.authenticationToken) {
634
- // Check with custom BEM Token
635
- let storedApiKey;
674
+ } else if (
675
+ options.authenticationToken || data.authenticationToken
676
+ || options.apiKey || data.apiKey
677
+ ) {
678
+ // Read token OR API Key from options or data
679
+ idToken = options.authenticationToken || data.authenticationToken
680
+ || options.apiKey || data.apiKey;
681
+
682
+ // Log the token
683
+ self.log('Found "authenticationToken" parameter', idToken);
684
+ } else {
685
+ // No token found
686
+ return _resolve(self.request.user);
687
+ }
688
+
689
+ // Check if the token is a JWT
690
+ if (isJWT(idToken)) {
691
+ // Check with firebase
636
692
  try {
637
- // Disabled this 5/11/24 because i dont know why we would need to do functions.config() if we already have the Manager
638
- // const workingConfig = self.Manager?.config || functions.config();
639
- storedApiKey = self.Manager?.config?.backend_manager?.key || '';
640
- } catch (e) {
641
- // Do nothing
642
- }
693
+ const decodedIdToken = await admin.auth().verifyIdToken(idToken);
643
694
 
644
- // Set idToken as working token of either backendManagerKey or authenticationToken
645
- idToken = data.backendManagerKey || data.authenticationToken;
695
+ // Log the token
696
+ if (options.debug) {
697
+ self.log('JWT token decoded', decodedIdToken.email, decodedIdToken.user_id);
698
+ }
646
699
 
647
- // Log the token
648
- self.log('Found "backendManagerKey" or "authenticationToken" parameter', {storedApiKey: storedApiKey, idToken: idToken});
700
+ // Get the user
701
+ await admin.firestore().doc(`users/${decodedIdToken.user_id}`)
702
+ .get()
703
+ .then((doc) => {
704
+ // Set the user
705
+ if (doc.exists) {
706
+ self.request.user = Object.assign({}, self.request.user, doc.data());
707
+ self.request.user.authenticated = true;
708
+ self.request.user.auth.uid = decodedIdToken.user_id;
709
+ self.request.user.auth.email = decodedIdToken.email;
710
+ }
711
+
712
+ // Log the user
713
+ if (options.debug) {
714
+ self.log('Found user doc', self.request.user)
715
+ }
716
+ })
649
717
 
650
- // Check if the token is correct
651
- if (storedApiKey && (storedApiKey === data.backendManagerKey || storedApiKey === data.authenticationToken)) {
652
- self.request.user.authenticated = true;
653
- self.request.user.roles.admin = true;
718
+ // Return the user
654
719
  return _resolve(self.request.user);
655
- }
656
- } else if (options.apiKey || data.apiKey) {
657
- const apiKey = options.apiKey || data.apiKey;
658
- self.log('Found "options.apiKey"', apiKey);
720
+ } catch (error) {
721
+ self.error('Error while verifying JWT:', error);
659
722
 
660
- if (apiKey.includes('test')) {
723
+ // Return the user
661
724
  return _resolve(self.request.user);
662
725
  }
663
-
726
+ } else {
727
+ // Query by API key
664
728
  await admin.firestore().collection(`users`)
665
- .where('api.privateKey', '==', apiKey)
729
+ .where('api.privateKey', '==', idToken)
666
730
  .get()
667
- .then(function(querySnapshot) {
668
- querySnapshot.forEach(function(doc) {
731
+ .then((querySnapshot) => {
732
+ querySnapshot.forEach((doc) => {
669
733
  self.request.user = doc.data();
734
+ self.request.user = Object.assign({}, self.request.user, doc.data());
670
735
  self.request.user.authenticated = true;
671
736
  });
672
737
  })
673
- .catch(function(error) {
738
+ .catch((error) => {
674
739
  console.error('Error getting documents: ', error);
675
740
  });
676
741
 
677
- return _resolve(self.request.user);
678
- } else {
679
- // self.log('No Firebase ID token was able to be extracted.',
680
- // 'Make sure you authenticate your request by providing either the following HTTP header:',
681
- // 'Authorization: Bearer <Firebase ID Token>',
682
- // 'or by passing a "__session" cookie',
683
- // 'or by passing backendManagerKey or authenticationToken in the body or query');
684
-
685
- return _resolve(self.request.user);
686
- }
687
-
688
- // Check with firebase
689
- try {
690
- const decodedIdToken = await admin.auth().verifyIdToken(idToken);
691
- if (options.debug) {
692
- self.log('Token correctly decoded', decodedIdToken.email, decodedIdToken.user_id);
693
- }
694
- await admin.firestore().doc(`users/${decodedIdToken.user_id}`)
695
- .get()
696
- .then(async function (doc) {
697
- if (doc.exists) {
698
- self.request.user = Object.assign({}, self.request.user, doc.data());
699
- }
700
- self.request.user.authenticated = true;
701
- self.request.user.auth.uid = decodedIdToken.user_id;
702
- self.request.user.auth.email = decodedIdToken.email;
703
- if (options.debug) {
704
- self.log('Found user doc', self.request.user)
705
- }
706
- })
707
- return _resolve(self.request.user);
708
- } catch (error) {
709
- self.error('Error while verifying Firebase ID token:', error);
742
+ // Return the user
710
743
  return _resolve(self.request.user);
711
744
  }
712
745
  };
@@ -726,7 +759,7 @@ BackendAssistant.prototype.parseRepo = function (repo) {
726
759
  }
727
760
 
728
761
  // Remove unnecessary parts
729
- repoSplit = repoSplit.filter(function(value, index, arr){
762
+ repoSplit = repoSplit.filter((value, index, arr) => {
730
763
  return value !== 'http:'
731
764
  && value !== 'https:'
732
765
  && value !== ''
@@ -1034,6 +1067,21 @@ BackendAssistant.prototype.parseMultipartFormData = function (options) {
1034
1067
  });
1035
1068
  }
1036
1069
 
1070
+ const isJWT = (token) => {
1071
+ // Ensure the token has three parts separated by dots
1072
+ const parts = token.split('.');
1073
+
1074
+ try {
1075
+ // Decode the header (first part) to verify it is JSON
1076
+ const header = JSON.parse(Buffer.from(parts[0], 'base64').toString('utf8'));
1077
+ // Check for expected JWT keys in the header
1078
+ return header.alg && header.typ === 'JWT';
1079
+ } catch (err) {
1080
+ // If parsing fails, it's not a valid JWT
1081
+ return false;
1082
+ }
1083
+ };
1084
+
1037
1085
  // Not sure what this is for? But it has a good serializer code
1038
1086
  // Disabled 2024-03-21 because there was another stringify() function that i was intending to use but it was actually using this
1039
1087
  // It was adding escaped quotes to strings