How to delete unverified e-mail addresses in Firebase Authentication/Flutter? - firebase

After registering with Firebase Authentication "Email / Password",saving e-mail without verification.I have application with Flutter firebase. When someone registers, I direct them to an email verification page and hold them there until they verify the email.The problem is that if someone uses my email and deletes app without verifying it, the mail still remains in the database.How do we delete unverified email addresses?

You can run a scheduled cloud function every day that checks for unverified users and deletes them. That also means you would have to use Admin SDK and cannot be done in Flutter. You can create a NodeJS Cloud Function with the following code and run it.
exports.scheduledFunction = functions.pubsub.schedule('every 24 hours').onRun((context) => {
console.log('This will be run every 24 hours!');
const users = []
const listAllUsers = (nextPageToken) => {
// List batch of users, 1000 at a time.
return admin.auth().listUsers(1000, nextPageToken).then((listUsersResult) => {
listUsersResult.users.forEach((userRecord) => {
users.push(userRecord)
});
if (listUsersResult.pageToken) {
// List next batch of users.
listAllUsers(listUsersResult.pageToken);
}
}).catch((error) => {
console.log('Error listing users:', error);
});
};
// Start listing users from the beginning, 1000 at a time.
await listAllUsers();
const unVerifiedUsers = users.filter((user) => !user.emailVerified).map((user) => user.uid)
//DELETING USERS
return admin.auth().deleteUsers(unVerifiedUsers).then((deleteUsersResult) => {
console.log(`Successfully deleted ${deleteUsersResult.successCount} users`);
console.log(`Failed to delete ${deleteUsersResult.failureCount} users`);
deleteUsersResult.errors.forEach((err) => {
console.log(err.error.toJSON());
});
return true
}).catch((error) => {
console.log('Error deleting users:', error);
return false
});
});

You can delete users through the Firebase Admin SDK.
You'll need a list of unverified users, either by listing all users and filtering it down, or from somewhere you store this yourself, and then you can delete the unverified users.

This works just perfect:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.scheduledFunction = functions.pubsub
.schedule("every 24 hours")
.onRun((context) => {
console.log("This will be run every 24 hours!");
var users = [];
var unVerifiedUsers = [];
const listAllUsers = async (nextPageToken) => {
// List batch of users, 1000 at a time.
return admin
.auth()
.listUsers(1000, nextPageToken)
.then((listUsersResult) => {
listUsersResult.users.forEach((userRecord) => {
users.push(userRecord);
});
if (listUsersResult.pageToken) {
// List next batch of users.
listAllUsers(listUsersResult.pageToken);
}
})
.catch((error) => {
console.log("Error listing users:", error);
});
};
// Start listing users from the beginning, 1000 at a time.
listAllUsers().then(() => {
unVerifiedUsers = users
.filter((user) => !user.emailVerified)
.map((user) => user.uid);
admin
.auth()
.deleteUsers(unVerifiedUsers)
.then((deleteUsersResult) => {
console.log(
`Successfully deleted ${deleteUsersResult.successCount} users`
);
console.log(
`Failed to delete ${deleteUsersResult.failureCount} users`
);
deleteUsersResult.errors.forEach((err) => {
console.log(err.error.toJSON());
});
return true;
})
.catch((error) => {
console.log("Error deleting users:", error);
return false;
});
});
});

Related

“user Does not exists” Firebase

