@edgedev/firebase 1.9.17 → 1.9.19
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/edgeFirebase.ts +151 -47
- package/package.json +1 -1
- package/src/functions.js +82 -0
package/edgeFirebase.ts
CHANGED
|
@@ -47,10 +47,13 @@ import {
|
|
|
47
47
|
browserPopupRedirectResolver,
|
|
48
48
|
signInWithPopup,
|
|
49
49
|
signInWithPhoneNumber,
|
|
50
|
+
sendSignInLinkToEmail,
|
|
50
51
|
updateEmail,
|
|
51
52
|
RecaptchaVerifier,
|
|
52
53
|
ConfirmationResult,
|
|
53
54
|
PhoneAuthProvider,
|
|
55
|
+
signInWithCustomToken,
|
|
56
|
+
updateProfile,
|
|
54
57
|
} from "firebase/auth";
|
|
55
58
|
|
|
56
59
|
import { getFunctions, httpsCallable, connectFunctionsEmulator } from "firebase/functions";
|
|
@@ -89,7 +92,7 @@ interface permissions {
|
|
|
89
92
|
|
|
90
93
|
type action = "assign" | "read" | "write" | "delete";
|
|
91
94
|
|
|
92
|
-
type authProviders = "email" | "microsoft" | "google" | "facebook" | "github" | "twitter" | "apple" | "phone";
|
|
95
|
+
type authProviders = "email" | "microsoft" | "google" | "facebook" | "github" | "twitter" | "apple" | "phone" | "emailLink" | "customToken";
|
|
93
96
|
|
|
94
97
|
interface role {
|
|
95
98
|
collectionPath: "-" | string; // - is root
|
|
@@ -133,8 +136,10 @@ interface newUser {
|
|
|
133
136
|
interface userRegister {
|
|
134
137
|
uid?: string;
|
|
135
138
|
email?: string;
|
|
139
|
+
token?: string;
|
|
140
|
+
identifier?: string;
|
|
136
141
|
phoneCode?: string;
|
|
137
|
-
|
|
142
|
+
phoneNumber?: string;
|
|
138
143
|
password?: string;
|
|
139
144
|
meta: object;
|
|
140
145
|
registrationCode: string;
|
|
@@ -441,19 +446,15 @@ export const EdgeFirebase = class {
|
|
|
441
446
|
});
|
|
442
447
|
};
|
|
443
448
|
|
|
444
|
-
public
|
|
445
|
-
const oldDiv = document.getElementById("recaptcha-container");
|
|
446
|
-
if (oldDiv) oldDiv.remove();
|
|
449
|
+
public loginWithCustomToken = async (token: string): Promise<void> => {
|
|
447
450
|
try {
|
|
448
|
-
|
|
449
|
-
const result = await confirmationResult.confirm(phoneCode);
|
|
451
|
+
const result = await signInWithCustomToken(this.auth, token);
|
|
450
452
|
if (!Object.prototype.hasOwnProperty.call(result, "user")) {
|
|
451
453
|
this.user.logInError = true;
|
|
452
454
|
this.user.logInErrorMessage = JSON.stringify(result)
|
|
453
455
|
this.logOut();
|
|
454
456
|
return;
|
|
455
457
|
}
|
|
456
|
-
console.log(result.user.uid);
|
|
457
458
|
const userRef = doc(this.db, "users", result.user.uid);
|
|
458
459
|
const userSnap = await getDoc(userRef);
|
|
459
460
|
if (!userSnap.exists()) {
|
|
@@ -468,6 +469,39 @@ export const EdgeFirebase = class {
|
|
|
468
469
|
this.logOut();
|
|
469
470
|
return;
|
|
470
471
|
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
public logInWithPhone = async (phoneNumber: string, phoneCode: string): Promise<void> => {
|
|
475
|
+
try {
|
|
476
|
+
const verifyCode: any = await this.runFunction("verifyPhoneNumber", {phone: phoneNumber, code: phoneCode});
|
|
477
|
+
if (verifyCode.data.success) {
|
|
478
|
+
const result = await signInWithCustomToken(this.auth, verifyCode.data.token);
|
|
479
|
+
if (!Object.prototype.hasOwnProperty.call(result, "user")) {
|
|
480
|
+
this.user.logInError = true;
|
|
481
|
+
this.user.logInErrorMessage = JSON.stringify(result)
|
|
482
|
+
this.logOut();
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const userRef = doc(this.db, "users", result.user.uid);
|
|
486
|
+
const userSnap = await getDoc(userRef);
|
|
487
|
+
if (!userSnap.exists()) {
|
|
488
|
+
this.user.logInError = true;
|
|
489
|
+
this.user.logInErrorMessage = "User does not exist";
|
|
490
|
+
this.logOut();
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
} else {
|
|
494
|
+
this.user.logInError = true;
|
|
495
|
+
this.user.logInErrorMessage = verifyCode.data.error;
|
|
496
|
+
this.logOut();
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
} catch (error) {
|
|
500
|
+
this.user.logInError = true;
|
|
501
|
+
this.user.logInErrorMessage = error.message;
|
|
502
|
+
this.logOut();
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
471
505
|
};
|
|
472
506
|
|
|
473
507
|
public logInWithMicrosoft = async (providerScopes: string[] = []): Promise<void> => {
|
|
@@ -535,24 +569,37 @@ export const EdgeFirebase = class {
|
|
|
535
569
|
}
|
|
536
570
|
}
|
|
537
571
|
|
|
538
|
-
public
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
}
|
|
572
|
+
public sendEmailLink = async (email: string): Promise<any> => {
|
|
573
|
+
const actionCodeSettings = {
|
|
574
|
+
url: window.location.href,
|
|
575
|
+
handleCodeInApp: true,
|
|
576
|
+
};
|
|
577
|
+
try {
|
|
578
|
+
await sendSignInLinkToEmail(this.auth, email, actionCodeSettings);
|
|
579
|
+
return true;
|
|
580
|
+
} catch (error) {
|
|
581
|
+
this.user.logInError = true;
|
|
582
|
+
this.user.logInErrorMessage = error.message;
|
|
583
|
+
this.logOut();
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
};
|
|
552
587
|
|
|
588
|
+
public sendPhoneCode = async (phone: string): Promise<any> => {
|
|
553
589
|
try {
|
|
554
|
-
const
|
|
555
|
-
|
|
590
|
+
const callable = httpsCallable(this.functions, "sendVerificationCode");
|
|
591
|
+
const result: any = await callable({phone: phone});
|
|
592
|
+
|
|
593
|
+
if (result.data.success !== false) {
|
|
594
|
+
console.log('good')
|
|
595
|
+
return result.data;
|
|
596
|
+
} else {
|
|
597
|
+
console.log('bad')
|
|
598
|
+
this.user.logInError = true;
|
|
599
|
+
this.user.logInErrorMessage = result.data.error;
|
|
600
|
+
this.logOut();
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
556
603
|
} catch (error) {
|
|
557
604
|
this.user.logInError = true;
|
|
558
605
|
this.user.logInErrorMessage = error.message;
|
|
@@ -608,17 +655,50 @@ export const EdgeFirebase = class {
|
|
|
608
655
|
}
|
|
609
656
|
} else if (authProvider === "microsoft") {
|
|
610
657
|
response = await this.registerUserWithMicrosoft(providerScopes);
|
|
658
|
+
} else if (authProvider === "customToken") {
|
|
659
|
+
if (!Object.prototype.hasOwnProperty.call(userRegister, 'token')) {
|
|
660
|
+
return this.sendResponse({
|
|
661
|
+
success: false,
|
|
662
|
+
message: "Token is required for registration when authProvider is customToken.",
|
|
663
|
+
meta: {}
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
if (!Object.prototype.hasOwnProperty.call(userRegister, 'identifier')) {
|
|
667
|
+
return this.sendResponse({
|
|
668
|
+
success: false,
|
|
669
|
+
message: "Identifier is required for registration when authProvider is customToken.",
|
|
670
|
+
meta: {}
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
try {
|
|
674
|
+
response = await signInWithCustomToken(this.auth, userRegister.token);
|
|
675
|
+
await updateProfile(response.user, {
|
|
676
|
+
displayName: userRegister.identifier
|
|
677
|
+
});
|
|
678
|
+
} catch (error) {
|
|
679
|
+
response = error;
|
|
680
|
+
}
|
|
611
681
|
} else if (authProvider === "phone") {
|
|
612
|
-
const oldDiv = document.getElementById("recaptcha-container");
|
|
613
|
-
if (oldDiv) oldDiv.remove();
|
|
614
682
|
try {
|
|
615
|
-
|
|
683
|
+
const verifyCode: any = await this.runFunction("verifyPhoneNumber", {phone: userRegister.phoneNumber, code: userRegister.phoneCode});
|
|
684
|
+
if (verifyCode.data.success) {
|
|
685
|
+
response = await signInWithCustomToken(this.auth, verifyCode.data.token);
|
|
686
|
+
await updateProfile(response.user, {
|
|
687
|
+
displayName: userRegister.phoneNumber
|
|
688
|
+
});
|
|
689
|
+
} else {
|
|
690
|
+
return this.sendResponse({
|
|
691
|
+
success: false,
|
|
692
|
+
message: verifyCode.data.error,
|
|
693
|
+
meta: {}
|
|
694
|
+
});
|
|
695
|
+
}
|
|
616
696
|
} catch (error) {
|
|
617
697
|
// Handle the case where the code is incorrect or expired
|
|
618
698
|
response = error;
|
|
619
699
|
}
|
|
620
|
-
|
|
621
|
-
|
|
700
|
+
} else if (authProvider === "emailLink") {
|
|
701
|
+
// TOOD: emailLink stuff
|
|
622
702
|
}
|
|
623
703
|
if (!Object.prototype.hasOwnProperty.call(response, "user")) {
|
|
624
704
|
return this.sendResponse({
|
|
@@ -627,14 +707,13 @@ export const EdgeFirebase = class {
|
|
|
627
707
|
meta: {}
|
|
628
708
|
});
|
|
629
709
|
}
|
|
630
|
-
|
|
710
|
+
|
|
631
711
|
let metaUpdate = {};
|
|
632
712
|
if (Object.prototype.hasOwnProperty.call(userRegister, 'meta')) {
|
|
633
713
|
metaUpdate = userRegister.meta;
|
|
634
714
|
}else{
|
|
635
715
|
metaUpdate = user.meta;
|
|
636
716
|
}
|
|
637
|
-
|
|
638
717
|
let stagedUserUpdate: {userId?: string, templateUserId?: string, dynamicDocumentFieldValue?: string, uid: string, meta: unknown, templateMeta?: unknown} = {userId: response.user.uid, uid: response.user.uid, meta: metaUpdate}
|
|
639
718
|
if (user.isTemplate) {
|
|
640
719
|
stagedUserUpdate = {templateUserId: response.user.uid, uid: response.user.uid, meta: user.meta, templateMeta: metaUpdate}
|
|
@@ -1391,28 +1470,52 @@ export const EdgeFirebase = class {
|
|
|
1391
1470
|
collectionPath: string,
|
|
1392
1471
|
docId: string
|
|
1393
1472
|
): Promise<actionResponse> => {
|
|
1394
|
-
// console.log(collectionPath)
|
|
1395
|
-
// console.log(docId)
|
|
1396
1473
|
const canRead = await this.permissionCheck("read", collectionPath + '/' + docId);
|
|
1397
1474
|
this.data[collectionPath + '/' + docId] = {};
|
|
1398
1475
|
this.stopSnapshot(collectionPath + '/' + docId);
|
|
1399
1476
|
this.unsubscibe[collectionPath + '/' + docId] = null;
|
|
1400
1477
|
if (canRead) {
|
|
1401
1478
|
const docRef = doc(this.db, collectionPath, docId);
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1479
|
+
|
|
1480
|
+
return new Promise<actionResponse>((resolve, reject) => {
|
|
1481
|
+
let firstRun = true;
|
|
1482
|
+
const unsubscribe = onSnapshot(docRef, (doc) => {
|
|
1483
|
+
if (doc.exists()) {
|
|
1484
|
+
const item = doc.data();
|
|
1485
|
+
item.docId = doc.id;
|
|
1486
|
+
this.data[collectionPath + '/' + docId] = item;
|
|
1487
|
+
|
|
1488
|
+
// Only resolve the Promise on first run of onSnapshot if document exists
|
|
1489
|
+
if(firstRun) {
|
|
1490
|
+
firstRun = false;
|
|
1491
|
+
resolve(this.sendResponse({
|
|
1492
|
+
success: true,
|
|
1493
|
+
message: "startDocumentSnapshot " + collectionPath + '/' + docId,
|
|
1494
|
+
meta: {}
|
|
1495
|
+
}));
|
|
1496
|
+
}
|
|
1497
|
+
} else {
|
|
1498
|
+
this.data[collectionPath + '/' + docId] = {};
|
|
1499
|
+
|
|
1500
|
+
// Resolve the Promise with failure response if no document exists
|
|
1501
|
+
if(firstRun) {
|
|
1502
|
+
firstRun = false;
|
|
1503
|
+
resolve(this.sendResponse({
|
|
1504
|
+
success: false,
|
|
1505
|
+
message: `No document found in "${collectionPath}/${docId}"`,
|
|
1506
|
+
meta: {}
|
|
1507
|
+
}));
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}, (error) => {
|
|
1511
|
+
// Reject the Promise with the error response
|
|
1512
|
+
reject(this.sendResponse({
|
|
1513
|
+
success: false,
|
|
1514
|
+
message: `Error fetching document from "${collectionPath}/${docId}": ${error.message}`,
|
|
1515
|
+
meta: {}
|
|
1516
|
+
}));
|
|
1517
|
+
});
|
|
1518
|
+
this.unsubscibe[collectionPath + '/' + docId] = unsubscribe;
|
|
1416
1519
|
});
|
|
1417
1520
|
} else {
|
|
1418
1521
|
return this.sendResponse({
|
|
@@ -1423,6 +1526,7 @@ export const EdgeFirebase = class {
|
|
|
1423
1526
|
}
|
|
1424
1527
|
};
|
|
1425
1528
|
|
|
1529
|
+
|
|
1426
1530
|
public startSnapshot = async (
|
|
1427
1531
|
collectionPath: string,
|
|
1428
1532
|
queryList: FirestoreQuery[] = [],
|
package/package.json
CHANGED
package/src/functions.js
CHANGED
|
@@ -1,5 +1,87 @@
|
|
|
1
1
|
// START @edge/firebase functions
|
|
2
2
|
|
|
3
|
+
const twilio = require('twilio')
|
|
4
|
+
const authToken = functions.config().twilio.auth_token
|
|
5
|
+
const accountSid = functions.config().twilio.sid
|
|
6
|
+
const systemNumber = functions.config().twilio.system_number
|
|
7
|
+
|
|
8
|
+
function formatPhoneNumber(phone) {
|
|
9
|
+
// Remove non-numeric characters from the phone number
|
|
10
|
+
const numericPhone = phone.replace(/\D/g, '')
|
|
11
|
+
// Return the formatted number
|
|
12
|
+
return `+1${numericPhone}`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
exports.sendVerificationCode = functions.https.onCall(async (data, context) => {
|
|
16
|
+
const code = (Math.floor(Math.random() * 1000000) + 1000000).toString().substring(1)
|
|
17
|
+
const phone = formatPhoneNumber(data.phone)
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const client = twilio(accountSid, authToken)
|
|
21
|
+
await client.messages.create({
|
|
22
|
+
body: `Your verification code is: ${code}`,
|
|
23
|
+
to: phone, // the user's phone number
|
|
24
|
+
from: systemNumber, // your Twilio phone number from the configuration
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.log(error)
|
|
29
|
+
return { success: false, error: 'Invalid Phone #' }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
// Use the formatted phone number as the document ID for Firestore
|
|
34
|
+
await db.collection('phone-auth').doc(phone).set({
|
|
35
|
+
phone,
|
|
36
|
+
code,
|
|
37
|
+
})
|
|
38
|
+
return phone
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
return { success: false, error }
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
exports.verifyPhoneNumber = functions.https.onCall(async (data, context) => {
|
|
46
|
+
const phone = data.phone
|
|
47
|
+
const code = data.code
|
|
48
|
+
|
|
49
|
+
// Get the phone-auth document with the given phone number
|
|
50
|
+
const phoneDoc = await db.collection('phone-auth').doc(phone).get()
|
|
51
|
+
|
|
52
|
+
if (!phoneDoc.exists) {
|
|
53
|
+
return { success: false, error: 'Phone number not found.' }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const storedCode = phoneDoc.data().code
|
|
57
|
+
|
|
58
|
+
if (storedCode !== code) {
|
|
59
|
+
return { success: false, error: 'Invalid verification code.' }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// If the code matches, authenticate the user with Firebase Custom Auth
|
|
63
|
+
try {
|
|
64
|
+
// You would typically generate a UID based on the phone number or another system
|
|
65
|
+
const uid = phone
|
|
66
|
+
|
|
67
|
+
// Create a custom token (this can be used on the client to sign in)
|
|
68
|
+
const customToken = await admin.auth().createCustomToken(uid)
|
|
69
|
+
|
|
70
|
+
return { success: true, token: customToken }
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.error('Error creating custom token:', error)
|
|
74
|
+
return { success: false, error: 'Failed to authenticate.' }
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// // Generate custom token example:
|
|
79
|
+
// exports.generateCustomToken = functions.https.onCall(async (data, context) => {
|
|
80
|
+
// // You would want to have some sort of validation here
|
|
81
|
+
// const token = await admin.auth().createCustomToken(data.customUid)
|
|
82
|
+
// return { token }
|
|
83
|
+
// })
|
|
84
|
+
|
|
3
85
|
exports.initFirestore = functions.https.onCall(async (data, context) => {
|
|
4
86
|
// checks to see of the collections 'collection-data' and 'staged-users' exist if not will seed them with data
|
|
5
87
|
const collectionData = await db.collection('collection-data').get()
|