secure your firestore data & send e-mails

Ionic meetup zürich

Author

Sandro Scalco

👨🏻‍💻 enabling digital societies | Co-Founder @StartHub_SH Schaffhausen | CEO @liitu_ch consulting gmbh | Product Manager Electronic Identity @ProcivisAG

How to secure your data and enable a mutli client enviroment with ionic and firebase

  • Work with custom claims
  • Custom functions for firestore rules
  • Update data via firebase functions
  • Firebase architecture
  • ionic views

Update claims with firebase functions

let user = await admin.auth().getUser(userRef.id); let customClaims = user.customClaims || {}; customClaims[teamRef.id] = true; admin.auth().setCustomUserClaims(userRef.id, customClaims);

Firestore Rules

rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { function hasCustomClaim(claim) { return request.auth.token[claim] || false; } } //TRAINING CHANGE / DELETE match /team/{teamId}/trainingList/{trainingId} { allow update, delete: if request.auth != null && hasCustomClaim(teamId); //Team Admin } }

Admin feature in ionic app (create something)

async checkUserhasTeamAdmin(): Promise < any >{ let user = await this.getUser(); let teamAdminListRef = await this.firestore.collection('userProfile').doc(user.uid).collection('teamAdminList').get().toPromise(); for (let snapshot of teamAdminListRef.docs){ if (snapshot.data()){ return true; }else{ return false; } } } async checkUserhasClubAdmin(): Promise < any >{ let user = await this.getUser(); let clubAdminList = await this.firestore.collection('userProfile').doc(user.uid).collection('clubAdminList').get().toPromise(); for (let snapshot of clubAdminList.docs){ if (snapshot.data()){ return true; }else{ return false; } } }

Add administrator to team

async addAdminToTeam(teamId, userId): Promise < any > { const user: firebase.User = await this.authService.getUser(); return firebase.firestore().collection(`userProfile`).doc(userId).collection('teamAdminList').doc(teamId).set({ "teamRef": firebase.firestore().collection('team').doc(teamId), "userRef": firebase.firestore().collection('userProfile').doc(user.uid) // für eigene Rule in Functions }); } match /userProfile/{userId}/teamList/{teamId} { allow delete: if request.auth != null && hasCustomClaim(teamId); //is Team ADMIN } match /userProfile/{userId}/teamAdminList/{teamId} { allow delete,create: if request.auth != null && hasCustomClaim(teamId); //is Team ADMIN } match /userProfile/{userId}/clubList/{clubId} { allow delete: if request.auth != null && hasCustomClaim(clubId); //is Club ADMIN } match /userProfile/{userId}/clubAdminList/{clubId} { allow delete, create: if request.auth != null && hasCustomClaim(clubId); //is Club ADMIN }

Send E-Mails

use nodemailer
https://dev.to/daviddalbusco/send-email-from-firebase-cloud-functions-5cpf
 
or 

Firebase firestore-send-email

Why?

For me.. 
- I have mail logs.
- I can trigger mails based on database events (eg. new user created, user deleted) just by adding data to the database.
- I can "block" requests from users to /mail collection in firebase. 
- no "external" function exposed to the internet.
- e-mails triggered from user are based on transactional events (member to team, make admin).
- e-mails triggered from user are saved to /userProfile/{userId}/mail collection 

E-Mail firebase extension

Firebase firestore-send-email

admin.firestore().collection('mail').add({ to: user.data().email, message: { subject: 'Neuer Request für deinen Club', text: 'Hallo ' + user.data().firstName, html: 'Hallo ' + user.data().firstName } }).then(() => console.log('Queued email for delivery!')); // FIRESTORE RULE match /mail { //No ACCESS allow read, write: if 1==2; }

Remove claims and data 

// leave Team exports.leaveTeam = functions.region('europe-west6').firestore.document('/userProfile/{userId}/teamList/{teamId}') .onDelete(async (snapshot, context) => { let userId = context.params.userId; let teamId = context.params.teamId; //delete team admin List from team and user let userTeamAdmin = await db.collection("userProfile").doc(userId).collection("teamAdminList").doc(teamId).delete(); let teamAdminList = await db.collection("team").doc(teamId).collection("teamAdminList").doc(`${userId}`).delete(); console.log(`User with id ${userId} leaves team ${teamId}`); let user = await admin.auth().getUser(userId); if (user && user.customClaims && user.customClaims[teamId]) { console.log('remove admin for team'); let customClaims = user.customClaims; delete customClaims[teamId]; await admin.auth().setCustomUserClaims(userId, customClaims); } //finally remove team Member return db.collection("team").doc(teamId).collection("memberList").doc(`${userId}`).delete(); });