I started this tutorial (https://www.freecodecamp.org/news/react-native-firebase-tutorial/) on Firebase and React Native. Everything is working well overall.
But I have this error: “User does not exist anymore.” for the Login.
However, users are well rooted in Firebase.
const onLoginPress = () => {
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then((response) => {
const uid = response.user.uid
const usersRef = firebase.firestore().collection('users')
usersRef
.doc(uid)
.get()
.then(firestoreDocument => {
if (!firestoreDocument.exists) {
alert("User does not exist anymore.")
return;
}
const user = firestoreDocument.data()
navigation.navigate('Home', {user})
})
.catch(error => {
alert(error)
});
})
.catch(error => {
alert(error)
})
}
With
const usersRef = firebase.firestore().collection('users')
usersRef
.doc(uid)
.get()
.then(firestoreDocument => {
if (!firestoreDocument.exists) {
alert("User does not exist anymore.")
return;
}
const user = firestoreDocument.data()
navigation.navigate('Home', {user})
})
you actually query the user document with the id corresponding to the user's uid in the users collection.
This document is normally created by the onRegisterPress() function in the tutorial. If you get the "User does not exist anymore." message, it means that the user document is not present in the collection.
So you need to check why this is the case: the onRegisterPress() function was not called? The doc was deleted? There are security rules that prevent creating the document? etc...

Handle facebook login with same account used with Google using firebase

I'm working on a react native project and I've came to a part where initially I implemented google sign in my project using react-native-google-signin and later on Facebook sign in using react-native-fbsdk packages with the help of firebase and both worked like a charm "individually".
The Problem
Let's say the user logged in using google account and it worked but later logged in using Facebook with the same account (I'm allowing only one email per user in firebase), I get an error
auth/account-exists-with-different-credentials
I want the user to be able to login using Facebook from the login screen or to be more specific to link his account from the login screen.
What have I tried?
I searched online and found some answers and got up with this solution or piece of code:
facebookSignin: async () => {
const result = await LoginManager.logInWithPermissions([
'public_profile',
'email',
]);
if (result.isCancelled) {
alert('User cancelled the login process');
this.setState({loginInProcess: false});
}
const data = await AccessToken.getCurrentAccessToken();
if (!data) {
alert('Something went wrong obtaining access token');
this.setState({loginInProcess: false});
}
const facebookCredential = auth.FacebookAuthProvider.credential(
data.accessToken,
);
await auth()
.signInWithCredential(facebookCredential)
// The problem starts here from the catch block
.catch((error) => {
if (
error.code === 'auth/account-exists-with-different-credential'
) {
var pendingCred = error.credential;
var email = error.email;
auth()
.fetchSignInMethodsForEmail(email)
.then(async (methods) => {
if (methods[0] === 'google.com') {
const {idToken} = await GoogleSignin.signIn();
const googleCredential = auth.GoogleAuthProvider.credential(
idToken,
);
auth()
.signInWithCredential(googleCredential)
.then((user) => {
user.linkWithCredential(pendingCred);
})
.catch((error) => console.log(error));
}
});
}
});
}
This code implements a function when triggered, if there is no user with the same email, it proceeds normally, however if there is an error (mentioned above), it will grant the user with a list of google accounts that are present in the user phone (google thing) and when he chooses his account (linked with google account) it doesn't work. The email isn't linked.
To be more specific, I would like somehow to not grant the user with all his google accounts but only with the email to be linked var email = error.email; (in the code snippet above) and for the Facebook provider to be linked successfully.
After a little of hard work, I've managed to make it work in react native and I'm gonna leave the answer here for peeps who are facing the same issue. Be ware that I used react-native-prompt-android to ask the user for confirming his password when trying to link with Facebook.
The user tries to sign with Facebook and gets this error:
auth/account-exists-with-different-credentials
This is how I handled it:
.catch((error) => {
// Catching the error
if (
error.code === 'auth/account-exists-with-different-credential'
) {
const _responseInfoCallback = (error, result) => {
if (error) {
alert('Error fetching data: ' + error.toString());
} else {
setEmail(result.email);
}
};
// Getting the email address instead of error.email from Facebook
const profileRequest = new GraphRequest(
'/me?fields=email',
null,
_responseInfoCallback,
);
new GraphRequestManager().addRequest(profileRequest).start();
if (email) {
auth()
.fetchSignInMethodsForEmail(email)
.then(async (methods) => {
// Checking the method
if (methods[0] === 'password') {
// Prompting the user to confirm/input his password for linking
const AsyncAlert = () => {
return new Promise((resolve, reject) => {
prompt(
'Password Confirmation',
'The email address is already linked with password account. Enter your password to process',
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Continue',
onPress: (password) =>
resolve(setPassword(password)),
},
],
{
type: 'secure-text',
cancelable: false,
placeholder: 'Password',
},
);
});
};
// Here the linking goes
await AsyncAlert().then(async () => {
await auth()
.signInWithEmailAndPassword(email, password)
.then(() => {
return auth().currentUser.linkWithCredential(
facebookCredential,
);
})
.catch(() => alert('Something went wrong'));
});
} else if (methods[0] === 'google.com') {
const {idToken} = await GoogleSignin.signIn(email);
const googleCredential = auth.GoogleAuthProvider.credential(
idToken,
);
await auth()
.signInWithCredential(googleCredential)
.then(() => {
return auth().currentUser.linkWithCredential(
facebookCredential,
);
});
}
});
} else {
alert('Something went wrong');
}
}
});

