@edgedev/firebase 1.7.11 → 1.8.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/README.md CHANGED
@@ -576,6 +576,8 @@ The user object is reactive and contains these items:
576
576
  interface UserDataObject {
577
577
  uid: string | null;
578
578
  email: string;
579
+ firebaseUser: object; // contains the entire auth from firebase
580
+ oAuthCredential: object; // contains oAuth ID and token information
579
581
  loggedIn: boolean;
580
582
  logInError: boolean;
581
583
  logInErrorMessage: string;
package/edgeFirebase.ts CHANGED
@@ -43,6 +43,9 @@ import {
43
43
  confirmPasswordReset,
44
44
  connectAuthEmulator,
45
45
  deleteUser,
46
+ OAuthProvider,
47
+ browserPopupRedirectResolver,
48
+ signInWithPopup,
46
49
  } from "firebase/auth";
47
50
 
48
51
 
@@ -80,6 +83,8 @@ interface permissions {
80
83
 
81
84
  type action = "assign" | "read" | "write" | "delete";
82
85
 
86
+ type authProviders = "email" | "microsoft" | "google" | "facebook" | "github" | "twitter" | "apple";
87
+
83
88
  interface role {
84
89
  collectionPath: "-" | string; // - is root
85
90
  role: "admin" | "editor" | "writer" | "user";
@@ -93,6 +98,8 @@ interface specialPermission {
93
98
  interface UserDataObject {
94
99
  uid: string | null;
95
100
  email: string;
101
+ firebaseUser: object;
102
+ oAuthCredential: { accessToken: string; idToken: string;}
96
103
  loggedIn: boolean;
97
104
  logInError: boolean;
98
105
  logInErrorMessage: string;
@@ -101,7 +108,6 @@ interface UserDataObject {
101
108
  specialPermissions: specialPermission[];
102
109
  stagedDocId: string;
103
110
  }
104
-
105
111
  interface newUser {
106
112
  roles: role[];
107
113
  specialPermissions: specialPermission[];
@@ -110,7 +116,7 @@ interface newUser {
110
116
  subCreate?: {
111
117
  rootPath: string, // This must be a collection path (odd number of segments) since a document will be created and assigned to ther user here.
112
118
  role: string,
113
- dynamicDocumentField: string, // This is the field in the document that will be set by the value of "dynamicDocumentFieldValue" passed during registration, like "name"
119
+ dynamicDocumentFieldValue: string, // This is the field in the document that will be set by the value of "dynamicDocumentFieldValue" passed during registration, like "name"
114
120
  documentStructure: {
115
121
  [key: string]: unknown
116
122
  }
@@ -118,8 +124,8 @@ interface newUser {
118
124
  }
119
125
 
120
126
  interface userRegister {
121
- email: string;
122
- password: string;
127
+ email?: string;
128
+ password?: string;
123
129
  meta: object;
124
130
  registrationCode: string;
125
131
  dynamicDocumentFieldValue?: string;
@@ -192,7 +198,7 @@ export const EdgeFirebase = class {
192
198
  persistence = browserLocalPersistence;
193
199
  }
194
200
 
195
- this.auth = initializeAuth(this.app, { persistence });
201
+ this.auth = initializeAuth(this.app, { persistence, popupRedirectResolver: browserPopupRedirectResolver });
196
202
  if (this.firebaseConfig.emulatorAuth) {
197
203
  connectAuthEmulator(this.auth, `http://localhost:${this.firebaseConfig.emulatorAuth}`)
198
204
  }
@@ -368,6 +374,7 @@ export const EdgeFirebase = class {
368
374
  if (userAuth) {
369
375
  this.user.email = userAuth.email;
370
376
  this.user.uid = userAuth.uid;
377
+ this.user.firebaseUser = userAuth;
371
378
  this.user.logInError = false;
372
379
  this.user.logInErrorMessage = "";
373
380
  this.logAnalyticsEvent("login", { uid: this.user.uid });
@@ -375,14 +382,57 @@ export const EdgeFirebase = class {
375
382
  } else {
376
383
  this.user.email = "";
377
384
  this.user.uid = null;
385
+ this.user.firebaseUser = null;
386
+ this.user.oAuthCredential.accessToken = "";
387
+ this.user.oAuthCredential.idToken = "";
378
388
  this.user.loggedIn = false;
379
- this.user.logInError = false;
380
- this.user.logInErrorMessage = "";
381
389
  }
382
390
  });
383
391
  };
392
+
393
+ public logInWithMicrosoft = async (providerScopes: string[] = []): Promise<void> => {
394
+ const result = await this.signInWithMicrosoft(providerScopes);
395
+ if (!Object.prototype.hasOwnProperty.call(result, "user")) {
396
+ this.user.logInError = true;
397
+ this.user.logInErrorMessage = result
398
+ this.logOut();
399
+ return;
400
+ }
401
+ console.log(result.user.uid);
402
+ const userRef = doc(this.db, "staged-users", result.user.uid);
403
+ const userSnap = await getDoc(userRef);
404
+ if (!userSnap.exists()) {
405
+ this.user.logInError = true;
406
+ this.user.logInErrorMessage = "User does not exist";
407
+ this.logOut();
408
+ }
409
+ };
410
+
411
+ private registerUserWithMicrosoft = async (providerScopes: string[] = []): Promise<any> => {
412
+ const result = await this.signInWithMicrosoft(providerScopes);
413
+ return result;
414
+ };
415
+
416
+ private signInWithMicrosoft = async (providerScopes: string[] = []): Promise<any> => {
417
+ const provider = new OAuthProvider("microsoft.com");
418
+ for (const scope of providerScopes) {
419
+ provider.addScope(scope);
420
+ }
421
+ try {
422
+ const result = await signInWithPopup(this.auth, provider);
423
+ const credential = OAuthProvider.credentialFromResult(result);
424
+ this.user.oAuthCredential.accessToken = credential.accessToken;
425
+ this.user.oAuthCredential.idToken = credential.idToken;
426
+ return result;
427
+ } catch (error) {
428
+ return error;
429
+ }
430
+ }
431
+
384
432
  public registerUser = async (
385
- userRegister: userRegister
433
+ userRegister: userRegister,
434
+ authProvider: authProviders = "email",
435
+ providerScopes: string[] = []
386
436
  ): Promise<actionResponse> => {
387
437
  if (!Object.prototype.hasOwnProperty.call(userRegister, 'registrationCode') || userRegister.registrationCode === "") {
388
438
  return this.sendResponse({
@@ -411,12 +461,27 @@ export const EdgeFirebase = class {
411
461
  });
412
462
  }
413
463
  }
414
- // TODO: check if user is already registered, if so return error with auth
415
- const response = await createUserWithEmailAndPassword(
416
- this.auth,
417
- userRegister.email,
418
- userRegister.password
419
- );
464
+ let response;
465
+ if (authProvider === "email") {
466
+ try {
467
+ response = await createUserWithEmailAndPassword(
468
+ this.auth,
469
+ userRegister.email,
470
+ userRegister.password
471
+ );
472
+ } catch (error) {
473
+ response = error;
474
+ }
475
+ } else if (authProvider === "microsoft") {
476
+ response = await this.registerUserWithMicrosoft(providerScopes);
477
+ }
478
+ if (!Object.prototype.hasOwnProperty.call(response, "user")) {
479
+ return this.sendResponse({
480
+ success: false,
481
+ message: response,
482
+ meta: {}
483
+ });
484
+ }
420
485
 
421
486
  let metaUpdate = {};
422
487
  if (Object.prototype.hasOwnProperty.call(userRegister, 'meta')) {
@@ -578,7 +643,6 @@ export const EdgeFirebase = class {
578
643
  }
579
644
  };
580
645
 
581
- //TODO: Document this function
582
646
  public deleteSelf = async (): Promise<actionResponse> => {
583
647
  const userId = this.user.uid;
584
648
  const userRef = doc(this.db, "users", userId);
@@ -856,10 +920,11 @@ export const EdgeFirebase = class {
856
920
  }
857
921
  signOut(this.auth).then(() => {
858
922
  this.user.uid = null;
923
+ this.user.firebaseUser = null;
924
+ this.user.oAuthCredential.accessToken = "";
925
+ this.user.oAuthCredential.idToken = "";
859
926
  this.user.email = "";
860
927
  this.user.loggedIn = false;
861
- this.user.logInError = false;
862
- this.user.logInErrorMessage = "";
863
928
  this.user.meta = {};
864
929
  this.user.roles = [];
865
930
  this.user.specialPermissions = [];
@@ -880,7 +945,9 @@ export const EdgeFirebase = class {
880
945
  .catch((error) => {
881
946
  this.user.email = "";
882
947
  this.user.uid = null;
883
-
948
+ this.user.firebaseUser = null;
949
+ this.user.oAuthCredential.accessToken = "";
950
+ this.user.oAuthCredential.idToken = "";
884
951
  this.user.loggedIn = false;
885
952
  this.user.logInError = true;
886
953
  this.user.logInErrorMessage = error.code + ": " + error.message;
@@ -906,11 +973,13 @@ export const EdgeFirebase = class {
906
973
  email: "",
907
974
  loggedIn: false,
908
975
  logInError: false,
909
- logInErrorMessage: "",
976
+ logInErrorMessage: "",
910
977
  meta: {},
911
978
  roles: [],
912
979
  specialPermissions: [],
913
980
  stagedDocId: null,
981
+ firebaseUser: null,
982
+ oAuthCredential: {accessToken: "", idToken: ""},
914
983
  });
915
984
 
916
985
  public state = reactive({
@@ -970,7 +1039,6 @@ export const EdgeFirebase = class {
970
1039
  return { data, next: nextLast };
971
1040
  };
972
1041
 
973
- // Class for wrapping a getSaticData to handle pagination
974
1042
  get SearchStaticData() {
975
1043
  const getStaticData = this.getStaticData;
976
1044
  const permissionCheckOnly = this.permissionCheckOnly;
@@ -1162,8 +1230,6 @@ export const EdgeFirebase = class {
1162
1230
  );
1163
1231
  };
1164
1232
 
1165
- // TODO: Document this and how the data is stored collectionPath + '/' + docId...
1166
- // Also stopSnapshot is by collectionPath + '/' + docId
1167
1233
  public startDocumentSnapshot = async (
1168
1234
  collectionPath: string,
1169
1235
  docId: string
@@ -1526,7 +1592,6 @@ export const EdgeFirebase = class {
1526
1592
  }
1527
1593
  };
1528
1594
 
1529
- // TODO: Add documentation for this function
1530
1595
  public changeDoc = async (
1531
1596
  collectionPath: string,
1532
1597
  docId: string,
@@ -1650,7 +1715,6 @@ export const EdgeFirebase = class {
1650
1715
 
1651
1716
  };
1652
1717
 
1653
- // Composable to delete a document
1654
1718
  public removeDoc = async (
1655
1719
  collectionPath: string,
1656
1720
  docId: string
@@ -1679,7 +1743,6 @@ export const EdgeFirebase = class {
1679
1743
  }
1680
1744
  };
1681
1745
 
1682
- // Composable to stop snapshot listener
1683
1746
  public stopSnapshot = (collectionPath: string): void => {
1684
1747
  if (this.unsubscibe[collectionPath] instanceof Function) {
1685
1748
  this.unsubscibe[collectionPath]();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edgedev/firebase",
3
- "version": "1.7.11",
3
+ "version": "1.8.0",
4
4
  "description": "Vue 3 / Nuxt 3 Plugin or Nuxt 3 plugin for firebase authentication and firestore.",
5
5
  "main": "index.ts",
6
6
  "scripts": {
@@ -1,4 +1,4 @@
1
- rules_version = '2';
1
+ // #EDGE FIREBASE RULES START
2
2
  service cloud.firestore {
3
3
 
4
4
  match /databases/{database}/documents/events/{event} {
@@ -282,4 +282,5 @@ service cloud.firestore {
282
282
  }
283
283
  }
284
284
  }
285
- }
285
+ }
286
+ // #EDGE FIREBASE RULES END
@@ -0,0 +1,103 @@
1
+ // START @edge/firebase functions
2
+ exports.updateUser = functions.firestore.document('staged-users/{docId}').onUpdate((change, context) => {
3
+ const eventId = context.eventId
4
+ const eventRef = db.collection('events').doc(eventId)
5
+ const stagedDocId = context.params.docId
6
+ let newData = change.after.data()
7
+ const oldData = change.before.data()
8
+ return shouldProcess(eventRef).then((process) => {
9
+ if (process) {
10
+ // Note: we can trust on newData.uid because we are checking in rules that it matches the auth.uid
11
+ // TODO: user might be invited to join another org with reg code.. if used when logged will combine new staged-user doc into first stage-user doc and sync to users.
12
+ if (newData.userId) {
13
+ const userRef = db.collection('users').doc(newData.userId)
14
+ setUser(userRef, newData, oldData, stagedDocId).then(() => {
15
+ return markProcessed(eventRef)
16
+ })
17
+ }
18
+ else {
19
+ if (newData.templateUserId !== oldData.templateUserId) {
20
+ newData.isTemplate = false
21
+ const templateUserId = newData.templateUserId
22
+ newData.meta = newData.templateMeta
23
+ delete newData.templateMeta
24
+ delete newData.templateUserId
25
+ if (Object.prototype.hasOwnProperty.call(newData, 'subCreate') && Object.values(newData.subCreate).length > 0) {
26
+ const subCreate = newData.subCreate
27
+ delete newData.subCreate
28
+ db.collection(subCreate.rootPath).add({ [subCreate.dynamicDocumentField]: newData.dynamicDocumentFieldValue }).then((addedDoc) => {
29
+ db.collection(subCreate.rootPath).doc(addedDoc.id).update({ docId: addedDoc.id }).then(() => {
30
+ delete newData.dynamicDocumentFieldValue
31
+ const newRole = { [`${subCreate.rootPath}-${addedDoc.id}`]: { collectionPath: `${subCreate.rootPath}-${addedDoc.id}`, role: subCreate.role } }
32
+ if (Object.prototype.hasOwnProperty.call(newData, 'collectionPaths')) {
33
+ newData.collectionPaths.push(`${subCreate.rootPath}-${addedDoc.id}`)
34
+ }
35
+ else {
36
+ newData.collectionPaths = [`${subCreate.rootPath}-${addedDoc.id}`]
37
+ }
38
+ const newRoles = { ...newData.roles, ...newRole }
39
+ newData = { ...newData, roles: newRoles }
40
+ const stagedUserRef = db.collection('staged-users').doc(templateUserId)
41
+ return stagedUserRef.set({ ...newData, userId: templateUserId }).then(() => {
42
+ const userRef = db.collection('users').doc(templateUserId)
43
+ setUser(userRef, newData, oldData, templateUserId).then(() => {
44
+ return markProcessed(eventRef)
45
+ })
46
+ })
47
+ })
48
+ })
49
+ }
50
+ else {
51
+ const stagedUserRef = db.collection('staged-users').doc(templateUserId)
52
+ return stagedUserRef.set({ ...newData, userId: templateUserId }).then(() => {
53
+ const userRef = db.collection('users').doc(templateUserId)
54
+ setUser(userRef, newData, oldData, templateUserId).then(() => {
55
+ return markProcessed(eventRef)
56
+ })
57
+ })
58
+ }
59
+ }
60
+ }
61
+ return markProcessed(eventRef)
62
+ }
63
+ })
64
+ })
65
+
66
+ function setUser(userRef, newData, oldData, stagedDocId) {
67
+ // IT's OK If "users" doesn't match exactly matched "staged-users" because this is only preventing
68
+ // writing from outside the @edgdev/firebase functions, so discrepancies will be rare since
69
+ // the package will prevent before it gets this far.
70
+ return userRef.get().then((user) => {
71
+ let userUpdate = { meta: newData.meta, stagedDocId }
72
+
73
+ if (Object.prototype.hasOwnProperty.call(newData, 'roles')) {
74
+ userUpdate = { ...userUpdate, roles: newData.roles }
75
+ }
76
+ if (Object.prototype.hasOwnProperty.call(newData, 'specialPermissions')) {
77
+ userUpdate = { ...userUpdate, specialPermissions: newData.specialPermissions }
78
+ }
79
+
80
+ if (!oldData.userId) {
81
+ userUpdate = { ...userUpdate, userId: newData.uid }
82
+ }
83
+ if (!user.exists) {
84
+ return userRef.set(userUpdate)
85
+ }
86
+ else {
87
+ return userRef.update(userUpdate)
88
+ }
89
+ })
90
+ }
91
+
92
+ function shouldProcess(eventRef) {
93
+ return eventRef.get().then((eventDoc) => {
94
+ return !eventDoc.exists || !eventDoc.data().processed
95
+ })
96
+ }
97
+
98
+ function markProcessed(eventRef) {
99
+ return eventRef.set({ processed: true }).then(() => {
100
+ return null
101
+ })
102
+ }
103
+ // END @edge/firebase functions
@@ -3,15 +3,32 @@
3
3
  # Check if the destination file exists
4
4
  if [ ! -f ./firestore.rules ]; then
5
5
  # If the file does not exist, create it and add the rules_version line
6
- echo 'rules_version = '\''2'\'';' > ./firestore.rules;
7
- else
8
- # If the file exists, add the rules_version line at the beginning using sed
9
- sed -i '1s/^/rules_version = \\'\''2\\'\'';\\n/' ./firestore.rules;
6
+ echo "rules_version = '2';" > ./firestore.rules;
10
7
  fi
11
8
 
9
+ [ "$(tail -c1 ./firestore.rules)" != "" ] && echo "" >> ./firestore.rules
10
+
12
11
  # Extract the code block from the source file and append it to the destination file
13
- awk '/\/\/ #EDGE FIREBASE RULES START/,/\/\/ #EDGE FIREBASE RULES END/' ./src/firestore.rules | \
14
- sed 's/\\/\\\\/g; s/&/\\&/g' | \
15
- sed -e 's/\"/\\\\\"/g' -e 's/\//\\\\\//g' | \
12
+ sed -i.backup '/\/\/ #EDGE FIREBASE RULES START/,/\/\/ #EDGE FIREBASE RULES END/d' ./firestore.rules
13
+
14
+ awk '/\/\/ #EDGE FIREBASE RULES START/,/\/\/ #EDGE FIREBASE RULES END/' ./node_modules/@edgedev/firebase/src/firestore.rules | \
15
+ sed '1d;$d' | \
16
16
  sed -e '1s/^/\/\/ #EDGE FIREBASE RULES START\n/' -e '$s/$/\n\/\/ #EDGE FIREBASE RULES END/' \
17
17
  >> ./firestore.rules;
18
+
19
+ if [ ! -f ./functions/index.js ]; then
20
+ mkdir -p ./functions
21
+ echo "const functions = require('firebase-functions');" > ./functions/index.js;
22
+ echo "const admin = require('firebase-admin');" >> ./functions/index.js;
23
+ echo "admin.initializeApp();" >> ./functions/index.js;
24
+ echo "const db = admin.firestore();" >> ./functions/index.js;
25
+ fi
26
+
27
+ [ "$(tail -c1 ./firestore.rules)" != "" ] && echo "" >> ./functions/index.js
28
+
29
+ sed -i.backup '/\/\/ START @edge\/firebase functions/,/\/\/ END @edge\/firebase functions/d' ./functions/index.js
30
+
31
+ awk '/\/\/ START @edge\/firebase functions/,/\/\/ END @edge\/firebase functions/' ./node_modules/@edgedev/firebase/src/functions.js | \
32
+ sed '1d;$d' | \
33
+ sed -e '1s/^/\/\/ START @edge\/firebase functions\n/' -e '$s/$/\n\/\/ END @edge\/firebase functions/' \
34
+ >> ./functions/index.js;