@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 +2 -0
- package/edgeFirebase.ts +88 -25
- package/package.json +1 -1
- package/src/firestore.rules +3 -2
- package/src/functions.js +103 -0
- package/src/postinstall.sh +24 -7
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
|
-
|
|
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
|
|
122
|
-
password
|
|
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
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
package/src/firestore.rules
CHANGED
package/src/functions.js
ADDED
|
@@ -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
|
package/src/postinstall.sh
CHANGED
|
@@ -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
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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;
|