Jest doesn't wait for async beforeAll to finish

Im trying to test getting all users from my REST API.
describe('GET', () => {
let userId;
// Setup create the mock user
beforeAll(async () => {
//Create the user
return await request
.post(routes.users.create)
.set('Accept', 'application/json')
.send(TEST_USER_DATA)
.then(res => userId = res.body.id)
})
// Clean up, deleting all the fake data that we created for this test suite
afterAll(async () => {
// Clean up, delete the user we created
return await request.delete(routes.users.delete(userId));
})
it('should get all users', async () => {
const usersResponse = await request
.get(routes.users.getAll)
.set('Accept', 'application/json')
.expect(200)
.expect('Content-Type', /json/);
// Logs an empty array
console.log(usersResponse.body);
expect(usersResponse.status).to.equal(200);
expect(Array.isArray(usersResponse.body)).to.be.true();
});
});
But it look as though my it() block doesn't wait for beforeAll() to finish, because userResponse.body() is just an empty array. But when I do the same think in Postman(e.g. Create a mock user, then get all users, it displays an array with the user that we created) so the problem is definitely not in the server-side.
I've already tried writing my beforeAll block like that:
beforeAll(async () => {
//Create the user
return await new Promise((resolve) => {
request
.post(routes.users.create)
.set('Accept', 'application/json')
.send(TEST_USER_DATA)
.then(res => userId = res.body.id)
.then(() => resolve)
})
})
And like that:
beforeAll(async (done) => {
//Create the user
request
.post(routes.users.create)
.set('Accept', 'application/json')
.send(TEST_USER_DATA)
.then(res => userId = res.body.id)
.then(() => done());
})
But neither of them worked.
EDIT
As #jonrsharpe suggested I changed my beforeAll a bit to check the response status, and that we actually created a user
beforeAll(async () => {
//Create the user
return await request
.post(routes.users.create)
.set('Accept', 'application/json')
.send(TEST_USER_DATA)
.expect(200)
.then(res => {
userId = res.body.id;
// Log the correct user
console.log(res.body);
})
})
And the beforeAll block doesn't fail, so the creation of a user by itself is working fine.

CloudFunctions: Request is sending twice

