@edgedev/firebase 1.9.23 → 2.0.3

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 CHANGED
@@ -110,7 +110,7 @@ interface UserDataObject {
110
110
  firebaseUser: object;
111
111
  oAuthCredential: { accessToken: string; idToken: string;}
112
112
  loggedIn: boolean;
113
- loggingIn: boolean;
113
+ loggingIn: boolean | null;
114
114
  logInError: boolean;
115
115
  logInErrorMessage: string;
116
116
  meta: object;
@@ -144,6 +144,7 @@ interface userRegister {
144
144
  meta: object;
145
145
  registrationCode: string;
146
146
  dynamicDocumentFieldValue?: string;
147
+ requestedOrgId?: string;
147
148
  }
148
149
 
149
150
 
@@ -423,6 +424,7 @@ export const EdgeFirebase = class {
423
424
 
424
425
  private setOnAuthStateChanged = (): void => {
425
426
  onAuthStateChanged(this.auth, (userAuth) => {
427
+ console.log('onAuthStateChanged')
426
428
  const oldDiv = document.getElementById("recaptcha-container");
427
429
  if (oldDiv) oldDiv.remove();
428
430
  if (userAuth) {
@@ -473,7 +475,7 @@ export const EdgeFirebase = class {
473
475
 
474
476
  public logInWithPhone = async (phoneNumber: string, phoneCode: string): Promise<void> => {
475
477
  try {
476
- const verifyCode: any = await this.runFunction("verifyPhoneNumber", {phone: phoneNumber, code: phoneCode});
478
+ const verifyCode: any = await this.runFunction("edgeFirebase-verifyPhoneNumber", {phone: phoneNumber, code: phoneCode});
477
479
  if (verifyCode.data.success) {
478
480
  const result = await signInWithCustomToken(this.auth, verifyCode.data.token);
479
481
  if (!Object.prototype.hasOwnProperty.call(result, "user")) {
@@ -552,7 +554,7 @@ export const EdgeFirebase = class {
552
554
  });
553
555
  }
554
556
  // userRegister.uid = this.user.uid;
555
- const result = await this.runFunction("currentUserRegister", userRegister as unknown as Record<string, unknown>);
557
+ const result = await this.runFunction("edgeFirebase-currentUserRegister", userRegister as unknown as Record<string, unknown>);
556
558
  const resultData = result.data as {success: boolean, message: string};
557
559
  if (resultData.success) {
558
560
  return this.sendResponse({
@@ -622,6 +624,18 @@ export const EdgeFirebase = class {
622
624
  meta: {}
623
625
  });
624
626
  }
627
+ if (Object.prototype.hasOwnProperty.call(userRegister, 'requestedOrgId')) {
628
+ if (userRegister.requestedOrgId) {
629
+ const result: any = await this.runFunction("edgeFirebase-checkOrgIdExists", {orgId: userRegister.requestedOrgId.toLowerCase()});
630
+ if (result.data.exists) {
631
+ return this.sendResponse({
632
+ success: false,
633
+ message: "Organization ID already exists.",
634
+ meta: {}
635
+ });
636
+ }
637
+ }
638
+ }
625
639
  const userRef = doc(this.db, "staged-users", userRegister.registrationCode);
626
640
  const userSnap = await getDoc(userRef);
627
641
  if (userSnap.exists()) {
@@ -680,7 +694,7 @@ export const EdgeFirebase = class {
680
694
  }
681
695
  } else if (authProvider === "phone") {
682
696
  try {
683
- const verifyCode: any = await this.runFunction("verifyPhoneNumber", {phone: userRegister.phoneNumber, code: userRegister.phoneCode});
697
+ const verifyCode: any = await this.runFunction("edgeFirebase-verifyPhoneNumber", {phone: userRegister.phoneNumber, code: userRegister.phoneCode});
684
698
  if (verifyCode.data.success) {
685
699
  response = await signInWithCustomToken(this.auth, verifyCode.data.token);
686
700
  await updateProfile(response.user, {
@@ -714,12 +728,15 @@ export const EdgeFirebase = class {
714
728
  }else{
715
729
  metaUpdate = user.meta;
716
730
  }
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}
731
+ let stagedUserUpdate: {userId?: string, templateUserId?: string, dynamicDocumentFieldValue?: string, uid: string, meta: unknown, templateMeta?: unknown, requestedOrgId?: unknown} = {userId: response.user.uid, uid: response.user.uid, meta: metaUpdate}
718
732
  if (user.isTemplate) {
719
733
  stagedUserUpdate = {templateUserId: response.user.uid, uid: response.user.uid, meta: user.meta, templateMeta: metaUpdate}
720
734
  if (Object.prototype.hasOwnProperty.call(userRegister, 'dynamicDocumentFieldValue')) {
721
735
  stagedUserUpdate = {templateUserId: response.user.uid, uid: response.user.uid, dynamicDocumentFieldValue: userRegister.dynamicDocumentFieldValue, meta: user.meta, templateMeta: metaUpdate}
722
736
  }
737
+ if (Object.prototype.hasOwnProperty.call(userRegister, 'requestedOrgId')) {
738
+ stagedUserUpdate = {templateUserId: response.user.uid, uid: response.user.uid, dynamicDocumentFieldValue: userRegister.dynamicDocumentFieldValue, meta: user.meta, templateMeta: metaUpdate, requestedOrgId: userRegister.requestedOrgId.toLowerCase()}
739
+ }
723
740
  }
724
741
  const initRoleHelper = {uid: response.user.uid}
725
742
  initRoleHelper["edge-assignment-helper"] = {permissionType: "roles"}
@@ -854,7 +871,7 @@ export const EdgeFirebase = class {
854
871
  }
855
872
  }
856
873
  if (removedFrom.length > 0) {
857
- const response = await this.runFunction("removeNonRegisteredUser", {uid: this.user.uid, docId});
874
+ const response = await this.runFunction("edgeFirebase-removeNonRegisteredUser", {uid: this.user.uid, docId});
858
875
  return this.sendResponse({
859
876
  success: true,
860
877
  message: "line 704",
@@ -1206,7 +1223,7 @@ export const EdgeFirebase = class {
1206
1223
  public user: UserDataObject = reactive({
1207
1224
  uid: null,
1208
1225
  email: "",
1209
- loggingIn: false,
1226
+ loggingIn: null,
1210
1227
  loggedIn: false,
1211
1228
  logInError: false,
1212
1229
  logInErrorMessage: "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edgedev/firebase",
3
- "version": "1.9.23",
3
+ "version": "2.0.3",
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": {
package/src/.env.dev ADDED
@@ -0,0 +1,7 @@
1
+ STRIPE_API_KEY=
2
+ STRIPE_SECRET=
3
+ STRIPE_RETURN_URL=
4
+ OPENAI_API_KEY=
5
+ TWILIO_SID=
6
+ TWILIO_AUTH_TOKEN=
7
+ TWILIO_SYSTEM_NUMBER=
package/src/.env.prod ADDED
@@ -0,0 +1,7 @@
1
+ STRIPE_API_KEY=
2
+ STRIPE_SECRET=
3
+ STRIPE_RETURN_URL=
4
+ OPENAI_API_KEY=
5
+ TWILIO_SID=
6
+ TWILIO_AUTH_TOKEN=
7
+ TWILIO_SYSTEM_NUMBER=
package/src/config.js ADDED
@@ -0,0 +1,38 @@
1
+ const functions = require('firebase-functions')
2
+ const admin = require('firebase-admin')
3
+
4
+ admin.initializeApp()
5
+
6
+ const { onCall, HttpsError } = require('firebase-functions/v2/https')
7
+ const { onSchedule } = require('firebase-functions/v2/scheduler')
8
+ const {
9
+ onDocumentWritten,
10
+ onDocumentCreated,
11
+ onDocumentUpdated,
12
+ onDocumentDeleted,
13
+ Change,
14
+ FirestoreEvent,
15
+ } = require('firebase-functions/v2/firestore')
16
+ const { logger } = require('firebase-functions/v2')
17
+ const { getFirestore } = require('firebase-admin/firestore')
18
+ const twilio = require('twilio')
19
+ const db = getFirestore()
20
+
21
+ module.exports = {
22
+ onSchedule,
23
+ onDocumentWritten,
24
+ onDocumentCreated,
25
+ onDocumentUpdated,
26
+ onDocumentDeleted,
27
+ Change,
28
+ FirestoreEvent,
29
+ onCall,
30
+ HttpsError,
31
+ logger,
32
+ getFirestore,
33
+ functions,
34
+ admin,
35
+ twilio,
36
+ db,
37
+ }
38
+
@@ -0,0 +1,309 @@
1
+ const { onCall, HttpsError, logger, getFirestore, functions, admin, twilio, db } = require('./config.js')
2
+
3
+ const authToken = process.env.TWILIO_AUTH_TOKEN
4
+ const accountSid = process.env.TWILIO_SID
5
+ const systemNumber = process.env.TWILIO_SYSTEM_NUMBER
6
+
7
+ function formatPhoneNumber(phone) {
8
+ // Remove non-numeric characters from the phone number
9
+ const numericPhone = phone.replace(/\D/g, '')
10
+ // Return the formatted number
11
+ return `+1${numericPhone}`
12
+ }
13
+
14
+ exports.sendVerificationCode = functions.https.onCall(async (data, context) => {
15
+ const code = (Math.floor(Math.random() * 1000000) + 1000000).toString().substring(1)
16
+ const phone = formatPhoneNumber(data.phone)
17
+
18
+ try {
19
+ const client = twilio(accountSid, authToken)
20
+ await client.messages.create({
21
+ body: `Your verification code is: ${code}`,
22
+ to: phone, // the user's phone number
23
+ from: systemNumber, // your Twilio phone number from the configuration
24
+ })
25
+ }
26
+ catch (error) {
27
+ console.log(error)
28
+ return { success: false, error: 'Invalid Phone #' }
29
+ }
30
+
31
+ try {
32
+ // Use the formatted phone number as the document ID for Firestore
33
+ await db.collection('phone-auth').doc(phone).set({
34
+ phone,
35
+ code,
36
+ })
37
+ return phone
38
+ }
39
+ catch (error) {
40
+ return { success: false, error }
41
+ }
42
+ })
43
+
44
+ exports.verifyPhoneNumber = functions.https.onCall(async (data, context) => {
45
+ const phone = data.phone
46
+ const code = data.code
47
+
48
+ // Get the phone-auth document with the given phone number
49
+ const phoneDoc = await db.collection('phone-auth').doc(phone).get()
50
+
51
+ if (!phoneDoc.exists) {
52
+ return { success: false, error: 'Phone number not found.' }
53
+ }
54
+
55
+ const storedCode = phoneDoc.data().code
56
+
57
+ if (storedCode !== code) {
58
+ return { success: false, error: 'Invalid verification code.' }
59
+ }
60
+
61
+ // If the code matches, authenticate the user with Firebase Custom Auth
62
+ try {
63
+ // You would typically generate a UID based on the phone number or another system
64
+ const uid = phone
65
+
66
+ // Create a custom token (this can be used on the client to sign in)
67
+ const customToken = await admin.auth().createCustomToken(uid)
68
+
69
+ return { success: true, token: customToken }
70
+ }
71
+ catch (error) {
72
+ console.error('Error creating custom token:', error)
73
+ return { success: false, error: 'Failed to authenticate.' }
74
+ }
75
+ })
76
+
77
+ exports.initFirestore = functions.https.onCall(async (data, context) => {
78
+ // checks to see of the collections 'collection-data' and 'staged-users' exist if not will seed them with data
79
+ const collectionData = await db.collection('collection-data').get()
80
+ const stagedUsers = await db.collection('staged-users').get()
81
+ if (collectionData.empty) {
82
+ // create a document with the id of '-' and one called '-default-':
83
+ const admin = { assign: true, delete: true, read: true, write: true }
84
+ const editor = { assign: false, delete: true, read: true, write: true }
85
+ const writer = { assign: false, delete: false, read: true, write: true }
86
+ const user = { assign: false, delete: false, read: true, write: false }
87
+ await db.collection('collection-data').doc('-').set({ admin, editor, writer, user })
88
+ await db.collection('collection-data').doc('-default-').set({ admin, editor, writer, user })
89
+ }
90
+ if (stagedUsers.empty) {
91
+ const templateUser = {
92
+ docId: 'organization-registration-template',
93
+ isTemplate: true,
94
+ meta: {
95
+ name: 'Organization Registration Template',
96
+ },
97
+ subCreate: {
98
+ documentStructure: {
99
+ name: '',
100
+ },
101
+ dynamicDocumentField: 'name',
102
+ role: 'admin',
103
+ rootPath: 'organizations',
104
+ },
105
+ userId: '',
106
+ }
107
+ await db.collection('staged-users').doc('organization-registration-template').set(templateUser)
108
+ }
109
+ })
110
+
111
+ exports.removeNonRegisteredUser = functions.https.onCall(async (data, context) => {
112
+ if (data.uid === context.auth.uid) {
113
+ const stagedUser = await db.collection('staged-users').doc(data.docId).get()
114
+ if (stagedUser.exists) {
115
+ const stagedUserData = stagedUser.data()
116
+
117
+ const rolesExist = stagedUserData.roles && Object.keys(stagedUserData.roles).length !== 0
118
+ const specialPermissionsExist = stagedUserData.specialPermissions && Object.keys(stagedUserData.specialPermissions).length !== 0
119
+ const userIdExistsAndNotBlank = stagedUserData.userId && stagedUserData.userId !== ''
120
+
121
+ if (!rolesExist && !specialPermissionsExist && !userIdExistsAndNotBlank) {
122
+ await db.collection('staged-users').doc(data.docId).delete()
123
+ return { success: true, message: '' }
124
+ }
125
+ else {
126
+ let message = ''
127
+ if (rolesExist && specialPermissionsExist) {
128
+ message = 'Cannot delete because the non-registered user still has roles and special permissions assigned.'
129
+ }
130
+ else if (rolesExist) {
131
+ message = 'Cannot delete because the non-registered user still has roles assigned.'
132
+ }
133
+ else if (specialPermissionsExist) {
134
+ message = 'Cannot delete because the non-registered user still has special permissions assigned.'
135
+ }
136
+ else if (userIdExistsAndNotBlank) {
137
+ message = 'Cannot delete because the user is registered.'
138
+ }
139
+ return { success: false, message }
140
+ }
141
+ }
142
+ }
143
+ return { success: false, message: 'Non-registered user not found.' }
144
+ })
145
+
146
+ exports.currentUserRegister = functions.https.onCall(async (data, context) => {
147
+ if (data.uid === context.auth.uid) {
148
+ const stagedUser = await db.collection('staged-users').doc(data.registrationCode).get()
149
+ if (!stagedUser.exists) {
150
+ return { success: false, message: 'Registration code not found.' }
151
+ }
152
+ else {
153
+ const stagedUserData = await stagedUser.data()
154
+ let process = false
155
+ if (stagedUserData.isTemplate) {
156
+ process = true
157
+ }
158
+ if (!stagedUserData.isTemplate && stagedUserData.userId === '') {
159
+ process = true
160
+ }
161
+ if (!process) {
162
+ return { success: false, message: 'Registration code not valid.' }
163
+ }
164
+ const newRoles = stagedUserData.roles || {}
165
+ const currentUser = await db.collection('users').doc(data.uid).get()
166
+ const currentUserData = await currentUser.data()
167
+ const currentRoles = currentUserData.roles || {}
168
+ const currentUserCollectionPaths = currentUserData.collectionPaths || []
169
+ let newRole = {}
170
+ if (stagedUserData.subCreate && Object.keys(stagedUserData.subCreate).length !== 0 && stagedUserData.isTemplate) {
171
+ if (!data.dynamicDocumentFieldValue) {
172
+ return { success: false, message: 'Dynamic document field value is required.' }
173
+ }
174
+ const rootPath = stagedUserData.subCreate.rootPath
175
+ const newDoc = stagedUserData.subCreate.documentStructure
176
+ newDoc[stagedUserData.subCreate.dynamicDocumentField] = data.dynamicDocumentFieldValue
177
+ const addedDoc = await db.collection(rootPath).add(newDoc)
178
+ await db.collection(rootPath).doc(addedDoc.id).update({ docId: addedDoc.id })
179
+ newRole = { [`${rootPath}-${addedDoc.id}`]: { collectionPath: `${rootPath}-${addedDoc.id}`, role: stagedUserData.subCreate.role } }
180
+ }
181
+ const combinedRoles = { ...currentRoles, ...newRoles, ...newRole }
182
+ Object.values(combinedRoles).forEach((role) => {
183
+ if (!currentUserCollectionPaths.includes(role.collectionPath)) {
184
+ currentUserCollectionPaths.push(role.collectionPath)
185
+ }
186
+ })
187
+ await db.collection('staged-users').doc(currentUserData.stagedDocId).update({ roles: combinedRoles, collectionPaths: currentUserCollectionPaths })
188
+ if (!stagedUserData.isTemplate) {
189
+ await db.collection('staged-users').doc(data.registrationCode).delete()
190
+ }
191
+ return { success: true, message: '' }
192
+ }
193
+ }
194
+ })
195
+
196
+ exports.checkOrgIdExists = onCall(async (request) => {
197
+ const data = request.data
198
+ const orgId = data.orgId.toLowerCase()
199
+ const orgDoc = await db.collection('organizations').doc(orgId).get()
200
+ return { exists: orgDoc.exists }
201
+ })
202
+
203
+ exports.updateUser = functions.firestore.document('staged-users/{docId}').onUpdate(async (change, context) => {
204
+ const eventId = context.eventId
205
+ const eventRef = db.collection('events').doc(eventId)
206
+ const stagedDocId = context.params.docId
207
+ let newData = change.after.data()
208
+ const oldData = change.before.data()
209
+
210
+ const shouldProcess = await eventRef.get().then((eventDoc) => {
211
+ return !eventDoc.exists || !eventDoc.data().processed
212
+ })
213
+
214
+ if (!shouldProcess) {
215
+ return null
216
+ }
217
+
218
+ // Note: we can trust on newData.uid because we are checking in rules that it matches the auth.uid
219
+ if (newData.userId) {
220
+ const userRef = db.collection('users').doc(newData.userId)
221
+ await setUser(userRef, newData, oldData, stagedDocId)
222
+ await markProcessed(eventRef)
223
+ }
224
+ else {
225
+ if (newData.templateUserId !== oldData.templateUserId) {
226
+ // Check if templateUserId already exists in the staged-users collection
227
+ const stagedUserRef = db.collection('staged-users').doc(newData.templateUserId)
228
+ const doc = await stagedUserRef.get()
229
+
230
+ // If it exists, skip the creation process
231
+ if (doc.exists) {
232
+ return null
233
+ }
234
+
235
+ newData.isTemplate = false
236
+ const templateUserId = newData.templateUserId
237
+ newData.meta = newData.templateMeta
238
+ delete newData.templateMeta
239
+ delete newData.templateUserId
240
+ if (Object.prototype.hasOwnProperty.call(newData, 'subCreate') && Object.values(newData.subCreate).length > 0) {
241
+ const subCreate = newData.subCreate
242
+ delete newData.subCreate
243
+ const addedDoc = await db.collection(subCreate.rootPath).add({ [subCreate.dynamicDocumentField]: newData.dynamicDocumentFieldValue })
244
+ await db.collection(subCreate.rootPath).doc(addedDoc.id).update({ docId: addedDoc.id })
245
+ delete newData.dynamicDocumentFieldValue
246
+ const newRole = { [`${subCreate.rootPath}-${addedDoc.id}`]: { collectionPath: `${subCreate.rootPath}-${addedDoc.id}`, role: subCreate.role } }
247
+ if (Object.prototype.hasOwnProperty.call(newData, 'collectionPaths')) {
248
+ newData.collectionPaths.push(`${subCreate.rootPath}-${addedDoc.id}`)
249
+ }
250
+ else {
251
+ newData.collectionPaths = [`${subCreate.rootPath}-${addedDoc.id}`]
252
+ }
253
+ const newRoles = { ...newData.roles, ...newRole }
254
+ newData = { ...newData, roles: newRoles }
255
+ const stagedUserRef = db.collection('staged-users').doc(templateUserId)
256
+ await stagedUserRef.set({ ...newData, userId: templateUserId })
257
+ const userRef = db.collection('users').doc(templateUserId)
258
+ await setUser(userRef, newData, oldData, templateUserId)
259
+ await markProcessed(eventRef)
260
+ }
261
+ else {
262
+ const stagedUserRef = db.collection('staged-users').doc(templateUserId)
263
+ await stagedUserRef.set({ ...newData, userId: templateUserId })
264
+ const userRef = db.collection('users').doc(templateUserId)
265
+ await setUser(userRef, newData, oldData, templateUserId)
266
+ await markProcessed(eventRef)
267
+ }
268
+ }
269
+ }
270
+ await markProcessed(eventRef)
271
+ })
272
+
273
+ function setUser(userRef, newData, oldData, stagedDocId) {
274
+ // IT's OK If "users" doesn't match exactly matched "staged-users" because this is only preventing
275
+ // writing from outside the @edgdev/firebase functions, so discrepancies will be rare since
276
+ // the package will prevent before it gets this far.
277
+ return userRef.get().then((user) => {
278
+ let userUpdate = { meta: newData.meta, stagedDocId }
279
+
280
+ if (Object.prototype.hasOwnProperty.call(newData, 'roles')) {
281
+ userUpdate = { ...userUpdate, roles: newData.roles }
282
+ }
283
+ if (Object.prototype.hasOwnProperty.call(newData, 'specialPermissions')) {
284
+ userUpdate = { ...userUpdate, specialPermissions: newData.specialPermissions }
285
+ }
286
+
287
+ if (!oldData.userId) {
288
+ userUpdate = { ...userUpdate, userId: newData.uid }
289
+ }
290
+ if (!user.exists) {
291
+ return userRef.set(userUpdate)
292
+ }
293
+ else {
294
+ return userRef.update(userUpdate)
295
+ }
296
+ })
297
+ }
298
+
299
+ function shouldProcess(eventRef) {
300
+ return eventRef.get().then((eventDoc) => {
301
+ return !eventDoc.exists || !eventDoc.data().processed
302
+ })
303
+ }
304
+
305
+ function markProcessed(eventRef) {
306
+ return eventRef.set({ processed: true }).then(() => {
307
+ return null
308
+ })
309
+ }
package/src/functions.js CHANGED
@@ -1,311 +1,3 @@
1
1
  // START @edge/firebase functions
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
-
85
- exports.initFirestore = functions.https.onCall(async (data, context) => {
86
- // checks to see of the collections 'collection-data' and 'staged-users' exist if not will seed them with data
87
- const collectionData = await db.collection('collection-data').get()
88
- const stagedUsers = await db.collection('staged-users').get()
89
- if (collectionData.empty) {
90
- // create a document with the id of '-' and one called '-default-':
91
- const admin = { assign: true, delete: true, read: true, write: true }
92
- const editor = { assign: false, delete: true, read: true, write: true }
93
- const writer = { assign: false, delete: false, read: true, write: true }
94
- const user = { assign: false, delete: false, read: true, write: false }
95
- await db.collection('collection-data').doc('-').set({ admin, editor, writer, user })
96
- await db.collection('collection-data').doc('-default-').set({ admin, editor, writer, user })
97
- }
98
- if (stagedUsers.empty) {
99
- const templateUser = {
100
- docId: 'organization-registration-template',
101
- isTemplate: true,
102
- meta: {
103
- name: 'Organization Registration Template',
104
- },
105
- subCreate: {
106
- documentStructure: {
107
- name: '',
108
- },
109
- dynamicDocumentField: 'name',
110
- role: 'admin',
111
- rootPath: 'organizations',
112
- },
113
- userId: '',
114
- }
115
- await db.collection('staged-users').doc('organization-registration-template').set(templateUser)
116
- }
117
- })
118
-
119
- exports.removeNonRegisteredUser = functions.https.onCall(async (data, context) => {
120
- if (data.uid === context.auth.uid) {
121
- const stagedUser = await db.collection('staged-users').doc(data.docId).get()
122
- if (stagedUser.exists) {
123
- const stagedUserData = stagedUser.data()
124
-
125
- const rolesExist = stagedUserData.roles && Object.keys(stagedUserData.roles).length !== 0
126
- const specialPermissionsExist = stagedUserData.specialPermissions && Object.keys(stagedUserData.specialPermissions).length !== 0
127
- const userIdExistsAndNotBlank = stagedUserData.userId && stagedUserData.userId !== ''
128
-
129
- if (!rolesExist && !specialPermissionsExist && !userIdExistsAndNotBlank) {
130
- await db.collection('staged-users').doc(data.docId).delete()
131
- return { success: true, message: '' }
132
- }
133
- else {
134
- let message = ''
135
- if (rolesExist && specialPermissionsExist) {
136
- message = 'Cannot delete because the non-registered user still has roles and special permissions assigned.'
137
- }
138
- else if (rolesExist) {
139
- message = 'Cannot delete because the non-registered user still has roles assigned.'
140
- }
141
- else if (specialPermissionsExist) {
142
- message = 'Cannot delete because the non-registered user still has special permissions assigned.'
143
- }
144
- else if (userIdExistsAndNotBlank) {
145
- message = 'Cannot delete because the user is registered.'
146
- }
147
- return { success: false, message }
148
- }
149
- }
150
- }
151
- return { success: false, message: 'Non-registered user not found.' }
152
- })
153
-
154
- exports.currentUserRegister = functions.https.onCall(async (data, context) => {
155
- if (data.uid === context.auth.uid) {
156
- const stagedUser = await db.collection('staged-users').doc(data.registrationCode).get()
157
- if (!stagedUser.exists) {
158
- return { success: false, message: 'Registration code not found.' }
159
- }
160
- else {
161
- const stagedUserData = await stagedUser.data()
162
- let process = false
163
- if (stagedUserData.isTemplate) {
164
- process = true
165
- }
166
- if (!stagedUserData.isTemplate && stagedUserData.userId === '') {
167
- process = true
168
- }
169
- if (!process) {
170
- return { success: false, message: 'Registration code not valid.' }
171
- }
172
- const newRoles = stagedUserData.roles || {}
173
- const currentUser = await db.collection('users').doc(data.uid).get()
174
- const currentUserData = await currentUser.data()
175
- const currentRoles = currentUserData.roles || {}
176
- const currentUserCollectionPaths = currentUserData.collectionPaths || []
177
- let newRole = {}
178
- if (stagedUserData.subCreate && Object.keys(stagedUserData.subCreate).length !== 0 && stagedUserData.isTemplate) {
179
- if (!data.dynamicDocumentFieldValue) {
180
- return { success: false, message: 'Dynamic document field value is required.' }
181
- }
182
- const rootPath = stagedUserData.subCreate.rootPath
183
- const newDoc = stagedUserData.subCreate.documentStructure
184
- newDoc[stagedUserData.subCreate.dynamicDocumentField] = data.dynamicDocumentFieldValue
185
- const addedDoc = await db.collection(rootPath).add(newDoc)
186
- await db.collection(rootPath).doc(addedDoc.id).update({ docId: addedDoc.id })
187
- newRole = { [`${rootPath}-${addedDoc.id}`]: { collectionPath: `${rootPath}-${addedDoc.id}`, role: stagedUserData.subCreate.role } }
188
- }
189
- const combinedRoles = { ...currentRoles, ...newRoles, ...newRole }
190
- Object.values(combinedRoles).forEach((role) => {
191
- if (!currentUserCollectionPaths.includes(role.collectionPath)) {
192
- currentUserCollectionPaths.push(role.collectionPath)
193
- }
194
- })
195
- await db.collection('staged-users').doc(currentUserData.stagedDocId).update({ roles: combinedRoles, collectionPaths: currentUserCollectionPaths })
196
- if (!stagedUserData.isTemplate) {
197
- await db.collection('staged-users').doc(data.registrationCode).delete()
198
- }
199
- return { success: true, message: '' }
200
- }
201
- }
202
- })
203
-
204
- exports.updateUser = functions.firestore.document('staged-users/{docId}').onUpdate(async (change, context) => {
205
- const eventId = context.eventId
206
- const eventRef = db.collection('events').doc(eventId)
207
- const stagedDocId = context.params.docId
208
- let newData = change.after.data()
209
- const oldData = change.before.data()
210
-
211
- const shouldProcess = await eventRef.get().then((eventDoc) => {
212
- return !eventDoc.exists || !eventDoc.data().processed
213
- })
214
-
215
- if (!shouldProcess) {
216
- return null
217
- }
218
-
219
- // Note: we can trust on newData.uid because we are checking in rules that it matches the auth.uid
220
- if (newData.userId) {
221
- const userRef = db.collection('users').doc(newData.userId)
222
- await setUser(userRef, newData, oldData, stagedDocId)
223
- await markProcessed(eventRef)
224
- }
225
- else {
226
- if (newData.templateUserId !== oldData.templateUserId) {
227
- // Check if templateUserId already exists in the staged-users collection
228
- const stagedUserRef = db.collection('staged-users').doc(newData.templateUserId)
229
- const doc = await stagedUserRef.get()
230
-
231
- // If it exists, skip the creation process
232
- if (doc.exists) {
233
- return null
234
- }
235
-
236
- newData.isTemplate = false
237
- const templateUserId = newData.templateUserId
238
- newData.meta = newData.templateMeta
239
- delete newData.templateMeta
240
- delete newData.templateUserId
241
- if (Object.prototype.hasOwnProperty.call(newData, 'subCreate') && Object.values(newData.subCreate).length > 0) {
242
- const subCreate = newData.subCreate
243
- delete newData.subCreate
244
- const addedDoc = await db.collection(subCreate.rootPath).add({ [subCreate.dynamicDocumentField]: newData.dynamicDocumentFieldValue })
245
- await db.collection(subCreate.rootPath).doc(addedDoc.id).update({ docId: addedDoc.id })
246
- delete newData.dynamicDocumentFieldValue
247
- const newRole = { [`${subCreate.rootPath}-${addedDoc.id}`]: { collectionPath: `${subCreate.rootPath}-${addedDoc.id}`, role: subCreate.role } }
248
- if (Object.prototype.hasOwnProperty.call(newData, 'collectionPaths')) {
249
- newData.collectionPaths.push(`${subCreate.rootPath}-${addedDoc.id}`)
250
- }
251
- else {
252
- newData.collectionPaths = [`${subCreate.rootPath}-${addedDoc.id}`]
253
- }
254
- const newRoles = { ...newData.roles, ...newRole }
255
- newData = { ...newData, roles: newRoles }
256
- const stagedUserRef = db.collection('staged-users').doc(templateUserId)
257
- await stagedUserRef.set({ ...newData, userId: templateUserId })
258
- const userRef = db.collection('users').doc(templateUserId)
259
- await setUser(userRef, newData, oldData, templateUserId)
260
- await markProcessed(eventRef)
261
- }
262
- else {
263
- const stagedUserRef = db.collection('staged-users').doc(templateUserId)
264
- await stagedUserRef.set({ ...newData, userId: templateUserId })
265
- const userRef = db.collection('users').doc(templateUserId)
266
- await setUser(userRef, newData, oldData, templateUserId)
267
- await markProcessed(eventRef)
268
- }
269
- }
270
- }
271
- await markProcessed(eventRef)
272
- })
273
-
274
- function setUser(userRef, newData, oldData, stagedDocId) {
275
- // IT's OK If "users" doesn't match exactly matched "staged-users" because this is only preventing
276
- // writing from outside the @edgdev/firebase functions, so discrepancies will be rare since
277
- // the package will prevent before it gets this far.
278
- return userRef.get().then((user) => {
279
- let userUpdate = { meta: newData.meta, stagedDocId }
280
-
281
- if (Object.prototype.hasOwnProperty.call(newData, 'roles')) {
282
- userUpdate = { ...userUpdate, roles: newData.roles }
283
- }
284
- if (Object.prototype.hasOwnProperty.call(newData, 'specialPermissions')) {
285
- userUpdate = { ...userUpdate, specialPermissions: newData.specialPermissions }
286
- }
287
-
288
- if (!oldData.userId) {
289
- userUpdate = { ...userUpdate, userId: newData.uid }
290
- }
291
- if (!user.exists) {
292
- return userRef.set(userUpdate)
293
- }
294
- else {
295
- return userRef.update(userUpdate)
296
- }
297
- })
298
- }
299
-
300
- function shouldProcess(eventRef) {
301
- return eventRef.get().then((eventDoc) => {
302
- return !eventDoc.exists || !eventDoc.data().processed
303
- })
304
- }
305
-
306
- function markProcessed(eventRef) {
307
- return eventRef.set({ processed: true }).then(() => {
308
- return null
309
- })
310
- }
311
- // END @edge/firebase functions
2
+ exports.edgeFirebase = require('./edgeFirebase')
3
+ // END @edge/firebase functions
@@ -24,11 +24,19 @@ awk '/\/\/ #EDGE FIREBASE RULES START/,/\/\/ #EDGE FIREBASE RULES END/' ./src/fi
24
24
 
25
25
  if [ ! -f "$project_root/functions/index.js" ]; then
26
26
  mkdir -p "$project_root/functions"
27
- echo "const functions = require('firebase-functions')" > "$project_root/functions/index.js";
28
- echo "const admin = require('firebase-admin')" >> "$project_root/functions/index.js";
29
- echo "admin.initializeApp()" >> "$project_root/functions/index.js";
30
- echo "const db = admin.firestore()" >> "$project_root/functions/index.js";
31
- fi
27
+ echo "require('dotenv').config({ path: process.env.NODE_ENV === 'production' ? '.env.prod' : '.env.dev' })" > "$project_root/functions/index.js";
28
+ fi
29
+
30
+ cp ./src/edgeFirebase.js "$project_root/functions/edgeFirebase.js"
31
+ cp ./src/config.js "$project_root/functions/config.js"
32
+
33
+ if [ ! -f "$project_root/.env.dev" ]; then
34
+ cp ./src/.env.dev "$project_root/.env.dev"
35
+ fi
36
+
37
+ if [ ! -f "$project_root/.env.prod" ]; then
38
+ cp ./src/.env.prod "$project_root/.env.prod"
39
+ fi
32
40
 
33
41
  [ "$(tail -c1 $project_root/functions/index.js)" != "" ] && echo "" >> "$project_root/functions/index.js"
34
42