I have an issue and I need help since I'm learning.
I have a flutter app that saves on Firebase/Firestore data, when a user requests a friendship I add it on both sender and target user, changing the IDs of sendTo and sentBy.
My problem is that CloudFunctions detect well that 2 collections from different users have been changed and notify me 2x (target user). So code is fine but should only notify once/target user
I'm using FCM to send local notifications.
exports.sendRequestNotification = functions.firestore
.document('users/{userId}/requests/{requestId}')
.onCreate((snap, context) => {
const docReq = snap.data()
/*console.log(docReq)*/
const sentBy = docReq.sentBy
const sentTo = docReq.sentTo
const contentRequest = docReq.code
if(contentRequest !== null){
// Get push token user to (receive)
admin
.firestore()
.collection('users')
.where('userId', '==', sentTo)
.get()
.then(querySnapshot => {
querySnapshot.forEach(userTo => {
/*console.log(`Found request user to: ${userTo.data().userId}`)*/
if (userTo.data().pushToken) {
// Get info user from (sent)
admin
.firestore()
.collection('users')
.where('userId', '==', sentBy)
.get()
.then(querySnapshot2 => {
querySnapshot2.forEach(userFrom => {
/*console.log(`Found request user from: ${userFrom.data().userId}`)*/
const payload = {
notification: {
title: `${userFrom.data().nickname}`,
body: contentRequest,
badge: '1',
sound: 'default'
}
}
// Let push to the target device
admin
.messaging()
.sendToDevice(userTo.data().pushToken, payload)
.then(response => {
/*console.log('Successfully sent request:', response)*/
})
.catch(error => {
console.log('Error sending request:', error)
})
})
})
} else {
console.log('User request or token not found')
}
})
})
return null
}
})
It is not very clear from your code why it would send the notification twice (since you check that userTo.data().userId !== sentBy). But what is sure is that you are not returning a Promise that resolves when all the asynchronous operations (get() and sendToDevice()) are completed.
I would suggest you watch the official Video Series (https://firebase.google.com/docs/functions/video-series/) which explain very well this point about returning Promises for background functions (in particular the ones titled "Learn JavaScript Promises").
In particular, you will see in the videos that if you don't return a Promise, the Cloud Function may terminate before asynchronous operations are completed, potentially resulting in some inconsistent (not logical) results .
So, you should give a try with the following adapted code, which returns the promises chain:
exports.sendRequestNotification = functions.firestore
.document('users/{userId}/requests/{requestId}')
.onCreate((snap, context) => {
const db = admin.firestore();
const docReq = snap.data();
/*console.log(docReq)*/
const sentBy = docReq.sentBy;
const sentTo = docReq.sentTo;
// Get push token user to (receive)
return db.collection('users')
.where('userId', '==', sentTo)
.get()
.then(querySnapshot => {
//We know there is only one document (i.e. one user with this Id), so lets use the docs property
//See https://firebase.google.com/docs/reference/js/firebase.firestore.QuerySnapshot.html#docs
const userTo = querySnapshot.docs[0];
if (userTo.data().pushToken && userTo.data().userId !== sentBy) {
// Get info user from (sent)
return db.collection('users')
.where('userId', '==', sentBy)
.get();
} else {
console.log('User request or token not found')
throw new Error('User request or token not found');
}
})
.then(querySnapshot => {
const userFrom = querySnapshot.docs[0];
const payload = {
notification: {
title: `${userFrom.data().nickname}`,
body: `requestNotify`,
badge: '1',
sound: 'default'
}
}
return admin
.messaging()
.sendToDevice(userTo.data().pushToken, payload);
})
.catch(error => {
console.log('Error:', error);
return false;
})
})

What is the suitable query for the following case?

This is my database structure:
I am trying to list all users with "locale" equal to "Cairo, Egypt" so I made the following query:
exports.calculateMatches = functions.https.onRequest((request, response) => {
// Access users' profiles that are located in the locale of the requesting user
databaseRef.child("users").orderByChild("locale").equalTo(request.query.locale).once("value")
.then(snap => {
snap.forEach(profile => {
console.log(profile);
});
});
});
Note this function is deployed to firebase cloud functions and this is what I get in the logs:
HTTPS type functions require that you send a response to the client in order to terminate the function. Without that, they will always time out, and the client will be waiting the whole time.
For example:
const databaseRef = admin.database().ref('')
exports.calculateMatches = functions.https.onRequest((request, response) => {
databaseRef.child("users").orderByChild("locale").equalTo(request.query.locale).once("value")
.then(snap => {
const profiles = []
snap.forEach(profile => {
profiles.push(profile.val())
});
response.send(profiles)
})
.catch(error => {
response.status(500).send(error)
});
});

Resources